Skip to content

gramster/delugemu

Repository files navigation

delugemu

A hardware emulator for the Synthstrom Audible Deluge, built as a custom machine model on top of QEMU.

Delugemu front panel

The goal is to boot unmodified Deluge firmware (the open-source DelugeFirmware) in a fully software-simulated environment so that development, debugging, automated testing and CI can happen without physical hardware.

Status: largely complete. USB device/host support is not wired in to the host due to technical complexity. Development was done on a Mac Mini m4; the build also runs on Windows via MSYS2/MinGW (see docs/windows.md), and a Linux port is pending. Because the OLED display can emulate a 7-seg, the 7-seg device is just a stub. Outgoing MIDI works well; triggering notes on the Deluge emulator can be a bit laggy due to an intermediate audio buffer; you can trade that off for a small amount of audio artifacts; see the --audio-buffer command-line option. Under heavy synthesis load the emulated Cortex-A9 can't always render audio at real time (a TCG throughput limit, not a buffering one), which can cause occasional breakup during dense playback; run.sh --tx-render-head <addr> keeps that graceful (brief gaps rather than distortion), and run.sh --icount eliminates the artifacts entirely at the cost of running slower-than-real-time under load (best for offline capture, not live play).

Target hardware

The Deluge is built around a Renesas RZ/A1L SoC:

Component Detail
CPU Arm Cortex-A9, single core, 400 MHz
On-chip SRAM 3 MB (mapped at 0x2000_0000)
External SDRAM 64 MB
Display OLED (128×48) or 7-segment array, depending on revision
Input RGB pad matrix + encoders, handled by an auxiliary PIC MCU
Audio I²S/SSI codec
Storage SD card

Firmware runs bare-metal (no OS), in C/C++ with some Arm assembly.

See docs/hardware.md for the full hardware breakdown and docs/memory-map.md for the SoC memory map.

How it works

QEMU does not ship with an RZ/A1L SoC or a Deluge board model. This project keeps vanilla upstream QEMU as a git submodule and maintains the custom hardware models under src/. A small integration step links our sources into the QEMU source tree and registers them with QEMU's Meson/Kconfig build, so upstream stays untouched and easy to rebase.

firmware (.elf/.bin)
        │
        ▼
  ┌───────────────────────────────────────────┐
  │ qemu-system-arm -M deluge                  │
  │   ┌─────────────────────────────────────┐ │
  │   │ RZ/A1L SoC model (src/hw/arm)        │ │
  │   │  • Cortex-A9 + GIC + timers          │ │
  │   │  • 3 MB SRAM, 64 MB SDRAM            │ │
  │   │  • SCIF, SSI, SD, INTC               │ │
  │   └─────────────────────────────────────┘ │
  │   ┌─────────────────────────────────────┐ │
  │   │ Deluge board peripherals             │ │
  │   │  • PIC (buttons/pads/encoders)       │ │
  │   │  • OLED / 7-segment display          │ │
  │   │  • audio codec                       │ │
  │   └─────────────────────────────────────┘ │
  └───────────────────────────────────────────┘

See docs/architecture.md for details.

Prebuilt binaries

If you just want to run firmware without setting up a build toolchain, grab a prebuilt bundle for your OS from the Releases page. Each bundle is self-contained — the required libraries are vendored alongside the binary — so no Homebrew / QEMU / MSYS2 install is needed.

macOS (DelugEmu-macos-<arch>.tar.gz):

tar -xzf DelugEmu-macos-arm64.tar.gz
cd DelugEmu-macos-arm64
./delugemu path/to/deluge_firmware.elf --sd deluge_sd.img
./delugemu --help

The build is ad-hoc signed but not notarized, so on first launch macOS Gatekeeper may block it — allow it under System Settings → Privacy & Security, or clear the quarantine attribute with xattr -dr com.apple.quarantine <bundle-dir>.

Linux (DelugEmu-linux-<arch>.tar.gz):

tar -xzf DelugEmu-linux-x86_64.tar.gz
cd DelugEmu-linux-x86_64
./delugemu path/to/deluge_firmware.elf --sd deluge_sd.img

