Replace mipidsi with a direct port of pico/main.py's ST7796 driver (per-command CS,
MADCTL 0x48, INVON, no offset, big-endian RGB565) — fixes the geometry/colour mangling.
Render the UI into a 300KB RAM framebuffer (embedded-graphics), then push only the
full-width row bands that changed vs the last frame (FNV row-diff) — flicker-free and
~144/480 rows per playhead step. Full-width windows only: the panel's MX bit mirror-maps
sub-width windows wrongly (that was the black screen). SPI 62.5 MHz per pico-cp to shrink
the tearing window (no TE pin is routed on this board, so hardware vsync isn't available).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the approved audio re-architecture (prototype-first): prove the click -> codec ->
transformer-isolated XLR chain on bought boards before any custom PCB, keeping the RP2350
firmware.
- pico-wm8960/code.py: CircuitPython bring-up for Pico 2 + SparkFun WM8960 breakout.
Synthesizes the click (familiar piezo pitches) -> I2S -> WM8960 -> HP/speaker; line-in
monitor hook; stereo/pan ready for polymeter spatialization. Uses the proven adafruit_wm8960
driver (no hand-rolled register driver).
- hardware/PROTOTYPE.md: shopping list, wiring, and bench milestones M1-M5 (M4 = the no-buzz
ground-loop test = acceptance gate).
Key findings baked in:
- Buzz was a ground loop; cure = transformer galvanic isolation, NOT +/-15 V (which was only
studio headroom and is dropped).
- WM8960 needs MCLK (CircuitPython I2SOut doesn't emit it); the SparkFun breakout's onboard
24 MHz oscillator supplies it -> resolves Risk R1 with zero extra parts.
Track-format conformance (node tests/run.mjs) stays green; DSL untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified workflow (not first-principles): host udev rules with uaccess ACL (maps by UID
into the toolbox; group access shows nobody:nobody), prebuilt probe-rs in the toolbox,
probe-rs run --chip RP235x pm-kit.elf for flash+RTT defmt streaming.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt proper embedded tooling for the blank-screen debug (user has a Pi Debug Probe):
- flip-link linker (baked into pm-rust:2): stack overflow faults cleanly instead of
silently corrupting .bss/.data (the SPI buffer -> black screen class of bug).
- defmt + defmt-rtt + panic-probe: firmware logs boot/heap-free/display/parse/loop
heartbeat over RTT; panics print message+location. .cargo runner = probe-rs run.
- Restore the full live metronome (from 08b0940) as the instrumented target.
- deploy + serve pm-kit.elf (probe-rs decodes defmt strings from the ELF).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
If blue → the 96KB heap memory was colliding (stack/buffer). If still black → the
allocator's presence itself. Narrowing the heap/display interaction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnosing the blank screen: strips everything but the heap init, one allocator
exercise (parse), and the confirmed-working draw_ui. Blue+corners => heap/parse/
display fine, bug is the metronome loop. Black => heap/parse breaks the display.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Temporary: draw_ui (blue + corners) instead of draw_metronome in the loop, keeping
heap+audio+inputs. Blue+corners shown => display/heap fine, draw_metronome is the
bug. Still black => heap/display path. Revert after diagnosis.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blank-screen fix attempt: the single boot-time draw (right after the init DISPON)
was likely lost; the peripheral test worked because it redrew every frame. Redraw
on change AND every ~140ms. (Audio timing checked between redraws; partial/playhead
redraw is the next step to tighten it.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The firmware is now an actual metronome (not a static screen):
- embedded-alloc heap → parses tracks with track-format on-device.
- 4 built-in grooves; clock-driven from the Timer; audio clicks on the master
lane's hits via the GP13 PWM (accent louder/longer), short edge-triggered pulses.
- Controls: A = play/stop, B = grid/notation view; joystick (rotated 90° CCW)
up/down = tempo +/-, left/right = prev/next groove.
- Renders draw_metronome or draw_notation; a cheap draw_progress strip animates the
bar position every frame (full redraw only on change → no flicker).
- Robust: all input reads use unwrap_or (no panics in the loop) — addresses the
self-test crash (likely an ADC unwrap on WouldBlock) and the continuous-buzzer.
Compile + simulator verified (grid renders all 4 grooves incl. triplets/polymeter).
NEEDS ON-DEVICE CHECK: audio timing, joystick directions, and that the crash is gone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gather notes per time-column across lanes; draw one shared stem per voice (hands
up / feet down) spanning the chord, so snare+hat on a beat share an up-stem. Ledger
lines for notes above/below the staff (hi-hat on its ledger line, crash higher).
Stems always clear the highest/lowest notehead; beams grouped per beat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
draw_notation() renders a bar as standard drum notation: 5-line staff + time
signature, voices mapped to staff positions and notehead types (oval drums,
cross hi-hat/cymbals), hands stem-up / feet stem-down, beamed eighths/sixteenths
grouped per beat, accents tinted. Developed entirely in the simulator
(uisim --bin notesim → PNG). Firmware build unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Honest answer to 'do the inputs/speaker work?': they had NO Rust code. Add the
drivers and a live self-test: buttons GP15/GP14 (pull-up), joystick GP26/GP27 via
ADC, speaker GP13 via PWM (~2 kHz click on button press). draw_peripheral_test
(pm-ui) shows button states, joystick dot + X/Y values, and beep activity; layout
verified in the simulator (uisim --bin periphsim) before flashing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the firmware from the bring-up diagnostic to pm_ui::draw_metronome with
static borrowed lane data (no allocator yet). Shows the actual metronome UI on the
device; live track + moving playhead come when pm-core is linked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
draw_metronome() renders the screen for any parsed track: track name + big BPM,
play/stop transport, and the polymeter lane grid — per-lane beat cells coloured by
level (accent amber / normal cyan / ghost purple / rest dark), playhead highlight,
beat gridlines, poly (~) marker. Pure no_std view over borrowed data (LaneView/
Screen) so the firmware build stays allocator-free.
uisim now parses a real track (track-format) and renders draw_metronome to PNG —
iterate the UI on the host, no bench. Firmware still draws the bring-up diagnostic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading mipidsi's interface/spi.rs: send_command writes the command byte and its
parameters as TWO separate SpiDevice transactions, so a normal SpiDevice de-asserts
CS between them. The ST7796 needs CS continuous across command+parameters, so
MADCTL/COLMOD/B6 args never loaded → default scan/orientation → 1/4 + rotated
(parameter-less commands and the pixel stream still worked, which is why it lit up).
CircuitPython's FourWire holds CS low for the whole command; replicate that: drive
the real CS (GP5) low for the session and give ExclusiveDevice a no-op CS. DC alone
selects command vs data.
Diagnosed entirely on the host: panelsim (new) decodes mipidsi's actual command/
pixel stream into a PNG and rendered perfectly, proving the protocol was right and
the bug was in the physical SPI/CS layer — then the driver source confirmed it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Host initdump proved mipidsi's MADCTL (0x48), COLMOD, and address window
(CASET 0..319 / RASET 0..479) already match CircuitPython exactly — so the 1/4
+ rotation wasn't an addressing bug. The missing piece was the ST7796 extension
init (B6/power/gamma) running as the PRIMARY bring-up right after reset (grafting
it onto mipidsi's already-DISPON'd panel blanked or under-configured it).
Now: manual hw reset + full CircuitPython st7796_init via the raw interface, THEN
Builder without reset_pin (re-asserts only the basics, extension setup persists).
initdump extended to also dump CASET/RASET draw windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 0xF0 extension unlock (gates gamma/power) was the likely blanker; 0xB6 is a
basic command and needs no unlock. Strip to just DISPOFF -> 0xB6(480 lines) ->
DISPON, the one change vs mipidsi's working-but-1/4 baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Host initdump (rust/uisim --bin initdump) showed mipidsi emits only SLPOUT,
MADCTL=0x48, INVON, COLMOD, NORON, DISPON — MADCTL already matches CircuitPython,
but the ST7796 extension setup (unlock, 0xB6 480-lines, power, gamma) is missing,
and sending it AFTER mipidsi's DISPON blanked the live panel. Replay the full
known-good st7796_init via Display::dcs() ending in its own DISPON. Adds the
initdump tool (capture init byte sequence on the host, no bench).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Answer to 'can you simulate it?': the UI now renders on the host.
- pm-ui: shared no_std embedded-graphics drawing (draw_ui), used by BOTH the
firmware and the simulator — single source, no divergence.
- uisim: host crate that draws pm-ui onto a framebuffer and exports a PNG (pure
Rust, no SDL). Confirmed the bring-up pattern renders correctly off-device, so
the black screen is a panel/controller issue, not a draw bug.
- pm-kit: use pm_ui::draw_ui; trim the ST7796 extension init to just unlock + 0xB6
(the gamma/VCOM sent after DISPON likely blanked it); LED solid during init then
slow 1 Hz blink so hung-init / running / reset-loop are distinguishable.
Note: the simulator covers WHAT we draw (layout/colour/logic). It does NOT model
the ST7796 controller's hardware quirks (0xB6 line count, MADCTL scan, SPI init) —
those still need the bench, but that's a one-time bring-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Pico Scroll Pack has no external I2C pull-up resistors (Pimoroni's C++ uses the
RP2040 internal pulls), so busio.I2C raised 'No pull up found on SDA or SCL' and the
firmware crashed before the splash. _make_i2c() now pre-enables the internal pull-ups
for busio and falls back to bitbangio (which uses them inherently). Pins GP4/GP5 were
correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A labelled developer/alpha section on the landing page with a brand-cyan download
button for the RP2350 Rust firmware + flash instructions, so it's grabbable from
the site (metronome.varasys.io/#rust) instead of the bare URL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mipidsi's ST7796 model uses the ST7789 init, which skips the ST7796-specific
extension-command unlock (0xF0) and Display Function Control (0xB6 = 480 driving
lines) — so the panel only scanned part of the screen (image confined to a corner
region + snow). After mipidsi's init, send the missing commands via Display::dcs()
using the known-good values from the CircuitPython st7796_init.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace clear() with same-path full-screen fill, add a 4-edge red border, four
distinct corner markers (TL green / TR yellow / BL cyan / BR magenta) and a TL
label, to pin down rotation/mirror/size from one flash. Apply flip_horizontal to
match the panel's MADCTL MX bit (CircuitPython uses 0x48).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The splash doubles as a liveness/pixel-map check (if 'PM-G1 GRID' reads correctly,
the firmware booted and the LED mapping is right). The BPM flash makes X/Y tempo
nudges visible from any view (previously invisible in Grid/Pendulum).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Init the Kit's ST7796 320x480 over SPI0 (SCK=GP2, MOSI=GP3, CS=GP5, DC=GP6,
RST=GP7; BGR, colours inverted, 16 MHz) via mipidsi 0.9 + embedded-graphics, and
draw a panel + "PM-KIT / RUST OK" so SPI + the graphics stack are verifiable on
screen. GP25 LED keeps blinking as a heartbeat.
Compiles for thumbv8m; runtime (does it draw? colours/orientation right?) is the
on-device check. Next: tune orientation/colour if needed, then inputs + audio + pm-core.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copies the built Rust .uf2 to the web root when present, so it can be flashed from
the website (alongside the CircuitPython firmware downloads). Conditional — only
served if rust/pm-kit/build.sh has produced it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First per-board binary. rust/pm-kit/ is a minimal rp235x-hal firmware that blinks
GP25 on the Pico 2 — proves the toolchain, RP2350 boot block (ImageDef), memory
layout, and flash before we add any drivers.
- src/main.rs + memory.x + build.rs + .cargo/config.toml: rp235x-hal blink, builds
for thumbv8m.main-none-eabihf.
- build.sh + uf2.py: one command builds the ELF in the container, objcopies to a raw
image, and packs pm-kit.uf2 (rp2350-arm-s family). Drag onto the Pico 2 in BOOTSEL.
Verified: builds clean; produces a valid 6-block UF2. Runtime (does it blink?) is the
on-device check. Next: drivers (display first) + link pm-core.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New form factor: a plain RP2040 Pico + Pico Scroll Pack (PIM545) -- a 17x7
single-colour LED matrix + 4 buttons. The 7x17 matrix maps onto the editor's
lane x step pad grid.
- pico-scroll/: CircuitPython firmware (DEVICE_ID "G"). Engine/scheduler/SysEx/
live-sync copied verbatim from pico-explorer (engine byte-identical, so it stays
on the track-format conformance lineage); vendored bulk-framebuffer IS31FL3731
driver (pins/map verified from pimoroni-pico); three LED views (Grid/Pendulum/BPM);
4-button input. Audio over USB-MIDI (no onboard speaker); optional P_BUZZER.
- grid.html + info-grid.html: widget page (canvas mirrors the 3 LED views) + spec
page with a ~$29 BOM.
- Registered in build.sh (precompile + ASCII assert + pm_g1_circuitpy.zip), deploy.sh,
embed.js, embed.html, index.html gallery, and both editors' FW_PATHS (device id G).
- docs/rust-port.md: core/driver architecture (pm-core no_std engine+protocol; per-board
drivers behind embedded-hal/embedded-graphics traits). CLAUDE.md + livesync-protocol.md
note the new edition + device id.
Python firmware stays in parallel with Rust (no abandonment yet).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- schedule.rs: ports the firmware's durs/timeline math (app.py tick/_prepare_next).
render(track, bars) yields the deterministic click timeline; tests/schedule.rs
asserts quarter-note spacing, subdivisions, swing 2/3:1/3, polymeter 5:4,
accents/ghosts, mute, and multi-bar looping. All green on the host.
- The crate is now #![no_std] + alloc and builds for thumbv8m.main-none-eabihf,
so the codec + scheduler are firmware-ready (verified:
cargo build --lib --target thumbv8m.main-none-eabihf).
./rust/run.sh -> 9 tests pass (2 conformance + 7 schedule). docs/rust-port.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Crystal Y1: footprint was a 4-pad type (Crystal_SMD_3225-4Pin) on a 2-pin symbol ->
pads 3/4 had no symbol pin. Switched to Crystal_SMD_2012-2Pin to match (set to the
chosen crystal's real footprint at BOM time; ground the case pads if 4-pad).
- USB-C J5: the library symbol's shield pin "S1" mismatches older KiCad footprints that
name the shell pads "SH" -> "pad S1 not found" ERROR. Dropped the shield net from the
schematic; the USB shell->chassis is now a layout-time tie (direct or 1Mohm||cap),
which is version-independent and standard practice. Error resolved; ERC 0.
The remaining ~46 import warnings are HARMLESS: "no net for pad N" on intentionally
unconnected pins (spare RP2350 GPIOs, unused relay NO contacts, unused ULN2003/74LVC14
channels, IC NC pins) -- expected on any board, nothing to fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
board.py now wraps each section in a SKiDL Group (Power/MCU/Audio/RTC/MIDI/Indicator/
Speaker/Interconnect) via inline grp()/endgrp() helpers (no reindent). The netlist now
carries a per-part (sheetpath (names "/<block>/")), so in Pcbnew you can right-click a
footprint -> Select -> Items in Same Hierarchical Sheet and move a whole block at once
instead of dragging 167 loose parts. 167 parts across 8 sheets; ERC 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maps each stage to its authoritative layout reference (EVM / datasheet layout section /
app-note / reference design) with URLs + the key takeaway, to complement LAYOUT.md's
general rules. Standouts that match our exact circuit:
- TPS65131EVM-839 (SLVUAW7) -- switcher layout to copy
- TPS7A30-49EVM-567 -- a +/-15V dual-LDO EVM = our exact rails
- RP2350 Minimal-KiCAD.zip -- official MCU/USB/crystal/QSPI reference layout (+ footprints)
Plus PCM5102A community refs, THAT1646/1240 reference boards, TI op-amp layout app-notes
(SBOA092B/SLOA046), RV-8803 app manual solder-pad section, USB-C/USBLC6 diff-pair rules,
and TI class-D layout for the PAM8302.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Added gnuplot to the EDA container; sims now wrdata their curves and plots.gp renders 5
PNGs in hardware/eda/sim/plots/ (open in any browser/viewer):
- input_loading: instrument(1M) preserves the +16dB pickup resonance the line(25k) flattens
- stage1_cmrr: common-mode leakage, 0.1% vs 1% resistor mismatch
- stage2_recon: DAC reconstruction filter (flat to 20k, -3dB ~75k)
- stage4_driver: balanced differential response (flat across audio)
- stage1_phantom: +48V transient clamped at the op-amp input, decays to ~0
(.data intermediates git-ignored.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A third implementation of the track DSL alongside engine.js and app.py, validated
against the same tests/fixtures/track-format.json:
- rust/track-format/: pure parse()/serialize() codec (std + alloc for now; no_std is
a later refinement). Ports the app.py/engine.js semantics exactly — grouping,
subdivisions, swing, ghost, polymeter, euclid, GM note-number aliases, unknown->beep,
default groove (group-start accents), tempo clamp, empty->beep, and the playback-flow
tokens (rep/end/relative-goto). Carries vol/cd too, so it's the most spec-complete
of the three.
- tests/conformance.rs: the Rust adapter — reads the shared fixtures, asserts each
case's normalized form (number-tolerant deep-equal) + serialize idempotency.
- rust/Containerfile + run.sh: Rust toolchain in a container (mirrors hardware/eda/),
with the thumbv8m.main-none-eabihf target for the eventual RP2350 firmware. Never
on the host.
Verified: ./rust/run.sh -> cargo test -> conformance + idempotent both pass.
docs/rust-port.md Stage 1 marked done.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the web editor's display in line with the PM_K-1 device screen:
- Elapsed stopwatch is always visible and counts while playing (was gated behind
the Timers switch); the switch now governs only the countdown.
- Tempo-ramp indicator in the display (↗/↘ amount/everyBars), shown whenever a
ramp is active — mirrors the device's ramp arrow.
- Header "device" badge that lights green with the port name while a PM_K-1 /
PM_X-1 is connected over USB-MIDI, updated live on connect/disconnect. The
editor requests MIDI on load (Chrome remembers the grant) so it reflects the
link automatically; the badge is also click-to-connect.
editor-beta.html (separate live-sync variant) left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- hardware/kicad/fp-lib-table: project footprint-library table registering pm_k1 ->
${KIPRJMOD}/pm_k1.pretty, so opening the pm_k1_core project auto-resolves the custom
TQ2SA relay + RV-8803-C7 footprints (fixes the 4 "footprint not found" import errors
for K1/K2/K3/U13).
- schematics/*.svg: inserted a white background rect (netlistsvg renders dark strokes on
a transparent bg -> invisible in dark-mode viewers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Added Node.js + netlistsvg to the EDA container; make_svg.py renders a SKiDL block to a
schematic SVG. Generated hardware/eda/schematics/*.svg for 12 blocks (audio stages 1-4 +
integrated, power tree, RP2350 core, RTC, MIDI, indicator, speaker) -- open in a browser.
Auto-routed (functional, not pretty); per-block so they're readable. interconnect omitted
(netlistsvg layout engine errors on the 24-pin USB-C + headers; its mapping is in DESIGN.md
s7). Intermediates (.json/_skin.svg) git-ignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grounds the native-Rust direction in what now exists: port inside-out, lowest risk
first, with tests/fixtures/track-format.json as the acceptance gate. Stage 1 (the
track-format crate as a third conformance adapter) is the concrete next PR -
host-testable in a container, no hardware. Toolchain goes in a container per the
develop-in-container rule, not the host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/playback-flow-test.md: on-device verification checklist for the runtime
(stop / rep / next / relative-goto / boundary / manual-override cases).
- editor.html + editor-beta.html: graphical "At end" control (loop / next / stop /
goto ±N) plus a rep-count input in the arrangement panel, wired through
state.rep/state.end -> currentSetup/currentPatch. Authoring is no longer
text-field-only.
- src/engine.js: patchToSetup now clamps tempo to [5,300] and defaults to a beep:4
lane when no lanes are given, matching the firmware. The editors keep their
"no lanes" hint by checking the raw input for a ':' token instead of parsed lanes.
- fixtures: tempo-clamp-high + empty-defaults-to-beep now pass on both engines.
Suite: 41 pass / 1 known (only the intentional vol/cd host boundary remains).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hardware/kicad/pm_k1.pretty/RV-8803-C7.kicad_mod, built from the RV-8803-C7 datasheet
(Mouser 2308195) Recommended Solder Pad drawing: SON-8, pad 0.4x0.8mm, pitch 0.9mm,
rows +/-0.65mm (0.5mm inner gap); top-view pin map per the package drawing (pin-1 index
bottom-left): bottom L->R 1-4, top L->R 8-5. Validated via `kicad-cli fp upgrade`.
With this + the TQ2SA relay, ALL 27 board.net footprints now resolve (19 stock KiCad +
2 in pm_k1.pretty). board.net imports 100% clean into Pcbnew once the pm_k1 project
footprint library is registered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hardware/kicad/pm_k1.pretty/Relay_DPDT_Panasonic_TQ2-SA.kicad_mod -- built from the
Panasonic TQ-SMD datasheet "Recommendable mounting pad, SA type" (2.54mm pitch, 1.0x2.94mm
pads, rows +/-3.31mm, pin map coil 1/10 / pole1 COM3 NC4 NO2 / pole2 COM8 NC7 NO9).
Validated via `kicad-cli fp upgrade` (parses to canonical KiCad 9 format). board.py now
references pm_k1:Relay_DPDT_Panasonic_TQ2-SA and pm_k1:RV-8803-C7.
RV-8803-C7 footprint NOT built: every host with its land pattern blocks/times-out my
fetcher (Micro Crystal, EM Micro, Mouser) and SnapEDA needs login. Won't guess a 0.8mm-pitch
land pattern (geometry + pin-pad mapping). Drop a verified RV-8803-C7.kicad_mod into
pm_k1.pretty (SnapEDA / datasheet) and it resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audited every footprint in board.net against the KiCad 9 libraries: 6 were wrong names,
now corrected to the real library footprints:
- QFN-24 -> ..._EP2.65x2.65mm (TPS65131); QFN-60 -> ..._EP3.4x3.4mm (RP2350A)
- HVSSOP-8 -> ..._EP1.57x1.89mm (LDOs); SOIC-8 wide -> 5.3x5.3mm (W25Q128)
- Keystone 1066 -> 1060 holder; inductor -> L_1210_3225Metric placeholder (set per part)
Now 25/27 footprints resolve; only RV-8803-C7 and the TQ2SA relay need custom footprints
(KiCad has neither) -- import from SnapEDA/manufacturer at layout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single reference doc for every component: datasheet title/doc#/rev, source URL, and the
pinout/key specs verified during capture (so it stands alone). PDFs are copyrighted ->
kept git-ignored in hardware/datasheets/ (11 downloaded during this work: RP2350 hw-design,
PCM5102A, THAT1240/1646, OPA1641, TPS65131, TPS7A4901/3001, ULN2003A, TQ2SA, PAM8302A).
Manufacturer-direct hosts (Diodes/ST/onsemi/Winbond/Micro Crystal) block automated fetch --
their URLs are listed for manual download. Also lists tools (KiCad/SKiDL/ngspice) + the
MIDI/USB/balanced-audio standards referenced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the per-track end-action model designed in docs/track-format.md §3, end to
end across both engines, both firmwares, and the editors.
Grammar (parsed + serialized by engine.js and both app.py):
rep=<n> cycles before the end-action fires (default 1)
end=stop stop after rep cycles
end=next advance one track (sugar for end=+1)
end=<±N> relative goto after rep cycles (e.g. end=-2 = D.S.)
(absent) loop forever — the metronome default
Firmware runtime (pico-cp + pico-explorer): _on_new_bar now consults a per-track
_end_plan() and fires stop / gapless-advance / relative-goto at the right bar.
A cycle = b<bars>, else one master bar; fire bar = rep * cycle. Explicit end=
governs; with no end, the global Continue toggle stays a default (=end=next, still
needs b<bars>) so existing set-lists and the CONT UI are unchanged. _prepare_next
takes a target index; the seam machinery, _do_advance and live-sync all carry rep/end.
Editors (editor.html + editor-beta.html): state.rep/state.end thread through
applySetup / currentSetup / currentPatch so load -> edit -> save preserves the
flow; authoring is via the program-string field (no graphical control yet).
Tests: the 3 playback-flow vectors now pass on both engines (39 pass / 3 known).
Runtime decision logic (_end_plan / _goto_target) unit-tested for stop, rep,
relative goto clamp/wrap, and legacy-Continue precedence. Codec round-trip
verified idempotent. Both firmwares compile + mpy-cross clean.
Also: untrack stale __pycache__/*.pyc build artifacts and gitignore them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close three real parser divergences the conformance suite flagged on the device
side (pico-cp + pico-explorer) — cases where the firmware produced a different
groove/sound than the web for the same patch:
- Euclidean (k,n,rot) shorthand (e.g. kick:4(3,8)) — was silently dropped to a
plain bar; now expands to the same hits as engine.js (added _euclid + parsing).
- GM note-number lane sounds (e.g. 36:4) — now resolve to the voice name (GM_NUM).
- Unknown sound names fall back to beep, matching the web.
vol/cd are NOT carried by the firmware by design: they are web-authoring fields
(the device has a hardware volume knob and no count-in). Documented as an
intentional, permanent host difference rather than a bug; the vol-and-countin
vector stays as expectFail[py] to mark the boundary.
tests/adapters/py_adapter.py: extract the new SOUND_GM/GM_NUM/_euclid nodes.
fixtures: euclid/unknown-sound/gm-note-number now pass on both engines.
docs §6 updated. node tests/run.mjs: 33 pass / 9 known, round-trips stable.
pico-explorer parser spot-checked identical to pico-cp.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- gen_bom.py + BOM_board.csv: authoritative BOM generated from board.net (70 line items,
167 placements), grouped with MPNs; refs match the integrated netlist; DNP ICs flagged.
(Supersedes the early hand-written BOM.csv, which used per-block refs.)
- LAYOUT.md: routing rulebook for board.net -- 4-layer stackup, the grounding/star-point
strategy, switcher loop isolation, analog separation, USB diff pair, RP2350/crystal/flash,
thermal, DNP blocks, pre-fab confirm list, DRC checklist.
- pcb_layout_tutorial.md: beginner orientation -- use KiCad; the schematic/netlist=contract
vs layout=physical-realization paradigm; the import->place->route->pour->DRC->Gerber
workflow; vocabulary; how our files fit; learning resources; honest expectations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>