overdub

microfreak-emu

gbryant/microfreak-emu · ★ 1

A Renode rehosting harness for the Arturia MicroFreak: STM32F4 platform, QSPI/SAI/OTG-FS peripheral models, and the firmware analysis behind them. Bring your own firmware image.

README from GitHub, Apache-2.0

microfreak-emu

The Arturia MicroFreak's firmware (5.0.0.2084) running under Renode, and documentation of what is in it.

Not affiliated with, authorised by or endorsed by Arturia. No Arturia code, firmware or binaries are in this repository. The emulator takes the path to an image you already own, and .gitignore refuses those file types.

This is the research half of a pair. The user-facing tool is microfreak-tools: presets, samples, wavetables, speech vocabulary and firmware flashing. This repository answers the questions that tool relies on.

What the emulator is for

A question like "does this opcode have a handler" should not cost a flash cycle. Under emulation it is a breakpoint. In practice that gives three things:

  • Checked claims. tests/test_encoder_roundtrip.py has the emulated firmware decode Arturia's own speech bank and compares it with an independent decode: 926 frames, 22 words, byte-identical.
  • Differential runs. Send one MIDI message twice with one byte changed, and diff all of SRAM.
  • Audio. Presets render to .wav, including a model of the analog filter that sits after the DAC.

Documentation

docs/ is the main product of this repository.

ARCHITECTURE.mdStart here. The instrument in one page: four processors, what runs where, how a key press becomes a sound
EMULATION.mdThe emulator: what it models, how to run it, how faithful each part is
FIRMWARE.mdThe firmware: addresses with a confidence mark, and what the code does
FORMATS.mdThe bytes at rest: flash layout, presets, the parameter stream, the mod matrix, samples, bank files
PROTOCOL.mdThe bytes on the wire: the SysEx frame, every opcode, reading and writing presets
SPEECH.mdThe Speech engine: Plaits LPC, lifted whole, data included
BOOTLOADER.mdThe low 48 KB, its three gates, and the recovery combination
FIRMWARE_UPDATE.mdThe update protocol: USB DFU with Arturia's own class requests
METHOD.mdHow to investigate this without wasting days, including the retractions
HARDWARE_TESTS.mdClaims only the device can settle, and what each outcome would mean
RELATED_PROJECTS.mdOther people's public work, and where it agrees with this
PRESET_SOURCES.mdWhere preset data comes from: public banks, the factory set, CC/NRPN

The preset format and sample encoding are documented in microfreak-tools: PRESET_FORMAT.md and SAMPLE_ENCODING.md.

Every document marks what was observed and what was inferred. Treat an address with no stated evidence as a lead, not a fact. METHOD.md explains the rule behind that: a cause with no control run is a guess.

Setup

Install Renode from renode.io or with brew install renode. It is an application, not a Python package.

Clone both repositories side by side. This one uses microfreak-tools from the sibling checkout, so its tests run against that code, and uv sync fails without it.

git clone https://github.com/gbryant/microfreak-tools
git clone https://github.com/gbryant/microfreak-emu
cd microfreak-emu
uv sync

Put your firmware in data/firmware/. The .mff works as downloaded:

cp ~/Downloads/MicroFreak_Firmware_Update_5_0_0_2084.mff data/firmware/

Then check the setup and boot it:

uv run mfemu check            # about a second, no emulation
uv run mfemu boot             # boot and report what happened

Presets, samples and wavetables

The firmware file contains no presets, so you supply them. There are two ways to get a preset export (.mfprojz):

# from MIDI Control Center's installed factory files
uv run mfreak asset import --outdir data --by-kind

# from your own instrument, with no Arturia software
uv run mfreak preset backup data/presets/mine.mfprojz

asset import is also the only source of the factory samples. Without samples, presets on the Sample and Grains engines render silent; that is 221 of the 384 in Factory6.0. Every render prints which samples and wavetables it used. The factory wavetables come from the firmware file if data/wavetables/ is empty.

Each data/ folder uses its export only when it holds exactly one. asset import writes four preset exports, so move all but one into a subfolder, which is never read:

mkdir -p data/presets/disabled
mv data/presets/Factory3.0.mfprojz data/presets/New\ Presets\ [45].0.mfprojz \
   data/presets/disabled/

Rendering

uv run mfemu render --list --grep bass
uv run mfemu render --preset 319 --midi ode_to_joy --out out.wav
uv run mfemu render --preset Buzzsaw --notes 60,67 --out buzz.wav

Presets are numbered as on the instrument, 1-512. The first render takes about a minute, because synthesis is not running until about 14 emulated seconds after boot. It then saves a snapshot, and later renders of the same export restore it in about a second. A restored render is byte-identical to a cold one. mfemu warm --list shows the snapshots and --clear deletes them.

Each render reports what the filter did:

Buzzsaw  preset 319  2.00 s  a.wav  LPF fc=236Hz Q=0.93
  DC +0.053 -> +0.001 (the model is AC-coupled, as the output is)

