Skip to content

EN MIDI

drew.po28@gmail.com edited this page Jul 6, 2026 · 7 revisions

MIDI

Menu path: Options → Audio → MIDI · Boards: RP2350 only (RP2040 has no MIDI) · Requires: nothing for the Software synth; a MIDI device on the board's MIDI_TX pin for the hardware modes; an instrument bank on SD for the GM.DLS wavetable (a .dls converts right on the device)

Overview

The emulator can turn a ZX-Spectrum program's MIDI output into real notes. ZX software drives MIDI in two different ways, and pico-spec emulates both, plus two built-in synthesisers that need no external hardware:

  • AY — the bit-banged MIDI of zx-midiplayer-style players. The program toggles AY register 14, bit 2 as a serial TX line; the emulator decodes that bit-banged 31250-baud UART frame and forwards each byte.
  • ShamaZX — the ShamaZX MIDI interface, accessed through I/O ports #A0CF (data) / #A1CF (status). The program writes MIDI bytes to the data port and polls the "transmitter busy" bit.
  • Software — a small internal procedural synth (SoftSynth): square/saw/triangle/ noise voices with ADSR and 8 presets. No sample data, no SD bank — works out of the box.
  • GM.DLS Wavetable — a fixed-point General MIDI wavetable synth that plays a real GM sound bank for far more realistic instruments. You supply the bank (gm_bank.bin, packed from a gm.dls / GUS set — see below).

In the AY and ShamaZX modes the decoded MIDI byte stream is sent out the board's MIDI_TX GPIO as a standard 31250-baud, 8-N-1 serial signal (the MIDI hardware standard), so you can wire a synth, a MIDI module (e.g. a SAM2695), or a Raspberry Pi running a soft-synth to it. The Software and GM.DLS modes ignore the pin entirely and mix their audio into the emulator's own output.

