Adds the per-track end-action model designed in docs/track-format.md §3, end to end across both engines, both firmwares, and the editors. Grammar (parsed + serialized by engine.js and both app.py): rep=<n> cycles before the end-action fires (default 1) end=stop stop after rep cycles end=next advance one track (sugar for end=+1) end=<±N> relative goto after rep cycles (e.g. end=-2 = D.S.) (absent) loop forever — the metronome default Firmware runtime (pico-cp + pico-explorer): _on_new_bar now consults a per-track _end_plan() and fires stop / gapless-advance / relative-goto at the right bar. A cycle = b<bars>, else one master bar; fire bar = rep * cycle. Explicit end= governs; with no end, the global Continue toggle stays a default (=end=next, still needs b<bars>) so existing set-lists and the CONT UI are unchanged. _prepare_next takes a target index; the seam machinery, _do_advance and live-sync all carry rep/end. Editors (editor.html + editor-beta.html): state.rep/state.end thread through applySetup / currentSetup / currentPatch so load -> edit -> save preserves the flow; authoring is via the program-string field (no graphical control yet). Tests: the 3 playback-flow vectors now pass on both engines (39 pass / 3 known). Runtime decision logic (_end_plan / _goto_target) unit-tested for stop, rep, relative goto clamp/wrap, and legacy-Continue precedence. Codec round-trip verified idempotent. Both firmwares compile + mpy-cross clean. Also: untrack stale __pycache__/*.pyc build artifacts and gitignore them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| app.py | ||
| boot.py | ||
| code.py | ||
| programs.json | ||
| README.md | ||
PM_X-1 "Explorer" — CircuitPython edition (Pimoroni Explorer · RP2350)
The CircuitPython firmware for the Pimoroni Explorer Kit (PIM744),
set up as a self-contained appliance. Sibling to the PM_K-1 build in ../pico-cp/ (the 52Pi EP-0172
kit) — same engine, same program strings, same programs.json, same web editor.
This board is a 2.8″ ST7789V LCD + 6 user buttons + piezo speaker built around an RP2350B (Pico 2 class chip). No touchscreen, no joystick, no RGB LED. Editing is done in the web editor with Live sync on; the device mirrors changes in real time and emits its own play/stop/bpm/sel deltas back.
Hold the device in portrait with the A/B/C buttons along the top and X/Y/Z along the
bottom. The firmware drives the LCD as a 240 × 320 portrait at display.rotation = 270 —
same UI shape as the PM_K-1 Kit, just shorter. If the screen comes up upside-down on your
unit, change DISPLAY_ROTATION near the top of app.py to 90 (or 180 if rotated 180°)
and re-flash.
Controls
| Button | Action |
|---|---|
| A | play / stop |
| B | tap tempo |
| C | menu (Settings / Help / About / Practice log) |
| X | prev track (hold to repeat) |
| Y | tempo −1 (hold to repeat; after ~1.5 s the step grows to −5) |
| Z | next track (hold to repeat) |
| X + Z | tempo +1 (chord; same hold-repeat as Y) |
In a menu: X / Z move the cursor up / down, Y decrements the focused value, A commits or cycles, B = back, C = close.
Install
- Flash CircuitPython for Pico 2 / RP2350. Hold BOOTSEL on the Explorer, plug it in over
USB-C, drop the Pimoroni Explorer (RP2350) CircuitPython
.uf2onto theRP2350drive. ACIRCUITPYdrive appears. - Copy the bundle onto
CIRCUITPY—boot.py,code.py,app.mpy,programs.json,font_s.bin/font_m.bin/font_l.bin,logo.bin/midi.bin/usb.bin,editor.html(offline editor). If an oldapp.pyis on the drive, delete it. - Power-cycle. It boots into appliance mode and runs.
Program it from the web
Open https://metronome.varasys.io in Chrome / Edge / Firefox. The set-list ⋯ menu →
📟 Save to device pushes a programs.json over USB-MIDI; the device persists it and reloads.
Click 🔗 Live sync to mirror edits in real time.
Pin reference
The display, buttons, and audio are wired into the board — no jumpers required. CircuitPython's
official board definition for pimoroni_explorer2350 exposes board.DISPLAY pre-initialized, so
the firmware just uses it.
| Function | GPIO |
|---|---|
| Button A | GP16 |
| Button B | GP15 |
| Button C | GP14 |
| Button X | GP17 |
| Button Y | GP18 |
| Button Z | GP19 |
| Piezo audio (PWM) | GP12 |
| Piezo amp enable | GP13 |
| I²C SDA (QwSTEMMA) | GP20 |
| I²C SCL (QwSTEMMA) | GP21 |
| Display | board.DISPLAY (8080 parallel bus on GP26..GP39, initialized by board.c) |
Calibration (flags at the top of app.py)
- Speaker too loud / quiet: the piezo + amp gain is fixed in hardware.
MUTE_SPEAKERsilences the click;SPEAKER_AUTO_MUTEauto-mutes when a MIDI host is listening. - Buttons feel inverted: the polarity is hard-coded to active-low (pull-up). If a button
fires on release instead of press, check the
BTN_*pin map at the top ofapp.py. - Display orientation: the board's CircuitPython init mounts the panel landscape (320 × 240). If your screen looks rotated, that's a board.c-level thing — file a CircuitPython bug, don't patch the firmware.
If app.py ever errors, CircuitPython prints the traceback on the screen and over USB serial —
send me that.