Files
sideline/scripts/demo-lfo-effects.py
David Gwilliam 247f572218 fix: Bug fixes and improvements
- fix(demo-lfo-effects): Fix math.sin() usage (was angle.__sin__())
- feat(pipeline): Add set_effect_intensity() method for runtime effect control
  - Allows changing effect intensity during pipeline execution
  - Returns False if effect not found or intensity out of range
  - Used by LFO modulation demo

The demo-lfo-effects.py script now works correctly with proper
math.sin() usage and the new set_effect_intensity() method provides
a clean API for runtime effect intensity control.
2026-03-21 19:27:06 -07:00

153 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
Pygame Demo: Effects with LFO Modulation
This demo shows how to use LFO (Low Frequency Oscillator) to modulate
effect intensities over time, creating smooth animated changes.
Effects modulated:
- noise: Random noise intensity
- fade: Fade effect intensity
- tint: Color tint intensity
- glitch: Glitch effect intensity
The LFO uses a sine wave to oscillate intensity between 0.0 and 1.0.
"""
import math
import sys
import time
from dataclasses import dataclass
from typing import Any
from engine import config
from engine.display import DisplayRegistry
from engine.effects import get_registry
from engine.pipeline import Pipeline, PipelineConfig, PipelineContext, list_presets
from engine.pipeline.params import PipelineParams
from engine.pipeline.preset_loader import load_presets
from engine.sensors.oscillator import OscillatorSensor
from engine.sources import FEEDS
@dataclass
class LFOEffectConfig:
"""Configuration for LFO-modulated effect."""
name: str
frequency: float # LFO frequency in Hz
phase_offset: float # Phase offset (0.0 to 1.0)
min_intensity: float = 0.0
max_intensity: float = 1.0
class LFOEffectDemo:
"""Demo controller that modulates effect intensities using LFO."""
def __init__(self, pipeline: Pipeline):
self.pipeline = pipeline
self.effects = [
LFOEffectConfig("noise", frequency=0.5, phase_offset=0.0),
LFOEffectConfig("fade", frequency=0.3, phase_offset=0.33),
LFOEffectConfig("tint", frequency=0.4, phase_offset=0.66),
LFOEffectConfig("glitch", frequency=0.6, phase_offset=0.9),
]
self.start_time = time.time()
self.frame_count = 0
def update(self):
"""Update effect intensities based on LFO."""
elapsed = time.time() - self.start_time
self.frame_count += 1
for effect_cfg in self.effects:
# Calculate LFO value using sine wave
angle = (
(elapsed * effect_cfg.frequency + effect_cfg.phase_offset) * 2 * 3.14159
)
lfo_value = 0.5 + 0.5 * math.sin(angle)
# Scale to intensity range
intensity = effect_cfg.min_intensity + lfo_value * (
effect_cfg.max_intensity - effect_cfg.min_intensity
)
# Update effect intensity in pipeline
self.pipeline.set_effect_intensity(effect_cfg.name, intensity)
def run(self, duration: float = 30.0):
"""Run the demo for specified duration."""
print(f"\n{'=' * 60}")
print("LFO EFFECT MODULATION DEMO")
print(f"{'=' * 60}")
print("\nEffects being modulated:")
for effect in self.effects:
print(f" - {effect.name}: {effect.frequency}Hz")
print(f"\nPress Ctrl+C to stop")
print(f"{'=' * 60}\n")
start = time.time()
try:
while time.time() - start < duration:
self.update()
time.sleep(0.016) # ~60 FPS
except KeyboardInterrupt:
print("\n\nDemo stopped by user")
finally:
print(f"\nTotal frames rendered: {self.frame_count}")
def main():
"""Main entry point for the LFO demo."""
# Configuration
effect_names = ["noise", "fade", "tint", "glitch"]
# Get pipeline config from preset
preset_name = "demo-pygame"
presets = load_presets()
preset = presets["presets"].get(preset_name)
if not preset:
print(f"Error: Preset '{preset_name}' not found")
print(f"Available presets: {list(presets['presets'].keys())}")
sys.exit(1)
# Create pipeline context
ctx = PipelineContext()
ctx.terminal_width = preset.get("viewport_width", 80)
ctx.terminal_height = preset.get("viewport_height", 24)
# Create params
params = PipelineParams(
source=preset.get("source", "headlines"),
display="pygame", # Force pygame display
camera_mode=preset.get("camera", "feed"),
effect_order=effect_names, # Enable our effects
viewport_width=preset.get("viewport_width", 80),
viewport_height=preset.get("viewport_height", 24),
)
ctx.params = params
# Create pipeline config
pipeline_config = PipelineConfig(
source=preset.get("source", "headlines"),
display="pygame",
camera=preset.get("camera", "feed"),
effects=effect_names,
)
# Create pipeline
pipeline = Pipeline(config=pipeline_config, context=ctx)
# Build pipeline
pipeline.build()
# Create demo controller
demo = LFOEffectDemo(pipeline)
# Run demo
demo.run(duration=30.0)
if __name__ == "__main__":
main()