Skip to content

Latest commit

 

History

567 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PicoVintageSynthCollection

Ten vintage synthesizer emulations for the RP2350, built from one shared codebase. Same board, same core, one firmware image per instrument.

PicoFaceMD front panel: diagnostics page, filter page, preset list, section menu
The shared front panel: about, diagnostics, two-value pages, menus, patch page, popup
Rendered on a host from the firmware's own drawing code (tools/host_tests/ui) — what the OLED shows, pixel for pixel.

Demo

PicoVintageSynthCollection demo video
▶ Demo video on YouTube

Instruments

Folder Instrument Binary
instruments/PicoFaceYC Yamaha reface YC (drawbar organ) PicoFaceYC.uf2
instruments/PicoFaceCP Yamaha reface CP (electric piano, mdaEPiano) PicoFaceCP.uf2
instruments/PicoFaceDX Yamaha reface DX (4-operator FM) PicoFaceDX.uf2
instruments/PicoFaceJ6 Roland Juno-6 PicoFaceJ6.uf2
instruments/PicoFaceMD Minimoog Model D PicoFaceMD.uf2
instruments/PicoFaceSM ARP/Eminent Solina String Ensemble PicoFaceSM.uf2
instruments/PicoFaceOB Oberheim OB-X (engine ported from OB-Xf) PicoFaceOB.uf2
instruments/PicoFaceRD Roland RD / MKS-20 (sample piano) local build only
instruments/PicoFaceJV Roland JV-880 (native engine over the original PCM data) local build only
instruments/PicoFaceD5 Roland D-50 (native LA engine over the original PCM data) local build only

The RD, the JV-880 and the D-50 are the exceptions to "download and flash": they only build where the respective ROM set is present, and those ROMs are not distributable, so none of the three is in the release binaries. Without them the configure step skips them and the seven above are unaffected. All three run on the hardware; see the flash note below for the one build option the JV-880 needs on a small board.

Hardware

  • RP2350; the reference board carries 16 MB of flash, 4 MB is enough for every instrument but the JV-880 (see How much flash an instrument needs). The board must feed the core from the RP2350's own regulator -- see The board has to take the overclock below before buying anything cheap.
  • I2S audio (PCM5102 DAC)
  • 128x64 OLED over I2C (SH1106)
  • Three rotary encoders with push buttons
  • USB MIDI and DIN MIDI

The pin map is the same for every instrument and lives in core/include/project_config.h.

Which RP2350 board. The default build assumes a Pico-2-format board with an RP2350A -- Pico 2, Waveshare RP2350-Plus and the like -- where I2S sits on GP26/27/28 at header pins 31/32/34. The Waveshare RP2350B-Plus-W has the same header but carries GP40/41/42 at those three positions (the RP2350B's ADC0-2 take the Pico's ADC spots; GP26-28 are only on the pads underneath), so a default image on it drives the I2S lines into thin air: display, MIDI and encoders work, only the sound is missing, and every counter reads healthy. It is a build-time choice, not a wiring one -- see Building for -DPICOFACE_BOARD=rp2350b_plus_w.

The board has to take the overclock. These engines are budgeted for a core clock of 444 MHz (480 for the RD) at a core voltage of 1.60 V: three times the RP2350's rated 150 MHz, and a voltage above what the SDK allows without an explicit override. That is not a tuning option, it is the design point -- below it the instruments do not finish their audio blocks. The firmware sets that voltage through the chip's on-board regulator, so the board has to route the core supply (DVDD) through that regulator, the way the Pico 2 reference design does. Known good: Raspberry Pi Pico 2, Waveshare RP2350-Plus, Waveshare RP2350B-Plus-W (with its build variant). Known bad: unbranded 16 MB "RP2350" boards from marketplace sellers. One was bought to settle issue #107: its chip and flash are fine, but the regulator register has no effect on its core voltage, so the core stays at the board's fixed supply and stops dead at 312 MHz. No build option, flash timing or stepping has anything to do with it, and no firmware can get 444 MHz from such a board. The symptom is either nothing on the screen with the release image, or "boots at 300 MHz, no sound" with a downclocked build. If a board does neither of the things above, the working image is the diagnosis: build PicoFaceMD with -DPICOFACE_BOOT_DIAG=ON and read the last line on the screen; the whole investigation, with the images that separate flash, core and voltage, is in the MD README under The board that stops at 300 MHz. Please check that section before opening an issue about a board that will not start: a chip marked A4 is not the reason either (the splash shows the bootrom revision as b4 now, and it changes nothing).

