Some checks failed
check / check (push) Has been cancelled
First commit of the repo. Contents beyond the v0.1 simulator and layout: - Full Pico 2 W firmware (firmware/app, drivers, engine port, host test). - BANK page and 404 mode A/B removed; learned pads play exactly what was learned. - 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs, layout labels; wiring rebuilt). - ROLL returns to page select after a re-roll; a freshly learned track turns on. - LCD contrast 66. - ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky. Intended music change: the scale table grew from 8 to 21 and AUTO picks from all of them, so every seed's scale changed. Vectors regenerated with npm run vectors, plus new key-override cases. Flash record is now version 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
5.6 KiB
Markdown
95 lines
5.6 KiB
Markdown
# 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.
|