RP2040 boards have no MIDI at all — the whole feature is compiled out (#if !PICO_RP2040). The MIDI row does not appear in the Audio menu on the ZERO and MURM (RP2040) boards.

Menu structure

Options → Audio → MIDI(Ext:P<pin>) ▸
├── OFF
├── AY               decode AY reg-14 bit-2 bit-bang  →  MIDI_TX pin
├── ShamaZX          #A0CF/#A1CF ports                →  MIDI_TX pin
├── Software MIDI ▸  internal procedural synth (no hardware)
│   └── Synth Preset ▸  GM / Piano / Chiptune / Strings / Rock / Organ / Music Box / Synth
└── GM.DLS Wavetable    GM wavetable from gm_bank.bin on SD (no hardware)

The menu title shows the active external TX pin for this board, e.g. MIDI(Ext:P22). Selecting Software MIDI opens the Synth Preset submenu; selecting GM.DLS Wavetable loads/installs the bank (see below).

Options

Item Values Default Description
MIDI OFF / AY / ShamaZX / Software MIDI / GM.DLS Wavetable OFF MIDI mode. Stored in NVS (midi)
Synth Preset GM / Piano / Chiptune / Strings / Rock / Organ / Music Box / Synth GM Voice set for the Software synth only. Stored in NVS (midipreset)
  • The mode is Config::midi (0=OFF, 1=AY, 2=ShamaZX, 3=Software, 4=GM.DLS); the Software preset is Config::midi_synth_preset (0=GM … 7=Synth). Saved on change.
  • Changing the mode tears down the previous mode (Midi::deinit()) and brings up the new one (Midi::init()) live — no reboot (the GM.DLS first install does reboot, see below).
  • The current MIDI state is also shown on the Emulator Info screen (Hardware → Emulator Info): Off, AY bitbang, ShamaZX, Software (<preset>), or GM.DLS (bank OK / no bank).

Modes at a glance

AY ShamaZX Software GM.DLS Wavetable
ZX-side interface AY reg 14, bit 2 #A0CF/#A1CF #A0CF/#A1CF (+AY bit-bang) #A0CF/#A1CF (+AY bit-bang)
Uses MIDI_TX pin yes (31250 8-N-1) yes no no
Needs external hardware yes yes no no
Needs a bank on SD no no no yes (gm_bank.bin)
Sound external device external device procedural (chip-like) real GM samples

The hardware UART runs at the MIDI standard 31250 baud, 8-N-1. Sends are non-blocking: a byte is dropped if the TX FIFO is full (the ShamaZX driver polls the busy bit at #A1CF first, so this should not happen in practice).

GM.DLS wavetable bank — loading the instruments

The GM.DLS Wavetable mode plays a real General MIDI sound bank. The bank is not included — you provide it once. Two ways to get it in:

  • On the device (easiest): copy the raw .dls file to SD and either open it from the F5 file browser, or pick "[+] Convert a .dls..." in the instrument-set picker — the device converts it itself (see below). No PC tools needed.
  • On a PC: pack the .dls into a .bin bank with tools/dls_pack.py and copy the result to SD.

The wavetable engine and the dls_pack / gus_pack conversion tools come from xrip/embedded-midi-synth — thanks to @xrip. See Credits & help.

What you need

  • A General MIDI DLS bank — e.g. Microsoft's gm.dls (shipped with Windows at C:\Windows\System32\drivers\gm.dls, ~3.4 MB), or an open GUS patch set (freepats).
  • An RP2350 board with ≥ 4 MB flash — the packed bank lives in a dedicated ~1.6 MB flash partition (or, on boards with PSRAM, loads straight into PSRAM). RP2040 boards have no MIDI at all.
  • A PC with Python 3 only if you prefer the offline packer — the on-device converter needs nothing.

Licensing. Microsoft's gm.dls is copyrighted and not redistributable. Packing your own copy (from a Windows licence you own) for personal use on your own device is fine, but do not commit gm.dls/gm_bank.bin to a repo or ship firmware containing it. For a freely shareable bank use freepats (open licence) instead.

1a — Convert on the device (no PC)

The engine plays a compact packed .bin bank, not the raw .dls — but the conversion can happen on the device itself. Copy the .dls to SD, then either:

  • open the .dls from the F5 file browser — it is converted and GM.DLS mode is enabled in one go; or
  • in Options → Audio → MIDI → GM.DLS Wavetable, pick the first row of the instrument-set picker, "[+] Convert a .dls...", and choose the file.

The converter streams the file (a progress percentage is shown), writes <name>.bin into /.config/pico-spec/, and selects it. A bank too big for the flash partition is rejected with a size message.

1b — Or pack the bank on a PC

Alternatively convert offline with tools/dls_pack.py, packed at 31250 Hz (pico-spec's audio rate — the engine does not resample, so the rate must match):

python3 tools/dls_pack.py gm.dls gm_bank.bin 31250

The result is ~1.6 MB (PCM stored as 8-bit µ-law). For a GUS / freepats patch set use tools/gus_pack.py instead, pointing it at the TiMidity config:

python3 tools/gus_pack.py timidity.cfg gm_bank.bin 31250

Both packers are pure Python (no dependencies) and emit the same gm_bank.bin format.

2 — Copy to SD (PC route only)

Put the packed bank on the SD card, either at the root (e.g. /gm_bank.bin) or in /.config/pico-spec/.

Instrument-set picker. Selecting GM.DLS Wavetable always opens an "Instrument set" picker: the first row is "[+] Convert a .dls..." (on-device conversion, see 1a), followed by every packed bank found on SD, the active one marked [*]. Declining the install later reverts the selection — nothing changes until you confirm.

3 — Select the mode

Options → Audio → MIDI → GM.DLS Wavetable, then pick a bank:

  • Boards with PSRAM load the bank live — sound works immediately, no reboot.
  • Without PSRAM the bank must go into flash, which only happens at early boot: confirm "Install this bank? (reboots)" and the device reboots and writes it during the next boot — the screen stays off and the LED blinks for ~20-30 s (do not power off). It then comes up with sound. Because the bank lives in its own flash partition, later boots are instant (no SD read) and it survives firmware updates (the partition is not part of the firmware .uf2).

Updating / reinstalling

  • New bank: copy a different gm_bank.bin to SD and select GM.DLS again — if it differs from the flash copy, it reinstalls on the next boot.
  • Force reinstall (e.g. to recover a bad bank): select GM.DLS Wavetable when it is already loaded and answer Yes to "Reinstall DLS bank from SD?" — it reboots and rewrites from SD.

Notes

  • Flash writing happens only at early boot, single-core (before video starts) — that is why the screen is off during the write; it avoids freezing the HDMI/video on core 1.
  • An interrupted write is safe: the bank header is committed last, so a partial/aborted write is simply rejected on boot and you can reinstall.
  • Instruments come from the bank, so the Synth Preset submenu does not apply to GM.DLS — it is only for the Software synth.
  • If gm_bank.bin is missing/invalid, GM.DLS mode is silent and the info line shows GM.DLS (no bank).
  • ALF cartridges no longer conflict. ALF TV Game cartridges now stream from SD instead of occupying the bank's flash region, so GM.DLS and a loaded cartridge coexist — no unload prompt. Only special ALF firmware builds that reclaim the bank partition for the firmware itself (NO_GM_DLS) drop the GM.DLS menu row entirely.

MIDI_TX pin per board

The external TX pin is fixed per board at build time (MIDI_TX_PIN). On RP2350 only an even GPIO can be a UART TX (odd GPIOs are RX-only); the code auto-selects the UART instance and funcsel from the pin number. (Applies to the AY / ShamaZX hardware modes only — Software and GM.DLS do not use the pin.)

Board MCU MIDI_TX GPIO UART Status
MURM2 RP2350A 22 UART1 OK — but shares the pin with Real sound-in (WAV): mutually exclusive (see below)
PICO_PC RP2350A 26 UART1 OK
ZERO2 RP2350B 22 UART1 OK
MURM-1 (RP2350 / MURM_P2) RP2350 22 UART1 OK — but shares the pin with Real sound-in (WAV): mutually exclusive (see below)
PICO_DV RP2350 21 — BROKEN — GPIO 21 is odd (RX-only), so it cannot be a UART TX. Hardware MIDI does not work on PICO_DV. (When the debug-probe UART is enabled, the pin is set to 255 = disabled.)
ZERO RP2040 — — No MIDI (RP2040 — feature compiled out)
MURM (RP2040) RP2040 — — No MIDI (RP2040 — feature compiled out)

On PICO_DV the Software and GM.DLS synths still work (they do not use a TX pin); only the AY and ShamaZX hardware-output modes are affected by the odd-pin bug.

See the full GPIO maps in Boards & pinout.

MIDI_TX shared with Real sound-in (WAV) — MURM2 / MURM-1

On MURM2 and MURM-1 (RP2350) the MIDI_TX pin (GPIO 22) is the same pin as LOAD_WAV_PIO — the GPIO used by Real sound-in (Storage → Tape → Real sound-in). They are mutually exclusive: only one can use the pin. If you enable a hardware MIDI mode (AY or ShamaZX) while Real sound-in is on, a warning appears:

MIDI and Real sound-in share GPIO 22. Disable one of them.

(The Software and GM.DLS synths do not use the pin, so they never conflict.)

Wiring (hardware modes)

MIDI uses the standard 31250-baud serial / current-loop signalling. Connect the board's MIDI_TX GPIO to the MIDI IN of your device. Both the Pico and most 3.3 V MIDI modules are 3.3 V logic; classic 5-pin DIN MIDI uses the usual opto-isolated current-loop circuit, while small UART-MIDI synth modules (e.g. SAM2695 breakouts) take the TX line directly. A common setup is a Raspberry Pi running a software synth fed from the Pico's TX pin. Full pinouts: Boards & pinout.

How to test

  1. Software synth (no hardware): set MIDI → Software MIDI, pick a preset, then run a ZX MIDI player. You should hear the procedural synth in the emulator's audio.
  2. GM.DLS wavetable: copy a .dls to SD, set MIDI → GM.DLS Wavetable and use "[+] Convert a .dls..." (or pack a bank on a PC first). On PSRAM boards sound is immediate; otherwise wait for the one-time flash install (LED blinks ~20-30 s after the reboot). Then run a MIDI player — you should hear real GM instruments.
  3. Hardware out: on a working board (MURM2 / PICO_PC / ZERO2 / MURM-1), set MIDI → AY or ShamaZX, wire MIDI_TX to a synth's MIDI IN, and run the same player.

Links

Credits & help

The GM.DLS Wavetable synth engine and the bank-conversion tools (dls_pack / gus_pack) are based on xrip/embedded-midi-synth — many thanks to @xrip for the engine and for permission to use it in pico-spec.

Need help? Ask in the Murmulator Telegram chat: https://t.me/ZX_MURMULATOR/241767

Clone this wiki locally