From d558dccbde5db7b727fc05c4e7191ff73f1c7db0 Mon Sep 17 00:00:00 2001 From: Me Here Date: Thu, 28 May 2026 22:20:35 -0500 Subject: [PATCH] Phase 2: editor 'Save/Load to device' + bundle the editor on the drive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- build.sh | 1 + editor.html | 48 +++++++++++++++++++++++++++++++++++++++++++++++ info-kit.html | 14 +++++++++----- pico-cp/README.md | 6 ++++++ 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index fc5461c..9b8919f 100755 --- a/build.sh +++ b/build.sh @@ -41,5 +41,6 @@ import zipfile # PM_K-1 CircuitPython drive bundle (download → unzip onto CIR with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z: for f in ("code.py", "programs.json", "font_s.bin", "font_m.bin", "font_l.bin", "README.md"): z.write("pico-cp/" + f, f) + z.write("dist/editor.html", "editor.html") # offline copy of the editor, on the drive print("zipped pm_k1_circuitpy.zip") PY diff --git a/editor.html b/editor.html index 4c23048..85bbe3d 100644 --- a/editor.html +++ b/editor.html @@ -347,6 +347,8 @@ + + @@ -1069,6 +1071,50 @@ function shareSetlist() { openShare("Share “" + (sl.title || "set list") + "”", shareLink("sl=" + setlistToCode(sl)), "Shares the whole set list (each item's settings)."); } +/* Device (PM_K-1) programs.json — the same grooves the firmware reads. + Save: writes the active set list straight onto the CIRCUITPY drive (File System Access, + Chrome/Edge) or downloads it to drag on. Load: reads a programs.json into a new set list. */ +function programsJSON() { + const sl = getSL(); if (!sl) return null; + return JSON.stringify({ title: sl.title || "PolyMeter", programs: sl.items.map((it) => ({ name: it.name, prog: setupToPatch(it) })) }, null, 2); +} +async function saveToDevice() { + const json = programsJSON(); if (!json) return alert("No set list selected to save."); + if (window.showSaveFilePicker) { + try { + const h = await showSaveFilePicker({ suggestedName: "programs.json", + types: [{ description: "PolyMeter programs", accept: { "application/json": [".json"] } }] }); + const w = await h.createWritable(); await w.write(json); await w.close(); + return alert("Saved programs.json — pick your CIRCUITPY drive and the device auto-reloads with these grooves."); + } catch (e) { if (e.name === "AbortError") return; } // cancelled or unsupported → fall through to download + } + const a = document.createElement("a"); + a.href = URL.createObjectURL(new Blob([json], { type: "application/json" })); + a.download = "programs.json"; a.click(); URL.revokeObjectURL(a.href); + alert("Downloaded programs.json — drag it onto the device's CIRCUITPY drive (it auto-reloads)."); +} +function importPrograms(text) { + try { + const d = JSON.parse(text); + const items = (d.programs || []).map((p) => ({ name: p.name || "Item", ...patchToSetup(p.prog) })); + if (!items.length) return alert("No programs found in that file."); + setlists.push({ title: d.title || "Device", description: "", items }); activeSL = setlists.length - 1; activeItem = 0; + saveSetlists(); renderSetlists(); applySetup(items[0]); + alert("Loaded " + items.length + " grooves from the device into a new set list."); + } catch (e) { alert("Load failed: " + e.message); } +} +async function loadFromDevice() { + if (window.showOpenFilePicker) { + try { + const [h] = await showOpenFilePicker({ types: [{ description: "PolyMeter programs", accept: { "application/json": [".json"] } }] }); + return importPrograms(await (await h.getFile()).text()); + } catch (e) { if (e.name === "AbortError") return; } + } + const inp = document.createElement("input"); inp.type = "file"; inp.accept = ".json,application/json"; + inp.onchange = () => { if (inp.files[0]) inp.files[0].text().then(importPrograms); }; + inp.click(); +} + // Apply a shared link on load. Returns true if it set the metronome state. function applyHashShare() { const h = location.hash || ""; @@ -1292,6 +1338,8 @@ $("shortcutsOverlay").addEventListener("click", (e) => { if (e.target.id === "sh $("exportBtn").addEventListener("click", () => { $("trayMenu").hidden = true; exportAll(); }); $("importBtn").addEventListener("click", () => { $("trayMenu").hidden = true; $("importFile").click(); }); $("importFile").addEventListener("change", (e) => { if (e.target.files[0]) importAll(e.target.files[0]); e.target.value = ""; }); +$("saveDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; saveToDevice(); }); +$("loadDeviceBtn").addEventListener("click", () => { $("trayMenu").hidden = true; loadFromDevice(); }); $("clearLogBtn").addEventListener("click", () => { $("trayMenu").hidden = true; clearLog(); }); $("resetAllBtn").addEventListener("click", () => { $("trayMenu").hidden = true; resetAll(); }); $("shareSettingsBtn").addEventListener("click", () => { $("trayMenu").hidden = true; shareSettings(); }); diff --git a/info-kit.html b/info-kit.html index ccd2e6f..1286a4b 100644 --- a/info-kit.html +++ b/info-kit.html @@ -133,9 +133,10 @@ CircuitPython edition — USB drive + editor (experimental)

