- Tap a beat to cycle it (off->normal->accent->ghost); the title turns red (unsaved).
Tap the title -> SAVE / REVERT modal. Editing a built-in saves a COPY into a "My edits"
user playlist (built-ins stay read-only); editing a user item updates it in place.
Saves persist to programs.json (NAKs gracefully in editor mode / read-only).
- New round-trippable serializer (lane_to_str/_prog_str): parser now keeps groups + @db
gain + ramp start; verified parse->serialize->parse on all 23 built-ins (0 mismatches).
- Continue (CONT) toggle, top-right of the tab line: when on, a playlist auto-advances to
the next item at the end of each item's b<n> segment (no log spam, keeps the stopwatch).
- Touch routing consolidated: tab=switch playlist / CONT, title=save-revert, pads=cycle,
log=delete; modal overlay drawn on top.
Verified in the harness: beat cycle+dirty, built-in edit -> My edits persisted (built-ins
untouched), revert, Continue arming at segment end, overlay SAVE-tap, and both renders.
Next (0.1.0): tap the instrument name -> lane-parameter table (reuses this save machinery).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The standard editor defaults (Styles / Practice / Song) are baked into app.py as
BUILTIN_SETLISTS (ASCII-fied — emoji/accents would break the 7-bit push + the fonts),
so they update with firmware and the user can't change or delete them. User playlists
live separately in programs.json and are merged after the built-ins.
Device:
- Set-list model: self.setlists = built-ins + user lists (deduped by normalized title,
so a baked built-in always wins). load()/goto() work within the current list.
- Navigation: a set-list "tab" (small, above the title) shows playlist + position,
muted for built-in / cyan for user; TAP it to switch playlists. Joystick L/R = item.
- SysEx 0x10 (push) writes programs.json -> rebuild user lists; built-ins untouched.
- Shipped programs.json is now empty ({"setlists":[]}) — built-ins come from firmware.
Editor:
- "Save to device" now syncs only YOUR set lists (filters out the built-in seeds) in the
new {setlists:[...]} format; warns if you have none. Load-from-device imports both the
new multi-list and old flat formats.
Verified in the harness: 3 read-only built-ins, set-list switching, user-list merge +
dedup of a pushed "styles", and the ramp engine on a built-in track (80->84->88, +4/4 bars).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
APP_VERSION -> 0.0.6. Device firmware + editor change in lockstep — one-time manual
copy of 0.0.6 needed (the broken single-shot updater can't deliver it).
Update transport (fixes the failed/bricking updates):
- Editor now pushes app.py in 512-byte flow-controlled chunks: begin(0x21,len) ->
data(0x22)* -> commit(0x23), waiting for each ACK before the next. A single ~38KB
SysEx overran the device's USB-MIDI input buffer and arrived corrupt.
- Device writes chunks straight to /app.new, and on commit verifies length + no NUL +
App().run()/APP_VERSION present before the A/B install; rejects (NAK) otherwise and
keeps the working build. All errors caught -> never bricks.
Run/stop indicator moved off the screen onto the RGB LED (per feedback that recoloring
the whole background is wrong — it forces a full-screen SPI repaint and fringes the
anti-aliased text):
- Dim GREEN when stopped ("on"), dim RED while playing; the beat pulse flashes brighter
and now decays back to the running base instead of to black. Background is static black.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause: a non-ASCII em-dash in an app.py comment. The A/B updater pushes app.py
as 7-bit SysEx (charCode & 0x7F), which turned the em-dash's bytes into a NUL byte ->
corrupt source -> the pushed build crashed on boot (black screen, onboard LED blinking
CircuitPython's error/safe-mode pattern). A dragged copy was fine (valid UTF-8); only
the over-MIDI path mangled it.
- Replace the em-dash with ASCII; app.py is now pure ASCII.
- build.sh now ASSERTS pico-cp/app.py is pure ASCII (fails the build otherwise) so this
class of bug can never ship again.
- Device 0x20 handler VALIDATES the pushed app.py before installing (reject if it
contains a NUL byte, or is missing App().run()/APP_VERSION) and now catches ALL
exceptions (not just OSError) -> a corrupt/truncated/oversized push NAKs and keeps the
working build instead of bricking. Longer pre-reload sleep so the ACK flushes.
APP_VERSION -> 0.0.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App.py-only (ships over the one-click updater). APP_VERSION -> 0.0.3.
- Run/stop is now a background tint (gray running / near-black stopped) instead of
STOP text, reclaiming the space.
- Running time + bar counter show "of total" when the track has a b<n> length:
"1:23 of 2:00" and "bar N of 16" (bar cycles 1..N); total time derived from
bars x master-beats-per-bar x 60/bpm. Parser now reads the b<n> token.
- Practice log is filtered to the current track (drops the redundant track column).
- Pads: squares for the main pulse, circles for subdivisions (was square + hollow
outline); fewer vectorio shapes too.
- Track number set apart from the title (small + dim, right) so it no longer reads
as part of the title.
On-device editing (tap instrument -> lane table; tap beat -> cycle state; dirty-name
-> confirm save/revert) is deferred to Phase 2, where "save" has a correct destination
(an edited built-in saves as a user copy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Device screen redesign (CircuitPython app.py), built proportional to WIDTH/HEIGHT
so it scales to other panels (one adaptive firmware, per-panel config — not a fork):
- gen_assets.py bakes logo.bin (VARASYS wordmark, no tagline), midi.bin (DIN-5),
usb.bin (trident) as 4-bit-alpha bitmaps (same packing as the fonts).
- Header: VARASYS logo (brand cyan) replaces the "PM_K-1 KIT" text; MIDI icon goes
green when a host is listening, USB icon lights when supervisor.runtime.usb_connected.
load_alpha/make_glyph are non-fatal — a missing .bin falls back to text, never a
black screen (addresses the corrupt-file failure mode we just hit).
- Pad grid: filled squares on main beats, hollow outline squares (outer+inner rect) on
off-beats; playhead fills the lit pad. Vertical gridlines at the master lane's beats
(full height) so beats line up across lanes.
- Stopwatch (m:ss) + bar counter (master-lane cycles), refreshed ~4x/s only on change.
The .bin assets ship in the drive bundle (the A/B updater only pushes app.py), so a
one-time re-copy is needed to pick them up. APP_VERSION -> 0.0.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split the CircuitPython firmware into a tiny stable loader (code.py) + the application (app.py,
carries APP_VERSION). The editor's ⋯ → "⬆ Update firmware" queries the device version (SysEx 0x02
-> 0x03 reply), fetches the latest app from the site (/pico-cp-app.py), shows device-vs-latest, and
pushes the new app.py over USB-MIDI (SysEx 0x20). The device installs it to a trial slot (old build
kept as app.bak), reboots, and the loader AUTO-ROLLS-BACK to app.bak if the new build fails to start;
a build that runs cleanly ~5s is confirmed (clears /trial). No BOOTSEL, no dragging; Chromium/Firefox.
app.py forced to pure ASCII so it pushes raw (no base64); SysEx buffer raised to 60KB.
build.sh/deploy.sh: bundle code.py+app.py and serve /pico-cp-app.py. Docs updated.
Verified in CPython: version reply, update install+reboot+ACK, rollback file dance; editor loads clean
with the updater wired.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>