feat: add math_wave color strip source type
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
This commit is contained in:
2026-04-05 00:41:07 +03:00
parent edc6d27e2e
commit ace24715c8
16 changed files with 977 additions and 5 deletions
@@ -38,6 +38,7 @@ from wled_controller.api.schemas.color_strip_sources import (
GradientCSSResponse,
KeyColorsCSSResponse,
MappedCSSResponse,
MathWaveCSSResponse,
NotificationCSSResponse,
NotifyRequest,
PictureAdvancedCSSResponse,
@@ -68,6 +69,7 @@ from wled_controller.storage.color_strip_source import (
GradientColorStripSource,
KeyColorsColorStripSource,
MappedColorStripSource,
MathWaveColorStripSource,
NotificationColorStripSource,
PictureColorStripSource,
ProcessedColorStripSource,
@@ -244,6 +246,12 @@ _RESPONSE_MAP: dict = {
smoothing=s.smoothing.to_dict(),
brightness=s.brightness.to_dict(),
),
MathWaveColorStripSource: lambda s, kw: MathWaveCSSResponse(
**kw,
waves=s.waves,
speed=s.speed.to_dict(),
gradient_id=s.gradient_id,
),
}
@@ -227,6 +227,13 @@ class KeyColorsCSSResponse(_CSSResponseBase):
brightness: Any = Field(description="Brightness")
class MathWaveCSSResponse(_CSSResponseBase):
source_type: Literal["math_wave"] = "math_wave"
waves: List[dict] = Field(description="Wave layer definitions")
speed: Any = Field(description="Global speed multiplier (bindable)")
gradient_id: Optional[str] = Field(None, description="Gradient entity ID for color mapping")
ColorStripSourceResponse = Annotated[
Union[
Annotated[PictureCSSResponse, Tag("picture")],
@@ -245,6 +252,7 @@ ColorStripSourceResponse = Annotated[
Annotated[ProcessedCSSResponse, Tag("processed")],
Annotated[WeatherCSSResponse, Tag("weather")],
Annotated[KeyColorsCSSResponse, Tag("key_colors")],
Annotated[MathWaveCSSResponse, Tag("math_wave")],
],
Discriminator("source_type"),
]
@@ -406,6 +414,13 @@ class KeyColorsCSSCreate(_CSSCreateBase):
)
class MathWaveCSSCreate(_CSSCreateBase):
source_type: Literal["math_wave"] = "math_wave"
waves: Optional[List[dict]] = Field(None, description="Wave layer definitions")
speed: Any = Field(default=None, description="Global speed multiplier (bindable, 0.1-10.0)")
gradient_id: Optional[str] = Field(None, description="Gradient entity ID for color mapping")
ColorStripSourceCreate = Annotated[
Union[
Annotated[PictureCSSCreate, Tag("picture")],
@@ -424,6 +439,7 @@ ColorStripSourceCreate = Annotated[
Annotated[ProcessedCSSCreate, Tag("processed")],
Annotated[WeatherCSSCreate, Tag("weather")],
Annotated[KeyColorsCSSCreate, Tag("key_colors")],
Annotated[MathWaveCSSCreate, Tag("math_wave")],
],
Discriminator("source_type"),
]
@@ -585,6 +601,13 @@ class KeyColorsCSSUpdate(_CSSUpdateBase):
)
class MathWaveCSSUpdate(_CSSUpdateBase):
source_type: Literal["math_wave"] = "math_wave"
waves: Optional[List[dict]] = Field(None, description="Wave layer definitions")
speed: Any = Field(default=None, description="Global speed multiplier (bindable)")
gradient_id: Optional[str] = Field(None, description="Gradient entity ID for color mapping")
ColorStripSourceUpdate = Annotated[
Union[
Annotated[PictureCSSUpdate, Tag("picture")],
@@ -603,6 +626,7 @@ ColorStripSourceUpdate = Annotated[
Annotated[ProcessedCSSUpdate, Tag("processed")],
Annotated[WeatherCSSUpdate, Tag("weather")],
Annotated[KeyColorsCSSUpdate, Tag("key_colors")],
Annotated[MathWaveCSSUpdate, Tag("math_wave")],
],
Discriminator("source_type"),
]