Disclosure: I’m Giang Pham, Pianory’s maker. AI helped draft this source-based article. It describes code paths and existing unit tests, not a measured production study or a claim about learners’ results.
A score player has two different transitions that are easy to blur together: starting playback and beginning another pass through a loop. A count-in can help mark the first entry. If the scheduler treats every loop boundary as a new Play command, it may insert an extra bar of clicks between repetitions and turn a short practice loop into a stop-and-start exercise.
In Pianory’s player, play() begins at the saved score position with the current count-in setting. When count-in is enabled, begin() looks up the measure containing that starting position. It uses that measure’s beat count and beat length, schedules the count-in clicks, then moves the first score anchor forward by the count-in duration. The count is not assumed to be four beats for every score.
The loop branch takes a different path. At the end of a bounded loop, it creates the next anchor at the loop start, resets the next score-event and beat indexes, and continues scheduling. It does not call begin() again, so it does not schedule a fresh count-in on every pass. That distinction keeps the loop boundary in the score’s timeline instead of treating it as another user-initiated start.
A simplified sketch of the scheduling choice is:
Play from position:
if count-in enabled: schedule measure beats; delay first score anchor
schedule score events from that anchor
Loop boundary:
anchor next pass at boundary time and loop-start score position
reset event/beat cursors
continue scheduling without a new count-in
There is another state change at the same boundary: if loop tempo stepping is enabled and the current percentage is below the written-tempo target, the branch raises the percentage by the configured step, capped at that target. Anchoring the next pass with the new percentage matters because a tempo change without a matching time anchor can shift events across the join.
The tests provide narrower evidence than an end-to-end listening claim. One unit case verifies that a 4/4 count-in delays the first note by one bar. Another verifies continuous loop passes, but it disables count-in. A separate case verifies tempo ramping up to written tempo. Together they describe the intended pieces; they do not directly prove the combination of count-in enabled and multiple loop passes. A focused regression test for that combination would assert one initial count-in, adjacent loop anchors, and no extra click bar after the first pass.
The broader design lesson is to model a loop boundary as a continuation, not a fresh command. Count-in belongs to the deliberate start transition. A repeated phrase should stay on the score’s timeline unless the product explicitly offers a different reset behavior. What other audio or animation loops in your app accidentally reuse initialization work at every boundary?
Top comments (0)