Commit graph

338 commits

Author SHA1 Message Date
Me Here
b1bb792df6 info-kit.html: drop the MicroPython firmware section; CircuitPython is THE firmware
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>
2026-05-30 06:21:17 -05:00
Me Here
30f02305e5 Editor: pause the Device-audio heartbeat during a firmware push
Push advanced to ~150/506 chunks then stalled intermittently. With Device audio on, the
heartbeat (0xFE every 250ms + a clock SysEx every ~3s) shares the MIDI link with the
firmware chunks and intermittently costs a chunk its ACK. Pause the heartbeat for the
duration of _pushFirmware and resume after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 15:14:58 -05:00
Me Here
937e7c332d Editor: fix firmware push stall - small chunks + send only to the Pico
Trace showed the push reaching "pushing" then stalling. Two causes: (1) _send went to
ALL MIDI outputs incl. "Midi Through Port-0" (a loopback that just echoes back); (2) the
512-char chunks overran the Pico's USB-MIDI receive buffer, so the device never saw the
end of a chunk's SysEx and never ACKed.

- _send now targets only the device port (name match pico/circuitpython/usb_midi; falls
  back to all outputs if none match) - no loopback echo.
- Firmware chunks 512 -> 64 base64 chars (a SysEx that fits the RX buffer); log progress
  every 50 chunks + a "committing" line so the console shows it advancing.

Editor-only; hard-reload to pick it up (no firmware change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 15:09:04 -05:00
Me Here
c5cc329185 Editor: add console breadcrumbs to updateFirmware to diagnose a silent no-op
User reports the updater does nothing with no visible error. Every code path shows a
dialog, so it's bailing before/at one of them (likely browser dialog-suppression making
confirm() return false, a stale/cached editor, or an uncaught throw). Log each step so a
console trace pinpoints where it stops.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 15:00:56 -05:00
Me Here
73d18ab1f3 PM_K-1: keep firmware versioning in the 0.0.x series (0.1.0 -> 0.0.13)
Per request, stay on 0.0.x; the lane-editor build is relabeled 0.0.13 (no code change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:55:36 -05:00
Me Here
dbc9fa7fdc PM_K-1 0.1.0: on-device lane editor - edit/add/remove lanes
Tap the instrument name -> a modal to change Sound (cycle the GM voices), Beats (1-12),
Subdivision (1-8), Swing, and Mute, plus + Lane / Remove (1..MAXLANES). Beats/sub changes
regenerate the lane's default accents; sound/swing/mute keep the pattern. Reuses the
existing dirty + Save/Revert + .mpy machinery (edits to a built-in save a copy to
"My edits"). The modal redraws live as you adjust; tap Done or outside to close.

Verified in harness: editor opens (13 hit-zones), sound cycles, beats/sub regen steps,
swing/mute toggle, add/remove lanes, the edited track serializes + round-trips, Done
closes; modal renders cleanly. app.mpy builds (C/v6).

This completes the Phase-2 editing set (beats + lanes + Continue + built-in/user split).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:44:04 -05:00
Me Here
ecd1d2a189 PM_K-1 0.0.12: Song = 4-bar sections, timer resets with the bar, smoother MIDI
- Built-in Song playlist: every section is now b4 (~4 bars) so Continue rolls one into
  the next quickly.
- On-screen timer now counts WITHIN the current segment and resets every time the bar
  counter wraps (new _seg_start, reset at each b<n> boundary + on _reset_clock). The
  practice-log duration still uses play_start (total). Unified the segment-boundary
  handling (timer reset + ramp restart + Continue advance) in _on_new_bar.
- MIDI stutter: display.refresh() BLOCKS on the SPI stream and was delaying the next
  beat's note. Cap refresh to ~30Hz and poll the GT911 touch ~30Hz (was every loop) so
  the scheduler fires notes on time; visuals lag a few ms (imperceptible).

Verified in harness: Build(b4,rmp92/4/2) bpm 92->96->reset@bar4, seg_start resets only at
the boundary, Continue arms there; edit tests pass; app.mpy builds (C/v6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:36:03 -05:00
Me Here
13318daf5b PM_K-1 0.0.11: fix boot crash - CircuitPython str has no .isalnum()
With the .mpy loading (no more OOM), the full app ran on hardware for the first time and
hit _slkey()'s c.isalnum() in the set-list dedup -> AttributeError (MicroPython/CircuitPython
str omits isalnum; my CPython harness has it, so it slipped through). Replaced with a
membership test against an explicit alnum set (uses only .lower()). Also compile the .mpy
from inside pico-cp/ so tracebacks read "app.py" instead of "pico-cp/app.py".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:17:12 -05:00
Me Here
7481f91935 PM_K-1 0.0.10: ship precompiled app.mpy (fixes boot OOM) + push .mpy over the air
The single-file app grew to ~57KB; CircuitPython compiling it at boot fragments the
RP2040 heap so badly that the fonts can't get a contiguous block (161KB free, yet a
~16KB alloc fails). Fix: precompile to app.mpy (Adafruit mpy-cross for CP 10.2.1, emits
CircuitPython mpy v6) so the device loads bytecode without compiling -> no fragmentation.

