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>
This commit is contained in:
parent
656eab53dd
commit
b419ad0daa
7 changed files with 626 additions and 0 deletions
5
build.sh
5
build.sh
|
|
@ -37,4 +37,9 @@ pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text())
|
|||
print("copied embed.js")
|
||||
pathlib.Path("dist/pico-main.py").write_text(pathlib.Path("pico/main.py").read_text()) # PM_K-1 firmware, downloadable
|
||||
print("copied pico-main.py")
|
||||
import zipfile # PM_K-1 CircuitPython drive bundle (download → unzip onto CIRCUITPY)
|
||||
with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
|
||||
for f in ("code.py", "programs.json", "README.md"):
|
||||
z.write("pico-cp/" + f, f)
|
||||
print("zipped pm_k1_circuitpy.zip")
|
||||
PY
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ for f in index.html editor.html player.html teacher.html stage.html micro.html s
|
|||
done
|
||||
cp "$DIST_DIR/embed.js" "$DEST_DIR/embed.js"; echo " embed.js ($(stat -c '%s' "$DEST_DIR/embed.js") bytes)"
|
||||
cp "$DIST_DIR/pico-main.py" "$DEST_DIR/pico-main.py"; echo " pico-main.py ($(stat -c '%s' "$DEST_DIR/pico-main.py") bytes)" # PM_K-1 firmware download
|
||||
cp "$DIST_DIR/pm_k1_circuitpy.zip" "$DEST_DIR/pm_k1_circuitpy.zip"; echo " pm_k1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_k1_circuitpy.zip") bytes)" # PM_K-1 CircuitPython bundle
|
||||
rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html
|
||||
rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/)
|
||||
# info-*.html are first-class pages again: each form factor has a lean widget page
|
||||
|
|
|
|||
|
|
@ -129,6 +129,26 @@
|
|||
</div>
|
||||
</details>
|
||||
|
||||
<details class="spec">
|
||||
<summary>CircuitPython edition — USB drive + editor (experimental)</summary>
|
||||
<div class="spec-body">
|
||||
<p class="sub">An alternative firmware that makes the Pico mount as a <b>USB drive</b> carrying the
|
||||
firmware and your tracks (<code>programs.json</code>) — 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.</p>
|
||||
<p>
|
||||
<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>
|
||||
</p>
|
||||
<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>)
|
||||
via BOOTSEL → the <code>CIRCUITPY</code> drive appears.</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>It runs on boot; editing <code>programs.json</code> auto‑reloads the grooves. See the bundle's README for calibration flags.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<p class="sub" style="max-width:760px;margin:14px auto 0">Embed this widget elsewhere with one <code><div></code> + a script —
|
||||
see <a href="/embed.html">the embed docs</a>.</p>
|
||||
</main>
|
||||
|
|
|
|||
55
pico-cp/README.md
Normal file
55
pico-cp/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# PM_K‑1 "Kit" — CircuitPython edition (USB drive + editor)
|
||||
|
||||
The **CircuitPython** firmware for the 52Pi EP‑0172 Pico kit. Unlike the MicroPython version
|
||||
(`../pico/main.py`), this makes the Pico mount as a **USB drive (`CIRCUITPY`)** that carries the
|
||||
firmware and your tracks — so you can edit on the web and reprogram it without Thonny. It runs the
|
||||
same program‑string language as <https://metronome.varasys.io>.
|
||||
|
||||
> **Status: experimental, phase 1.** This drives the screen/touch/joystick/buzzer and reads your
|
||||
> grooves from `programs.json`. The editor's one‑click "Save to device" and USB‑MIDI audio‑to‑computer
|
||||
> are landing in later phases. The simpler **MicroPython** firmware (`../pico/main.py`) remains the
|
||||
> rock‑solid fallback — and the Pico can't be bricked (BOOTSEL → drag a MicroPython `.uf2` back).
|
||||
|
||||
## Install
|
||||
|
||||
1. **Flash CircuitPython:** hold **BOOTSEL**, plug in, and drop the CircuitPython `.uf2` for your board
|
||||
onto the `RPI‑RP2` drive (<https://circuitpython.org/board/raspberry_pi_pico/> — or the Pico 2 / W
|
||||
build). It reboots and a **`CIRCUITPY`** drive appears.
|
||||
2. **Copy the files** onto `CIRCUITPY` (drag‑and‑drop — it's a normal drive now):
|
||||
- `code.py` (this firmware — runs on boot)
|
||||
- `programs.json` (your grooves)
|
||||
3. It starts immediately. Editing `programs.json` (or re‑saving it from the editor) makes CircuitPython
|
||||
**auto‑reload** with the new tracks.
|
||||
|
||||
## Controls (same as the MicroPython build)
|
||||
|
||||
- **Touch:** on‑screen `◀◀ / ▶ / ▶▶` (prev · play/stop · next) and `− / TAP / +`.
|
||||
- **Joystick:** up/down = tempo, left/right = previous/next groove.
|
||||
- **Button A (GP15)** play/stop · **Button B (GP14)** tap tempo.
|
||||
- **RGB LED** flashes each beat; **buzzer** clicks (accent/normal/ghost).
|
||||
|
||||
## programs.json
|
||||
|
||||
```json
|
||||
{ "title": "PolyMeter",
|
||||
"programs": [ { "name": "Four on the floor", "prog": "t120;kick:4;snare:4=.x.x;hatClosed:4/2" } ] }
|
||||
```
|
||||
|
||||
Each `prog` is a program string from the web editor. Add/replace entries and save — the device reloads.
|
||||
|
||||
## Calibration (flip flags at the top of `code.py`)
|
||||
|
||||
- **Red/blue swapped:** change `MADCTL = 0x40` to `0x48`.
|
||||
- **Colours look negative:** toggle `INVERT_COLORS`.
|
||||
- **Taps land wrong:** set `TOUCH_DEBUG = True`, watch the serial output, then set
|
||||
`TOUCH_SWAP_XY` / `TOUCH_INVERT_X` / `TOUCH_INVERT_Y`.
|
||||
- **Joystick reversed:** toggle `JOY_INVERT_X` / `JOY_INVERT_Y`.
|
||||
- **Screen blank / garbled:** the panel lot may differ; drop `SPI_BAUD`, and if it's a 240×320 ILI9341
|
||||
instead of the 320×480 ST7796, the init/size need changing (this targets the 320×480 you have).
|
||||
- **No RGB LED:** the WS2812 needs the `neopixel` library on `CIRCUITPY/lib` (`circup install neopixel`)
|
||||
— everything else works without it.
|
||||
|
||||
If `code.py` ever errors, CircuitPython prints the traceback **on the screen and over USB serial** —
|
||||
copy that to me and I'll fix it.
|
||||
|
||||
The fonts are the same baked anti‑aliased blobs as the MicroPython build (see `../pico/gen_font.py`).
|
||||
BIN
pico-cp/__pycache__/code.cpython-312.pyc
Normal file
BIN
pico-cp/__pycache__/code.cpython-312.pyc
Normal file
Binary file not shown.
448
pico-cp/code.py
Normal file
448
pico-cp/code.py
Normal file
File diff suppressed because one or more lines are too long
97
pico-cp/programs.json
Normal file
97
pico-cp/programs.json
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
"title": "PolyMeter",
|
||||
"programs": [
|
||||
{
|
||||
"name": "Four on the floor",
|
||||
"prog": "t120;kick:4;snare:4=.x.x;hatClosed:4/2"
|
||||
},
|
||||
{
|
||||
"name": "Swing ride",
|
||||
"prog": "t150;ride:4/2s;kick:4=X..x;snare:4=.x.x"
|
||||
},
|
||||
{
|
||||
"name": "Purdie half shuffle",
|
||||
"prog": "t92;kick:4/3=X....x...x..;snare:4/3=..gg.gX.gg.g;hatClosed:4/3=X.xX.xX.xX.x"
|
||||
},
|
||||
{
|
||||
"name": "Samba (2/4)",
|
||||
"prog": "t104;tomLow:2/4=x...X...;hatClosed:2/4;woodblock:2/4=X.xx.xX."
|
||||
},
|
||||
{
|
||||
"name": "Nanigo (6/8 bembe)",
|
||||
"prog": "t130;cowbell:4/3=X.xx.x.xx.x.;kick:4/3=X.....X.....;hatClosed:4/3=..x..x..x..x"
|
||||
},
|
||||
{
|
||||
"name": "6/8 groove",
|
||||
"prog": "t100;kick:3+3=x..x..;snare:3+3=...x..;hatClosed:3+3/2"
|
||||
},
|
||||
{
|
||||
"name": "7/8 (2+2+3)",
|
||||
"prog": "t130;kick:2+2+3=x..x..x;hatClosed:2+2+3/2"
|
||||
},
|
||||
{
|
||||
"name": "5/4 (3+2)",
|
||||
"prog": "t112;kick:3+2=x..x.;snare:3+2=..x..;hatClosed:3+2/2"
|
||||
},
|
||||
{
|
||||
"name": "5 over 4 polyrhythm",
|
||||
"prog": "t100;kick:4;claves:5~"
|
||||
},
|
||||
{
|
||||
"name": "3 over 2 hemiola",
|
||||
"prog": "t96;woodblock:2;cowbell:3~"
|
||||
},
|
||||
{
|
||||
"name": "2 & 4 & 3 per bar",
|
||||
"prog": "t100;kick:3;cowbell:2~;claves:4~"
|
||||
},
|
||||
{
|
||||
"name": "Triplet hats",
|
||||
"prog": "t100;kick:4;snare:4=.x.x;hatClosed:4/3"
|
||||
},
|
||||
{
|
||||
"name": "Accents",
|
||||
"prog": "t92;kick:4=X..X;snare:4=.X.X;hatClosed:4/2"
|
||||
},
|
||||
{
|
||||
"name": "Tempo builder 80+",
|
||||
"prog": "t80;woodblock:4;rmp80/4/4"
|
||||
},
|
||||
{
|
||||
"name": "Gap trainer 2/2",
|
||||
"prog": "t100;kick:4;hatClosed:4/2;tr2/2"
|
||||
},
|
||||
{
|
||||
"name": "Intro - hats+kick",
|
||||
"prog": "t88;kick:4=X.x.;hatClosed:4/2=gggggggg"
|
||||
},
|
||||
{
|
||||
"name": "Groove in",
|
||||
"prog": "t88;kick:4=X.x.;snare:4=.X.X;hatClosed:4/2"
|
||||
},
|
||||
{
|
||||
"name": "Half-time shuffle",
|
||||
"prog": "t92;kick:4/3=X....x...x..;snare:4/3=..gg.gX.gg.g;hatClosed:4/3=X.xX.xX.xX.x"
|
||||
},
|
||||
{
|
||||
"name": "Build 92 to 120",
|
||||
"prog": "t92;kick:4;snare:4=.X.X;hatClosed:4/2"
|
||||
},
|
||||
{
|
||||
"name": "Four-floor (909)",
|
||||
"prog": "t124;kick909:4;clap909:4=.X.X;hat909:4/2=.X.X.X.X"
|
||||
},
|
||||
{
|
||||
"name": "Samba break",
|
||||
"prog": "t116;tomLow:2/4=x...X...;hatClosed:2/4;woodblock:2/4=X.xx.xX."
|
||||
},
|
||||
{
|
||||
"name": "Peak - 16ths",
|
||||
"prog": "t132;kick:4=X..x;snare:4=.X.X;hatClosed:4/4"
|
||||
},
|
||||
{
|
||||
"name": "Outro",
|
||||
"prog": "t132;kick:4=X..x;hatClosed:4/2=gggggggg"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue