Files
portal/src/patterns/rite.py
T
jimmyandCursor 3d444d1cb5 Ship the portal FastAPI controller with presets, sequences, and live audio.
Move the app under src/, add ring-spanning patterns with shared fonts and
beat-driven motion, and wire global audio settings into the web UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 00:59:03 +12:00

94 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Temple rite: beat-locked rings with energy racing the portal perimeter."""
from __future__ import annotations
import math
from leds.bpm_limits import (
band,
resolve_beat_index,
resolve_beat_phase,
resolve_pulse,
)
from leds.colors import dim
from leds.pattern import Color, Param, Pattern, run_cli, set_pixel, size
from leds.portal_span import global_x, ring_metrics
class Rite(Pattern):
id = "rite"
label = "Rite"
fps = 32
colors = {
"gold": Color("#f0c060"),
"blood": Color("#6a1208"),
"void": Color("#060408"),
}
params = {
"intensity": Param(0.85, min=0.2, max=1.0, step=0.05, label="Intensity"),
}
def draw(self, surface, t, params):
w, h = size(surface)
extras = params["extras"]
intensity = float(extras["intensity"])
gold = params["colors"]["gold"]
blood = params["colors"]["blood"]
void = params["colors"]["void"]
pulse = resolve_pulse(params, t, fallback_bpm=120, attack=0.18)
phase = resolve_beat_phase(params, t, fallback_bpm=120)
step = resolve_beat_index(params, t, fallback_bpm=120)
kick = band(params, "kick")
bass = band(params, "bass")
energy = band(params, "energy")
x_offset, ring, panel = ring_metrics(params, w)
cx = (w - 1) / 2.0
cy = (h - 1) / 2.0
radius = phase * (max(w, h) * 0.85)
hit = max(pulse, kick, bass * 0.7)
for y in range(h):
for x in range(w):
gx = global_x(x, params, w)
dist = math.hypot(x - cx, y - cy)
ring_glow = math.exp(-((dist - radius) ** 2) / 2.8)
floor = 0.12 + 0.1 * math.sin(gx * 0.08 + t * 0.7) + 0.08 * energy
base = dim(void, floor)
warm = dim(
blood,
(0.25 + 0.55 * hit) * intensity * (1.0 - dist / (max(w, h))),
)
rim = dim(gold, ring_glow * intensity * (0.4 + 0.6 * hit))
set_pixel(
surface,
x,
y,
(
min(255, base[0] + warm[0] + rim[0]),
min(255, base[1] + warm[1] + rim[1]),
min(255, base[2] + warm[2] + rim[2]),
),
)
courier = int(((step + phase) / 8.0) * ring) % max(ring, 1)
for dx in range(-2, 3):
gx = (courier + dx) % max(ring, 1)
local = gx - x_offset
if not (0 <= local < w):
continue
level = (1.0 - abs(dx) / 3.0) * (0.45 + 0.55 * hit) * intensity
for y in range(h):
edge = 1.0 - abs(y - cy) / (cy + 0.5)
set_pixel(surface, local, y, dim(gold, level * edge))
face = step % 5
if panel == face:
for x in range(w):
set_pixel(surface, x, 0, dim(gold, 0.2 + 0.4 * hit))
set_pixel(surface, x, h - 1, dim(gold, 0.15 + 0.35 * hit))
if __name__ == "__main__":
run_cli(Rite)