The core C runtime and the graphics/driver stack (OpenGL, X11/Wayland) are deliberately left to the host, so a recent desktop distribution is assumed.

Windows (DelugEmu-windows-<arch>.zip):

:: Extract the zip, then from the extracted folder:
delugemu.cmd                        :: auto-detect / offer to download firmware
delugemu.cmd path\to\firmware.bin   :: or boot a specific image
delugemu.cmd --help                 :: full option list

The Windows bundle includes a native PowerShell launcher (delugemu.ps1, wrapped by delugemu.cmd) that mirrors run.sh without MSYS2: optional firmware with auto-download, raw SD images and SD folders (--sd, with write-back for _rw folders), MIDI/USB-MIDI chardevs, audio backends, and display modes. See docs/windows.md for details. Windows SmartScreen may warn on first launch because the build is unsigned.

To produce a bundle yourself from a local build, run ./scripts/package.sh on the matching OS (it detects the host automatically; see Building from source first).

Quick start

The build scripts are Bash and drive QEMU's Meson/Ninja build. On macOS and Linux run them directly from a terminal. On Windows, run them from an MSYS2/MinGW shell — see docs/windows.md for the one-time toolchain setup; the commands below are otherwise identical.

# 1. Fetch the QEMU submodule (large, one-time)
./scripts/bootstrap.sh

# 2. Link our device models into the QEMU tree and configure the build
./scripts/integrate.sh

# 3. Build qemu-system-arm with the Deluge machine
./scripts/build.sh

# 4. Run firmware (opens the front-panel skin window by default)
./scripts/run.sh path/to/deluge_firmware.elf

# No firmware handy? Run with no image: run.sh looks for a .bin/.elf in the
# firmware/ folder and, if none is there, offers to download the open-source
# Deluge community firmware release and use it from then on.
./scripts/run.sh

# Options: attach an SD image, route MIDI to a chardev, add an audio
# backend, or pick a display mode (console/headless/none). See --help.
./scripts/run.sh path/to/deluge_firmware.elf --sd build/deluge_sd.img

# Audio plays on your speakers by default (the SSIF/I2S output opens the OS
# default backend: coreaudio on macOS, pa on Linux, dsound on Windows). Play a
# note in an instrument clip to get 44.1 kHz stereo. Pass --audio <driver> only
# to select a non-default backend (e.g. sdl / wav / none).
./scripts/run.sh path/to/deluge_firmware.elf --sd build/deluge_sd.img

# Output buffer: the SSIF keeps a small (~15 ms) cushion to absorb burst jitter.
# Raise it with --audio-buffer <ms> if you hear dropouts, or lower it to trim
# the delay when playing the emulated Deluge live from external MIDI.
./scripts/run.sh path/to/deluge_firmware.elf --midi coremidi --audio-buffer 15

# Run without a window (serial + monitor on the terminal):
./scripts/run.sh path/to/deluge_firmware.elf --display headless

# Window size: the native front panel is 2256x1584. run.sh detects your primary
# monitor and renders the panel down-sampled so the window fits (within ~90% of
# the screen); zoom-to-fit then scales it to any later window size. Force a
# specific size with --skin-scale <percent|native>:
./scripts/run.sh path/to/deluge_firmware.elf --skin-scale native   # full 2256x1584
./scripts/run.sh path/to/deluge_firmware.elf --skin-scale 50        # half size

# MIDI: on macOS, pass 'coremidi' to either MIDI flag to expose the Deluge as a
# real host MIDI in/out device (it appears in your DAW as "DelugEmu DIN" and/or
# "DelugEmu USB"). --midi is the DIN ports, --usb-midi is the USB-MIDI port. A
# small helper (scripts/midi_bridge.c, built automatically) bridges the byte
# stream to CoreMIDI virtual ports.
./scripts/run.sh path/to/deluge_firmware.elf --usb-midi coremidi --midi coremidi

Firmware

