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>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user