diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index d69387f..5f53660 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -356,6 +356,7 @@ def _zoomed_art(art_bytes, zoom_scale, zoom_position, subsonic_id): # keyframe that was visible: roughly the top third and an overlapping # middle band, each about 30% of the frame's height. GLITCH_HOLD_TICKS = 5 # ~250ms per jitter state at the default 50ms/tick +GLITCH_PAUSE_TICKS = 15 # ~750ms of clean art between bursts GLITCH_STATES = [ # (top1, height1, shift1, hue1, top2, height2, shift2, hue2) (3, 9, -2, 150.0, 19, 8, 2, -60.0), @@ -364,6 +365,16 @@ GLITCH_STATES = [ (1, 10, 1, 150.0, 21, 7, -2, -60.0), ] +# render.Sequence doesn't loop on its own (see _glitch_scanlines below), so +# to repeat the burst-then-pause pattern for the whole show_static window, +# the repeats have to be spelled out as one long children list up front +# rather than relying on any runtime looping. Rounds up so the window is +# always fully covered, even if that runs a little past +# STATIC_OVERLAY_SECONDS -- better than the jitter cutting out early. +_GLITCH_CYCLE_TICKS = len(GLITCH_STATES) * GLITCH_HOLD_TICKS + GLITCH_PAUSE_TICKS +_GLITCH_TICKS_PER_SECOND = 20 # 1000ms / 50ms-per-tick +GLITCH_REPEATS = int(STATIC_OVERLAY_SECONDS * _GLITCH_TICKS_PER_SECOND / _GLITCH_CYCLE_TICKS) + 1 + def _glitch_band(zoomed_art, top, height, shift_x, hue_change): tinted = filter.Hue(child = zoomed_art, change = hue_change) band = render.Padding( @@ -379,29 +390,36 @@ def _glitch_band(zoomed_art, top, height, shift_x, hue_change): # the 32px art slot, bleeding into the text column next to it. return render.Box(width = ART_SLOT, height = ART_SLOT, child = shifted) -def _glitch_jitter(zoomed_art): - states = [] - for top1, h1, shift1, hue1, top2, h2, shift2, hue2 in GLITCH_STATES: - state = render.Stack(children = [ - _glitch_band(zoomed_art, top1, h1, shift1, hue1), - _glitch_band(zoomed_art, top2, h2, shift2, hue2), - ]) +def _glitch_hold(child, duration): + # A no-op Transformation purely to pin a Sequence child's FrameCount + # to `duration` ticks, so render.Sequence holds on it for that long + # before advancing to the next child -- Sequence has no other way to + # control how long a given child plays. + return animation.Transformation( + child = child, + duration = duration, + width = ART_SLOT, + height = ART_SLOT, + keyframes = [ + animation.Keyframe(percentage = 0.0, transforms = []), + animation.Keyframe(percentage = 1.0, transforms = []), + ], + ) - # Wrapping in a no-op Transformation just to pin this state's - # FrameCount to GLITCH_HOLD_TICKS, so render.Sequence holds on it - # for that many ticks before advancing to the next state -- - # Sequence has no other way to control how long a child plays. - states.append(animation.Transformation( - child = state, - duration = GLITCH_HOLD_TICKS, - width = ART_SLOT, - height = ART_SLOT, - keyframes = [ - animation.Keyframe(percentage = 0.0, transforms = []), - animation.Keyframe(percentage = 1.0, transforms = []), - ], - )) - return render.Sequence(children = states) +def _glitch_jitter(zoomed_art): + pause = _glitch_hold(render.Box(width = ART_SLOT, height = ART_SLOT), GLITCH_PAUSE_TICKS) + + sequence = [] + for _ in range(GLITCH_REPEATS): + for top1, h1, shift1, hue1, top2, h2, shift2, hue2 in GLITCH_STATES: + state = render.Stack(children = [ + _glitch_band(zoomed_art, top1, h1, shift1, hue1), + _glitch_band(zoomed_art, top2, h2, shift2, hue2), + ]) + sequence.append(_glitch_hold(state, GLITCH_HOLD_TICKS)) + sequence.append(pause) + + return render.Sequence(children = sequence) def _glitch_scanlines(): # render.Sequence doesn't loop (it just stops painting once its total