If the screen stays dark. The panel is on I2C at 1 MHz with only the chip's internal pull-ups. That is above what an SH1106 datasheet promises (400 kHz) and it is what the reference board runs, because the display push is paced in half tile rows of roughly 1.5 ms of I2C each -- the bus rate is directly the UI's frame time, and 400 kHz would make a full screen 60 ms instead of 24. On longer jumper leads it is the first thing to suspect. Build with -DPICOFACE_OLED_I2C_HZ=400000, and consider real 2.2k-4.7k pull-ups before blaming the display.

A display that is simply absent costs nothing: the I2C write returns an error on a NACK and the firmware carries on. A display that holds a line low used to take the whole instrument down -- the wait for the bus is unbounded, and the first transfer happens before the splash loop, which is the only place USB gets serviced during startup, so the board would not even enumerate. It reads exactly like a firmware that does not boot. That write is now bounded at 5 ms and the panel is written off after twenty consecutive failures, so a broken display costs you the display and not the MIDI, the audio and the encoders.

On the board setting. The build defaults to PICO_BOARD=sparkfun_promicro_rp2350, and that is a statement about flash size rather than about hardware: the prototype runs a Waveshare RP2350-Plus, and the SparkFun definition was picked because it declares 16 MB where the plain pico2 definition declares 4. Any RP2350 board with 16 MB works if the pins above are reachable on it. For a Pico-format board the honest pair is

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPICO_BOARD=pico2 -DPICO_FLASH_SIZE_BYTES=16777216

Without that second flag, pico2 caps the image at 4 MB. The CP's sample sets were cut for exactly that size, so every instrument but the JV fits within it (the CP at 3.99 MB, with 190 KB to spare); the flag is for the JV, and for a board that really has 16 MB. Nothing else in the SparkFun definition reaches the firmware: the flash timing and the system clock are set by the project itself in core/src/pico_hw.cpp, and PICO_FLASH_SPI_CLKDIV is identical in both. The one thing it declares that the prototype does not have is an 8 MB PSRAM region on GPIO 19 — nothing is placed there, so it costs nothing, but PSRAM is not a thing to reach for on this hardware without checking the board first.

Switching instruments

One board, ten firmware images, so choosing a different instrument is something you do rather than something a factory does once. Hold all three encoder buttons together. After half a second the display takes over and counts down from two; let go and nothing happens. Hold it out and the board silences itself, drains the audio it had already queued, and reappears as the RPI-RP2 drive — drop the next .uf2 on it.

No button combination in any instrument uses all three at once, so it cannot happen by accident, and the BOOTSEL button on the module is never needed for this. Keep that button reachable anyway: if a firmware is too broken to reach its own user interface, BOOTSEL plus a power cycle is the way back.

How much flash an instrument needs

The reference board carries 16 MB. That is not a requirement of the design: three instruments ship sample data, and only one of them needs a build option to fit the 4 MB that a base Raspberry Pi Pico 2 and many other RP2350 boards provide:

Instrument Image On a 4 MB board
YC, DX, J6, MD, SM, OB 90-190 KB fits anywhere
PicoFaceCP 3.99 MB fits, 190 KB to spare
PicoFaceJV 4.34 MB only with -DPICOFACEJV_4MB=ON (3.76 MB, banks A+B)
PicoFaceD5 0.86 MB fits
PicoFaceRD 2.56 MB fits

The JV's reduced variant drops something real: the user bank, and the 22 samples only those patches used. Banks A and B are otherwise untouched -- nothing is resampled or requantised.

