Compare commits

..

No commits in common. "d9a2be7389b829e7476a379bd6b680c116f40695" and "3664b7a29e9984be2b2dd098ce0b8b3623ae2ef6" have entirely different histories.

5 changed files with 6 additions and 65 deletions

View file

@ -1 +1 @@
0.0.60 0.0.59

View file

@ -41,6 +41,5 @@ 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: 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"): 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("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") print("zipped pm_k1_circuitpy.zip")
PY PY

View file

@ -347,8 +347,6 @@
<button id="exportBtn">⭳ Export all (file)</button> <button id="exportBtn">⭳ Export all (file)</button>
<button id="importBtn">⭱ Import file…</button> <button id="importBtn">⭱ Import file…</button>
<input type="file" id="importFile" accept="application/json" style="display:none"> <input type="file" id="importFile" accept="application/json" style="display:none">
<button id="saveDeviceBtn" title="Write this set list to a PM_K-1 device drive (programs.json)">📟 Save to device</button>
<button id="loadDeviceBtn" title="Load a device's programs.json into a new set list">📥 Load from device</button>
<button id="clearLogBtn">🗑 Clear log</button> <button id="clearLogBtn">🗑 Clear log</button>
<button id="resetAllBtn" style="color:#ff7b6b">♻ Reset everything…</button> <button id="resetAllBtn" style="color:#ff7b6b">♻ Reset everything…</button>
</div> </div>
@ -1071,50 +1069,6 @@ function shareSetlist() {
openShare("Share “" + (sl.title || "set list") + "”", shareLink("sl=" + setlistToCode(sl)), "Shares the whole set list (each item's settings)."); 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. // Apply a shared link on load. Returns true if it set the metronome state.
function applyHashShare() { function applyHashShare() {
const h = location.hash || ""; const h = location.hash || "";
@ -1338,8 +1292,6 @@ $("shortcutsOverlay").addEventListener("click", (e) => { if (e.target.id === "sh
$("exportBtn").addEventListener("click", () => { $("trayMenu").hidden = true; exportAll(); }); $("exportBtn").addEventListener("click", () => { $("trayMenu").hidden = true; exportAll(); });
$("importBtn").addEventListener("click", () => { $("trayMenu").hidden = true; $("importFile").click(); }); $("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 = ""; }); $("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(); }); $("clearLogBtn").addEventListener("click", () => { $("trayMenu").hidden = true; clearLog(); });
$("resetAllBtn").addEventListener("click", () => { $("trayMenu").hidden = true; resetAll(); }); $("resetAllBtn").addEventListener("click", () => { $("trayMenu").hidden = true; resetAll(); });
$("shareSettingsBtn").addEventListener("click", () => { $("trayMenu").hidden = true; shareSettings(); }); $("shareSettingsBtn").addEventListener("click", () => { $("trayMenu").hidden = true; shareSettings(); });

View file

@ -133,10 +133,9 @@
<summary>CircuitPython edition — USB drive + editor (experimental)</summary> <summary>CircuitPython edition — USB drive + editor (experimental)</summary>
<div class="spec-body"> <div class="spec-body">
<p class="sub">An alternative firmware that makes the Pico mount as a <b>USB drive</b> carrying the <p class="sub">An alternative firmware that makes the Pico mount as a <b>USB drive</b> carrying the
firmware, your tracks (<code>programs.json</code>) and a copy of this editor — design grooves on the firmware and your tracks (<code>programs.json</code>) — edit on the web and reprogram it without
web and write them straight to the device, no Thonny. A full lanes/pads display with antialiased Thonny. Coming next: oneclick "Save to device" and USBMIDI audio out to your computer's speakers.
text drives the touchscreen. The MicroPython firmware above stays the simple, rocksolid option. The MicroPython firmware above stays the simple, rocksolid option.</p>
(USBMIDI audio out to your computer's speakers is the next step.)</p>
<p> <p>
<a class="dl" href="/pm_k1_circuitpy.zip" download>Download CircuitPython bundle ↓</a> <a class="dl" href="/pm_k1_circuitpy.zip" download>Download CircuitPython bundle ↓</a>
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-cp" target="_blank" rel="noopener">Source + README ↗</a> <a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-cp" target="_blank" rel="noopener">Source + README ↗</a>
@ -144,11 +143,8 @@
<ol class="steps"> <ol class="steps">
<li>Flash <b>CircuitPython</b> (<a href="https://circuitpython.org/board/raspberry_pi_pico/" target="_blank" rel="noopener">raspberry_pi_pico</a>) <li>Flash <b>CircuitPython</b> (<a href="https://circuitpython.org/board/raspberry_pi_pico/" target="_blank" rel="noopener">raspberry_pi_pico</a>)
via BOOTSEL → the <code>CIRCUITPY</code> drive appears.</li> via BOOTSEL → the <code>CIRCUITPY</code> drive appears.</li>
<li>Unzip the bundle onto <code>CIRCUITPY</code> (it's a normal drive — just drag everything on). It runs on boot.</li> <li>Unzip the bundle onto <code>CIRCUITPY</code> (<code>code.py</code> + <code>programs.json</code>) — it's a normal drive, just drag them on.</li>
<li><b>Reprogram it from the web:</b> build a set list in the <a href="/editor.html">editor</a>, then the <li>It runs on boot; editing <code>programs.json</code> autoreloads the grooves. See the bundle's README for calibration flags.</li>
setlist <b></b> menu → <b>📟 Save to device</b> → pick the <code>CIRCUITPY</code> drive. The Pico
autoreloads with your grooves. (In Chrome/Edge it writes straight to the drive; otherwise it
downloads <code>programs.json</code> to drag on.) <b>📥 Load from device</b> reads it back.</li>
</ol> </ol>
</div> </div>
</details> </details>

View file

@ -19,7 +19,6 @@ same programstring language as <https://metronome.varasys.io>.
- `code.py` (this firmware — runs on boot) - `code.py` (this firmware — runs on boot)
- `programs.json` (your grooves) - `programs.json` (your grooves)
- `font_s.bin`, `font_m.bin`, `font_l.bin` (the antialiased fonts — kept as files to save RAM) - `font_s.bin`, `font_m.bin`, `font_l.bin` (the antialiased 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 resaving it from the editor) makes CircuitPython 3. It starts immediately. Editing `programs.json` (or resaving it from the editor) makes CircuitPython
**autoreload** with the new tracks. **autoreload** with the new tracks.
@ -39,11 +38,6 @@ same programstring language as <https://metronome.varasys.io>.
Each `prog` is a program string from the web editor. Add/replace entries and save — the device reloads. 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 setlist **⋯** menu → **📟 Save to device** → pick the `CIRCUITPY` drive. In Chrome/Edge it
writes `programs.json` straight onto the drive (the Pico autoreloads); 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`) ## Calibration (flip flags at the top of `code.py`)
- **Red/blue swapped:** flip `MADCTL` between `0x48` (default) and `0x40`. - **Red/blue swapped:** flip `MADCTL` between `0x48` (default) and `0x40`.