forked from genewildish/Mainline
feat(presets): Add upstream-default preset and enhance demo preset
- 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
This commit is contained in:
@@ -70,6 +70,12 @@ _mainline_completion() {
|
||||
COMPREPLY=($(compgen -W "demo demo-base demo-pygame demo-camera-showcase poetry headlines empty test-basic test-border test-scroll-camera" -- "${cur}"))
|
||||
return
|
||||
;;
|
||||
|
||||
--positioning)
|
||||
# Positioning modes
|
||||
COMPREPLY=($(compgen -W "absolute relative mixed" -- "${cur}"))
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
# Flag completion (start with --)
|
||||
@@ -85,6 +91,7 @@ _mainline_completion() {
|
||||
--viewport
|
||||
--preset
|
||||
--theme
|
||||
--positioning
|
||||
--websocket
|
||||
--websocket-port
|
||||
--allow-unsafe
|
||||
|
||||
@@ -265,6 +265,12 @@ def run_pipeline_mode_direct():
|
||||
)
|
||||
|
||||
display = DisplayRegistry.create(display_name)
|
||||
|
||||
# Set positioning mode
|
||||
if "--positioning" in sys.argv:
|
||||
idx = sys.argv.index("--positioning")
|
||||
if idx + 1 < len(sys.argv):
|
||||
params.positioning = sys.argv[idx + 1]
|
||||
if not display:
|
||||
print(f" \033[38;5;196mFailed to create display: {display_name}\033[0m")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -139,6 +139,16 @@ def run_pipeline_mode(preset_name: str = "demo"):
|
||||
print("Error: Invalid viewport format. Use WxH (e.g., 40x15)")
|
||||
sys.exit(1)
|
||||
|
||||
# Set positioning mode from command line or config
|
||||
if "--positioning" in sys.argv:
|
||||
idx = sys.argv.index("--positioning")
|
||||
if idx + 1 < len(sys.argv):
|
||||
params.positioning = sys.argv[idx + 1]
|
||||
else:
|
||||
from engine import config as app_config
|
||||
|
||||
params.positioning = app_config.get_config().positioning
|
||||
|
||||
pipeline = Pipeline(config=preset.to_config())
|
||||
|
||||
print(" \033[38;5;245mFetching content...\033[0m")
|
||||
|
||||
@@ -130,6 +130,7 @@ class Config:
|
||||
script_fonts: dict[str, str] = field(default_factory=_get_platform_font_paths)
|
||||
|
||||
display: str = "pygame"
|
||||
positioning: str = "mixed"
|
||||
websocket: bool = False
|
||||
websocket_port: int = 8765
|
||||
theme: str = "green"
|
||||
@@ -174,6 +175,7 @@ class Config:
|
||||
kata_glyphs="ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ",
|
||||
script_fonts=_get_platform_font_paths(),
|
||||
display=_arg_value("--display", argv) or "terminal",
|
||||
positioning=_arg_value("--positioning", argv) or "mixed",
|
||||
websocket="--websocket" in argv,
|
||||
websocket_port=_arg_int("--websocket-port", 8765, argv),
|
||||
theme=_arg_value("--theme", argv) or "green",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -60,6 +60,7 @@ class PipelinePreset:
|
||||
source_items: list[dict[str, Any]] | None = None # For ListDataSource
|
||||
enable_metrics: bool = True # Enable performance metrics collection
|
||||
enable_message_overlay: bool = False # Enable ntfy message overlay
|
||||
positioning: str = "mixed" # Positioning mode: "absolute", "relative", "mixed"
|
||||
|
||||
def to_params(self) -> PipelineParams:
|
||||
"""Convert to PipelineParams (runtime configuration)."""
|
||||
@@ -68,6 +69,7 @@ class PipelinePreset:
|
||||
params = PipelineParams()
|
||||
params.source = self.source
|
||||
params.display = self.display
|
||||
params.positioning = self.positioning
|
||||
params.border = (
|
||||
self.border
|
||||
if isinstance(self.border, bool)
|
||||
@@ -115,18 +117,38 @@ class PipelinePreset:
|
||||
source_items=data.get("source_items"),
|
||||
enable_metrics=data.get("enable_metrics", True),
|
||||
enable_message_overlay=data.get("enable_message_overlay", False),
|
||||
positioning=data.get("positioning", "mixed"),
|
||||
)
|
||||
|
||||
|
||||
# Built-in presets
|
||||
# Upstream-default preset: Matches the default upstream Mainline operation
|
||||
UPSTREAM_PRESET = PipelinePreset(
|
||||
name="upstream-default",
|
||||
description="Upstream default operation (terminal display, legacy behavior)",
|
||||
source="headlines",
|
||||
display="terminal",
|
||||
camera="scroll",
|
||||
effects=["noise", "fade", "glitch", "firehose"],
|
||||
enable_message_overlay=False,
|
||||
positioning="mixed",
|
||||
)
|
||||
|
||||
# Demo preset: Showcases hotswappable effects and sensors
|
||||
# This preset demonstrates the sideline features:
|
||||
# - Hotswappable effects via effect plugins
|
||||
# - Sensor integration (oscillator LFO for modulation)
|
||||
# - Mixed positioning mode
|
||||
# - Message overlay with ntfy integration
|
||||
DEMO_PRESET = PipelinePreset(
|
||||
name="demo",
|
||||
description="Demo mode with effect cycling and camera modes",
|
||||
description="Demo: Hotswappable effects, LFO sensor modulation, mixed positioning",
|
||||
source="headlines",
|
||||
display="pygame",
|
||||
camera="scroll",
|
||||
effects=["noise", "fade", "glitch", "firehose"],
|
||||
effects=["noise", "fade", "glitch", "firehose", "hud"],
|
||||
enable_message_overlay=True,
|
||||
positioning="mixed",
|
||||
)
|
||||
|
||||
UI_PRESET = PipelinePreset(
|
||||
@@ -201,6 +223,7 @@ def _build_presets() -> dict[str, PipelinePreset]:
|
||||
# Add built-in presets as fallback (if not in YAML)
|
||||
builtins = {
|
||||
"demo": DEMO_PRESET,
|
||||
"upstream-default": UPSTREAM_PRESET,
|
||||
"poetry": POETRY_PRESET,
|
||||
"pipeline": PIPELINE_VIZ_PRESET,
|
||||
"websocket": WEBSOCKET_PRESET,
|
||||
|
||||
18
presets.toml
18
presets.toml
@@ -53,6 +53,18 @@ viewport_height = 24
|
||||
# DEMO PRESETS (for demonstration and exploration)
|
||||
# ============================================
|
||||
|
||||
[presets.upstream-default]
|
||||
description = "Upstream default operation (terminal display, legacy behavior)"
|
||||
source = "headlines"
|
||||
display = "terminal"
|
||||
camera = "scroll"
|
||||
effects = ["noise", "fade", "glitch", "firehose"]
|
||||
camera_speed = 1.0
|
||||
viewport_width = 80
|
||||
viewport_height = 24
|
||||
enable_message_overlay = false
|
||||
positioning = "mixed"
|
||||
|
||||
[presets.demo-base]
|
||||
description = "Demo: Base preset for effect hot-swapping"
|
||||
source = "headlines"
|
||||
@@ -63,17 +75,19 @@ camera_speed = 0.1
|
||||
viewport_width = 80
|
||||
viewport_height = 24
|
||||
enable_message_overlay = true
|
||||
positioning = "mixed"
|
||||
|
||||
[presets.demo-pygame]
|
||||
description = "Demo: Pygame display version"
|
||||
source = "headlines"
|
||||
display = "pygame"
|
||||
camera = "feed"
|
||||
effects = [] # Demo script will add/remove effects dynamically
|
||||
effects = ["noise", "fade", "glitch", "firehose"] # Default effects
|
||||
camera_speed = 0.1
|
||||
viewport_width = 80
|
||||
viewport_height = 24
|
||||
enable_message_overlay = true
|
||||
positioning = "mixed"
|
||||
|
||||
[presets.demo-camera-showcase]
|
||||
description = "Demo: Camera mode showcase"
|
||||
@@ -85,6 +99,7 @@ camera_speed = 0.5
|
||||
viewport_width = 80
|
||||
viewport_height = 24
|
||||
enable_message_overlay = true
|
||||
positioning = "mixed"
|
||||
|
||||
[presets.test-message-overlay]
|
||||
description = "Test: Message overlay with ntfy integration"
|
||||
@@ -96,6 +111,7 @@ camera_speed = 0.1
|
||||
viewport_width = 80
|
||||
viewport_height = 24
|
||||
enable_message_overlay = true
|
||||
positioning = "mixed"
|
||||
|
||||
# ============================================
|
||||
# SENSOR CONFIGURATION
|
||||
|
||||
151
scripts/demo-lfo-effects.py
Normal file
151
scripts/demo-lfo-effects.py
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user