import utime class SegmentsTransition: def __init__(self, driver): self.driver = driver def run(self, preset): """ SegmentsTransition: fade from whatever is currently on the strips to a new static Segments layout defined by n1–n8 and c[0..3]. - Uses the existing strip buffers as the starting state. - Target state matches the Segments pattern: up to 4 colored bands along the logical reference strip, mapped to all physical strips. - Transition duration is taken from preset.d (ms), minimum 50ms. """ strips = self.driver.strips if not strips: while True: yield # Snapshot starting GRB buffers (already scaled by per-strip brightness) start_bufs = [bytes(strip.ar) for strip in strips] # Prepare target buffers (same length as each strip's ar) target_bufs = [bytearray(len(strip.ar)) for strip in strips] # Base colors (up to 4), missing ones default to black colors = list(preset.c) if getattr(preset, "c", None) else [] while len(colors) < 4: colors.append((0, 0, 0)) # Apply preset/global brightness once per color bright_colors = [ self.driver.apply_brightness(colors[0], preset.b), self.driver.apply_brightness(colors[1], preset.b), self.driver.apply_brightness(colors[2], preset.b), self.driver.apply_brightness(colors[3], preset.b), ] # Logical reference length for all strips (from scale_map[0]) ref_len = len(self.driver.scale_map[0]) if self.driver.scale_map else 0 if ref_len <= 0: # Fallback: nothing to do, just hold current state while True: yield # Helper to clamp and normalize a logical range [a, b] (inclusive) over ref_len. # Returns (start, end_exclusive) suitable for range(start, end_exclusive). def norm_range(a, b): a = int(a) b = int(b) if a > b: a, b = b, a if b < 0 or a >= ref_len: return None a = max(0, a) b = min(ref_len - 1, b) if a > b: return None return a, b + 1 raw_ranges = [ (getattr(preset, "n1", 0), getattr(preset, "n2", -1), bright_colors[0]), (getattr(preset, "n3", 0), getattr(preset, "n4", -1), bright_colors[1]), (getattr(preset, "n5", 0), getattr(preset, "n6", -1), bright_colors[2]), (getattr(preset, "n7", 0), getattr(preset, "n8", -1), bright_colors[3]), ] # Build target buffers using the same logical indexing idea as Segments for strip_idx, strip in enumerate(strips): bright = strip.brightness scale_map = self.driver.scale_map[strip_idx] buf = target_bufs[strip_idx] n_leds = strip.num_leds # Start from black everywhere for i in range(len(buf)): buf[i] = 0 # Apply each logical range to this strip for a, b, color in raw_ranges: rng = norm_range(a, b) if not rng: continue start, end = rng r, g, bl = color for logical_idx in range(start, end): if logical_idx < 0 or logical_idx >= len(scale_map): continue phys_idx = scale_map[logical_idx] if phys_idx < 0 or phys_idx >= n_leds: continue base = phys_idx * 3 if base + 2 >= len(buf): continue buf[base] = int(g * bright) buf[base + 1] = int(r * bright) buf[base + 2] = int(bl * bright) # Duration in ms for the whole transition (slower by default) # If preset.d is provided, use it; otherwise default to a slow 3000ms fade. raw_d = int(getattr(preset, "d", 3000) or 3000) duration = max(1000, raw_d) # enforce at least 1s for a clearly visible transition start_time = utime.ticks_ms() while True: now = utime.ticks_ms() elapsed = utime.ticks_diff(now, start_time) if elapsed >= duration: # Final frame: commit target buffers and hold, then update all strips together for strip, target in zip(strips, target_bufs): ar = strip.ar for i in range(len(ar)): ar[i] = target[i] self.driver.show_all() while True: yield # Interpolation factor in [0,1] factor = elapsed / duration inv = 1.0 - factor # Blend from start to target in GRB space per byte for idx, strip in enumerate(strips): start_buf = start_bufs[idx] target_buf = target_bufs[idx] ar = strip.ar for i in range(len(ar)): ar[i] = int(start_buf[i] * inv + target_buf[i] * factor) self.driver.show_all() yield