metronome/docs/playback-flow-test.md
Me Here 8bba218f67 Editor controls for playback flow + close web-side divergences
- 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>
2026-05-31 16:11:43 -05:00

46 lines
3.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
1. Flash the new firmware: copy `dist/app.mpy` (PM_K) or `dist/explorer-app.mpy` (PM_X) onto the
device as `/app.mpy` (or use the editor's one-click push — it serves this build).
2. 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.)
3. 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 46): 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 23): no extra click after the stop; speaker goes quiet; the play
session is logged.
- **Ramp/trainer interaction**: a track with `rmp…`/`tr…` *and* an `end=` should ramp/gap normally
and still fire the end-action at `rep × cycle`.
- **Memory**: relative-goto and advance both go through `_prepare_next` (which `gc.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.