run.sh takes the firmware image as an optional first argument. When you run it without one, it resolves a firmware automatically:

  1. It looks for a .bin (or .elf) image in the firmware folder — firmware/ in the repo root by default, or wherever DELUGEMU_FIRMWARE_DIR points. If several .bin files are present it prefers the OLED build (the emulator's OLED display also renders the 7-segment UI).
  2. If the folder has no image and the terminal is interactive, it offers to download the open-source Deluge community firmware release (~900 KB) from Synthstrom, unzips it into the firmware folder, and uses it. The download is kept, so subsequent runs pick it up automatically.
# First run with no firmware: prompts to fetch the community release.
./scripts/run.sh

# Later runs reuse the downloaded image with no prompt.
./scripts/run.sh --sd build/deluge_sd.img

The firmware is not redistributed with this project — it is fetched directly from Synthstrom's official release on demand. Pass an explicit path any time to override the auto-detected image.

On Windows, the coremidi shortcut is not available (it uses Apple's CoreMIDI). Generic chardev MIDI routing (e.g. --midi udp:127.0.0.1:1999) still works; see docs/windows.md.

Playing the emulator from real gear (macOS)

The coremidi bridge exposes the Deluge as virtual ports, but macOS does not auto-connect them to a physical instrument. The scripts/midi_route.py helper wires two CoreMIDI endpoints together by name and prints every message that crosses, so you can play your hardware into the emulated Deluge and hear/see the Deluge's output back on your gear. It needs python-rtmidi (pip install python-rtmidi).

Given two name fragments A and B, it connects both directions:

A out (source)  ──▶  B in  (destination)
B out (source)  ──▶  A in  (destination)
# Start the emulator with the DIN MIDI exposed as a CoreMIDI port:
./scripts/run.sh path/to/deluge_firmware.elf --midi coremidi

# In another terminal, bridge your instrument to the Deluge and watch traffic.
# Defaults are A="Summit", B="DelugEmu"; pass your own names to override:
./scripts/midi_route.py                      # Summit  <->  DelugEmu
./scripts/midi_route.py Summit "DelugEmu DIN"

# List the available CoreMIDI ports (to find the right names) and exit:
./scripts/midi_route.py --list

Names are matched as case-insensitive substrings of the CoreMIDI port name. If a fragment is ambiguous (e.g. DelugEmu matches both DelugEmu DIN and DelugEmu USB), pass the full name. Press Ctrl-C to stop. The script is also included under scripts/ in the release bundle.

SD card image

The Deluge loads songs, synths, kits and samples from an SD card, so most of the firmware (song load, audio playback, the file browser) only works with a card attached. The card is a raw FAT32 disk image passed with --sd (or a directory, which is snapshotted into one for you — see Folder-backed card below).

If you don't pass --sd, run.sh automatically uses an sdcard_rw or sdcard directory in the current working directory if one exists (the writable _rw variant takes precedence). So from the repo root you can simply run with the bundled factory content with no --sd flag at all. If no such folder exists and the terminal is interactive, run.sh offers to download the Synthstrom factory card contents into ./sdcard and uses that (set DELUGEMU_SD_DIR to change the folder).

QEMU's SD device requires the image to be a power-of-two size (e.g. 128 MiB, 256 MiB, 512 MiB, 1 GiB); a non-power-of-two image is rejected with Invalid SD card size.

The easiest way is the helper script, which sizes the image correctly for you:

# Build build/deluge_sd.img from the bundled factory content in sdcard/
./scripts/mksd.sh

# Or from your own content directory, to a custom output path
./scripts/mksd.sh path/to/my_card_content build/my_sd.img

mksd.sh measures the content, adds slack, rounds the capacity up to the next power of two (minimum 128 MiB), formats it FAT32 with volume label DELUGE, and copies the directory contents to the root of the card. On macOS it uses the built-in hdiutil / newfs_msdos; on Linux it needs dosfstools (mkfs.fat) and mtools (mcopy) — install them with your package manager first.

Lay out the content directory like a real Deluge card (factory folders are in sdcard/ to copy from):

SONGS/      Song .XML files
SYNTHS/     Synth preset .XML files
KITS/       Kit preset .XML files
SAMPLES/    Audio samples (.wav)

To build an image by hand instead, create a power-of-two raw image, format it FAT32, and copy your content in:

# 256 MiB (= 2^28 bytes) raw image, FAT32, label DELUGE
dd if=/dev/zero of=build/deluge_sd.img bs=1m count=256

# macOS
dev=$(hdiutil attach -nomount -imagekey diskimage-class=CRawDiskImage build/deluge_sd.img | head -1 | awk '{print $1}')
newfs_msdos -F 32 -v DELUGE "$dev"
diskutil mount "$dev"            # then copy SONGS/ SYNTHS/ KITS/ SAMPLES/ into the mounted volume
diskutil unmount "$dev"; hdiutil detach "$dev"

# Linux (needs dosfstools + mtools)
mkfs.fat -F 32 -n DELUGE build/deluge_sd.img
mcopy -i build/deluge_sd.img -s SONGS SYNTHS KITS SAMPLES ::/

Then run with --sd:

./scripts/run.sh path/to/deluge_firmware.elf --sd build/deluge_sd.img

Folder-backed card (no manual image)

Instead of an image file, --sd also accepts a directory. The folder is snapshotted into a temporary FAT image at launch (using the same mksd.sh sizing/rounding logic), so you don't have to build an image by hand:

./scripts/run.sh path/to/deluge_firmware.elf --sd path/to/card_folder

By default the snapshot is read-only with respect to your folder: the guest can write to the card, but those changes live only in the temporary image and are discarded on exit, leaving your folder untouched.

To have guest changes written back to the folder when the emulator quits, name the folder so it ends in _rw:

./scripts/run.sh path/to/deluge_firmware.elf --sd path/to/card_folder_rw

On a clean exit the temporary image is mirrored back into the folder (files the guest added or modified are copied in, files it deleted are removed), then the temporary image is discarded. Write-back is best-effort and only happens on a normal shutdown; if QEMU is killed, the folder is left as it was. On Linux, write-back requires mtools (mcopy).

When invoked without --sd, run.sh looks for these folders by name in the current working directory and auto-attaches the first it finds — sdcard_rw (writable, changes mirrored back on exit) is tried before sdcard (read-only snapshot).

Controls

With the interactive skin window (--display console), the front panel is driven by mouse and keyboard:

  • Click a pad or button to press it (a momentary press/release). Clicking the silkscreen circle of an encoder presses it in.
  • Encoder rotation: each of the six encoders has a small ▽ (left) and △ (right) triangle inside its circle. Click ▽ to turn one detent CCW or △ to turn one detent CW; press and hold a triangle to repeat. The mouse wheel over an encoder also turns it.
  • Multi-press latch: hold the Left Alt/Option key to latch clicks. While Alt is held, clicking a pad or button presses and holds it; clicking the same control again releases it. Releasing Alt drops every still-latched control at once. This lets a single mouse build chord-style combinations — e.g. hold several pads, or a pad together with a function button — that one pointer otherwise could not hold simultaneously.
  • Keyboard: common controls are bound to keys — Space = PLAY, R = RECORD, Shift = SHIFT, Backspace = BACK, Enter = SELECT-encoder click, Tab = SESSION, C = CLIP, K = KEYBOARD, Q/W/E/T = SYNTH/KIT/MIDI/CV, and the number keys 1–8 trigger the sidebar audition column. See src/hw/input/deluge_input.c for the full map.

Performance

Audio is the most performance-sensitive part of the emulator, and how well it runs depends on your host CPU.

Jitter and the audio buffer. The emulated SoC produces audio in small bursts on the same thread that runs the guest CPU, so the rate at which finished samples reach the host is uneven — a periodic skin redraw, a GC pause in the guest, or the OS scheduling the QEMU thread out all introduce jitter. To stop that jitter from becoming audible gaps, the host audio backend buffers a cushion of samples ahead of playback (run.sh --audio-buffer <ms>, and the host-side out.buffer-length). A larger buffer rides out bigger scheduling hiccups, but every millisecond of buffer is also a millisecond of latency between pressing a pad (or sending a MIDI note) and hearing the sound. So the buffer is a direct trade-off: raise it if you hear dropouts, lower it for tighter, more playable response. The defaults are tuned per-platform (smaller on macOS/Linux, larger on Windows where the audio voice shares QEMU's main loop).

Raw CPU throughput. The buffer only smooths out jitter — it cannot create throughput that isn't there. The Deluge's firmware expects a 400 MHz Cortex-A9 rendering audio in real time, and QEMU emulates that core with TCG (dynamic binary translation). On an underpowered host the translated DSP code simply cannot generate 44.1 kHz of audio fast enough to keep up, so the buffer drains faster than it refills and you get constant, unavoidable dropouts no matter how large the buffer is set. This is a throughput limit, not a buffering one.

In practice an Apple M4 Pro (or better) comfortably renders audio at real time with room to spare; development was done on an M4 Mac Mini. Slower machines may still boot and run the UI fine but struggle with dense synthesis. Two launcher options change what overload sounds like when you hit the throughput wall — neither makes samples arrive faster, they pick a nicer failure mode:

  • run.sh --tx-render-head <addr> makes overload graceful (brief silences instead of distortion). By default the emulator's audio sampler tracks the hardware DMA play head, which advances on the host's wall clock; under load the firmware hasn't finished rendering the ring slots the play head has already passed, so those half-written samples get played as distortion. Pointing the sampler at the firmware's own render head — the variable that records how far it has actually rendered into the I²S ring — makes it read only finished samples, turning that distortion into a short gap. <addr> is the guest memory address of that variable (the AudioEngine::i2sTXBufferPos symbol). It is firmware-build specific, so you look it up in the symbol table of the exact firmware you are running, e.g.

    arm-none-eabi-nm firmware/deluge.elf | grep i2sTXBufferPos
    # 20038fdc b _ZN11AudioEngine14i2sTXBufferPosE  -> --tx-render-head 0x20038fdc

    (or read it from the build's .map file). Pass it as hex, e.g. --tx-render-head 0x20038fdc. A stripped release binary with no symbols can't be looked up this way, which is why the option is opt-in and the wall-clock play head is the firmware-independent default.

  • run.sh --icount trades correct-speed-but-broken audio for clean-but-slow audio. Normally the guest runs as fast as the host allows while the audio hardware clock ticks on real wall-clock time, so the two clocks drift apart the moment the host can't keep up — that mismatch is what produces the glitches. --icount makes the guest's instruction-counted virtual clock the single timebase for everything, audio included, so the sample producer (firmware) and consumer (audio device) advance in lockstep and can never slip relative to each other. The catch is exactly what you'd expect: when the host can't render fast enough, virtual time simply falls behind real time, so the guest — and its audio — runs slow. You hear a clean stream at a lower pitch/tempo rather than a correct-pitch stream full of dropouts. That makes it ideal for rendering a clean capture offline on a slow machine (just not in real time), but poor for live play.

Repository layout

delugemu/
├── docs/                Hardware notes, architecture, roadmap
├── scripts/             bootstrap / integrate / build / run helpers
├── src/
│   ├── hw/              QEMU device & machine models (C)
│   │   ├── arm/         deluge board + RZ/A1L SoC container
│   │   ├── display/     OLED / 7-segment models
│   │   └── misc/        PIC and other board glue
│   └── include/hw/      Public headers for the models above
├── tests/               Emulation / regression tests
└── qemu/                Upstream QEMU (git submodule)

Debugging & profiling

Firmware developers can attach GDB, capture exception/IO logs, run TCG profiling plugins (hot blocks, hot pages, cache simulation), and drive the panel over QMP. See DEBUGGING.md for verified commands.

License

GPL-2.0-or-later, matching QEMU so the device models can link against it. See LICENSE.

The Synthstrom Deluge name and hardware are property of Synthstrom Audible Ltd. This is an independent, unofficial project and is not affiliated with or endorsed by Synthstrom Audible.

About

Hardware emulator for Synthstrom Deluge

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors