Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
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>
This commit is contained in:
2026-10-11 20:39:36 +03:00
commit 2196633ed5
77 changed files with 52207 additions and 0 deletions

94
firmware/README.md Normal file
View File

@@ -0,0 +1,94 @@
# 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.