# Firmware plan Nothing here yet. This is the plan for porting the simulator to the Pico 2 W in C with the Pico SDK. ## Rule one: the engine must match the vectors `sim/engine.js` is the reference. The C engine is done when it reproduces every file in `test/vectors/` bit for bit: 1. **primitives.json**: `mix()`, the xorshift32 `Rng` (`next`, `below`, `chance`) and `genome()`. 2. **gen0 … gen4 \*.json**: full renders, 8 bars per case, 189 cases. Each case has a checksum (FNV-1a over the rows), so a failure shows up at once. The first differing bar tells you where to look. Build and test the engine **on the host first**: plain C, no SDK, a test runner that reads the JSON (or a header generated from it). Only then put it on the Pico. If the music needs to change, change `engine.js`, listen in the simulator, regenerate the vectors (`npm run vectors`), and commit both together. ### JS → C traps - **Integers**: - Every hash and RNG value is `uint32_t`. - `Math.imul(a,b)>>>0` is `(uint32_t)a*(uint32_t)b`. - `x>>>n` is a logical shift on `uint32_t`. - **`fdiv` and `mod`** are floor division and a positive modulo. C's `/` and `%` truncate toward zero, so write `fdiv`/`mod` helpers that handle negatives. - **`>>` on negative numbers**: JS shifts arithmetically, for example `(e-64)>>2`. Use `fdiv(x,4)` or make sure the compiler does an arithmetic shift (GCC on ARM does). - **`%` in JS** truncates like C. It only matches `mod()` when the left side is non-negative. - **`Math.floor(a*b/c)`** with non-negative ints is plain integer division. - **Sorting**: - `Array.prototype.sort` is stable (ES2019), and the engine relies on it: events that tie on the sort keys keep their insertion order. - Use a stable sort, such as insertion sort on small arrays. - Sort keys: `renderBar` uses (t, track, voice), and AMEN humanising uses (t, voice). - **No allocation in the audio path.** Fix the maximum events per bar and use static arrays. The vectors peak at 54 events in a bar (automata, energy 127); size buffers for 128. - **`genome` has a `formName` string.** It's only for display. `describe()` is display only and doesn't need porting exactly. ## Structure (suggested) ``` firmware/ engine/ engine.c/.h portable, no SDK, the only thing the vectors test test/ host test runner gcc, reads test/vectors app/ main.c core 0: UI, screen, inputs; core 1: scheduler + MIDI out drivers/ midi_uart.c pcd8544.c matrix.c encoder.c pots.c CMakeLists.txt ``` ## Runtime - **Two cores.** - Core 1 owns time: it renders a bar ahead into a double buffer and sends MIDI from a hardware timer. - 840 ticks per quarter. - Clock (0xF8) every 35 ticks. - Start (0xFA) and stop (0xFC) on RUN. - Core 0 runs the UI and hands parameter changes over through a small queue or atomics. - **MIDI out**: UART0 TX on GP0 (OUT KIT) and UART1 TX on GP4 (OUT SYNTH), both at 31 250 baud. Running status is optional. - **MIDI in**: UART0 RX on GP1, only used by LEARN. A note-on stores {channel, note}. - **Track gating** matches app.js: - Every event is rendered. - Muted tracks skip their note-ons, but note-offs still go out. - Unlearned kit tracks are skipped. - A held button flips the track while held. - Optional: changes wait for the bar line. - **Stop** sends note-offs for everything sounding, CC 120 on each kit channel in use, and CC 120 and 123 on the synth channel. - **Storage**: keep slots, learned pads, BPM, generator, MIDI channel and octave in the last flash sector. Write only on change, debounced. ## Drivers - **PCD8544** (Nokia 5110): - SPI0 (SCK GP18, MOSI GP19, CE GP17), with DC on GP20 and RST on GP21. - Up to 4 MHz. - The 84 × 48 framebuffer is 504 bytes. - The screen layout and the 5 × 7 font are in app.js (`screenText`, `drawMap`, `FONT`). - Contrast (Vop) varies between modules, so make it a setting. - **Matrix**: - Rows GP2, GP3, GP5 are inputs with pull-ups. - Columns GP6–GP9 are driven low one at a time and Hi-Z otherwise, with the input buffer off or a pull-up on (E9). - Scan at about 1 kHz with a 5–10 ms debounce. - Key map (row, col) → track: (1,1) KICK, (1,2) KICK 2, (1,3) SNARE, (1,4) none; (2,1) HAT, (2,2) HAT 16, (2,3) PERC, (2,4) GHOST; (3,1) BASS, (3,2) MEL A, (3,3) MEL B, (3,4) PAD. Same as `PANEL` in app.js, which also orders the screen map. - No FILL button: always pass `fill = false` to the engine (bar-4 fills still happen on their own). - **Encoder**: GP10/GP11 quadrature with a state-table decoder; push on GP12. Long push (about 600 ms) is the SLOT save. - **Pots**: ADC0/ADC1 (GP26/GP27). Average, then 7-bit with hysteresis so the values don't flicker. - **SEED** (GP13) and **RUN** (GP14): inputs with pull-ups, debounced. ## Pico notes - RP2350 erratum E9: use pull-ups, not pull-downs, on inputs. The whole design is active-low. - `gpio_set_drive_strength(0, GPIO_DRIVE_STRENGTH_12MA)`, and the same for pin 4. Each MIDI loop draws about 6–8 mA. - The W's wireless chip isn't used in v0.1. - BOOTSEL can't be reached with the box closed. Keep the SDK's USB reset interface in the build (`pico_enable_stdio_usb`), so `picotool reboot -f -u` drops the board into the bootloader for flashing. - Matrix columns that aren't being driven: input buffer off or pull-up on (E9). ## Order of work 1. Port the engine on the host until `test/vectors` pass. 2. Bring up the board with a blink and the UARTs: send a test note to each device. 3. Screen driver and font. 4. Inputs: matrix, encoder, pots, SEED, RUN. 5. Scheduler on core 1, running the ported engine. Compare it by ear with the simulator on the same seed. 6. Pages, LEARN, slots, flash storage.