C++ firmware for a new 4-voice polyphonic generative sequencer Eurorack
module, built on a Raspberry Pi Pico 2 (RP2350). Despite the repo name,
this is not a port of the MicroPython firmware in
Pi-Pico-Random-Looping-Sequencer's
Software/ — it's a from-scratch engine for a new module that happens to
share the spirit (random looping per-step CV/trig). The original
MicroPython module remains its own thing.
The code runs on two hosts that share the same C++17 engine and App layer:
playground— a macOS sim (GLFW + Dear ImGui + PortAudio audition synth). Develop without hardware.firmware/— a Pico-SDK host that boots on a real Pico 2 driving an SSD1306 OLED + 8 buttons over USB-C power.
4-voice polyphony with independent per-voice state. Each voice has:
- Per-step CV + trigger sequences (16 steps, but step count is variable)
- Scale-quantised pitch from one of 46 scale presets, configurable octave range and starting note
- Per-step CV / trigger mutability locks (freeze individual steps)
- Glide / portamento, euclidean rhythm, step direction (Fwd/Rev/Pend/Rand), swing/shuffle, neighbour-weighted CV picking, pattern rotate, and one-shot Randomize
Engine-wide: internal clock with tap tempo, or external clock-in (jack not wired yet — see "Hardware status" below).
One-time:
brew install glfw portaudio pkg-configThen from the repo root:
make # first run clones Dear ImGui v1.91.0 into ./imgui/
./playground
# or
make runThe sim mirrors the pattern from the vault note Quick Tips/Simulating module UI on macOS and the reference implementation in
DIYSynthMNL/vcdo-daisy. A std::chrono tick thread (ClockThreadMain)
replaces the external Eurorack clock when the engine is in INT-clock
mode; PortAudio drives the 4-voice audition synth so you can hear what
the module would output.
| Window | Contents |
|---|---|
| Simulated OLED (128×64) | Header bar, body, status footer. PlaybackViews + menus render here exactly as on the firmware OLED. |
| Encoder / buttons | On-screen mirrors of the 8 hardware buttons + encoder. |
| Audition | Per-voice mute toggles (V1–V4) and master gain for the summed PortAudio output. |
| Engine inspector | Live state dump (per-voice CV/trig, clock period, locks) for debugging. |
| Key | Action |
|---|---|
← / → |
Encoder rotate (−1 / +1) |
Space |
Encoder press (Click) |
Backspace |
Encoder long-press (Back / cancel) |
1–9 |
Jump to numbered submenu (root list only) |
P |
Play / stop |
T |
Tap tempo |
Tab |
Cycle active voice (V1 → V2 → V3 → V4) |
V |
Cycle PlaybackView layout |
M |
Open main menu |
H |
Go home (back to PlaybackView) |
X |
Randomize active voice |
S |
Toggle clock source Ext ↔ Int |
G |
Manual external-clock pulse |
D |
Manual digital-in pulse |
R |
Reset engine to defaults |
A |
Toggle audition audio on/off |
Esc / Q |
Quit |
The firmware targets a Raspberry Pi Pico 2 (RP2350) driving:
- an SSD1306 OLED (128×64, I²C, address
0x3C) — the menu/UI display - 8 momentary switches — menu navigation + transport + per-voice actions
| Signal | Pico 2 pin | GPIO | Notes |
|---|---|---|---|
| OLED VCC | 36 | — | 3V3 OUT (most SSD1306 modules also accept 5 V on VSYS) |
| OLED GND | 38 | — | any GND pin |
| OLED SDA | 6 | GP4 | I²C0 SDA |
| OLED SCL | 7 | GP5 | I²C0 SCL |
| Up button | 9 | GP6 | rotate −1 (each button: one leg to GPIO, other leg to GND) |
| Down button | 10 | GP7 | rotate +1 |
| Click button | 11 | GP8 | encoder press |
| Back button | 12 | GP9 | long-press / cancel |
| Play button | 14 | GP10 | transport toggle |
| Tap button | 15 | GP11 | tap tempo |
| Voice button | 16 | GP12 | cycle active voice (V1 → V2 → V3 → V4) |
| Random button | 17 | GP13 | scramble the active voice's pattern (CV + triggers; respects step locks) |
| USB-C | — | — | Power + USB-CDC serial for boot/debug prints |
| LED | onboard | GP25 | heartbeat |
No external resistors needed for the buttons — the Pico's internal pull-ups (~50 kΩ) are enabled in software. For the OLED, the Pico's internal pull-ups on SDA/SCL are usually fine at 400 kHz over short jumper wires; if you see bus errors over longer runs, add external 4.7 kΩ pull-ups from SDA/SCL to 3V3.
External Eurorack jacks (clock in, digital in, CV out, gate out) are not wired yet — see Hardware status below for the entry points each will hook into.
One-time setup for building + flashing the Pico 2 firmware.
# 1. VS Code (skip if already installed)
brew install --cask visual-studio-code
# 2. Optional but handy: install the `code` CLI shim so `code .` works
# from the terminal. From inside VS Code:
# Cmd+Shift+P -> "Shell Command: Install 'code' command in PATH"
# 3. Install the official Raspberry Pi Pico extension
code --install-extension raspberry-pi.raspberry-pi-pico
# 4. Optional: a CLI serial reader for the USB-CDC logs
brew install picocom # nicer than the built-in `screen`The Pico extension bundles the Pico SDK, the ARM toolchain (arm-none-eabi-gcc),
OpenOCD, CMake, Ninja, and picotool — so you don't need to brew-install
any of those separately. On first project import it downloads everything
into ~/.pico-sdk/ (takes a few minutes — watch the progress in VS Code's
bottom-right corner).
If you'd rather build from the command line without VS Code, see the
header comment in firmware/CMakeLists.txt for the bare brew install
cmake/makerecipe.
Open the repo and let the Pico extension take it from there:
code .Then in VS Code:
- Cmd+Shift+P → "Raspberry Pi Pico: Import Project" → pick the
firmware/subfolder; board typepico2; SDK 2.x. - Click Compile Project in the Pico sidebar (raspberry icon on the left).
- Hold the BOOTSEL button on the Pico while plugging in USB → the
board mounts as a USB drive called
RPI-RP2→ dragfirmware/build/seq_firmware.uf2onto it. The Pico reboots automatically and starts running the firmware.
USB-CDC serial prints boot status + button events to the host:
screen /dev/cu.usbmodem* 115200 # macOS built-in (Ctrl-A K to quit)
# or
brew install picocom
picocom -b 115200 --imap lfcrlf /dev/cu.usbmodem*Direct (no VS Code): see header comment in firmware/CMakeLists.txt for
the cmake / make commands.
Engine + shared App layer (portable C++17, linked by both hosts):
| File | Purpose |
|---|---|
Voice.{h,cpp} |
One voice's full state — CV/trig sequences, scale, locks, glide, euclidean, swing, RNG, etc. Voice::Randomize, RotatePattern, Tick. |
Sequencer.{h,cpp} |
Multi-voice orchestrator. Owns kVoiceCount = 4 voices, internal clock generator with tap tempo, external-edge dispatch, transport. |
Menu.{h,cpp} |
OLED menu framework — items, sections, edit views, render. Host-agnostic. |
App.{h,cpp} |
Shared App layer between sim + firmware. Owns the engine instance, all menu items, the BuildMenu + OnMenuCommit wiring, PlaybackView (7 layouts), AppMutex / AppLock (std::mutex on sim, pico/mutex.h on firmware). |
Scales.h |
46 scale intervals + BuildScale() helper. |
FakeOled.h |
128×64 monochrome framebuffer with line/rect/text primitives + 6×8 inline bitmap font. Used directly by the sim; the firmware Ssd1306 driver consumes the same buffer. |
Sim host:
| File | Purpose |
|---|---|
playground.cpp |
GLFW + Dear ImGui window, clock thread, key handling, OLED window, audition audio via PortAudio. Provides App with NowMs() + file-backed SaveStateLocked / LoadStateLocked. |
Makefile |
macOS-only build recipe (clones ImGui on first run). |
Firmware host (firmware/):
| File | Purpose |
|---|---|
main.cpp |
Pico-SDK init, I²C0 + button GPIO setup, run loop (poll buttons → dispatch Do* helpers → Sequencer::Tick → render g_views into the SSD1306 framebuffer). |
Ssd1306.{h,cpp} |
Minimal I²C driver for SSD1306; pushes a 128×64 framebuffer per frame. |
Buttons.{h,cpp} |
Software-debounced 8-button polling. |
CMakeLists.txt |
Pico-SDK build; links App + engine sources from the repo root. |
pico_sdk_import.cmake |
Canonical SDK import shim. |
What the firmware drives today and what's still pending wiring:
| Feature | Status |
|---|---|
| SSD1306 OLED over I²C0 | Working |
| 8 buttons (Up/Down/Click/Back/Play/Tap/Voice/Random) | Working |
| Engine + all UI from the sim | Working |
| Onboard LED heartbeat | Working |
| USB-CDC boot/debug logs | Working |
| CV outputs (4× to a DAC over SPI; DAC8568 candidate) | Not wired. Engine exposes voice(i).last_dac(). |
| Gate / trigger outputs (4× GPIO) | Not wired. Engine exposes voice(i).trig_active(). |
| External clock-in jack | Not wired. Sequencer::OnClockEdge is the entry point. |
| Digital-in / reset jack | Not wired. Sequencer::OnDigitalEdge is the entry point. |
| Flash-backed persistence | SaveStateLocked / LoadStateLocked are no-ops on firmware. Sim uses a text file (playground.state); the firmware version will use the Pico's onboard flash sector. |
- Sim: macOS 25.3 (Darwin), Apple clang 17, glfw 3.4, portaudio 19.7, Dear ImGui v1.91.0.
- Firmware: Raspberry Pi Pico 2 (RP2350), Pico SDK 2.2.0, ARM
toolchain
14_2_Rel1, picotool 2.2.0-a4.
Bug reports, build problems, and PRs are welcome.
Filing an issue — please include:
- Which host hit the problem (sim or firmware-on-Pico-2).
- For firmware: the relevant USB-CDC log output (
screen /dev/cu.usbmodem* 115200) — boot lines have[BOOT]tags and button presses log[BTN] …, both of which usually pinpoint where things went sideways. - For sim: macOS version + how you built (plain
make, or something custom). - Steps to reproduce. "Press X, see Y, expected Z" beats "doesn't work".
Scope — this repo is firmware (engine + sim + Pico-SDK host) for a new polyphonic module. The hardware schematic / PCB for the original MicroPython single-voice module lives in Pi-Pico-Random-Looping-Sequencer; file hardware issues there. PCB/panel for this new poly module is still TBD.
Pull requests — match surrounding style (2-space indent, clang-format
not enforced but consistent with the existing tree). Keep changes
focused; one logical change per PR. The engine is host-agnostic
(Voice/Sequencer/Menu/App) — please don't pull pico/stdlib.h,
GLFW, or PortAudio into those translation units; that boundary is what
lets the sim and firmware share code.
License — undecided. Until a LICENSE file lands in the repo, the
code is all-rights-reserved by default. If you'd like to fork, build,
or redistribute in the meantime, open an issue and we can figure it
out.