The RD used to need one too, and no longer does. Its packs held four sampled velocity layers of every note; they hold the parameter ROM's own corners now and the engine interpolates, which is both exact at all 128 velocities and a fifth the size. The whole instrument went from 5.15 MB to 2.56 MB. -DPICOFACERD_MODEL=MKS20 or =MK80 still builds one machine on its own -- eight patches, and half a ROM set is enough for it -- but nobody needs it to fit a board any more.

The CP has no reduced variant either, and needs none. It used to be 4.22 MB and carried its five built sample sets as one indivisible whole, with no subset to drop; the sets were then cut for a 4 MB flash without dropping anything: every sample keeps its attack byte for byte and loops 40-100 ms earlier, at a loop cycle chosen to stay within 1 dB and 10 % spectral centroid of the cycle it looped before. The rule and the numbers are in tools/cp_sampleprep. Anything from 8 MB up holds any of the ten instruments. The D-50 is the one sample-based instrument that never needed cutting -- its whole sample ROM is 512 KB.

An oversized .uf2 fails to copy in a way that names no reason: the file transfer stops or the drive rejects it, with nothing on screen about flash size. That is the symptom, and it is not a corrupt download.

Building

git clone --recurse-submodules https://github.com/Michi71/PicoVintageSynthCollection.git
cd PicoVintageSynthCollection
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build

The artifacts are then in build/<instrument>.uf2.

Board variant. -DPICOFACE_BOARD=pico (the default) builds for any Pico-2-format RP2350A board. -DPICOFACE_BOARD=rp2350b_plus_w builds for the Waveshare RP2350B-Plus-W: it selects an RP2350B board profile (48 GPIOs, 16 MB), puts I2S on GP40/41/42, and re-bases PIO0 to GPIO 16-47 before the I2S program is loaded, because an RP2350 PIO addresses either GPIO 0-31 or 16-47. The firmware also puts such a board's flash into quad by itself when the bootrom left it in dual -- the Puya P25Q128H on the reference board ships with its quad-enable bit clear, and the bootrom never sets one -- and then tops the flash timing ladder one rung lower (111 MHz instead of 148; RD 96 instead of 120): that part verifies 111 MHz in quad and hangs at 148, and a rung that hangs cannot be stepped down from. See the PicoFaceMD README, The bit the bootrom never sets. A board with a faster flash can ask for the full rung with -DPICOFACE_QMI_M0_TIMING_TARGET=PICOFACE_QMI_M0_TIMING_RX4. The released UF2s are the default variant; the RP2350B build is one cmake flag away and produces the same ten images.

cmake -S . -B build-b -G Ninja -DCMAKE_BUILD_TYPE=Release -DPICOFACE_BOARD=rp2350b_plus_w
cmake --build build-b

The splash screen names the build. Every instrument shows a version taken from git describe, not a number typed into its instrument.cmake: on a release tag it is the release (1.7.0), anywhere else it says how far past that tag and from which commit (1.7.0-4-ga39504b), and a trailing + means the tree had uncommitted changes when it was built. The About page shows the same string. Quote it in a bug report and the image is identified exactly; the version used to read 0.1 in every build ever made, which identified nothing.

A tarball without .git builds fine and shows unknown.

The three that need ROMs

PicoFaceRD, PicoFaceJV and PicoFaceD5 play the original machines' own data, which is not distributable. Each looks in its own roms/ directory — instruments/<name>/roms/, gitignored — and removes itself from the build with a note if what it needs is not there. Nothing else is affected.

what goes in roms/
PicoFaceD5 two PCM ROMs and a program EPROM identified by CRC32, so names do not matter
plus optional *.syx bulk dumps for the patch banks
PicoFaceJV jv880_rom2.bin, jv880_waverom1.bin, jv880_waverom2.bin exact names, 256 KB + 2 MB + 2 MB
PicoFaceRD mks20_15179736.BIN41.BIN, MK80_IC5.bin, MK80_IC6.bin, MK80_IC7.bin exact names, 128 KB each
pack_p0.rdppack_p15.rdp built, not found — see below

The RD needs one thing nobody else does. Its sixteen .rdp packs are note descriptors derived from those ROMs, and they have to be made once:

R=instruments/PicoFaceRD/roms
python3 tools/rd_extract/rd_descramble.py $R \
    RD200_B.bin mks20_15179757.BIN /tmp/prog.bin /tmp/prm.bin
python3 tools/rd_extract/rd_make_packs.py /tmp/prog.bin /tmp/prm.bin $R \
    0 0x000000 1 0x008000 …

Python and the ROMs, nothing else -- no emulator, no toolchain beyond the one already building the firmware.

To check that all of this actually works from scratch, tools/check_clean_build.sh clones into a temporary directory, copies the ROMs in from outside git, and builds all ten. That last part is the point: the ROM sets are not in this repository and must never be, so a build here is the one thing CI cannot cover. Name a directory to build there instead and keep the result; add --variants for the reduced images too.

RD also builds as a single machine — -DPICOFACERD_MODEL=MKS20 (1.73 MB) or =MK80 (1.23 MB), eight patches each, and only that machine's ROMs are needed. The default is both machines and 2.56 MB, which fits a 4 MB board. See instruments/PicoFaceRD/README.md for the whole recipe and all sixteen patch offsets.

Building a single instrument:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPICOFACE_INSTRUMENTS_FILTER=PicoFaceMD
cmake --build build

If the very first cmake call dies with an architecture error

On macOS a mixed package manager prefix produces this, and it reads like a problem with the project:

dyld: Library not loaded: /opt/local/lib/libarchive.13.dylib
  Referenced from: /opt/local/bin/cmake
  Reason: (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64'))
Abort trap: 6

It is not. The complaint comes from dyld about loading cmake itself, before any of this repository is read, and "need 'x86_64'" means the cmake binary is an Intel build sitting next to Apple Silicon libraries — a MacPorts or Homebrew prefix that was installed under Rosetta, or carried over from an Intel Mac without the migration. Check with:

uname -m                 # arm64 on Apple Silicon
file $(which cmake)      # must say the same

If they disagree, reinstall the toolchain for the machine's own architecture (MacPorts documents a migration procedure for exactly this). Apple Silicon itself is fine: the collection is developed on it.

Repository layout

core/            shared runtime: audio pipeline, hardware, USB/DIN MIDI, GUI, persistence
cmake/           picoface_add_instrument() and the SDK import helpers
lib/             third-party static libraries (audio, encoder, u8g2) and the SDK submodules
instruments/     one folder per instrument: instrument.cmake, src/, include/, doc/, README.md
tools/           host-side tools; not part of any firmware image
docs/            documentation shared by all instruments
hardware/        the module as a board: schematic, two layouts, panel, parts, and
                 what the first built pair taught
img/             the instruments' screens and the panel sheets above, rendered
                 from the firmware by tools/host_tests/ui

Documentation

Shared:

  • Architecture - how core and instruments fit together, and why
  • Adding an instrument - the three steps needed
  • The module bus - the hardware interface several modules in one case would share: power, MIDI and audio. Planned, not built
  • Hardware - the module as a 10 HP Eurorack board: schematic, the two layouts and the panel, parts, and the first built pair
  • Host tools

Per instrument, under instruments/<name>/doc/: MIDI implementation charts, persistence formats, preset tables and the engineering logs of the individual ports. The per-instrument README links them.

Manufacturer manuals and service documentation are not part of this repository. Where an emulation follows a specific manual or schematic, the instrument's README names the document so it can be obtained separately.

Status

All ten instruments build from a single configure run, each with its own USB PID, and all ten run on the hardware. Seven are in the release binaries; the RD, the JV-880 and the D-50 need a local ROM set and are therefore built locally only. Open points are listed in docs/ARCHITECTURE.md, section 8.

License

GNU General Public License, version 3 or later - see LICENSE. Copyright (C) 2026 Michi71.

The collection was assembled in August 2026 from seven single-instrument repositories -- YC, CP, DX, RD, J6, MD and SM; OB, JV and D5 were written here afterwards, which is why the number is seven and not ten. Each of those seven already shipped the identical GPL-3 licence text, and every instrument README states GPL-3. It is also the lowest common denominator of what the engines are built on. Where an instrument derives from someone else's work:

Part Upstream it derives from That upstream's license
core, cmake, tools own work -
PicoFaceYC OpenB3 / BeatrixCPP - tone generation concepts only, no code AGPL-3.0 upstream, does not reach here; see below
PicoFaceCP mda-EPiano (engine, in tree) GPL-3.0-or-later
PicoFaceDX an ESP32 reface DX emulation (engine) see that project
PicoFaceRD giulioz/rdpiano + MAME (reference emulator; host-side only, not in this repository) GPL
PicoFaceJ6 junox (patch table, parameter scaling) GPL-3
PicoFaceMD BelaMiniMoogEmulation (ladder filter) stated by its author to be under no copyright
PicoFaceSM string-machine (DSP models) Boost Software License 1.0
PicoFaceOB OB-Xf (engine) GPL-3.0-or-later
PicoFaceJV own engine over the machine's own PCM data, measured against giulioz/jv880_juce (host-side reference; tone field layout) non-commercial; not vendored, see below
PicoFaceD5 munt - the LA32's wave generation and the Boss reverb topology as that project documents them, no code LGPL-2.1-or-later upstream, does not reach here; see below

Two rows in that table need a sentence more than a table cell holds.

PicoFaceRD used to carry the reference emulator's sources in the tree, and three decoded ROM sets with them. Neither is here any more. The emulator is a separate checkout the regression harness is pointed at, and the sample data is built at configure time from a local ROM set into a blob -- the same arrangement as the JV and the D5, and for the same reason. What the device plays is RdNewEngine over descriptors that emulator captured offline.

PicoFaceJV contains none of that emulator's code, and cannot: its licence forbids commercial use and fits neither MIT nor GPL-3. It was used as a host-side measuring instrument — the harness in tools/jv_extract/ drives it to take the readings the engine was calibrated against, and the patch and tone field layout comes from its dataStructures.h. The engine itself is written from the ROM formats. tools/jv_extract/README.md records the reasoning; the work is Giulio Zausa's, and his emulator in turn derives from NukeYKT's Nuked-SC55.

instruments/PicoFaceOB/ additionally carries its own LICENSE, identical in text, because that instrument's engine is a direct port of OB-Xf and its files keep the upstream copyright headers. Every instrument builds into its own binary, so a stricter licence on one of them stays confined to that binary.

Source files carry a two-line SPDX header rather than the full notice, to keep it out of the way of the code. Three kinds exist:

  • own work: GPL-3.0-or-later plus the copyright line;
  • ported trees (instruments/PicoFaceDX/include/dx_engine/): the licence line plus a note that copyright is shared with the upstream authors - no sole claim is made there;
  • upstream files (instruments/PicoFaceOB/include/obxf/, CP's mdaEPiano.*, the SDK-derived usb_descriptors.c, get_serial.* and tusb_config.h): untouched, they keep the header they came with. The last four are MIT and BSD-3-Clause, not GPL.

On PicoFaceD5 and munt. The D-50 and the MT-32 share the LA32 sound chip, and the MT-32 emulator munt has read that chip out and written down what it does: how a cutoff becomes the width of a cosine edge, how the resonance decays, and - from the same era's Boss reverb chip, whose data lines were traced by Lord_Nightmare, balrog and Mok - the topology the reverb here follows. munt is LGPL-2.1-or-later. Nothing was copied: the engine under instruments/PicoFaceD5/include/d5_engine/ was written for this project from that written description and from the D-50's own firmware, which is why no file there carries an upstream copyright header. The description is what mattered, and a description is not the program.

On PicoFaceYC and the AGPL. OpenB3 / BeatrixCPP, whose tone generation concepts the YC drawbar engine follows, is AGPL-3.0 - stricter than GPL-3, and not something that can be dropped by relicensing. It does not apply here: the engine under instruments/PicoFaceYC/include/yc_engine/ was written for this project and contains no upstream code, which is why no file there carries an upstream copyright header. Copyright covers the expression, not the concepts, so GPL-3 is the right licence for it.

About

Vintage synthesizer emulations for the RP2350

Topics

Resources

Stars

45 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages