A Raspberry Pi Pico 2-based digital synthesizer platform with 16 potentiometers, clock in/out, and I2S stereo audio output. It hosts swappable generative instrument firmwares that are selected at compile time.
- Raspberry Pi Pico 2
- 16 potentiometers read through a CD4067-style 4-to-16 analog multiplexer
- I2S DAC (data on GPIO 9, clock base on GPIO 10)
- Clock input with normalling detection (for syncing to external gear)
- Clock output (Teenage Engineering-style, every second 16th note)
Audio runs at 24 kHz stereo with 256-sample buffers.
Only one firmware is active at a time — uncomment the desired #include in
platform16.cpp and rebuild.
A generative subtractive monosynth. A PolyBLEP saw oscillator and optional coloured noise are fed through a Moog-style 4-pole ladder filter (LP24/HP24) with resonance and a soft-clip overdrive stage.
Sequencing is built around 32 random steps with probability-based skips. Each step carries per-step pitch and filter modulation amounts. Nine "algorithm" modes sort the pitch data into melodic contours (triangles, interleaved patterns, etc.). An "evolve" knob gradually mutates the sequence over time. Pitch is quantized to one of eight musical scales (or left unquantized). Bidirectional attack-or-decay envelopes control both volume and filter cutoff.
A generative 3-voice FM/PM chord synthesizer. Three parallel 2-operator phase modulation voices form a triad (root, 3rd, 5th) derived from the harmonic minor chord scale. Attack-or-decay envelopes shape each note. Two sine LFOs modulate the FM modulation depth ("timbre") and the envelope time.
Sequencing uses a procedural sequencer driven by density, complexity, spread, and bias parameters. A "scramble" knob probabilistically mutates the sequence seeds.
A dual-oscillator arpeggiator driven by three independent Euclidean rhythms. Two detunable PolyBLEP saw oscillators feed a Moog ladder filter (LP24) with a soft-clip overdrive stage.
The three Euclidean rhythms independently control volume accents, cutoff accents, and when the arpeggio advances to the next chord tone. Chords are 4-note voicings (triad + 6th) from the natural minor scale. Ten arpeggio modes are available: up, down, up-down, down-up, converge, diverge, converge-diverge, diverge-converge, random, and off. Per-step glide smooths frequency, cutoff, and volume transitions.
Reusable DSP and utility modules shared across firmwares, found in lib/:
| Module | Description |
|---|---|
| oscillator.hpp | Multi-waveform oscillator with PolyBLEP anti-aliasing (from DaisySP) |
| ladder.hpp | Moog-style 4-pole ladder filter, Huovilainen model (from DaisySP) |
| variablesawosc.hpp | Variable-shape saw/notch oscillator (from Mutable Instruments Plaits) |
| pm2.hpp | 2-operator phase modulation (FM) synth (from DaisySP) |
| decay.hpp | Simple exponential decay envelope |
| attackordecay.hpp | Bidirectional attack-or-decay envelope |
| sequencer.hpp | Procedural 32-step gate + CV sequencer |
| rhythms.hpp | Pre-computed Euclidean rhythm pattern lookup table |
| arpeggio.hpp | 10-mode arpeggiator (up, down, converge, diverge, random, etc.) |
| quantize.hpp | Pitch quantization to scales, chord type lookup, 88-key frequency table |
| metro.hpp | Metronome / clock tick generator (from DaisySP) |
| inoutclock.hpp | Internal/external clock manager with division and multiplication |
| pots.hpp | 16-pot multiplexer reader with interpolation |
| buttons.hpp | Debounced button input with single/double/long press detection |
| gpio.hpp | Pin definitions and boot button helper |
| parameters.hpp | Typed parameter mappings (bipolar, integer range, exponential, etc.) |
| utils.hpp | DSP utilities: soft clip, PolyBLEP helpers, clamping, random |
Each firmware follows a three-part pattern:
- State (
*-state.hpp) — a plain struct holding all parameter values. - Controller (
*-controller.hpp) — maps the 16 physical pots to state parameters using the typed parameter abstractions fromlib/parameters.hpp. - Instrument (
*-instrument.hpp) — the DSP engine. Exposesinit(),update()(called once per buffer to read state), andprocess()(called once per sample to produce audio).
The main loop in platform16.cpp ties it together: read one pot per iteration,
update the controller, fill an audio buffer sample-by-sample, and hand it off
to I2S DMA.
Requires the Raspberry Pi Pico SDK (v2.1.0) and Pico Extras. The VS Code Raspberry Pi Pico extension handles toolchain setup. To build:
- Configure with CMake (the extension does this automatically).
- Run the "Compile Project" task, or from the terminal:
ninja -C build - Flash
build/platform16.uf2to the Pico 2 via USB or use the "Run Project" task with picotool.
├── platform16.cpp Main application entry point
├── CMakeLists.txt Build configuration
├── firmware/
│ ├── sds/ Stochastic Decay (Subtractive)
│ ├── pmd/ Phase Modulation with Decay
│ └── tep/ Two-tone Euclidean Polymeters
├── lib/ Shared DSP and utility modules
├── build/ Build output (generated)
├── pico_sdk_import.cmake Pico SDK integration
└── pico_extras_import.cmake Pico Extras integration (I2S audio)