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>
94 lines
3.1 KiB
Python
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)
|