First upload, 18 controller version
This commit is contained in:
276
app/patterns/base.py
Normal file
276
app/patterns/base.py
Normal file
@@ -0,0 +1,276 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable
|
||||
|
||||
from app.config.models import InfinityMirrorConfig, TileConfig
|
||||
from app.core.types import PatternParameters, TilePatternSample, clamp
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ParameterSpec:
|
||||
key: str
|
||||
label: str
|
||||
kind: str
|
||||
minimum: float = 0.0
|
||||
maximum: float = 1.0
|
||||
step: float = 0.01
|
||||
reset_value: float | None = None
|
||||
options: tuple[tuple[str, str], ...] = ()
|
||||
tooltip: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PatternDescriptor:
|
||||
pattern_id: str
|
||||
display_name: str
|
||||
description: str
|
||||
supported_parameters: tuple[str, ...]
|
||||
accent_hex: str = "#4D7CFF"
|
||||
temporal_profile: str = "smooth"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PatternContext:
|
||||
config: InfinityMirrorConfig
|
||||
params: PatternParameters
|
||||
time_s: float
|
||||
tempo_bpm: float = 60.0
|
||||
tempo_phase: float = 0.0
|
||||
|
||||
@property
|
||||
def rows(self) -> int:
|
||||
return self.config.logical_display.rows
|
||||
|
||||
@property
|
||||
def cols(self) -> int:
|
||||
return self.config.logical_display.cols
|
||||
|
||||
def sorted_tiles(self) -> list[TileConfig]:
|
||||
return self.config.sorted_tiles()
|
||||
|
||||
@property
|
||||
def tempo_hz(self) -> float:
|
||||
return max(0.05, float(self.tempo_bpm) / 60.0)
|
||||
|
||||
@property
|
||||
def tempo_multiplier(self) -> float:
|
||||
return clamp(float(self.params.tempo_multiplier), 0.25, 8.0)
|
||||
|
||||
@property
|
||||
def pattern_tempo_hz(self) -> float:
|
||||
return self.tempo_hz * self.tempo_multiplier
|
||||
|
||||
@property
|
||||
def pattern_tempo_phase(self) -> float:
|
||||
return self.tempo_phase * self.tempo_multiplier
|
||||
|
||||
|
||||
class BasePattern(ABC):
|
||||
descriptor: PatternDescriptor
|
||||
|
||||
@abstractmethod
|
||||
def render(self, context: PatternContext) -> dict[str, TilePatternSample]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class PatternRegistry:
|
||||
def __init__(self, patterns: Iterable[BasePattern]) -> None:
|
||||
self._patterns = {pattern.descriptor.pattern_id: pattern for pattern in patterns}
|
||||
|
||||
def get(self, pattern_id: str) -> BasePattern:
|
||||
return self._patterns[pattern_id]
|
||||
|
||||
def descriptors(self) -> list[PatternDescriptor]:
|
||||
return [self._patterns[key].descriptor for key in sorted(self._patterns)]
|
||||
|
||||
def ids(self) -> list[str]:
|
||||
return list(sorted(self._patterns))
|
||||
|
||||
|
||||
COMMON_PARAMETER_SPECS: dict[str, ParameterSpec] = {
|
||||
"brightness": ParameterSpec("brightness", "Brightness", "slider", 0.0, 2.0, 0.01, reset_value=1.0, tooltip="Pattern output level."),
|
||||
"fade": ParameterSpec("fade", "Smoothing", "slider", 0.0, 1.0, 0.01, reset_value=0.0, tooltip="Higher values create softer transitions."),
|
||||
"tempo_multiplier": ParameterSpec(
|
||||
"tempo_multiplier",
|
||||
"Tempo Multiplier",
|
||||
"slider",
|
||||
0.25,
|
||||
8.0,
|
||||
0.05,
|
||||
reset_value=1.0,
|
||||
tooltip="Scales this pattern relative to the global BPM.",
|
||||
),
|
||||
"direction": ParameterSpec(
|
||||
"direction",
|
||||
"Direction",
|
||||
"combo",
|
||||
options=(
|
||||
("left_to_right", "Left to Right"),
|
||||
("right_to_left", "Right to Left"),
|
||||
("top_to_bottom", "Top to Bottom"),
|
||||
("bottom_to_top", "Bottom to Top"),
|
||||
("outward", "Outward"),
|
||||
("inward", "Inward"),
|
||||
),
|
||||
tooltip="Primary motion direction.",
|
||||
),
|
||||
"checker_mode": ParameterSpec(
|
||||
"checker_mode",
|
||||
"Checker Mode",
|
||||
"combo",
|
||||
options=(
|
||||
("classic", "Classic"),
|
||||
("diagonal", "Diagonal Split"),
|
||||
("checkerd", "Checkerd"),
|
||||
),
|
||||
tooltip="Classic checker, diagonal half-pixels, or diagonal flip animation.",
|
||||
),
|
||||
"scan_style": ParameterSpec(
|
||||
"scan_style",
|
||||
"Scan Style",
|
||||
"combo",
|
||||
options=(
|
||||
("line", "Line"),
|
||||
("bands", "Bands"),
|
||||
),
|
||||
tooltip="Single moving scan band or repeating band pattern.",
|
||||
),
|
||||
"angle": ParameterSpec(
|
||||
"angle",
|
||||
"Angle",
|
||||
"angle",
|
||||
minimum=0.0,
|
||||
maximum=315.0,
|
||||
step=45.0,
|
||||
tooltip="Scan direction in 45 degree steps.",
|
||||
),
|
||||
"on_width": ParameterSpec(
|
||||
"on_width",
|
||||
"On Width",
|
||||
"slider",
|
||||
0.1,
|
||||
2.0,
|
||||
0.05,
|
||||
tooltip="Length of the active scan window.",
|
||||
),
|
||||
"off_width": ParameterSpec(
|
||||
"off_width",
|
||||
"Off Width",
|
||||
"slider",
|
||||
0.1,
|
||||
2.0,
|
||||
0.05,
|
||||
tooltip="Gap between active scan windows.",
|
||||
),
|
||||
"band_thickness": ParameterSpec(
|
||||
"band_thickness",
|
||||
"Band Thickness",
|
||||
"slider",
|
||||
0.1,
|
||||
2.0,
|
||||
0.05,
|
||||
tooltip="Visible thickness of the lit band inside the active window.",
|
||||
),
|
||||
"flip_horizontal": ParameterSpec(
|
||||
"flip_horizontal",
|
||||
"Flip Horizontal",
|
||||
"checkbox",
|
||||
tooltip="Mirror scan evaluation left-to-right for installation alignment.",
|
||||
),
|
||||
"flip_vertical": ParameterSpec(
|
||||
"flip_vertical",
|
||||
"Flip Vertical",
|
||||
"checkbox",
|
||||
tooltip="Mirror scan evaluation top-to-bottom for installation alignment.",
|
||||
),
|
||||
"strobe_mode": ParameterSpec(
|
||||
"strobe_mode",
|
||||
"Strobe Mode",
|
||||
"combo",
|
||||
options=(
|
||||
("global", "Global"),
|
||||
("random_pixels", "Random Pixels"),
|
||||
("random_leds", "Random LEDs"),
|
||||
),
|
||||
tooltip="Whole-wall strobe, grouped random pixel blocks, or fully shuffled per-LED timing.",
|
||||
),
|
||||
"stopwatch_mode": ParameterSpec(
|
||||
"stopwatch_mode",
|
||||
"Stopwatch Mode",
|
||||
"combo",
|
||||
options=(
|
||||
("sync", "Sync"),
|
||||
("random", "Random"),
|
||||
),
|
||||
tooltip="Run all tiles together or with deterministic random offsets.",
|
||||
),
|
||||
"color_mode": ParameterSpec(
|
||||
"color_mode",
|
||||
"Color Mode",
|
||||
"combo",
|
||||
options=(
|
||||
("dual", "Dual"),
|
||||
("palette", "Palette"),
|
||||
("mono", "Mono"),
|
||||
("complementary", "Complementary"),
|
||||
("random_colors", "Random Colors"),
|
||||
("custom_random", "Custom Random"),
|
||||
),
|
||||
tooltip="How colors are chosen for the pattern.",
|
||||
),
|
||||
"primary_color": ParameterSpec("primary_color", "Primary Color", "color", tooltip="Main color."),
|
||||
"secondary_color": ParameterSpec("secondary_color", "Secondary Color", "color", tooltip="Secondary color."),
|
||||
"palette": ParameterSpec("palette", "Palette", "combo", tooltip="Palette for palette-driven patterns."),
|
||||
"symmetry": ParameterSpec(
|
||||
"symmetry",
|
||||
"Mirror",
|
||||
"combo",
|
||||
options=(("none", "None"), ("horizontal", "Horizontal"), ("vertical", "Vertical"), ("both", "Both")),
|
||||
tooltip="Mirrors pattern coordinates around the center.",
|
||||
),
|
||||
"center_pulse_mode": ParameterSpec(
|
||||
"center_pulse_mode",
|
||||
"Pulse Mode",
|
||||
"combo",
|
||||
options=(
|
||||
("expand", "Expand"),
|
||||
("reverse", "Reverse"),
|
||||
("outline", "Outline"),
|
||||
("outline_reverse", "Outline Reverse"),
|
||||
),
|
||||
tooltip="Expand from the center, run inward, or use only the rectangular outline rings.",
|
||||
),
|
||||
"block_size": ParameterSpec("block_size", "Block Size", "slider", 0.1, 6.0, 0.1, tooltip="Width of active bands."),
|
||||
"pixel_group_size": ParameterSpec(
|
||||
"pixel_group_size",
|
||||
"Pixel Group",
|
||||
"slider",
|
||||
1.0,
|
||||
5.0,
|
||||
1.0,
|
||||
reset_value=1.0,
|
||||
tooltip="Treat several adjacent LEDs as one strobe pixel.",
|
||||
),
|
||||
"strobe_duty_cycle": ParameterSpec(
|
||||
"strobe_duty_cycle",
|
||||
"Duty / Density",
|
||||
"slider",
|
||||
0.005,
|
||||
0.98,
|
||||
0.005,
|
||||
reset_value=0.5,
|
||||
tooltip="Controls strobe on-time or sparkle fill density depending on the pattern.",
|
||||
),
|
||||
"randomness": ParameterSpec(
|
||||
"randomness",
|
||||
"Randomness",
|
||||
"slider",
|
||||
0.0,
|
||||
1.5,
|
||||
0.01,
|
||||
reset_value=0.35,
|
||||
tooltip="Controls variation in patterns that intentionally use randomness.",
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user