Commit Graph
27 Commits
Author SHA1 Message Date
poprhythm bdd5993ed1 Shorten the RGB-split glitch bands
Reduced each band's height in GLITCH_STATE_POOL by roughly a third,
keeping top positions and shift/hue values unchanged.
2026-10-08 18:39:31 -04:00
poprhythm 1d5c86fb61 Vary glitch band positions across loop cycles; soften corner ticks
GLITCH_STATES was a fixed list of 4 band configs that every repeat cycle
played identically, so the jitter looked the same each time it looped.
Expanded to an 8-entry GLITCH_STATE_POOL and rotate through a different
4-state window each cycle (wrapping back to the start via modular
indexing, since Starlark list slicing doesn't wrap) -- verified via an
isolated two-cycle test that cycle 2's band positions genuinely differ
from cycle 1's, not just coincidentally similar-looking.

Also added alpha transparency to the corner tick color (#33ff9980) so
they read as a subtle HUD accent rather than a solid opaque overlay.
2026-10-08 17:17:49 -04:00
poprhythm 78621b3831 Loop the RGB-split glitch burst with pauses, instead of playing once
The jitter previously played its 4 states once (~1s total) then went
quiet for the rest of the 5s static window, since render.Sequence doesn't
loop. Now repeats the burst-then-pause pattern enough times to cover the
whole window: GLITCH_REPEATS is computed from STATIC_OVERLAY_SECONDS so
it still fully covers the window if either changes, rather than a
hardcoded repeat count silently falling short.

Verified via an isolated single-cycle test (burst of 4 states + 1 pause,
outside the main zoom animation) that the pause frame is genuinely clean
(no tint bands) -- pixlet collapses each multi-tick hold into a single
deduped output frame, which made an earlier attempt to verify this by
guessing raw frame indices unreliable; confirmed instead by exhaustively
inspecting all 5 of the isolated test's (correctly deduped) output
frames.
2026-10-08 16:53:30 -04:00
poprhythm 6cd5decdf5 Fix glitch effect bleeding into text column and wrong corner tick orientation
render.Padding grows its child's declared size by the pad amount rather
than nudging content within a fixed frame. _glitch_band's horizontal
shift_x relied on Padding without ever clipping the result back down to
ART_SLOT, so the RGB-split bands (and everything stacked with them) ended
up wider than the 32px art slot, bleeding into the text column next to
it. Fixed by wrapping each band (and the Sequence states, and the overall
effect) in an explicit Box(ART_SLOT, ART_SLOT) to clip back down.

_glitch_corner_ticks had the same missing-size issue (no explicit box
constraining it to the art slot at all), which is why the right-side
ticks were landing at the far edge of the full 64px canvas instead of the
32px art box's actual right edge.

Also fixed the ticks all being identical L-shapes (opening down-right)
regardless of which corner they were in, so only the true top-left one
looked correct. Each tick's two line segments are now explicitly pushed
to the correct edge of its own small box via Padding, so all four open
toward the image's center as intended.
2026-10-08 16:18:29 -04:00
poprhythm 2993e9e9a9 Replace bundled static.gif with a live RGB-split glitch effect
Rebuilt the track-transition "static overlay" as a chromatic-aberration
glitch on the *live* album art instead of a bundled, pre-baked animated
GIF: two duplicate, differently-tinted copies of the actual art (via
filter.Hue, confirmed real and working), each cropped to a shifting
horizontal band, plus scanlines and viewfinder-style corner tick marks.

Loosely modeled on SUB/WAVE's own web UI, which does something similar to
its cover art on track change (found via its compiled CSS/JS: a
.v3-cover-live class gating an RGB-split band glitch, sweeping scanlines,
and corner tick marks). The exact keyframe timing past the first step
wasn't visible in the minified output, so GLITCH_STATES is an original
jitter sequence in the same spirit, not a reproduction of unseen code.

This is the third design for this effect: sparse noise specks, then a
version derived from a real "VHS glitch" stock-footage clip (dropped
because recognizable footage content showed through at 32x32, and it
compressed worse than procedural noise), now this. Since it needs no
bundled asset, the app goes back to a single-file upload -- reverted the
Deploy section accordingly.

Verified: renders cleanly across all zoom-level/static-overlay
combinations, payload stays well under the 460KB limit, and
render.Sequence's lack of looping (confirmed in pixlet's own source)
means the RGB-split jitter plays once per show_static window and settles
to just scanlines/ticks over clean art for the remainder, not a
constant distraction for the whole window.
2026-10-08 15:58:15 -04:00
poprhythm 5a54376f8e Add an optional TV-static overlay during track start/end
Shows a brief static effect (sparse noise specks, bundled as a small
animated GIF) over the album art during the first/last 5 seconds of a
track, as a transition cue. Implemented via render.Stack compositing a
bundled images/static.gif over the zoomed art -- pixlet has no per-frame
procedural drawing hook, so the noise is pre-baked with Pillow rather
than generated at render time; render.Image plays animated GIFs back
natively, independent of the zoom's own animation timeline.

Tuned the GIF's frame count/density/flicker speed down after an initial
version pushed total payload within a few percent of the 460KB device
limit on some album art -- more, faster-changing frames give WebP less
opportunity to dedupe near-identical frames, which compounds with the
zoom animation's own dedupe needs. Final version adds ~50KB regardless of
art complexity, verified via a pinned-cover test harness for reliable
before/after comparison (the live station's currently-playing track kept
changing between test runs otherwise).

Added a "Static overlay" On/Off schema toggle (default On) per request.
2026-10-02 22:32:24 -04:00
poprhythm f6893bdcdf Rotate the random zoom spot over time, not just per-track
Random zoom position was previously picked once per track (stable via
subsonic_id) and held for that track's entire runtime. Changed it to
rotate on a ~10s cadence based on wall-clock time, still offset by a
per-track hash so different tracks don't all land on the same spot in
the same window. This lets a single long track show off different
sections of its own artwork over time, rather than being stuck on one
spot until the next track starts.
2026-10-02 22:19:59 -04:00
poprhythm d8add24c5e Replace Translate-based centering with animation.Origin; add zoom position config
The previous "fix" for Scale's crop bias paired Scale with a computed
Translate, on the mistaken assumption that Scale's default pivot is the
top-left corner. It isn't: pixlet's docs say the default origin is
already the center (0.5, 0.5). That Translate was sized assuming Scale
had no self-centering at all, so it overcorrected, visibly skewing the
zoom toward the bottom-right -- the opposite of the bug it meant to fix.

animation.Origin does directly and correctly what that Translate was
clumsily approximating, so the Translate (and its rounding="none"
workaround) is removed entirely in favor of Origin. This also means
"strong" no longer needs the 1.45 safety cap -- restored to 1.7, since
the black-border risk was specific to the Translate formula, not Scale +
Origin (verified via the calibration tool at several off-center origins).

Added a "Zoom position" schema option: Center (default) or Random. Random
picks from a handful of preset off-center spots, chosen per-track from
subsonic_id the same way the old corner-selection logic worked, so a
still-playing track doesn't jump between spots on every refresh.

Verified: no black border, no popping (confirmed via frame-delta
analysis -- smooth throughout, no Translate means rounding is moot), and
payload well under the 460KB limit at every zoom level/position
combination, against the live station.
2026-10-02 22:12:25 -04:00
poprhythm 4a2b3caac8 Fix the periodic zoom pop at its root: disable Translate's pixel rounding
Root-caused the "pop" from the Scale+Translate centered zoom (reverted a
few commits back) to pixlet's own source: render/animation/translate.go
rounds a Translate's x/y to the nearest whole pixel every frame by
default (rounding = "round"), while animation/scale.go never rounds
Scale. A continuously-interpolating Scale combined with a rounded
Translate holds still for several frames, then jumps a whole pixel at
once -- a real, confirmed pixlet behavior, not a flaw in this app's
formula.

Transformation accepts a `rounding` parameter; passing "none" disables
the snapping. Confirmed via frame-by-frame pixel-delta analysis: the
per-frame change is now even throughout the animation (consistently
~900 AE out of 1024 possible, every frame) instead of long near-zero
stretches punctuated by sudden full-frame jumps.

Re-enables the centered zoom (Scale + Translate) that was reverted for
jerkiness, now smooth. ZOOM_LEVELS["strong"] stays capped at 1.45 --
that black-border ceiling is unrelated to rounding and still applies.
Verified against the live station: no pop, no black border at any zoom
level, payload ~370KB (up from ~220-320KB, since rounding="none" means
WebP can dedupe fewer near-identical frames) but still well under the
460KB device limit.
2026-10-02 22:03:05 -04:00
poprhythm 9f63adbfbf Revert centered zoom; Scale+Translate causes a periodic rendering pop
Combining animation.Scale with animation.Translate to center the zoom
(two commits back) visually fixed the top-left crop bias, but introduced
a worse problem: pixlet periodically re-rasterizes the whole image with a
visible "pop" roughly every 0.7-1s, confirmed with both WebP and GIF
output and independent of zoom amplitude -- a property of pixlet's
animation engine when Scale and Translate are combined, not something
fixable from this app's math. Scale alone, with no Translate, has none of
this popping (confirmed via isolated testing).

Reverted _art_widget to Scale-only (as it was before centering), and
restored ZOOM_LEVELS["strong"] to 1.7 (the strong-zoom black border only
applied to the Translate-based centering, not plain Scale). This brings
back the original top-left crop bias as a known, accepted cosmetic
trade-off -- better than a periodic visible pop. Updated the README and
dev/calibration_test.star to match and documented the full history for
anyone who wants to revisit centering later.
2026-10-01 22:48:15 -04:00
poprhythm 5f42319e26 Revert jerky zoom fix; cap strong zoom level instead
Dividing the centering shift by top_scale (previous commit) avoided the
strong-zoom black border, but made the shift a nonlinear function of the
scale value being interpolated -- mid-animation, the pan no longer
tracked the zoom at a constant rate, which read as jerky/skippy motion.

Reverted shift to the original linear formula, (1-s)*(ART_SIZE/2), which
moves smoothly in lockstep with Scale's own eased interpolation. Instead,
capped ZOOM_LEVELS["strong"] at 1.45 (down from 1.7) -- the highest value
where this plain linear formula stays safely inside the art's bounds,
verified with the calibration tool (no black border across the full
animation cycle, at both 1.45x and against a live station).
2026-10-01 22:34:45 -04:00
poprhythm 9f2216c692 Fix black border at strong album art zoom by scaling the recenter shift
The centering Translate formula, (1-s)*(ART_SIZE/2), was derived assuming
it's applied in final output pixels. That held up at a moderate zoom
(1.4x) but pushed past the image's actual edge at a strong one (1.7x) --
a real, on-device black border in the bottom-right corner, confirmed with
the calibration tool (true black RGB(0,0,0) pixels, not just dark
content).

Dividing the shift by the scale factor keeps the crop inside the image's
bounds at every zoom level the app offers, verified via the calibration
tool at 1.0/1.15/1.4/1.7x and against a live station -- at the cost of
the most extreme (1.7x) setting being very slightly off-center rather
than perfectly centered, which is a trade worth making since going
outside the art's bounds is worse. Moderate and subtle remain visibly
centered.
2026-10-01 22:23:52 -04:00
poprhythm 975ff6da28 Center the album art zoom instead of favoring the top-left
animation.Scale's pivot is fixed at the top-left corner with no way to
change it (an explicit origin param has no effect at all). Left alone,
that meant zooming in cropped progressively more off the bottom and right
than the top and left -- and since most of the animation's duration is
spent between keyframes rather than sitting at the symmetric rest state,
this read as a persistent "bottom and right are cropped" on the real
device.

Fix: pair Scale with a Translate that's a deterministic function of the
same scale value, re-centering the zoom so it crops evenly from all
sides. Verified against a synthetic calibration grid image (concentric
color rings + corner markers) rendered through the actual _art_widget
code -- confirms even margins at rest and symmetric cropping as it zooms
in, with no black border anywhere in the cycle. Safe because scale never
drops below 1.0 here; the earlier black-border bug came from combining
Translate with scale below 1.0, not from Translate itself.
2026-10-01 21:57:18 -04:00
poprhythm 60ba5f8a3b Fix album art zoom's rest frame differing from zoom-off's static frame
zoom_level=off skipped animation.Transformation entirely, rendering the
art as a bare Image in a Box. Every other zoom level wrapped the same
Image in a Transformation. Empirically, that wrapper shifts the crop by a
couple pixels versus the bare Image, even at an explicit identity
Scale(1.0, 1.0) keyframe -- so "off" and the rest state of every other
zoom level didn't actually agree on framing, despite both nominally being
scale 1.0. That's what looked like the zoom always starting "a little
zoomed in" relative to off.

Fix: always go through the same Transformation, using a degenerate
animation (identical Scale(1.0, 1.0) keyframes, no visible movement) for
zoom_level=off. Verified pixel-identical (AE=0) first frames across off/
subtle/moderate/strong against the same live track.
2026-09-29 21:13:55 -04:00
poprhythm fb048df885 Always render the show/host and time rows, even when empty
Previously these rows were only added when the underlying data was
present, so the layout would shift depending on what SUB/WAVE happened to
report at the moment -- e.g. the time row would jump up a line whenever
there was no active show. Now both rows always render (with blank text
where needed) so they hold a fixed vertical position regardless of what
data is available.
2026-09-29 19:34:44 -04:00
poprhythm 4a01de30de Make album art zoom and color scheme configurable
Zoom level is a dropdown (Off/Subtle/Moderate/Strong) controlling how far
the album art zooms in during its breathing effect; Off skips the
animation.Transformation entirely and just shows a static crop.

Color schemes now match SUB/WAVE's own web UI theme picker (Blueprint,
Classic Dark, Classic Light, Cyberpunk, Flare, Recon, Signal, Vinyl) so a
station can mirror its own site theme on the device, including a
background fill that was previously hardcoded to black.
2026-09-29 19:33:19 -04:00
poprhythm 6e346a0cf8 Fix album art zoom always showing a cropped corner, not the full image
The rest state used Scale(ART_SLOT/ART_SIZE, ...) (~0.71) on a 45x45
image to shrink it down and reveal the whole picture, but scaling below
1.0 turns out to be unsafe: empirically it reintroduces the black-border
bug well before the math would predict any gap (a visible gap opens up
below roughly 0.9-0.95, not just when the scaled image shrinks smaller
than the display slot). So the whole "shrink down from oversized to
reveal everything" design doesn't work.

