Mathematical wave generator that produces per-LED colors from configurable waveform layers (sine, triangle, sawtooth, square) with superposition, mapped through a gradient palette. Supports sync clocks, bindable speed, and up to 8 wave layers. - Storage model with wave validation and apply_update - Numpy-vectorized stream with gradient LUT color mapping - API schemas (create/update/response) and route registration - Frontend editor with dynamic wave layer rows, gradient picker, speed widget, and IconSelect waveform selectors - i18n in en/ru/zh
7.1 KiB
music_sync Color Strip Source — Implementation Plan
Overview
A higher-level music-reactive CSS that provides semantic audio analysis (BPM detection, beat tracking, energy envelope, drop detection, frequency band energy) and multiple visualization modes. Builds on existing AudioCaptureManager and AudioAnalysis infrastructure. No new external dependencies — uses numpy-only analysis.
Requirements
- BPM estimation from real-time audio
- Beat onset detection with configurable threshold
- Smoothed RMS energy envelope with attack/release
- Drop detection: energy drops/buildups
- Frequency band energy: bass (20-250 Hz), mid (250-4k Hz), treble (4k-20k Hz)
- Four visualization modes:
pulse_on_beat,energy_gradient,spectrum_bands,strobe_on_drop - Uses existing audio engine infrastructure (no new audio capture code)
- No external dependencies beyond numpy
Phase 1: Music Analysis Engine
File: server/src/wled_controller/core/audio/music_analyzer.py (new)
1.1 MusicFeatures dataclass (frozen=True)
bpm: float— estimated BPM (0 if unknown)beat: bool— beat detected this framebeat_intensity: float— 0.0-1.0beat_phase: float— 0.0-1.0 position in beat cycleenergy: float— smoothed RMS 0.0-1.0energy_delta: float— rate of changebass_energy, mid_energy, treble_energy: float— 0.0-1.0drop_state: str— "idle"|"buildup"|"drop"|"recovery"drop_intensity: float— 0.0-1.0
1.2 MusicAnalyzer class
- State: rolling energy buffer (~4s at ~43 Hz = ~172 samples), beat history timestamps (last 30), smoothed band energies, BPM estimate, drop state machine
update(analysis: AudioAnalysis) -> MusicFeatures: main entry point- BPM estimation: Track beat timestamps, compute median inter-beat interval, exponential smoothing, clamp 40-220 BPM
- Beat tracking: Pass through
AudioAnalysis.beat+ computebeat_phase(position in current beat cycle) - Energy envelope: Smoothed RMS with configurable attack/release
- Drop detection: State machine:
idle -> buildup(energy rising steadily 1-2s),buildup -> drop(energy drops >50% within 100ms),drop -> recovery(after 500ms),recovery -> idle - Frequency bands: Sum spectrum bins into 3 bands from 64-band spectrum
Phase 2: Storage Model
File: server/src/wled_controller/storage/color_strip_source.py
- Add
MusicSyncColorStripSourcedataclass afterAudioColorStripSource - Fields:
visualization_mode: str— default"pulse_on_beat"audio_source_id: str— references AudioSourcesensitivity: BindableFloat— default 1.0smoothing: BindableFloat— default 0.3palette: str— default"rainbow"gradient_id: Optional[str]color: BindableColor— primary colorcolor_secondary: BindableColor— for two-color modesbeat_decay: BindableFloat— default 0.15led_count: int— 0 = auto-sizemirror: bool
- Add
"music_sync": MusicSyncColorStripSourceto_SOURCE_TYPE_MAP
Phase 3: Stream Implementation
File: server/src/wled_controller/core/processing/music_sync_stream.py (new)
- Class
MusicSyncColorStripStream(ColorStripStream)followingAudioColorStripStreampattern - Constructor: accept source + audio_capture_manager + stores. Create
MusicAnalyzerinstance start(): Acquire audio stream, start background threadstop(): Release audio stream, stop thread_animate_loop():- Get
AudioAnalysisfrom audio stream - Apply audio filter pipeline (if any)
- Feed to
MusicAnalyzer.update()→MusicFeatures - Dispatch to visualization renderer
- Double-buffer output
- Get
Visualization Renderers
pulse_on_beat: Full-strip flash on beat with exponential decay. Between beats: sine-wave pulsing synced to BPM. Color from palette indexed by beat_intensity.energy_gradient: Maps bass→warm, treble→cool. Overall brightness from energy. Gradient scrolls with beat_phase.spectrum_bands: 3 zones (bass/mid/treble), each fills proportionally to band energy. Mirror mode: bass center, treble edges.strobe_on_drop: Idle=gentle breathing. Buildup=increasing pulse. Drop=rapid strobe at 10 Hz. Recovery=fade back.
Phase 4: API Integration
File: server/src/wled_controller/api/schemas/color_strip_sources.py
- Add
MusicSyncCSSResponse,MusicSyncCSSCreate,MusicSyncCSSUpdate - Add to all three union types
File: server/src/wled_controller/api/routes/color_strip_sources.py
- Import + add to
_RESPONSE_MAP
File: server/src/wled_controller/core/processing/color_strip_stream_manager.py
- Add
music_syncbranch inacquire()(same pattern asaudiobranch) - Update
refresh_audio_filter_pipelines()to includeMusicSyncColorStripStream
Phase 5: Frontend
File: server/src/wled_controller/static/js/core/icons.ts
- Add
music_sync: _svg(P.radio)icon
File: server/src/wled_controller/templates/modals/css-editor.html
- Add
<div id="css-editor-music-sync-section">with:- Visualization mode selector (IconSelect)
- Audio source dropdown
- Sensitivity, Smoothing, Beat Decay (BindableScalarWidget containers)
- Palette/gradient selector (EntitySelect)
- Primary + Secondary color (BindableColorWidget)
- Mirror checkbox
File: server/src/wled_controller/static/js/features/color-strips-music-sync.ts (new, extracted)
- Editor logic, widget factories, card renderer
File: server/src/wled_controller/static/js/features/color-strips.ts
- Register type in
CSS_TYPE_KEYS,CSS_SECTION_MAP,CSS_TYPE_SETUP,NON_PICTURE_TYPES - Import and register from
color-strips-music-sync.ts
Files: en.json, ru.json, zh.json
- Add i18n keys for type, visualization modes, all field labels
Phase 6: Testing
File: server/tests/core/audio/test_music_analyzer.py (new)
- BPM estimation from regular beats (within 5 BPM accuracy)
- BPM handles no beats gracefully
- Beat phase progression
- Energy envelope attack/release
- Frequency band splitting
- Drop detection state machine transitions
- No false drops on steady signal
File: server/tests/core/processing/test_music_sync_stream.py (new)
- Stream lifecycle (start/stop)
- Produces valid colors
- Hot-update parameters
- Auto-size from device
- All 4 visualization modes produce valid (n,3) uint8 arrays
- Mirror mode symmetry
Storage + API tests:
from_dictroundtrip- CRUD via test client
Risks & Mitigations
- BPM accuracy — Median-of-recent-IBIs is robust for dance/electronic. For ambient, BPM noisy but
energy_gradientandspectrum_bandsdon't depend on BPM - Drop detection false positives — Require minimum energy threshold + sustained increase before buildup state
- Frontend file size — Extract to
color-strips-music-sync.ts(following composite/notification pattern) - Strobe photosensitivity — Cap at 10 Hz (below 15-25 Hz danger zone), add UI warning
- Thread safety —
MusicAnalyzerowned exclusively by one stream thread, no shared access
Dependency Order
math_wave should be implemented first (simpler, no audio dependency), then music_sync.