"""Pulse (breathing) animation — brightness oscillates via sine wave.""" import math from lightsync.animations.base import AnimationBase class PulseAnimation(AnimationBase): """Smooth breathing effect — brightness oscillates via sine wave. speed=1.0 -> 0.1s period (fast), speed=0.0 -> 5.0s period (slow). """ def __init__( self, color: tuple[int, int, int] = (255, 255, 255), speed: float = 0.5, min_brightness: int = 0, max_brightness: int = 255, white: int = 0, ) -> None: self.color = tuple(color) # type: ignore[arg-type] self.speed = speed self.min_brightness = min_brightness self.max_brightness = max_brightness self.white = white def render(self, t: float, led_count: int) -> list[tuple]: # Map speed to period: speed=1.0 -> 0.1s, speed=0.0 -> 5.0s period = 0.1 + (1.0 - self.speed) * 4.9 # Sine oscillation: 0.0 to 1.0 over one period brightness = ( self.min_brightness + (self.max_brightness - self.min_brightness) * (0.5 + 0.5 * math.sin(2 * math.pi * t / period)) ) scale = brightness / 255.0 r = min(255, max(0, int(self.color[0] * scale))) g = min(255, max(0, int(self.color[1] * scale))) b = min(255, max(0, int(self.color[2] * scale))) pixel = (r, g, b) return [pixel] * led_count @classmethod def from_params(cls, params: dict) -> "PulseAnimation": return cls( color=tuple(params.get("color", [255, 255, 255])), # type: ignore[arg-type] speed=params.get("speed", 0.5), min_brightness=params.get("min_brightness", 0), max_brightness=params.get("max_brightness", 255), white=params.get("white", 0), )