Universal Controller MIDI
Blog Internals 9 min read

Virtual MIDI Ports on macOS, Windows, and Linux — Explained

What a virtual MIDI port actually is, how each operating system implements it, and why some are 1 ms and others are 14 ms. The deep-dive.

By Aidxn Design

Every gamepad-to-MIDI bridge in existence depends on the same trick: a virtual MIDI port. The bridge writes MIDI bytes into the port, the DAW reads MIDI bytes out the other side, and as far as the DAW is concerned a piece of physical hardware is plugged in. Behind that simple abstraction is a wildly different implementation on each operating system — and the differences explain why Linux is 1 ms, macOS is 2 ms, and Windows is occasionally 14 ms with no obvious reason. Behold: the unglamorous plumbing under every modern MIDI rig.

TL;DR
  • macOS: CoreMIDI's IAC Driver. Kernel-side, single-digit microseconds, rock solid.
  • Windows: WDM kernel driver (new MIDI Services on 11 24H2) or userland shim like loopMIDI. Variable, 2–14 ms.
  • Linux: ALSA's snd-virmidi module plus aconnect. Fastest of the three, ~0.4 ms.
  • What the bridge ships: on macOS it talks to IAC directly. On Windows it installs its own WDM driver. On Linux it uses ALSA sequencer ports — no install.

What is a virtual MIDI port

A MIDI port is a unidirectional byte stream. A physical MIDI port is a 5-pin DIN cable carrying 31,250-baud serial. A virtual MIDI port is an OS-provided pipe that looks identical to applications but is backed by software on both ends instead of a cable. Writes from process A become reads in process B, in order, with whatever latency the OS scheduler imposes. That's it. There is no magic.

The reason virtual ports exist is that without them, you cannot route MIDI between two pieces of software on the same machine without buying physical loopback hardware. With them, every app on the system can act as either a MIDI source or a MIDI sink. Gamepad bridges, software synths, DAWs, and even web browsers (via Web MIDI) all rely on the same infrastructure.

macOS — CoreMIDI and the IAC Driver

Apple has shipped CoreMIDI since OS X 10.0 in 2001. It includes a built-in virtual driver called IAC Driver (Inter-Application Communication). The driver runs in kernel space, exposes configurable ports, and routes bytes between any CoreMIDI client with sub-millisecond latency. On an M-series Mac you can measure round-trip times of ~600 microseconds between two userland processes.

# Verify IAC is online
system_profiler SPMIDIDataType | grep -A 4 "IAC"

# Create a virtual source from a Python script using rtmidi
import rtmidi
out = rtmidi.MidiOut()
out.open_virtual_port("UCMIDI Test")
out.send_message([0x90, 60, 100])  # note on C4 velocity 100

Notice that on macOS you can create virtual sources from userland without touching the kernel — CoreMIDI exposes MIDIClientCreate and MIDISourceCreate APIs that any app can call. The bridge uses these directly, no IAC needed if you don't want it.

Windows — the WDM driver mess

Windows is the painful one. Until 2024, there was no built-in virtual MIDI driver. You either installed a userland shim like loopMIDI (which adds ~3–6 ms of latency through user-mode context switching) or shipped your own kernel-mode WDM driver (which is signed-only and a nightmare to ship). Microsoft finally addressed this in Windows 11 24H2 with the new Microsoft MIDI Services, which provide a real virtual-port API.

Universal Controller MIDI takes the kernel route. The installer drops a signed umpdrv.sys into System32\drivers and registers a virtual port that any DAW can see. No loopMIDI, no MIDI Yoke, no licensing.

# Inspect the registered virtual MIDI driver
Get-PnpDevice -Class "MEDIA" | Where-Object FriendlyName -like "*UCMIDI*"

# Check the kernel driver is loaded
sc query umpdrv

# Round-trip latency probe (PowerShell, requires the bridge running)
.\ucmidi.exe diag loopback --samples 1000
Get Universal Controller MIDI Pro — $49 →

Linux — ALSA sequencer and snd-virmidi

Linux wins this category and it isn't close. ALSA's snd-virmidi kernel module exposes raw MIDI device nodes that the ALSA sequencer routes between clients. Round-trip latency is dominated by the scheduler — on a PREEMPT_RT kernel it's reliably under 400 microseconds. You can route ports with one shell command:

# Load two virtual MIDI devices
sudo modprobe snd-virmidi midi_devs=2

# List sequencer clients
aconnect -l

# Route the bridge output (client 128) to fluidsynth input (client 129)
aconnect 128:0 129:0

# Watch the bytes flow
aseqdump -p 128

The bridge on Linux uses ALSA sequencer ports directly, no kernel module needed. If you want a fixed device node, modprobe snd-virmidi and the bridge will attach to it instead.

Why latency differs so wildly

Three factors:

  • Kernel vs userland. Kernel-mode drivers avoid context switches. macOS IAC and Linux snd-virmidi are both kernel. loopMIDI on Windows is userland — every byte crosses the kernel boundary twice.
  • Scheduling latency. macOS uses a high-priority Mach thread for MIDI. Windows uses regular user threads unless you opt into MMCSS. Linux PREEMPT_RT preempts everything for audio.
  • Buffering policy. Some drivers batch up to 1 ms of MIDI before flushing. CoreMIDI flushes immediately. Older Windows drivers batch.

How Universal Controller MIDI picks the right path

On launch, the bridge probes the OS and picks the fastest virtual-port API available:

switch (os.platform()) {
  case 'darwin':
    return new CoreMIDIVirtualSource('UCMIDI Out');     // ~0.6 ms
  case 'win32':
    if (hasMidiServices()) return new MidiServicesPort('UCMIDI Out');  // ~1.2 ms
    return new UmpDrvPort('UCMIDI Out');                 // ~2.4 ms
  case 'linux':
    return new AlsaSeqPort('UCMIDI Out');                // ~0.4 ms
}

Pro users can override the transport in Settings → Advanced, but the defaults are tuned for lowest latency on each platform.

Limitations and gotchas

  • Live caches the port list at startup. Add the virtual port before launching the DAW or restart after.
  • FL Studio on Windows ignores the device until you hit "Rescan MIDI devices" in Options.
  • Linux PipeWire wraps ALSA but adds ~200 microseconds. Disable the PipeWire MIDI bridge if you need absolute minimum latency.
  • Bluetooth MIDI is not a virtual port. It's a real transport with its own 7–11 ms cost on top.

Virtual MIDI ports are the most boring, most important piece of plumbing in modern music software. Universal Controller MIDI picks the fastest one on your OS automatically — you don't need to think about it, but if you ever wondered why your latency numbers looked the way they did, that's why.

Keep reading

More setup walkthroughs