Commit graph

266 commits

Author SHA1 Message Date
Me Here
04350f9d09 pm-kit: peripheral self-test — buttons + joystick (ADC) + speaker (PWM)
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>
2026-05-31 23:56:13 -05:00
Me Here
0ea442d68d pm-kit: draw the real metronome screen on the panel (static sample data)
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>
2026-05-31 23:48:50 -05:00
Me Here
676d9879fa pm-ui: first real metronome screen (header/BPM/transport + polymeter lane grid)
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>
2026-05-31 23:38:48 -05:00
Me Here
d7b393b7c2 docs: Stage 3 — display milestone confirmed on hardware (ST7796 + CS-low fix)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:33:30 -05:00
Me Here
b154ccf493 pm-kit: hold CS low — fix ST7796 1/4 screen (mipidsi toggles CS mid-command)
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>
2026-05-31 23:01:04 -05:00
Me Here
026c20523d pm-kit: full st7796 init as PRIMARY bring-up, then mipidsi for drawing
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>
2026-05-31 22:50:51 -05:00
Me Here
b2ea27f506 pm-kit: minimal 0xB6 (no unlock) bracketed by DISP off/on for line count
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>
2026-05-31 22:28:44 -05:00
Me Here
508fae56fb pm-kit: replay full CircuitPython st7796_init (extension setup before DISPON)
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>
2026-05-31 22:06:31 -05:00
Me Here
0fa32a827f Rust UI: host simulator (PNG) + shared pm-ui crate; trim panel init
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>
2026-05-31 21:54:20 -05:00
Me Here
67182cd74c PM_G-1: fix I2C init on the pull-up-less Scroll Pack (busio+internal-pulls, bitbangio fallback)
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>
2026-05-31 21:53:45 -05:00
Me Here
4afac44d06 index: add Rust firmware (alpha) section with pm-kit.uf2 download
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>
2026-05-31 21:36:04 -05:00
Me Here
0c8f370a5c pm-kit: send ST7796 extension init (0xB6 480 lines) mipidsi omits
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>
2026-05-31 21:32:08 -05:00
Me Here
17d2aa134d pm-kit: diagnostic display pattern + flip_horizontal (fix mirror)
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>
2026-05-31 21:16:34 -05:00
Me Here
35726b57ac PM_G-1: scrolling boot splash (model name) + transient BPM flash on tempo nudge
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>
2026-05-31 21:08:33 -05:00
Me Here
4275187008 pm-kit milestone 2: ST7796 display bring-up
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>
2026-05-31 20:56:07 -05:00
Me Here
8b4fca2a74 deploy: serve rust/pm-kit/pm-kit.uf2 (RP2350 Rust firmware download)
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>
2026-05-31 20:49:49 -05:00
Me Here
0e224393f7 Rust port Stage 3 milestone 1: pm-kit boot-proof blink (RP2350)
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>
2026-05-31 20:34:46 -05:00
Me Here
400d896518 Add PM_G-1 "Grid" form factor (Pimoroni Pico Scroll Pack) + Rust core/driver plan
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>
2026-05-31 20:30:15 -05:00
Me Here
c1601d9e46 Rust port Stage 2: scheduler timing + no_std (builds for RP2350)
- 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>
2026-05-31 19:34:02 -05:00
Me Here
adc92c7c02 PM_K-1 hardware: fix the 2 real import issues (crystal footprint + USB shield error)
- 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>
2026-05-31 19:30:48 -05:00
Me Here
4fb3365f36 PM_K-1 hardware: tag parts into circuit-block "sheets" for KiCad select-by-block
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>
2026-05-31 19:21:18 -05:00
Me Here
9792729be0 PM_K-1 hardware: note locally-saved EVM/reference layouts in LAYOUT_REFERENCES.md
Downloaded the 3 priority reference layouts into hardware/datasheets/ (git-ignored):
- TPS65131EVM_SLVUAW7.pdf (switcher EVM 4-layer plots + BOM)
- TPS7A30-49EVM_SLVU405.pdf (+/-15V dual-LDO EVM, schematic+layout+BOM)
- RP2350-Minimal-KiCAD.zip (official RP2350 KiCad: nested RP2350A/QFN-60 + RP2350B/QFN-80)
Verified the switcher EVM contains the full Top/Inner1/Inner2/Bottom layer plots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:51:51 -05:00
Me Here
795252fbce PM_K-1 hardware: LAYOUT_REFERENCES.md -- manufacturer reference layouts per stage
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>
2026-05-31 18:48:19 -05:00
Me Here
af8e7e236b PM_K-1 hardware: render simulations as PNG plots (gnuplot) so they're viewable
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>
2026-05-31 18:39:46 -05:00
Me Here
be524ce1ea Rust port Stage 1: track-format codec crate (passes the golden vectors)
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>
2026-05-31 18:36:59 -05:00
Me Here
6aeca94222 PE-1 editor display: match the device (always-elapsed, ramp, device link)
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>
2026-05-31 18:29:53 -05:00
Me Here
8cbd53a2ee PM_K-1 hardware: register pm_k1 fp-lib (fix import errors) + white bg on SVGs
- 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>
2026-05-31 17:57:10 -05:00
Me Here
39fe087b2c PM_K-1 hardware: per-block schematic SVGs (netlistsvg) so the design is viewable
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>
2026-05-31 16:42:04 -05:00
Me Here
f201892c9c docs: staged Rust-port plan (codec crate first, gated by the golden vectors)
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>
2026-05-31 16:13:32 -05:00
Me Here
8bba218f67 Editor controls for playback flow + close web-side divergences
- 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>
2026-05-31 16:11:43 -05:00
Me Here
42eefdf250 PM_K-1 hardware: custom RV-8803-C7 footprint (from datasheet) -> all 27 resolve
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>
2026-05-31 16:00:48 -05:00
Me Here
fb9afe2b27 PM_K-1 hardware: custom TQ2SA relay footprint (verified) + project fp lib
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>
2026-05-31 15:49:49 -05:00
Me Here
88af54e544 PM_K-1 hardware: fix footprint names so board.net imports cleanly into KiCad
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>
2026-05-31 15:41:04 -05:00
Me Here
751c3f7803 PM_K-1 hardware: REFERENCES.md (all datasheet URLs + verified specs)
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>
2026-05-31 00:52:23 -05:00
Me Here
da7c94e67f Implement per-track playback flow (rep / end / relative goto)
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>
2026-05-31 00:37:06 -05:00
Me Here
9701f49913 Firmware: parse euclid, GM note-numbers, and unknown-sound fallback
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>
2026-05-31 00:15:25 -05:00
Me Here
0dc9daf54f PM_K-1 hardware: consolidated BOM + LAYOUT.md + PCB-layout tutorial
- 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>
2026-05-31 00:15:15 -05:00
Me Here
bf74c860e5 Track format: unify default (no-pattern) groove across web + firmware
A lane with no =pattern produced different defaults on web vs device — a real,
shipped divergence the new conformance suite caught (e.g. hatClosed:4/2 in
"Four-on-the-floor" played steady 8ths in the browser but quarter-notes on the
device). Adopt one rule everywhere: every subdivision sounds at normal level,
accents fall ONLY on group starts (the grouping is the accent map).

- pico-cp/app.py, pico-explorer/app.py: off-beat subdivisions sound at normal (1)
  instead of resting (0); group-start accenting was already correct.
- src/engine.js: default beatsOn accents group starts only (was: every beat);
  laneCfgToStr isDefault check updated to match so round-trips stay idempotent.
- docs + fixtures: document the rule; default-pattern vectors now pass on both.

Audible effect (intended): device subdivided hat/ride lanes gain their off-beat
strokes (now match the web); web stops over-accenting every beat. Lanes with an
explicit =pattern are unchanged. Verified green via node tests/run.mjs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:03:34 -05:00
Me Here
47edf4eb2a PM_K-1 hardware: FULL-BOARD integration -- single master netlist (board.py)
circuits/board.py re-implements every block (power tree, RP2350 core, audio chain, RTC,
MIDI-DNP, interconnects, SIG/CLIP-DNP, speaker-DNP) with shared net objects and SKiDL
auto-assigned reference designators -> one coherent board.net for PCB layout.

167 components, unique refs U1-U18 / K1-K3 / J1-J5. ERC 0 errors; netlist 0 errors.
Remaining ERC warnings are all benign unconnected-pin notes on intentionally-spare pins
(relay NO contacts, 4 unused ULN2003 channels, spare GPIOs, 2 unused 74LVC14 inverters,
RTC CLKOUT, TPS65131 BSW) and OC<->GPIO notes (comparator/opto outputs read by the MCU).
MCLK-less: PCM5102A SCK tied to GND (internal PLL).

This is the complete schematic deliverable: board.net + BOM.csv ready for layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:56:42 -05:00
Me Here
754ed1c22d Formalize track format: spec + golden-vector conformance suite
Single source of truth for the track ("program"/"patch") grammar, which was
implemented by hand in src/engine.js and pico-cp/app.py with no cross-check and
had quietly drifted.

- docs/track-format.md: formal grammar, container (programs.json) schema with a
  version field, the new per-track playback-flow model (rep/end + relative goto;
  default = loop forever), normalization rules, and a list of known divergences.
- tests/: golden vectors + a runner that loads the REAL engine.js and app.py
  grammar (no copies; app.py via ast extraction) and compares both against the
  spec. Exit non-zero on unexpected mismatch or round-trip break -> usable as CI.

Surfaces real divergences for follow-up: default accent pattern (no =pattern)
differs web vs device and affects shipped presets; euclid not parsed on device;
vol/cd dropped on device; unknown-sound fallback; tempo clamp; empty patch.
The rep/end playback-flow vectors are the acceptance test for building that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:54:20 -05:00
Me Here
25d0c57d79 PM_K-1 hardware: fix indicator.py scoping bug (p15 rebind in threshold())
The committed indicator.py errored (UnboundLocalError: p15) -- an augmented assign on a
global Net inside threshold() made it local. Flipped to rt[1]+=p15. Now ERC 0 / netlist 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:49:50 -05:00
Me Here
367951c903 PM_K-1 hardware: SIG/CLIP indicator + monitor speaker amp (both DNP)
indicator.py: peak-detect (Schottky+RC) on STAGE1_OUT (signal-present) and MIX_OUT (clip)
-> LM393 (powered from +15V) vs tunable threshold dividers -> open-collector outputs pulled
to +3V3 = SIG_LED/CLIP_LED (drive face LEDs + read on GPIO19/20).
speaker.py: PAM8302A filterless class-D (pinout verified, Diodes SO-8) fed from MIX_OUT
(single-ended, RIN=68k -> ~+5.7dB), SD pulled high, BTL out -> SPK_P/SPK_N.
Both ERC 0 errors; netlists 0 errors. Threshold/gain values tunable; DNP per form factor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:49:18 -05:00
Me Here
7eea02f1d2 PM_K-1 hardware: interconnects (digital ribbon + analog + MIDI headers + USB-C/ESD)
circuits/interconnect.py maps the board nets onto connectors per DESIGN.md s7:
- J3 digital ribbon 2x13 (Pico-pinout): display SPI, touch I2C, joystick ADC, buttons,
  WS2812, panel-switch inputs, SIG/CLIP LED lines, power.
- J4 analog 2x5: balanced out hot/cold, shield, balanced in hot/cold, speaker (kept off
  the fast digital ribbon).
- J5 MIDI 1x8: OUT/IN/THRU loops + power (DNP MIDI only).
- J1 USB-C receptacle (KiCad lib symbol) + USBLC6-2SC6 ESD + 5.1k CC pulldowns; D+/D-
  from the core's 27R series, VBUS to the power tree.
ERC 0 errors; netlist 0 errors. Confirm USB-C connector variant (24-pin sym vs 16-pin
GCT USB4085) + USBLC6 footprint at layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:33:21 -05:00
Me Here
8f662598e4 PM_K-1 hardware: MIDI block (DNP) -- opto IN + buffered OUT/THRU
circuits/midi.py, a do-not-populate option (USB-MIDI is the default). H11L1 opto-isolated
IN (breaks the MIDI ground loop), 74LVC14-buffered OUT (TX through two inverters) and THRU
(re-buffered IN). Connector is a face choice; core exposes the loop nets MIDI_IN/OUT/THRU_A/B
plus MIDI_TX/RX to the RP2350 UART. ERC 0 errors; netlist 0 errors.
CONFIRM: H11L1 pinout (standard; datasheet fetch timed out) + 3.3V-MIDI series-R values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 23:28:59 -05:00
Me Here
8fe9ade210 PM_X-1 0.0.5: clear logo/divider overlap + simpler always-on amp
User reported the y=28 divider crossed the bottom of the V in the VARASYS
logo. The logo is actually 29 px tall (gen_assets.py blob: w=156 h=29), so
positioned at y=8 it ran to y=37 - the divider sat ~halfway through the V.

Fix: logo now at y=4 (top-margin 4 px), divider at y=34 (~1 px clearance
below the logo bottom at y=33). Everything below the divider shifted down
6 px: BPM/time y=44, bar y=56, train y=64, tab y=78, title y=94, GRID_TOP=116,
LOG_TOP=224 with 5 rows (was 6).

Simpler amp control. 0.0.4's per-click _amp(True)/_amp(False) toggling was
both timing-sensitive (the brief 22 ms PWM burst would race with amp settling
time) and polarity-guessed. Replaced with: hold amp_en at the polarity-correct
"enable" value the whole time. One config flag (AMP_EN_ON_VALUE, default True)
controls polarity. If the user still hears no sound after dragging this on,
flip the flag to False and re-flash.

Also still check Settings -> Speaker. If a saved /settings.json from an earlier
build has speaker=auto, Live sync's heartbeat will silence the piezo - cycle
to "Always" with A in the Settings menu.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 23:28:09 -05:00
Me Here
5aac3ab172 PM_K-1 hardware: stop tracking generated SKiDL artifacts (.erc/.log/_sklib.py)
These build outputs leaked into git (the .gitignore only covered .net/.pdf/etc). Added
the patterns and untracked the files (kept on disk). Keeps the repo to source only and
stops generated churn from being swept into unrelated commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:54:28 -05:00
Me Here
c625a8aaa2 Firmware push fix on both Kit (0.0.24) + Explorer (0.0.4)
Diagnosed from the user's console output - 25 chunks pushed cleanly at
~124ms each, then stalled. Two coupled causes:

1) Bus contention. tick() and Live sync share self.midi with the chunk
   ACKs. While the device was processing a chunk, a Note On / Clock Out /
   Live-sync FULL heartbeat could land on the same MIDI OUT stream and
   the host's parser dropped the interleaved ACK SysEx.

   Fix: self._fw_pushing flag set on 0x21 BEGIN, cleared on 0x23 COMMIT
   or any error. midi_send / Clock Out / _sync_broadcast / _sync_broadcast_full
   all early-out when _fw_pushing is True. Only ACKs go out during a push.

2) SysEx assembler garbage. self._sx = bytearray() per chunk leaks ~70
   bytes / chunk that only GC'd every 50 chunks. 25 chunks of trash plus a
   slow heap walked the wrong way explains the ramp-up to 174 -> 119 -> 124
   ms ACK times. GC every chunk now (~30ms cost on RP2040/RP2350 with
   small heap) so the assembler buffer is always fresh.

Same patch on both pico-cp/ and pico-explorer/ since the bug is identical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 22:51:46 -05:00
Me Here
3805c5ee00 PM_X-1 0.0.4 + editor push diagnostics
Layout fixes (user reported BPM/time were still bumping the header at 0.0.3):
- All Y coords below the header divider shifted down 6px: BPM 30->38,
  time 32->38, bar 44->50, train 52->58, setlist tab 66->72, title 82->88.
- GRID_TOP 104 -> 110.

