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>
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
#!/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)
|