Replace the single 16-px strip with a 4×16 WS2812 matrix (four 16-px strips,
still PIO-driven on the RP2040):
- bottom row = the beat (cyan downbeats / amber group-starts, current beat
bright, the rest a dim grid) — separated from the rows above by a divider;
- the three rows above stack the CURRENT beat's subdivisions as they pass:
a column climbs row-by-row with each subdivision and resets on the next beat,
with faint "slots" showing the ladder it will climb.
Subdivisions are driven by the finest lane that shares lane 1's beat grid
(non-poly, same beatsPerBar, max stepsPerBeat) — so an 8th-note hat shows one
row, 16ths show three, straight quarters show none. Also fixed a stray
"#05measure" typo left in the old .npx border rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /player-asbuilt.html showing the PM-1 with parts you'd actually solder
for an RP2040 build, alongside the idealized /player.html:
- 128×64 MONOCHROME OLED (SSD1306 class): rendered as a true 1-bit
framebuffer — drawn, then thresholded to crisp on/off pixels and scaled
with image-rendering:pixelated — so the cramped real layout is honest
(position / big BPM / grouping / scrolling name / bar·beat).
- Fixed 16-px WS2812 ("NeoPixel") RGB beat bar on a strip PCB: lights the
first beatsPerBar slots (cyan downbeats, amber group-starts, dim others),
the rest dark — showing the fixed-count hardware honestly.
- EC11 rotary encoder you actually turn (wheel / vertical drag) for tempo,
tactile buttons, MAX98357A-style speaker grille, USB-C, PWR LED, matte case.
Shares the same firmware via src/engine.js + src/setlists.js (same seed set
lists, same scheduler); only the panel rendering differs. The device is fixed
dark hardware; the page chrome follows light/dark/system. build.sh + deploy.sh
now assemble/serve all three pages; player.html links to it ("As-built ↗").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>