import utime class Pulse: def __init__(self, driver): self.driver = driver def run(self, preset): # Get colors from preset colors = preset.c if not colors: colors = [(255, 255, 255)] bg_base = preset.background_or(colors) self.driver.fill(self.driver.apply_brightness(bg_base, preset.b)) color_index = self.driver.step % max(1, len(colors)) if not preset.a: # Manual / beat trigger: each select restarts this generator and resets # cycle_start below. Advancing step here makes each beat the next colour # without requiring a full wall-clock cycle between beats. nclr = max(1, len(colors)) self.driver.step = (color_index + 1) % nclr cycle_start = utime.ticks_ms() # State machine based pulse using a single generator loop while True: bg_color = self.driver.apply_brightness(bg_base, preset.b) # Read current timing parameters from preset attack_ms = max(0, int(preset.n1)) # Attack time in ms hold_ms = max(0, int(preset.n2)) # Hold time in ms decay_ms = max(0, int(preset.n3)) # Decay time in ms delay_ms = max(0, int(preset.d)) total_ms = attack_ms + hold_ms + decay_ms + delay_ms if total_ms <= 0: total_ms = 1 now = utime.ticks_ms() elapsed = utime.ticks_diff(now, cycle_start) base_color = colors[color_index % len(colors)] if elapsed < attack_ms and attack_ms > 0: # Attack: fade 0 -> 1 factor = elapsed / attack_ms color = tuple(int(c * factor) for c in base_color) self.driver.fill(self.driver.apply_brightness(color, preset.b)) elif elapsed < attack_ms + hold_ms: # Hold: full brightness self.driver.fill(self.driver.apply_brightness(base_color, preset.b)) elif elapsed < attack_ms + hold_ms + decay_ms and decay_ms > 0: # Decay: fade 1 -> 0 dec_elapsed = elapsed - attack_ms - hold_ms factor = max(0.0, 1.0 - (dec_elapsed / decay_ms)) color = tuple(int(c * factor) for c in base_color) self.driver.fill(self.driver.apply_brightness(color, preset.b)) elif elapsed < total_ms: # Delay phase: LEDs off between pulses self.driver.fill(bg_color) else: # End of cycle: auto advances colour and loops; manual already # advanced step at run start for the next beat. if not preset.a: break color_index = (color_index + 1) % max(1, len(colors)) self.driver.step = color_index cycle_start = now yield continue # Yield once per tick yield