An alternative firmware that makes the Pico mount as a USB drive carrying the - firmware and your tracks (programs.json) — edit on the web and reprogram it without - Thonny. Coming next: one‑click "Save to device" and USB‑MIDI audio out to your computer's speakers. - The MicroPython firmware above stays the simple, rock‑solid option.

+ firmware, your tracks (programs.json) and a copy of this editor — design grooves on the + web and write them straight to the device, no Thonny. A full lanes/pads display with anti‑aliased + text drives the touchscreen. The MicroPython firmware above stays the simple, rock‑solid option. + (USB‑MIDI audio out to your computer's speakers is the next step.)

Download CircuitPython bundle ↓ Source + README ↗ @@ -143,8 +144,11 @@

  1. Flash CircuitPython (raspberry_pi_pico) via BOOTSEL → the CIRCUITPY drive appears.
  2. -
  3. Unzip the bundle onto CIRCUITPY (code.py + programs.json) — it's a normal drive, just drag them on.
  4. -
  5. It runs on boot; editing programs.json auto‑reloads the grooves. See the bundle's README for calibration flags.
  6. +
  7. Unzip the bundle onto CIRCUITPY (it's a normal drive — just drag everything on). It runs on boot.
  8. +
  9. Reprogram it from the web: build a set list in the editor, then the + set‑list menu → 📟 Save to device → pick the CIRCUITPY drive. The Pico + auto‑reloads with your grooves. (In Chrome/Edge it writes straight to the drive; otherwise it + downloads programs.json to drag on.) 📥 Load from device reads it back.
diff --git a/pico-cp/README.md b/pico-cp/README.md index c5b21ee..9340e0f 100644 --- a/pico-cp/README.md +++ b/pico-cp/README.md @@ -19,6 +19,7 @@ same program‑string language as . - `code.py` (this firmware — runs on boot) - `programs.json` (your grooves) - `font_s.bin`, `font_m.bin`, `font_l.bin` (the anti‑aliased fonts — kept as files to save RAM) + - `editor.html` (an offline copy of the web editor, so the drive carries its own programmer) 3. It starts immediately. Editing `programs.json` (or re‑saving it from the editor) makes CircuitPython **auto‑reload** with the new tracks. @@ -38,6 +39,11 @@ same program‑string language as . Each `prog` is a program string from the web editor. Add/replace entries and save — the device reloads. +**Easiest way to (re)program it:** in the editor (the web app, or the `editor.html` on the drive), build a +set list, then the set‑list **⋯** menu → **📟 Save to device** → pick the `CIRCUITPY` drive. In Chrome/Edge it +writes `programs.json` straight onto the drive (the Pico auto‑reloads); elsewhere it downloads the file to drag +on. **📥 Load from device** reads a `programs.json` back into a new set list. + ## Calibration (flip flags at the top of `code.py`) - **Red/blue swapped:** flip `MADCTL` between `0x48` (default) and `0x40`.