refactor: rename project to LedGrab, split HA integration into separate repo
Lint & Test / test (push) Successful in 1m56s
Lint & Test / test (push) Successful in 1m56s
- Rename Python package: wled_controller -> ledgrab - Rename env var prefix: WLED_ -> LEDGRAB_ (with auto-migration for old vars) - Rename localStorage key: wled_api_key -> ledgrab_api_key (with migration) - Rename HA integration domain: wled_screen_controller -> ledgrab - Update all imports, build scripts, Docker, installer, config, docs - Remove HA integration (moved to ledgrab-haos-integration repo) - Remove hacs.json (belongs in HA repo now) - Add startup warning for users with old WLED_ env vars - All tests pass (715/715), ruff clean, tsc clean, frontend builds
This commit is contained in:
@@ -1,109 +0,0 @@
|
||||
# math_wave Color Strip Source — Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
A new CSS type that generates LED colors from configurable mathematical wave functions. Each LED position gets a wave value based on spatial position and time, mapped to a color via a gradient palette. Supports multiple superimposed wave layers, sync clocks, and bindable parameters.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Waveform types: sine, triangle, sawtooth, square
|
||||
- Parameters per wave layer: waveform, frequency, amplitude, phase, offset
|
||||
- Global parameters: speed (bindable), gradient_id (color mapping)
|
||||
- Wave superposition: list of wave layers combined additively
|
||||
- Spatial dimension: wave value depends on LED position (0.0-1.0) + time
|
||||
- Sync clock integration for time parameter
|
||||
- Color mapping: combined wave output (0.0-1.0) mapped through a gradient
|
||||
|
||||
## Phase 1: Storage Model
|
||||
|
||||
**File: `server/src/wled_controller/storage/color_strip_source.py`**
|
||||
- Add `MathWaveColorStripSource` dataclass after `GameEventColorStripSource`
|
||||
- Fields:
|
||||
- `waves: list` — default `[{"waveform": "sine", "frequency": 1.0, "amplitude": 1.0, "phase": 0.0, "offset": 0.0}]`
|
||||
- `speed: BindableFloat` — default 1.0
|
||||
- `gradient_id: Optional[str]` — references Gradient entity
|
||||
- Implement `to_dict`, `from_dict`, `create_from_kwargs`, `apply_update` (follow `CandlelightColorStripSource` pattern)
|
||||
- Valid waveforms: `{"sine", "triangle", "sawtooth", "square"}`
|
||||
- Add `"math_wave": MathWaveColorStripSource` to `_SOURCE_TYPE_MAP`
|
||||
|
||||
## Phase 2: Stream Implementation
|
||||
|
||||
**File: `server/src/wled_controller/core/processing/math_wave_stream.py`** (new)
|
||||
- Class `MathWaveColorStripStream(ColorStripStream)` following `CandlelightColorStripStream` pattern
|
||||
- Key methods: `__init__`, `_update_from_source`, `configure`, `start`, `stop`, `get_latest_colors`, `update_source`, `set_clock`, `set_gradient_store`
|
||||
- Animation loop (`_animate_loop`):
|
||||
- Get `t` from clock (if set) or wall clock
|
||||
- For each LED at normalized position `p = i / (N-1)`:
|
||||
- Sum all wave layers: `sum += amplitude * waveform(2*pi*frequency*(p + speed*t) + phase) + offset`
|
||||
- Clamp result to [0.0, 1.0]
|
||||
- Map per-LED values to RGB via gradient LUT
|
||||
- Waveform functions (vectorized with numpy):
|
||||
- `sine`: `0.5 + 0.5 * np.sin(x)`
|
||||
- `triangle`: `2.0 * np.abs(np.mod(x / (2*pi), 1.0) - 0.5)`
|
||||
- `sawtooth`: `np.mod(x / (2*pi), 1.0)`
|
||||
- `square`: `(np.sin(x) >= 0).astype(float)`
|
||||
- Double-buffering pattern (same as candlelight)
|
||||
- Use `self.resolve("speed", self._speed)` for bindable speed
|
||||
- Gradient resolution via `set_gradient_store` pattern
|
||||
|
||||
## Phase 3: API Integration
|
||||
|
||||
**File: `server/src/wled_controller/api/schemas/color_strip_sources.py`**
|
||||
- Add `MathWaveCSSResponse`, `MathWaveCSSCreate`, `MathWaveCSSUpdate`
|
||||
- Add to `ColorStripSourceResponse`, `ColorStripSourceCreate`, `ColorStripSourceUpdate` unions
|
||||
|
||||
**File: `server/src/wled_controller/core/processing/color_strip_stream_manager.py`**
|
||||
- Import `MathWaveColorStripStream`
|
||||
- Add `"math_wave": MathWaveColorStripStream` to `_SIMPLE_STREAM_MAP`
|
||||
- Existing `set_gradient_store` and `_inject_clock` injection handles it automatically
|
||||
|
||||
## Phase 4: Frontend
|
||||
|
||||
**File: `server/src/wled_controller/static/js/types.ts`**
|
||||
- Add `'math_wave'` to `CSSSourceType` union
|
||||
|
||||
**File: `server/src/wled_controller/static/js/core/icons.ts`**
|
||||
- Add `math_wave: _svg(P.activity)` to `_colorStripTypeIcons`
|
||||
|
||||
**File: `server/src/wled_controller/templates/modals/css-editor.html`**
|
||||
- Add `<option value="math_wave">` to type select
|
||||
- Add `<div id="css-editor-math-wave-section">` with:
|
||||
- Gradient picker (EntitySelect)
|
||||
- Speed (BindableScalarWidget)
|
||||
- Wave layers list (dynamic rows: waveform IconSelect, frequency/amplitude/phase/offset inputs, add/remove buttons)
|
||||
|
||||
**File: `server/src/wled_controller/static/js/features/color-strips.ts`**
|
||||
- Add to `CSS_TYPE_KEYS`, `CSS_SECTION_MAP`, `CSS_TYPE_SETUP`, `NON_PICTURE_TYPES`
|
||||
- Add to `clockTypes` array in `saveCSSEditor`
|
||||
- Add type handler: `load(css)`, `reset()`, `getPayload(name)`
|
||||
- Add card renderer showing wave count, gradient swatch, speed, clock badge
|
||||
- Add wave layer management: `_renderMathWaveRow`, `addMathWaveLayer`, `removeMathWaveLayer`
|
||||
|
||||
**Files: `en.json`, `ru.json`, `zh.json`**
|
||||
- Add i18n keys for type name, description, all field labels, waveform names
|
||||
|
||||
## Phase 5: Testing
|
||||
|
||||
**File: `server/tests/core/test_math_wave_stream.py`** (new)
|
||||
- Test wave functions produce expected values at known inputs
|
||||
- Test single wave spatial pattern
|
||||
- Test wave superposition
|
||||
- Test gradient color mapping
|
||||
- Test clock integration
|
||||
- Test `update_source` hot-update
|
||||
- Test `configure` auto-sizing
|
||||
|
||||
**File: `server/tests/e2e/test_color_strip_flow.py`**
|
||||
- Add `test_math_wave_crud` to lifecycle tests
|
||||
|
||||
**Storage model tests:**
|
||||
- Test `from_dict` roundtrip
|
||||
- Test `create_from_kwargs` with valid/invalid waveforms
|
||||
- Test `apply_update`
|
||||
- Test `_SOURCE_TYPE_MAP` dispatch
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
- **Wave layer UI complexity** — Follow existing composite layers / game event mappings patterns
|
||||
- **Performance with many layers** — Vectorize with numpy; cap max wave layers to 8
|
||||
- **Gradient resolution** — Stream manager already injects `set_gradient_store` automatically
|
||||
@@ -1,151 +0,0 @@
|
||||
# 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 frame
|
||||
- `beat_intensity: float` — 0.0-1.0
|
||||
- `beat_phase: float` — 0.0-1.0 position in beat cycle
|
||||
- `energy: float` — smoothed RMS 0.0-1.0
|
||||
- `energy_delta: float` — rate of change
|
||||
- `bass_energy, mid_energy, treble_energy: float` — 0.0-1.0
|
||||
- `drop_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` + compute `beat_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 `MusicSyncColorStripSource` dataclass after `AudioColorStripSource`
|
||||
- Fields:
|
||||
- `visualization_mode: str` — default `"pulse_on_beat"`
|
||||
- `audio_source_id: str` — references AudioSource
|
||||
- `sensitivity: BindableFloat` — default 1.0
|
||||
- `smoothing: BindableFloat` — default 0.3
|
||||
- `palette: str` — default `"rainbow"`
|
||||
- `gradient_id: Optional[str]`
|
||||
- `color: BindableColor` — primary color
|
||||
- `color_secondary: BindableColor` — for two-color modes
|
||||
- `beat_decay: BindableFloat` — default 0.15
|
||||
- `led_count: int` — 0 = auto-size
|
||||
- `mirror: bool`
|
||||
- Add `"music_sync": MusicSyncColorStripSource` to `_SOURCE_TYPE_MAP`
|
||||
|
||||
## Phase 3: Stream Implementation
|
||||
|
||||
**File: `server/src/wled_controller/core/processing/music_sync_stream.py`** (new)
|
||||
- Class `MusicSyncColorStripStream(ColorStripStream)` following `AudioColorStripStream` pattern
|
||||
- Constructor: accept source + audio_capture_manager + stores. Create `MusicAnalyzer` instance
|
||||
- `start()`: Acquire audio stream, start background thread
|
||||
- `stop()`: Release audio stream, stop thread
|
||||
- `_animate_loop()`:
|
||||
1. Get `AudioAnalysis` from audio stream
|
||||
2. Apply audio filter pipeline (if any)
|
||||
3. Feed to `MusicAnalyzer.update()` → `MusicFeatures`
|
||||
4. Dispatch to visualization renderer
|
||||
5. Double-buffer output
|
||||
|
||||
### 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_sync` branch in `acquire()` (same pattern as `audio` branch)
|
||||
- Update `refresh_audio_filter_pipelines()` to include `MusicSyncColorStripStream`
|
||||
|
||||
## 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_dict` roundtrip
|
||||
- CRUD via test client
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
- **BPM accuracy** — Median-of-recent-IBIs is robust for dance/electronic. For ambient, BPM noisy but `energy_gradient` and `spectrum_bands` don'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** — `MusicAnalyzer` owned exclusively by one stream thread, no shared access
|
||||
|
||||
## Dependency Order
|
||||
|
||||
`math_wave` should be implemented first (simpler, no audio dependency), then `music_sync`.
|
||||
Reference in New Issue
Block a user