- docs/playback-flow-test.md: on-device verification checklist for the runtime (stop / rep / next / relative-goto / boundary / manual-override cases). - editor.html + editor-beta.html: graphical "At end" control (loop / next / stop / goto ±N) plus a rep-count input in the arrangement panel, wired through state.rep/state.end -> currentSetup/currentPatch. Authoring is no longer text-field-only. - src/engine.js: patchToSetup now clamps tempo to [5,300] and defaults to a beep:4 lane when no lanes are given, matching the firmware. The editors keep their "no lanes" hint by checking the raw input for a ':' token instead of parsed lanes. - fixtures: tempo-clamp-high + empty-defaults-to-beep now pass on both engines. Suite: 41 pass / 1 known (only the intentional vol/cd host boundary remains). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.1 KiB
3.1 KiB
Per-track playback flow — on-device test checklist
The parsing/serialization and the _end_plan/_goto_target decision logic are covered by
tests/run.mjs and unit tests. What still needs a real device (PM_K-1 / PM_X-1) is the
runtime: that the gapless seam fires stop / advance / relative-goto at the right bar.
Setup
- Flash the new firmware: copy
dist/app.mpy(PM_K) ordist/explorer-app.mpy(PM_X) onto the device as/app.mpy(or use the editor's one-click push — it serves this build). - Author the test tracks in the web editor: paste each program string into the program-string field, Save it as a set-list item, then Save to device. (Multi-item cases: create the items in order in one set-list.)
- On the device, select the set-list and press play (Button A).
b<n> sets the cycle length in bars; a cycle = b<bars>, else one master bar. The action fires
after rep × cycle bars (rep defaults to 1).
Cases
| # | Track(s) | Expect | ✓ |
|---|---|---|---|
| 1 | t120;kick:4 |
Loops forever. Joystick-right advances manually. | ☐ |
| 2 | t120;b2;kick:4;end=stop |
Plays 2 bars, then stops by itself (LED → green, transport stops). | ☐ |
| 3 | t120;b2;kick:4;rep=3;end=stop |
Plays 6 bars (3×2), then stops. | ☐ |
| 4 | A=t120;b2;kick:4;end=next B=t100;b2;snare:4 |
A plays 2 bars then gaplessly advances to B (no gap, no count-in, tempo jumps to 100). | ☐ |
| 5 | A=t120;b2;kick:4;rep=2;end=next B=t100;b2;snare:4 |
A plays 4 bars (2×2) then advances to B. | ☐ |
| 6 | 1=t120;b2;kick:4 2=t120;b2;snare:4;end=next 3=t120;b2;clap:4;end=-2 |
3 jumps back 2 → track 1 after its cycle (a looping verse→chorus section). | ☐ |
| 7 | 1=t120;b2;kick:4;end=-2 (first item) |
end=-2 before the start clamps to track 1 → it just loops itself. |
☐ |
| 8 | last item …;end=next |
Advancing past the last item wraps to the first (set-list loops). | ☐ |
| 9 | any of the above, mid-flow | Manual joystick-right / footswitch always advances immediately, regardless of end=. |
☐ |
| 10 | global Continue ON + an item t120;b2;kick:4;end=stop |
The item still stops — explicit end= overrides the global Continue default. |
☐ |
| 11 | global Continue ON + t120;b2;kick:4 (no end=) |
Advances after 2 bars — legacy behavior preserved (Continue = default end=next). |
☐ |
What to watch for
- Seam quality (cases 4–6): the swap should be click-on-the-beat with no audible gap, dropout, or double-trigger at the boundary. This is the highest-risk part of the change.
- Stop cleanliness (cases 2–3): no extra click after the stop; speaker goes quiet; the play session is logged.
- Ramp/trainer interaction: a track with
rmp…/tr…and anend=should ramp/gap normally and still fire the end-action atrep × cycle. - Memory: relative-goto and advance both go through
_prepare_next(whichgc.collect()s before parsing). Watch for any MemoryError fallback (it leaves the track looping instead).
Report any case that misbehaves with its number and what happened.