Restored the Kit-style footer practice log:
- LOG_TOP=218, LOG_ROWH=14, LOG_ROWS=6.
- MAXLANES dropped from 6 visible to 4 visible (rowh capped at 26 so the grid
  doesn't run into the log). Tracks with more lanes still play silently.
- _build_scene now appends g_log (with a divider above it).
- draw_log() draws the current-track log into the footer; load() + _log_play()
  + the seam apply path all call it. The Practice-log menu entry is kept for
  the full scrollable history.

Editor diagnostics for the firmware push (the user got chunk-1 ACK then the
device's MIDI badge went gray, meaning chunks 2+ never reached it):
- editor.html + editor-beta.html _pushFirmware() now logs every MIDI output
  + input it sees along with which ones _isDevicePort() matched, plus per-
  chunk send/ACK timing for the first 3 chunks and any failed chunk.
- This narrows down whether the failure is (a) wrong-port routing
  (filter doesn't match the Pimoroni Explorer's name), (b) ACK never arriving
  back to the host, or (c) chunks sent fine but the device's RX buffer is
  dropping them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 22:40:11 -05:00
Me Here
ea7bb9bfee PM_X-1 0.0.3: compact 240x320 layout + piezo polarity flag + Pimoroni device filter
Three fixes the user reported on 0.0.2:

1. Layout still overlapped. 0.0.2 ported the Kit's pixel positions verbatim,
   but those were designed for 480 px of height; on 320 the same Y values
   stack on top of each other. This pass actually scales everything down:
   - BPM big number was FONT_L (~30 px tall) -> FONT_M (~16 px tall).
   - Time + bar meters were FONT_M -> FONT_S, tightly stacked at y=32/44
     instead of y=50/78.
   - Setlist tab + CONT y=66 (was 118); track title y=82 (was 134).
   - GRID_TOP=104 (was 138). Frees ~34 px more vertical room for the
     pad grid.
   - Modal panels: PX/PW shrunk to use less of the 240-wide canvas, RH
     22 (was 26), inter-line spacing 13-14 (was 14-16). Title strings
     trimmed ("Practice log" instead of "Practice log (this track)").

2. No sound from the piezo. Two likely causes:
   - SPEAKER_AUTO_MUTE was True. Live sync sends a FULL heartbeat over
     USB-MIDI every 5s; the firmware sees those bytes and treats it as
     "host listening" -> mutes the piezo. Default now False on Explorer
     (toggle to Auto in Settings if you ARE using "Device audio" in the
     editor).
   - AMP_EN polarity. Added AMP_EN_ACTIVE_HIGH config flag (default True)
     and a _amp(on) helper. If your specific board's amp is active-low,
     flip to False at the top of CONFIG.

3. Firmware push stalled at chunk 1. Editor's _isDevicePort() only matched
   "pico" / "circuitpython" / "usb_midi"; the Pimoroni Explorer reports
   under a different name, so the editor broadcast to all MIDI outputs
   and the ACK got lost in routing. Filter now also matches "pimoroni",
   "explorer", "rp2350", and "varasys" (future-proofing).

The .mpy build dropped from 29.8 KB to 29.3 KB (smaller font footprint plus
fewer modal hint strings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 22:05:56 -05:00
Me Here
edb736c1d3 PM_K-1 hardware: RP2350 core (MCU + flash + crystal + USB + boot/reset + SWD)
circuits/mcu_core.py using the authoritative KiCad MCU_RaspberryPi:RP2350A symbol.
Minimal design per RP "Hardware design with RP2350" (RP-008280):
- Core SMPS: VREG_VIN<-3V3, VREG_LX->3.3uH->DVDD, VREG_FB senses DVDD; VREG_AVDD via
  33ohm+4.7uF RC filter; ADC_AVDD filtered; 100nF per power pin.
- 12MHz crystal, MCLK-LESS (RP2350 makes I2S BCK/LRCK/DIN; PCM5102A uses its internal
  PLL) -- no audio oscillator, no MCLK net. Cheaper/simpler/robust; inaudible difference
  for a metronome (decided with the user).
- W25Q128JVS QSPI flash (Fig-8 pinout); BOOTSEL = QSPI_SS via 1k + button; RUN 10k
  pull-up + reset button; SWD header; USB D+/D- via 27ohm series.
- Full GPIO map assigned (DESIGN.md s7.1 + audio control: SPI/I2C/ADC/buttons/LED +
  I2S + relay-enable/mute/gnd-lift + DAC_XSMT).
ERC 0 errors; netlist 0 errors. CONFIRM at layout: crystal load caps, QFN-60 footprint,
and the USB-C connector + USBLC6-2 ESD + CC resistors (USB sub-block; D+/D- exit on 27R).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:56:12 -05:00