Device parser now reads the rmp<start>/<amt>/<every> and tr<play>/<mute> tokens it previously ignored, and the firmware performs them: - Tempo ramp: steps bpm by <amt> every <every> bars (resets to the start at each b<n> segment boundary). Shows an amber ramp arrow + "+amt/everyb" (up/down by sign; no starting bpm, per request). - Gap trainer: cycles <play> audible bars then <mute> silent bars (no click/MIDI/LED; playheads keep moving). Shows a play|rest symbol + "play/muteb". - Practice log entries now record + show bars played. Verified in the CPython harness: ramp 92->96->100->104->108 (+4 every 2 bars), gap mute cycle play,play,mute,mute, and the on-screen ramp indicator renders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
7.1 KiB
Markdown
105 lines
7.1 KiB
Markdown
# PM_K‑1 "Kit" — CircuitPython edition (USB drive · push programming · MIDI audio · practice log)
|
||
|
||
The **CircuitPython** firmware for the 52Pi EP‑0172 Pico kit, set up as a self‑contained appliance.
|
||
It runs the same program‑string language as <https://metronome.varasys.io>. The simpler
|
||
**MicroPython** firmware (`../pico/main.py`) stays as a rock‑solid fallback — and the Pico can't be
|
||
bricked (BOOTSEL → drag a MicroPython `.uf2` back).
|
||
|
||
What it does: drives the 3.5″ touchscreen with a lanes/pads display + anti‑aliased text, plays the
|
||
buzzer + RGB beat light, **logs your practice to `/history.json`**, accepts new set lists **pushed
|
||
from the web editor over USB‑MIDI**, and plays through your **computer's speakers** over USB‑MIDI.
|
||
|
||
## Two power‑on modes (set by `boot.py`)
|
||
|
||
- **Appliance mode — default (just plug in / power up).** The *firmware* owns the filesystem, so it
|
||
saves your practice log and writes set lists the editor pushes over USB‑MIDI. The drive is then
|
||
**read‑only to the computer** — which also **protects the firmware from accidental deletion**.
|
||
- **Editor mode — hold BUTTON A while plugging in.** The drive is **writable by the computer**, so you
|
||
can drag `programs.json` / `code.py` / fonts on from any OS or browser (the universal fallback).
|
||
Reset afterwards to return to appliance mode.
|
||
|
||
## Install
|
||
|
||
1. **Flash CircuitPython:** hold **BOOTSEL**, plug in, drop the CircuitPython `.uf2` onto `RPI‑RP2`
|
||
(<https://circuitpython.org/board/raspberry_pi_pico/> — Pico 2 / W builds also fine). A `CIRCUITPY`
|
||
drive appears.
|
||
2. **Copy the whole bundle** onto `CIRCUITPY`: `boot.py`, `code.py` (loader) + `app.py` (the application),
|
||
`programs.json`, `font_s.bin` / `font_m.bin` / `font_l.bin`, `logo.bin` / `midi.bin` / `usb.bin` (the
|
||
on-screen logo + MIDI/USB status icons), `editor.html` (offline editor), and the helper scripts.
|
||
(`code.py` is a tiny stable loader; `app.py` is what firmware updates replace. The `.bin` assets — like
|
||
the fonts — ride in the bundle, since the one-click updater only pushes `app.py`; if a `.bin` is missing
|
||
the firmware just falls back to text and never fails to boot.)
|
||
3. **Power‑cycle** (so `boot.py` takes effect). It boots into appliance mode and runs.
|
||
|
||
## Program it from the web (push over USB‑MIDI)
|
||
|
||
In the editor (Chrome / Edge / **Firefox**), build a set list → set‑list **⋯** menu → **📟 Save to device**.
|
||
The editor sends it to the Pico over USB‑MIDI (SysEx); the firmware writes `/programs.json`, reloads, and
|
||
acknowledges — the editor shows **Saved ✓**. **📥 Load from device** reads it back.
|
||
|
||
*Universal fallback (any browser / OS, even Safari):* Save to device **downloads** `programs.json` when no
|
||
device answers — boot the Pico in **editor mode** (hold A) and drag the file onto the `CIRCUITPY` drive.
|
||
|
||
## Firmware updates (one‑click, A/B with auto‑rollback)
|
||
|
||
`code.py` is a small stable **loader**; the application is `app.py` (it carries `APP_VERSION`). To update:
|
||
the editor's ⋯ menu → **⬆ Update firmware…** queries the device's version, fetches the latest from the site,
|
||
shows *device vs latest*, and on confirm **pushes the new `app.py` over USB‑MIDI**. The device installs it to
|
||
a **trial slot** (keeping the old build as `app.bak`) and reboots; if the new build **doesn't boot, the loader
|
||
automatically rolls back** to `app.bak`. A build that runs cleanly for ~5 s is confirmed. No BOOTSEL, no
|
||
dragging. (Updating CircuitPython *itself* still uses BOOTSEL + a `.uf2`, but that's rare. And the Pico is
|
||
unbrickable as the ultimate backstop.)
|
||
|
||
## Play through the computer's speakers
|
||
|
||
The Pico is a USB‑MIDI device and sends a note per click (GM drum note per lane, velocity by accent).
|
||
In the editor click **🎹 Device audio**, grant MIDI access, and press play *on the device* — the editor
|
||
voices the groove through its full synth, out your speakers, locked to the device's clock. While a host is
|
||
listening the screen shows a green **MIDI** badge and the **buzzer auto‑mutes** (the computer plays instead).
|
||
The editor also syncs the device clock, so the practice log gets real wall‑clock timestamps.
|
||
|
||
## Controls & the practice log
|
||
|
||
- **Joystick:** up/down = tempo, left/right = previous/next groove.
|
||
- **Button A (GP15):** play / stop. **Button B (GP14):** tap tempo.
|
||
- **Screen:** VARASYS logo + firmware version + MIDI/USB status icons up top. Running time and bar count
|
||
show **of the segment total** when the track has a bar length (`b<n>`), e.g. `1:23 of 2:00` and `bar 3
|
||
of 16`. A track with a tempo ramp (`rmp`) shows a **ramp arrow + amount/every-bars** (e.g. `+4/2b`); a
|
||
gap-trainer track (`tr`) shows a **play|rest symbol + bars** (e.g. `2/2b`). Main beats are **squares**,
|
||
subdivisions are **circles**, with vertical gridlines lining the beats up across lanes.
|
||
- **RGB LED = run state:** dim **green** when stopped ("on"), dim **red** while playing, with the beat
|
||
pulsing brighter on top. (The screen background stays black — recoloring it forces a full-screen repaint.)
|
||
- The firmware **performs** ramps (tempo steps every N bars) and gap-trainer cycles (silent rest bars).
|
||
- **Touchscreen:** the bottom shows the **practice log for the current track** (time · BPM · duration · bars)
|
||
— newest first. Plays under 5 s aren't logged. **Tap a row to arm it (turns amber), tap again to delete.**
|
||
- **RGB LED** flashes the beat (amber accent / cyan normal / violet ghost); the **buzzer** clicks to match.
|
||
- The log is saved to `/history.json` (next to `programs.json`) in appliance mode and survives power‑cycles.
|
||
|
||
## programs.json
|
||
|
||
```json
|
||
{ "title": "PolyMeter",
|
||
"programs": [ { "name": "Four on the floor", "prog": "t120;kick:4;snare:4=.x.x;hatClosed:4/2" } ] }
|
||
```
|
||
|
||
Each `prog` is a program string from the editor (tempo, lanes, patterns, `/2` subdivision, `/2s` swing,
|
||
`(3,8)` Euclid, `~` polymeter, `@-3` dB). The push above is the easy way to update it.
|
||
|
||
## Calibration (flags at the top of `code.py`)
|
||
|
||
- **Red/blue swapped:** flip `MADCTL` between `0x48` (default) and `0x40`.
|
||
- **Colours look negative:** toggle `INVERT_COLORS`.
|
||
- **Taps land wrong:** set `TOUCH_DEBUG = True`, read the raw coords over USB serial, then set
|
||
`TOUCH_SWAP_XY` / `TOUCH_INVERT_X` / `TOUCH_INVERT_Y`.
|
||
- **Joystick reversed:** toggle `JOY_INVERT_X` / `JOY_INVERT_Y`.
|
||
- **Computer audio:** `MIDI_ENABLED` (default on); `MUTE_BUZZER` forces the buzzer off even standalone.
|
||
- **LED too bright/dim:** `LED_BRIGHTNESS` (0..1, default 0.15).
|
||
- **Screen tearing:** SPI panels have no tearing‑effect sync; `SPI_BAUD` (default 62.5 MHz) is pushed fast
|
||
to minimise it — lower only if unstable.
|
||
- **Blank / garbled:** the panel lot may differ; drop `SPI_BAUD`, and if it's a 240×320 ILI9341 rather than
|
||
the 320×480 ST7796, the init/size need changing (this targets the 320×480 you have).
|
||
- **RGB LED** uses the core `neopixel_write` (no library to install).
|
||
|
||
If `code.py` ever errors, CircuitPython prints the traceback **on the screen and over USB serial** — send
|
||
me that. The fonts are the baked anti‑aliased blobs from `../pico/gen_font.py`. `protect-firmware.sh` (hide
|
||
the firmware files) is mainly for editor mode — appliance mode already keeps the drive read‑only.
|