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
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user