Files
ledgrab/plans/math-wave/PLAN.md
T
alexei.dolgolyov ace24715c8
Lint & Test / test (push) Has been cancelled
feat: add math_wave color strip source type
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
2026-04-05 00:41:07 +03:00

5.1 KiB

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