- build.sh precompiles pico-cp/app.py -> dist/app.mpy via tools/mpy-cross (gitignored
  binary); the bundle ships app.mpy (NOT app.py); serves pico-cp-app.mpy + pico-cp-app.py
  (the .py only for the editor's version regex + as readable reference).
- Loader (code.py) imports app.mpy and rolls back app.bak as .mpy.
- One-click updater now pushes the .mpy: editor base64-encodes it and sends it over the
  existing flow-controlled chunked transport (512-char = mult-of-4 chunks); the device
  base64-decodes each chunk to /app.new and verifies the CircuitPython .mpy header
  (magic 'C', v6, >=4KB) before the A/B install. Version still read from the served .py.

Verified: mpy-cross emits magic 'C'/v6; build produces a 21.8KB app.mpy; editing-logic
harness + scene render still pass; and a simulated push (base64 -> 57 chunks -> a2b_base64)
reassembles the .mpy byte-exact and passes the device's header check.

One-time recovery: delete app.py from the drive, copy app.mpy + code.py from the new zip.
After that, updates are one-click again (and can't brick: header check + A/B rollback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:01:57 -05:00
Me Here
7dd567fb44 PM_K-1 0.0.9: on-device editing (tap beats, save/revert) + Continue auto-advance
- 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>
2026-05-29 13:23:27 -05:00
Me Here
2d243c9ef8 PM_K-1 0.0.8: built-in playlists (baked, read-only) vs user playlists (separate)
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>
2026-05-29 12:29:09 -05:00
Me Here
88104e3d5c PM_K-1 0.0.7: perform tempo ramps + gap trainer, show their indicators, log bars
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>
2026-05-29 11:43:54 -05:00
Me Here
5e71df6b17 PM_K-1: chunked firmware transfer (reliable), LED run/stop indicator, revert bg tint
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>
2026-05-29 11:33:43 -05:00
Me Here
711a02fcc1 Fix firmware-update brick: app.py must be ASCII for the 7-bit MIDI push (+ guards)
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>
2026-05-29 10:14:13 -05:00
Me Here
2b113a18cc PM_K-1 firmware: show version (small, dim) top-right of the logo. APP_VERSION -> 0.0.4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 09:49:25 -05:00
Me Here
ca44aa833d PM_K-1 firmware: screen refinements (run-state bg tint, time/bar totals, per-track log, square/circle pads, separated track #)
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>
2026-05-29 09:41:57 -05:00
Me Here
dec6c61fce PM_K-1 firmware Phase 1: VARASYS logo, MIDI/USB status icons, square/outline beats, beat gridlines, stopwatch + bar counter
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>
2026-05-29 08:56:45 -05:00
Me Here
591fd8cfe5 Firmware versioning starts at 0.0.1 (was 1.0.0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 08:12:27 -05:00
Me Here
e24a39e4e8 Firmware update: file-picker fallback so the OFFLINE on-device editor can update too
The editor that ships on the device opens via file://, whose null origin can't
fetch() anything — so "Update firmware" died at the download-the-latest step
(CORS-blocked) before the USB-MIDI push. Now _firmwareSource() tries the site
(same-origin on the https editor; absolute URL when online) and, failing that,
lets the user pick app.py — mirroring the programs.json download/drag fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 08:10:51 -05:00
Me Here
72bf3a2da2 Bump version to 0.0.68 2026-05-29 06:55:58 -05:00
Me Here
e8945ee1d1 PM_K-1: one-click A/B firmware updates over USB-MIDI (+ version check)
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>
2026-05-29 06:55:58 -05:00
Me Here
72ea70da59 Bump version to 0.0.67 2026-05-29 00:38:08 -05:00
Me Here
7d743c18a1 PM_K-1: appliance model — push-programming over USB-MIDI, on-device practice log, swing fix
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>
2026-05-29 00:38:08 -05:00
Me Here
5b10af189d Bump version to 0.0.66 2026-05-28 23:58:12 -05:00
Me Here
09b20a9e69 PM_K-1: add firmware-protect helper (hide files so users only see editor + programs)
protect-firmware.sh sets the FAT hidden attribute on the firmware files (code.py, boot.py,
font_*.bin, README) on a mounted CIRCUITPY drive, so an end user sees only editor.html +
programs.json and can't accidentally delete the program — the hidden files keep running and
Save to device still works. Documented in pico-cp/README (incl. the read-only boot.py
hard-lock alternative) and bundled into pm_k1_circuitpy.zip. README.md verified accurate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:58:12 -05:00
Me Here
4ceb80b4f4 Bump version to 0.0.65 2026-05-28 23:38:53 -05:00
Me Here
cc56741483 PM_K-1: on-screen MIDI indicator + auto-mute buzzer when a host is listening
The editor's 'Device audio' now sends a MIDI Active-Sensing heartbeat (0xFE, every 250ms)
to the device while on. The firmware reads usb_midi.ports[0]; while it hears the heartbeat
(<1s) it shows a green 'MIDI' badge top-right and silences the buzzer (the computer plays);
~1s after it stops, it reverts to the buzzer and hides the badge. Manual MUTE_BUZZER still
works. Verified headless: host detected -> MIDI shown + buzzer duty 0; timeout -> reverts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:38:53 -05:00
Me Here
6b508ab86e Bump version to 0.0.64 2026-05-28 23:08:48 -05:00
Me Here
ba8d57e7ab PM_K-1 CircuitPython: fix polymeter (~) timing — true ratio polyrhythm
The firmware ran every lane at a fixed beat/sub, ignoring the ~ flag, so a poly lane
(e.g. cowbell:3~) played quarter-notes instead of fitting its cycle into lane 1's bar —
the duple 'and' coincided with a triplet note. Now match the web engine: a poly lane's
whole cycle spans the master lane's bar (dur = master_bar / steps). Verified: claves:5~
over kick:4 -> both cycles = 2.400s (5-over-4); 3-over-2 lands correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:08:48 -05:00
Me Here
8254bb042c Bump version to 0.0.63 2026-05-28 22:57:23 -05:00
Me Here
b5584454b3 PM_K-1 CircuitPython: add boot.py to guarantee the USB-MIDI port appears
If the device isn't seen as a MIDI input (USB endpoint pressure from drive+serial+HID+MIDI),
boot.py disables the unused HID interface and enables usb_midi — copy it on and power-cycle.
Bundled into pm_k1_circuitpy.zip; documented in the README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:57:23 -05:00
Me Here
186b51ae23 Bump version to 0.0.62 2026-05-28 22:55:57 -05:00
Me Here
aaf5c4d260 editor: MIDI 'Device audio' diagnostics (show device name + pulse on note)
User reported no computer audio + 'no device being controlled'. Add visibility to
diagnose: the button now shows the connected MIDI input's name (or 'no device'), the
toggle alert lists detected inputs, and the button pulses green on each Note-On
received — so it's clear whether the device is seen and whether notes are arriving.
Also call ensureAudio() in the message handler as a guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:55:57 -05:00
Me Here
8433c5bfe2 Bump version to 0.0.61 2026-05-28 22:40:09 -05:00
Me Here
7ccc75e399 Phase 3: USB-MIDI audio — play the device through the computer's speakers
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>
2026-05-28 22:40:08 -05:00
Me Here
d9a2be7389 Bump version to 0.0.60 2026-05-28 22:20:35 -05:00
Me Here
d558dccbde Phase 2: editor 'Save/Load to device' + bundle the editor on the drive
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>
2026-05-28 22:20:35 -05:00
Me Here
3664b7a29e Bump version to 0.0.59 2026-05-28 22:16:36 -05:00
Me Here
6edb89e33c PM_K-1 grid: smaller circles + align beats vertically across lanes
Place step circles by proportional position in the bar (beat = column start) instead
of centring in per-lane slots, so same-meter lanes' beats land at the same x (e.g. the
8-step hat's beats sit directly under the 4-step kick/snare). Cap circle radius at 6
(was up to ~18). Verified by printing per-lane beat x-positions + rendering the grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:16:36 -05:00
Me Here
c34d37d9c6 Bump version to 0.0.58 2026-05-28 22:07:00 -05:00
Me Here
ec43c694a1 PM_K-1 CircuitPython: circle pad grid, small labels, dimmer LED, faster SPI
From on-board feedback (works well; minor tweaks):
  - Pad grid uses circles now: big circle on each beat (division), small on the
    subdivisions (vectorio.Circle — native, no extra cost), coloured/lit as before.
  - Lane labels use a new small font (font_s.bin, ~12px via gen_font.py) so they're
    half-size and show more of the voice name (e.g. 'hatClos').
  - LED was blinding -> LED_BRIGHTNESS scale (default 0.15) applied on every write.
  - Residual tearing -> SPI back to 62.5 MHz (vendor speed; smaller tear window on a
    panel with no tearing-effect pin). Both are CONFIG flags.
Verified by rendering the full scene headless. font_s.bin added to gen_font.py + bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:07:00 -05:00
Me Here
6421e525da Bump version to 0.0.57 2026-05-28 21:56:27 -05:00
Me Here
c499910df4 PM_K-1 CircuitPython: add the lanes/pads view
Replace the single beat-dot row with a full pad grid: each lane is a row of step
pads coloured by dynamics (mute/normal/accent/ghost), with the playhead lit as it
plays (per-lane, so polymeter shows). Header (title/BPM/RUN/item) is compacted above
it; transport stays below. Pads are vectorio rects sharing one 8-colour palette and
recolour in place via color_index (cheap, tear-free); the grid only rebuilds on track
change. Caps at MAXLANES=5 rows (extra lanes still play). Verified by rendering the whole
displayio scene graph headless (layout + playhead lighting correct).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:56:27 -05:00
Me Here
47c19dea8c Bump version to 0.0.56 2026-05-28 21:43:48 -05:00
Me Here
ffededd05b PM_K-1 CircuitPython: self-contained RGB LED + fix screen tearing
From on-board feedback (memory + colours now good):
  - LED: drive the WS2812 via the core neopixel_write module (no neopixel library to
    install) — a tiny RGB class. Self-contained: it works straight from the bundle.
  - Tearing: switch displayio to auto_refresh=False and push a complete frame only when
    the scene changed (dirty flag, capped at the panel's refresh rate) so updates are
    never shown mid-paint. Beat dots now recolour in place (vectorio color_index) instead
    of being rebuilt every beat, shrinking the dirty region.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:43:48 -05:00
Me Here
365d242339 Bump version to 0.0.55 2026-05-28 21:30:03 -05:00
Me Here
111da49b5a PM_K-1 CircuitPython: fix MemoryError + red/blue swap (from on-board test)
On hardware the app rendered, beeped and took input, then died with MemoryError at
the text Bitmap alloc — the two ~37KB base64 font strings stayed pinned in RAM. Move
the fonts to small binary files read at boot (font_m.bin / font_l.bin), drop the
base64 + binascii, and gc.collect() before each text bitmap. code.py 56KB -> 20KB and
RAM use drops ~37KB+. Also: cyan rendered as yellow (R/B swapped) -> MADCTL 0x40 -> 0x48.
Bundle + README updated to include the font blobs. (LED still needs the neopixel lib.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:30:03 -05:00
Me Here
2c40baec58 Bump version to 0.0.54 2026-05-28 21:10:34 -05:00
Me Here
b419ad0daa Phase 1: CircuitPython firmware (USB-drive edition) for the Kit
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>
2026-05-28 21:10:34 -05:00
Me Here
656eab53dd Bump version to 0.0.53 2026-05-28 21:01:40 -05:00