ace24715c8
Lint & Test / test (push) Has been cancelled
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
110 lines
5.1 KiB
Markdown
110 lines
5.1 KiB
Markdown
# 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
|