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,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gold Latin mottoes scrolling continuously around the portal ring."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from leds.bpm_limits import band, resolve_pulse, resolve_scroll
|
||||
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
|
||||
from leds.text import CHAR_HEIGHT, draw_text, text_width
|
||||
|
||||
MOTTOES = (
|
||||
"AVE",
|
||||
"SPQR",
|
||||
"VENI VIDI VICI",
|
||||
"CARPE DIEM",
|
||||
"TEMPUS FUGIT",
|
||||
"MEMENTO MORI",
|
||||
"ALEA IACTA EST",
|
||||
)
|
||||
SEPARATOR = " * "
|
||||
DEFAULT_TEXT = SEPARATOR.join(MOTTOES)
|
||||
|
||||
|
||||
class Inscription(Pattern):
|
||||
id = "inscription"
|
||||
label = "Inscription"
|
||||
fps = 28
|
||||
colors = {
|
||||
"ink": Color("#e0b44a"),
|
||||
"stone": Color("#2a1c10"),
|
||||
}
|
||||
params = {
|
||||
"text": Param(
|
||||
DEFAULT_TEXT,
|
||||
kind="text",
|
||||
max_length=160,
|
||||
label="Text",
|
||||
placeholder="AVE * SPQR",
|
||||
),
|
||||
"text_brightness": Param(1.0, min=0.0, max=1.0, step=0.05, label="Text brightness"),
|
||||
"bg_brightness": Param(0.55, min=0.0, max=1.0, step=0.05, label="Background"),
|
||||
}
|
||||
|
||||
def draw(self, surface, t, params):
|
||||
w, h = size(surface)
|
||||
extras = params["extras"]
|
||||
pulse = resolve_pulse(params, t, fallback_bpm=96, attack=0.18)
|
||||
bass = band(params, "bass")
|
||||
energy = band(params, "energy")
|
||||
ink = dim(
|
||||
params["colors"]["ink"],
|
||||
float(extras["text_brightness"]) * (0.85 + 0.15 * max(pulse, bass)),
|
||||
)
|
||||
stone = params["colors"]["stone"]
|
||||
bg_b = float(extras["bg_brightness"])
|
||||
banner = str(extras.get("text") or DEFAULT_TEXT).strip() or DEFAULT_TEXT
|
||||
x_offset, ring, _ = ring_metrics(params, w)
|
||||
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
gx = global_x(x, params, w)
|
||||
grain = 0.55 + 0.45 * math.sin(gx * 0.37 + y * 1.1)
|
||||
level = grain * (0.92 + 0.08 * energy)
|
||||
set_pixel(surface, x, y, dim(stone, level * bg_b))
|
||||
|
||||
span = text_width(banner)
|
||||
if span <= 0:
|
||||
return
|
||||
loop = max(span + max(ring // 8, 8), ring)
|
||||
scroll = resolve_scroll(
|
||||
params,
|
||||
t,
|
||||
fallback_bpm=96,
|
||||
pixels_per_beat=max(2.0, ring / 8.0),
|
||||
loop=loop,
|
||||
)
|
||||
origin = -scroll - x_offset
|
||||
y0 = max(0, (h - CHAR_HEIGHT) // 2)
|
||||
draw_text(surface, banner, origin, y0, ink)
|
||||
draw_text(surface, banner, origin + loop, y0, ink)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_cli(Inscription)
|
||||
Reference in New Issue
Block a user