Research-Stack/5-Applications/scripts/palette_dsp_slave.py

312 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Palette Generator Slaved to DSP Math
NES palette generator (proto-shader) controlled by DSP math operations on audio lines.
Architecture:
- DSP math on audio lines generates values
- Values control NES palette generator
- Palette generator affects visual output
- Feedback loop: audio → math → palette → visual
This is horrific because:
- Audio DSP math controls video palette
- Cross-domain repurposing (audio → video)
- Palette generator becomes slave to computation
This is wonderful because:
- Video synthesizer controlled by audio math
- Generative visuals from DSP operations
- Maximum retro insanity: audio math = video palette
"""
import math
from typing import List, Tuple, Dict
from dataclasses import dataclass
from enum import Enum
# ═══════════════════════════════════════════════════════════════════════════
# NES Palette Generator (Proto-Shader)
# Generates NES palette colors from parameters
# ═══════════════════════════════════════════════════════════════════════════
@dataclass
class NESColor:
"""NES color (RGB, 2 bits per channel)"""
r: int # 0-3
g: int # 0-3
b: int # 0-3
def to_index(self) -> int:
"""Convert to NES palette index (0-63)"""
return (self.r << 4) | (self.g << 2) | self.b
@staticmethod
def from_index(index: int) -> 'NESColor':
"""Convert from NES palette index"""
return NESColor(
r=(index >> 4) & 0x03,
g=(index >> 2) & 0x03,
b=index & 0x03
)
def to_rgb888(self) -> Tuple[int, int, int]:
"""Convert to 8-bit RGB"""
return (self.r * 85, self.g * 85, self.b * 85)
class PaletteGenerator:
"""NES palette generator acting as proto-shader"""
@staticmethod
def generate_color(params: Tuple[float, float, float]) -> NESColor:
"""
Generate NES color from parameters (x, y, t).
This acts as a fragment shader - takes parameters and outputs color.
"""
x, y, t = params
# Map parameters to color channels
# x → red, y → green, t → blue
r = int((math.sin(x + t) + 1) * 1.5) % 4
g = int((math.cos(y + t) + 1) * 1.5) % 4
b = int((math.sin(x + y + t) + 1) * 1.5) % 4
return NESColor(r, g, b)
@staticmethod
def generate_palette(num_colors: int, params: List[Tuple[float, float, float]]) -> List[NESColor]:
"""Generate a palette of colors"""
palette = []
for i in range(num_colors):
if i < len(params):
color = PaletteGenerator.generate_color(params[i])
else:
# Default color if no params
color = NESColor(0, 0, 0)
palette.append(color)
return palette
# ═══════════════════════════════════════════════════════════════════════════
# DSP Math to Palette Control
# Map DSP math operations to palette generator parameters
# ═══════════════════════════════════════════════════════════════════════════
@dataclass
class AudioSignal:
"""Audio signal from DSP math"""
frequency: float
amplitude: float
duty_cycle: float
class DSPPaletteSlave:
"""DSP math slaved to control palette generator"""
def __init__(self):
self.palette_generator = PaletteGenerator()
self.audio_history: List[AudioSignal] = []
self.palette_history: List[List[NESColor]] = []
def audio_to_palette_params(self, signal: AudioSignal) -> Tuple[float, float, float]:
"""
Convert audio signal to palette generator parameters.
Maps: frequency → x, amplitude → y, duty_cycle → t
"""
# Normalize parameters to 0-2π range
x = signal.frequency / 1000.0 * math.pi # Normalize freq
y = signal.amplitude * 2 * math.pi # Normalize amplitude
t = signal.duty_cycle * 2 * math.pi # Normalize duty
return (x, y, t)
def dsp_to_palette(self, signals: List[AudioSignal]) -> List[NESColor]:
"""
Convert DSP audio signals to palette.
Each audio signal controls one palette entry.
"""
params_list = [self.audio_to_palette_params(s) for s in signals]
palette = self.palette_generator.generate_palette(len(signals), params_list)
self.audio_history = signals
self.palette_history.append(palette)
return palette
def dsp_operation_to_palette(self, operation: str, signals: List[AudioSignal]) -> List[NESColor]:
"""
Perform DSP operation and convert result to palette.
Operations: add, multiply, integrate, differentiate
"""
if operation == "add":
# Add signals: combine amplitudes
combined_amp = sum(s.amplitude for s in signals)
combined_freq = sum(s.frequency for s in signals) / len(signals)
combined_duty = sum(s.duty_cycle for s in signals) / len(signals)
combined = AudioSignal(combined_freq, combined_amp, combined_duty)
return self.dsp_to_palette([combined])
elif operation == "multiply":
# Multiply signals: AM modulation
if len(signals) >= 2:
mul_amp = signals[0].amplitude * signals[1].amplitude
mul_freq = math.sqrt(signals[0].frequency * signals[1].frequency)
mul_duty = signals[0].duty_cycle if signals[0].duty_cycle >= 0.5 else 1.0 - signals[0].duty_cycle
mul_duty *= signals[1].duty_cycle if signals[1].duty_cycle >= 0.5 else 1.0 - signals[1].duty_cycle
combined = AudioSignal(mul_freq, mul_amp, mul_duty)
return self.dsp_to_palette([combined])
elif operation == "integrate":
# Integrate: accumulate over history
if self.audio_history:
integrated = AudioSignal(
frequency=sum(s.frequency for s in self.audio_history + signals),
amplitude=sum(s.amplitude for s in self.audio_history + signals),
duty_cycle=sum(s.duty_cycle for s in self.audio_history + signals) / (len(self.audio_history) + len(signals))
)
return self.dsp_to_palette([integrated])
elif operation == "differentiate":
# Differentiate: rate of change
if len(signals) >= 2:
diff = AudioSignal(
frequency=signals[-1].frequency - signals[-2].frequency,
amplitude=signals[-1].amplitude - signals[-2].amplitude,
duty_cycle=signals[-1].duty_cycle - signals[-2].duty_cycle
)
return self.dsp_to_palette([diff])
# Default: direct conversion
return self.dsp_to_palette(signals)
# ═══════════════════════════════════════════════════════════════════════════
# Video Synthesizer
# Audio DSP math controls video palette generation
# ═══════════════════════════════════════════════════════════════════════════
class VideoSynthesizer:
"""Video synthesizer controlled by audio DSP math"""
def __init__(self):
self.dsp_slave = DSPPaletteSlave()
self.frame_count = 0
def generate_frame(self, audio_signals: List[AudioSignal],
dsp_operation: str = "none") -> List[NESColor]:
"""
Generate video frame from audio DSP math.
Audio signals → DSP operation → Palette → Video frame
"""
self.frame_count += 1
if dsp_operation == "none":
palette = self.dsp_slave.dsp_to_palette(audio_signals)
else:
palette = self.dsp_slave.dsp_operation_to_palette(dsp_operation, audio_signals)
return palette
def animate(self, base_signals: List[AudioSignal],
frames: int, dsp_operation: str = "none") -> List[List[NESColor]]:
"""
Generate animation frames.
Modulates audio signals over time to create animated palette.
"""
animation = []
for frame in range(frames):
# Modulate signals over time
modulated = []
for i, signal in enumerate(base_signals):
modulated_signal = AudioSignal(
frequency=signal.frequency * (1 + 0.1 * math.sin(frame * 0.1 + i)),
amplitude=signal.amplitude * (1 + 0.1 * math.cos(frame * 0.1 + i)),
duty_cycle=(signal.duty_cycle + 0.01 * math.sin(frame * 0.05 + i)) % 1.0
)
modulated.append(modulated_signal)
frame_palette = self.generate_frame(modulated, dsp_operation)
animation.append(frame_palette)
return animation
# ═══════════════════════════════════════════════════════════════════════════
# Test / Demo
# ═══════════════════════════════════════════════════════════════════════════
def run_test():
"""Run palette generator slave to DSP math test"""
print("=" * 70)
print("PALETTE GENERATOR SLAVED TO DSP MATH")
print("=" * 70)
print("\n[*] Architecture:")
print(" Audio DSP math → Palette generator parameters → Video palette")
print(" Frequency → x (red channel)")
print(" Amplitude → y (green channel)")
print(" Duty cycle → t (blue channel)")
print(" DSP operations (add, multiply, integrate) modulate palette")
synthesizer = VideoSynthesizer()
# Create base audio signals
print("\n[*] Creating base audio signals...")
base_signals = [
AudioSignal(frequency=440.0, amplitude=0.5, duty_cycle=0.5),
AudioSignal(frequency=880.0, amplitude=0.7, duty_cycle=0.25),
AudioSignal(frequency=220.0, amplitude=0.3, duty_cycle=0.75),
]
print(f" Signal 1: {base_signals[0].frequency}Hz, amp={base_signals[0].amplitude}, duty={base_signals[0].duty_cycle}")
print(f" Signal 2: {base_signals[1].frequency}Hz, amp={base_signals[1].amplitude}, duty={base_signals[1].duty_cycle}")
print(f" Signal 3: {base_signals[2].frequency}Hz, amp={base_signals[2].amplitude}, duty={base_signals[2].duty_cycle}")
# Direct conversion
print("\n[*] Direct audio-to-palette conversion...")
palette = synthesizer.generate_frame(base_signals, "none")
print(" Palette generated:")
for i, color in enumerate(palette):
rgb = color.to_rgb888()
print(f" Entry {i}: R={rgb[0]}, G={rgb[1]}, B={rgb[2]} (index={color.to_index()})")
# DSP addition
print("\n[*] DSP addition (add signals)...")
palette_add = synthesizer.generate_frame(base_signals, "add")
print(" Palette after addition:")
for i, color in enumerate(palette_add):
rgb = color.to_rgb888()
print(f" Entry {i}: R={rgb[0]}, G={rgb[1]}, B={rgb[2]} (index={color.to_index()})")
# DSP multiplication
print("\n[*] DSP multiplication (AM modulation)...")
palette_mul = synthesizer.generate_frame(base_signals, "multiply")
print(" Palette after multiplication:")
for i, color in enumerate(palette_mul):
rgb = color.to_rgb888()
print(f" Entry {i}: R={rgb[0]}, G={rgb[1]}, B={rgb[2]} (index={color.to_index()})")
# Animation
print("\n[*] Generating 10-frame animation...")
animation = synthesizer.animate(base_signals, frames=10, dsp_operation="multiply")
print(f" Animation generated: {len(animation)} frames")
print(" Frame 0 palette:")
for i, color in enumerate(animation[0]):
rgb = color.to_rgb888()
print(f" Entry {i}: R={rgb[0]}, G={rgb[1]}, B={rgb[2]}")
print(" Frame 5 palette:")
for i, color in enumerate(animation[5]):
rgb = color.to_rgb888()
print(f" Entry {i}: R={rgb[0]}, G={rgb[1]}, B={rgb[2]}")
print("\n" + "=" * 70)
print("PALETTE-DSP SLAVE SYSTEM COMPLETE")
print("=" * 70)
print("\n[*] Horrific: Audio DSP math controls video palette")
print("[*] Wonderful: Video synthesizer from audio computation")
print("[*] Maximum retro insanity: audio math = video palette")
if __name__ == "__main__":
run_test()