Files
seedbox/firmware/README.md
Gee 2196633ed5
Some checks failed
check / check (push) Has been cancelled
Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
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>
2026-10-11 20:39:36 +03:00

95 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.