Speaker rename (production device has a full audio circuit, not a buzzer):
- MUTE_BUZZER -> MUTE_SPEAKER, self.buz -> self.spk, P_BUZ -> P_SPK
- New SPEAKER_AUTO_MUTE flag (default True): mute the speaker when a MIDI host is
detected (the old hardcoded behavior; now a setting).
Gapless seam between tracks (Continue):
- _prepare_next() pre-parses the next playlist item during the LAST bar so the swap is
allocation-free. _do_advance() swaps lanes/bpm/bars/ramp/trainer in with
lanes[0]['next'] = seam_t (the wall-clock time of the boundary step we just hit), no
_reset_clock - the next tick fires step 0 of the new track exactly at the boundary.
tick() breaks out at the seam so the old voice's boundary beat is NOT fired (it'd be
the new track's step 0 a few ms later). Visuals (build_grid + draws) are deferred
one display-refresh cycle behind the audio via _need_redraw, so the audio doesn't
wait for them.
Continuous ramp:
- Replaced the bar-boundary set_bpm step with per-master-step linear interpolation:
bpm = _ramp_base + amt * ((m_steps/mlen) % bars) / ramp.every (clamped 30..300).
The integer-clamped bpm glides smoothly across the segment. draw_bpm() is now lazy
(skips the bitmap alloc if the displayed integer hasn't changed), and the periodic
meter tick in run() also redraws BPM so the big number follows the ramp.
MIDI Clock Out (master):
- New flags: MIDI_CHANNEL (default 10 = GM drum), MIDI_CLOCK_OUT (default OFF),
MIDI_CLOCK_OUT_TRANSPORT (default ON). midi_send() now uses the configured channel.
In tick(), when running + MIDI_CLOCK_OUT, stream 0xF8 at 24 PPQN with the interval
computed live from self.bpm (so it follows the continuous ramp). toggle() sends
0xFA on Start and 0xFC on Stop when transport is enabled.
Verified in harness: seam keeps lanes[0]['next'] = seam_t (no _reset_clock); ramp 80
glides via +0.25/step (visible as 80->81 in 4 master steps at rmp80/4/4); Clock Out
math sound (60/120/180 BPM -> 41.67/20.83/13.89 ms tick interval).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promote the CircuitPython "appliance" section to the single, default-open firmware
section, and update the hero/about/meta to match (drop "copy one file" / PROGRAMS list /
MicroPython flash dance phrasing -> precompiled bundle + on-device editing + Save to
device).
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>
Firmware (pico-cp/): the Pico now owns its filesystem by default (boot.py), so it can save the
practice log and write editor-pushed set lists; the drive is read-only to the computer, which also
protects the firmware. Hold button A at power-on for editor mode (drive writable; universal drag).
- Replaced the on-screen touch buttons with an on-device PRACTICE LOG (time · BPM · duration ·
track), newest-first, persisted to /history.json next to programs.json. Plays < 5s aren't logged;
tap a row twice to delete it. Real timestamps once the editor syncs the clock.
- USB-MIDI SysEx receiver: clock-set (0x01 -> RTC) and program-push (0x10 -> write programs.json,
reload, ACK/NAK). disable autoreload so our own writes never self-restart.
- Fixed swing: the parser was discarding the 's' flag, so /2s never swung. Now the scheduler uses a
per-step duration with long-short (2:1, SWING_RATIO 2/3) pairs on even subdivisions, matching the
web engine. Verified: ride:4/2s -> 266/133ms vs straight 200/200.
Editor (editor.html): requestMIDIAccess({sysex:true}); Save to device now pushes programs.json as
SysEx to the device (+ clock sync), waits for ACK, shows "Saved ✓", and falls back to downloading the
file (drag onto the drive in editor mode) when no device answers. Heartbeat also keeps the clock synced.
Web MIDI works in Chromium AND Firefox; the drag fallback covers any browser/OS incl. Safari.
Docs (pico-cp/README, info-kit, README) updated for the two modes, push programming, and the log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Firmware (pico-cp/code.py): on every click, send a USB-MIDI note-on per firing lane —
GM drum note by voice (SOUND_GM), velocity by level (accent/normal/ghost) — via the
default-enabled usb_midi.ports[1]. Polyphonic, so the computer plays the full groove.
New CONFIG: MIDI_ENABLED (default on), MUTE_BUZZER (silence the buzzer when using
computer audio).
Editor (editor.html): a '🎹 Device audio' toggle uses the Web MIDI API
(requestMIDIAccess) to voice incoming notes through the existing synth — Note-On ->
GM_NUM[note] / velocity-to-gain -> playInstrument(). The device is the clock; the
browser is the sound module, locked in sync. Chrome/Edge.
Verified: firmware emits the right notes (kick+hat on beat 1 of four-on-the-floor,
snare's rest skipped); editor loads clean with the toggle + handlers present. Docs
(info-kit, both READMEs) updated. The on-device buzzer/screen still work standalone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add to the editor's set-list ⋯ menu:
- 📟 Save to device — writes the active set list as programs.json (the same file the
PM_K-1 firmware reads). Uses the File System Access API to write straight onto the
CIRCUITPY drive (Chrome/Edge); falls back to a download to drag on. Reuses
setupToPatch() per item -> {title, programs:[{name, prog}]}.
- 📥 Load from device — reads a programs.json back into a new set list (patchToSetup
per item; reuses the existing import path).
Bundle the built editor.html into pm_k1_circuitpy.zip so the drive carries its own
offline programmer. info-kit + pico-cp/README document the workflow.
Verified: editor loads with no console errors; both menu buttons + all four functions
present; zip contains editor.html. (FSA save needs a real user gesture to test on-device.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New pico-cp/ — a CircuitPython port of the PM_K-1 firmware so the Pico mounts as a
CIRCUITPY drive carrying its code + tracks (the MicroPython pico/main.py stays the
simple fallback):
- pico-cp/code.py: displayio BusDisplay driving ST7796 via a custom init_sequence;
smooth anti-aliased text via displayio Bitmap+Palette (reuses the baked font blobs);
vectorio rects for dots/buttons; DIY GT911 touch (16-bit regs, edge-detected);
pwmio buzzer, analogio joystick, digitalio buttons, optional neopixel RGB; the
polymeter engine on a time.monotonic_ns scheduler. Reads /programs.json (falls back
to baked defaults); CircuitPython auto-reloads on file change.
- pico-cp/programs.json: the 23 default grooves. pico-cp/README.md: flash + calibrate.
- build.sh/deploy.sh: bundle + serve /pm_k1_circuitpy.zip. info-kit.html: experimental
'CircuitPython edition — USB drive' section.
Verified in CPython (stubbed displayio): init sequence well-formed, parser handles the
grooves incl. (3,8) euclid + @-4 gain, and code.py's actual make_text renders identical
smooth AA text. Hardware bits (panel/touch/MIDI) await on-board testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A user dropped main.py onto the RPI-RP2 BOOTSEL drive (which only accepts a .uf2)
and it vanished on reboot. Clarify in pico/README.md and info-kit.html that
flashing is two distinct steps: (1) drag-and-drop the MicroPython .uf2, then
(2) copy main.py over USB serial with Thonny/mpremote — the Pico is not a USB
drive once MicroPython is running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A new, first-actually-buildable form factor for the 52Pi EP-0172 "Pico Breadboard
Kit Plus" (Raspberry Pi Pico; 3.5" ST7796 320x480 cap-touch via GT911, PSP
joystick on ADC0/1, WS2812 RGB on GP12, buzzer GP13, buttons GP14/15):
- pico/main.py — one self-contained MicroPython file: ST7796 direct-draw driver,
GT911 touch (16-bit register addressing), WS2812 RGB (neopixel), PWM buzzer,
ADC joystick, buttons. It parses the project's own program-string language
(verified against the web engine's semantics) and runs a non-blocking
ticks_us scheduler with an on-screen touch UI. CONFIG flags cover panel /
colour / touch / joystick calibration. pico/README.md has flashing +
calibration steps.
- kit.html — lean widget that mirrors the firmware's on-screen UI (portrait
320x480 canvas) plus a joystick / RGB / buzzer / A-B buttons; plays via the
shared engine. info-kit.html — the real EP-0172 pinout, a parts list
(~$45 incl. Pico) and the firmware to flash (downloads /pico-main.py, links
the README + source).
- Landing + embed page list the Kit; build.sh/deploy.sh build the two pages and
serve pico/main.py as /pico-main.py for download.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>