The filter is a model of the analog stage, set from each preset's own filter settings and the cutoff the firmware computes. It was calibrated on the instrument; see filter_cal/README.md. A render warns when a preset's cutoff falls outside the calibrated range. --no-filter writes the DAC signal instead. The model also runs on its own:

uv run python -m mfemu.filter_model in.wav out.wav --type LPF --cutoff 60 --resonance 20

--cutoff and --resonance are knob positions, 0-100.

To record the same thing from the instrument, mfreak render takes the same flags and records through an audio interface:

uv run mfreak render --preset 319 --midi data/midi/ode_to_joy.mid --out hw.wav

Commands

mfemu checkis this machine set up, and is the firmware the right build
mfemu pathswhere everything resolves to, and what is missing
mfemu imagewhat is inside a .mff, headered .bin or .raw
mfemu bootboot the firmware and report what happened
mfemu renderrender presets to .wav from a MIDI file or notes
mfemu flashbuild an image of the external SPI flash
mfemu dfurun a firmware update against the emulator
mfemu serveboot and keep running, for microfreak-tools to talk to
mfemu panelpress buttons, turn the encoder and read the display of a running serve
mfemu warmmanage the snapshots renders start from

mfemu <command> --help shows each one's options. boot, render and dfu take --firmware FILE to use another image. To try someone else's build:

uv run mfemu check --firmware theirs.mff    # what it is
uv run mfemu boot  --firmware theirs.mff    # does it boot

The addresses in docs/ are for 5.0.0.2084 and may not fit another build.

Driving it with microfreak-tools

The emulator answers the instrument's own SysEx, so mfreak works against it unchanged:

uv run mfemu serve --preset 8=Speech             # leave this running
MFREAK_EMULATOR=1 uv run mfreak preset read 8    # in another shell

Run mfreak from this checkout, which has both packages installed. A preset read takes about 2 seconds; a write with verification takes about 17.

Emulating a firmware update

By default the emulator starts the application directly, as the bootloader would. With a dump of your own bootloader it boots through the real one, and mfemu dfu can run a whole update: reboot to DFU, erase, download and finish.

Arturia ship only the application, so the bootloader has to come from your instrument. mfreak peek dump-bootloader reads it; its help gives the steps. The file contains your serial number, so keep it to yourself.

cp /path/to/your/bootloader.bin data/bootloader/
uv run mfemu dfu --image update.mff --finish

docs/FIRMWARE_UPDATE.md has the details.

The data folder

data/ sits beside the code. Each subfolder is a drop folder: put a file in and it is used.

data/firmware/    Arturia's update: a .mff, a headered .bin or a payload
data/bootloader/  optional: your own bootloader dump, for updates
data/presets/     optional: an MCC preset export (.mfprojz)
data/samples/     optional: an MCC sample export (.mfsbz)
data/wavetables/  optional: an MCC wavetable export (.mfwbz)
data/midi/        optional: MIDI files, found by bare name

--firmware, --bootloader, --source, --samples and --wavetables override these for one command. MF_RENODE and MF_GHIDRA say where Renode and Ghidra are installed, if not in the usual places. To keep the data elsewhere, make data a symlink.

data/ is not tracked by git. A fresh clone has none of it, and git clean -xdf deletes it. Back it up.

Tests

Tests are plain scripts. Each prints what it checked and exits non-zero on failure. Run them in this order on a new machine; the first three take about a second and need no boot.

uv run python tests/test_cli_surface.py       # needs nothing
uv run python tests/test_preflight.py         # checks the firmware hash
uv run python tests/test_filter_model.py      # no firmware needed
uv run python tests/test_boot.py              # boots it, with a control
uv run python tests/test_peripherals.py       # the peripheral models
uv run python tests/test_determinism.py       # boots twice, compares
uv run python tests/test_encoder_roundtrip.py # needs a preset
uv run python tests/test_warm_snapshot.py     # warm render == cold render
uv run python tests/test_panel.py             # the front panel saves an Init
uv run python tests/test_dfu_handoff.py       # app to bootloader, over USB
uv run python tests/test_dfu_download.py      # a full update

They test the emulator on Arturia's unmodified firmware, and each has a control that can fail. Optional settings for them:

MF_TEST_PRESET=..   which preset test_warm_snapshot renders (default 6)
MF_TEST_SOURCE=..   which preset export it renders from

Layout

docs/              the firmware documentation
src/mfemu/         the emulator, importable as `mfemu`
tests/             test scripts
renode/            machine description, peripheral models, scripts
filter_cal/        the rig that measured the analog filter
ghidra/            headless disassembly and symbol scripts

The peripheral models in renode/ are not MicroFreak-specific: STM32_SAI.cs, STM32_QUADSPI.cs, STM32_OTGFS.cs, STM32F4DMA.cs, STM32F4_Flash.cs and MicroFreakCodec.cs. They should make the next STM32F4 instrument much quicker to emulate.

Licence

Apache-2.0 for this project's own code; see LICENSE and NOTICE. The documentation describes Arturia's firmware without reproducing it: addresses, layouts and behaviour, not code.