Files
portal/src/patterns/inscription.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

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)