Commit Graph
20 Commits
Author SHA1 Message Date
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