Instead, keep the oversized render just barely larger (36x36 vs. the
32x32 slot) so the safe floor of scale = 1.0 already shows ~89% of the
image -- close enough to read as "fully zoomed out" -- while still
leaving headroom above 1.0 for a real zoom-in effect.
2026-09-29 19:23:06 -04:00
poprhythm 67c20e20e9 Start/end the album art zoom fully zoomed out, not zoomed in
Swapped the keyframes so scale=1.0 (full frame) is the loop's start/end
state and the zoom-in happens at the midpoint, matching how it's meant to
read: settle on the full picture, breathe in, breathe back out.
2026-09-29 15:52:55 -04:00
poprhythm dc0c5019ca Shorten album art zoom to a 10s round-trip so it actually completes
At the old 60s round-trip, a fresh render (triggered by the server well
before a minute passes) kept restarting the animation from tick 0, so only
the outbound zoom-out was ever visible on-device -- it never appeared to
zoom back in. Shortening the loop to 10s total makes it far more likely a
full out-and-back cycle plays before the next refresh.
2026-09-29 15:48:47 -04:00
poprhythm cda256d1c2 Fix black border in album art zoom by only ever scaling up
The corner-reveal zoom scaled down from 1.0 toward ~0.71 (via Translate
to shift toward a corner), which showed up on a real device as a black
border cutting into the art. Empirical testing against the actual Row
layout at native canvas size showed the outer Box auto-fits an oversized
child at Scale(1.0, 1.0), so scaling down from there shrinks the already-
fitted image further and exposes background outside its bounds. Scaling
up only ever crops further into the image, which is always safe.

This drops Translate and the per-track corner selection (animation.Scale
pivots at the top-left corner by default with no way to change it -- an
explicit origin parameter was tried and had no effect), so the effect now
always zooms into the top-left corner instead of a track-dependent one.
2026-09-29 15:42:15 -04:00
poprhythm 83b817c3b2 Loop art zoom back-and-forth instead of one-shot reveal, to reduce refresh jumpiness 2026-09-29 15:21:52 -04:00
poprhythm 27e29705c9 Add slow zoom/pan reveal to album art; fix JSON-null crash on activeShow 2026-09-29 15:12:01 -04:00
poprhythm 74be033a29 Revert to native Marquee for all lines; remove album-art overlap 2026-09-29 07:03:53 -04:00
poprhythm f864d43888 Fix Marquee desync bug; add time/temp footer with progress bar at full width 2026-09-28 23:19:35 -04:00
poprhythm 607fe9abe4 Pin progress bar to bottom, add show/host line with new color scheme 2026-09-28 23:11:12 -04:00
poprhythm 1d2021e6f2 Add show name and track progress bar 2026-09-28 22:58:20 -04:00
poprhythm 65862ce3ae Initial commit — SUB/WAVE Now Playing Tronbyt app 2026-09-28 22:53:18 -04:00