- Add upstream-default preset matching upstream mainline behavior: - Terminal display (not pygame) - No message overlay - Classic effects: noise, fade, glitch, firehose - Mixed positioning mode - Enhance demo preset to showcase sideline features: - Hotswappable effects via effect plugins - LFO sensor modulation (oscillator sensor) - Mixed positioning mode - Message overlay with ntfy integration - Includes hud effect for visual feedback - Update all presets to use mixed positioning mode - Update completion script for --positioning flag Usage: python -m mainline --preset upstream-default --display terminal python -m mainline --preset demo --display pygame
152 lines
4.6 KiB
Python
152 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 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 * (angle.__sin__())
|
|
|
|
# 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()
|