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

5.6 KiB
Raw Blame History

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.