A native C++ userspace engine for the Digidesign Command|8 control surface on Linux, Windows and macOS: a DAW-agnostic core library + bridges for Reaper (OSC) and any Mackie-Control-capable DAW (Bitwig, …). The protocol documentation (docs/PROTOCOL.md), the Linux kernel quirk (quirk/) and the Reaper OSC pattern (reaper/) are all included here.
The device's MIDIStreaming input descriptor is malformed, so a standard class parser does not create an input port — and each platform needs a different way around that:
| getting the input | MCU bridge needs | |
|---|---|---|
| Linux | snd-usb-audio quirk (quirk/) |
snd-virmidi |
| Windows | Digidesign/Avid's own driver, or the dongle | a loopback pair |
| macOS | claim the USB interface directly (libusb) | nothing |
macOS is the odd one out in both columns. CoreMIDI has no quirk mechanism, so the device's own ports enumerate but stay inert; the backend bypasses CoreMIDI on the device side and speaks USB-MIDI packets over libusb instead. In exchange, macOS can create MIDI endpoints from an application, so the MCU bridge publishes its own virtual pair and needs no loopback utility.
Everything else (protocol translation, the wake/keepalive handshake, LED/fader/meter/ring/LCD feedback) is ordinary userspace logic: So this engine is a normal compiled program that talks to the device over ALSA (Linux), RtMidi/WinMM (Windows) or libusb (macOS), giving full access to the surface controls and feedback, but with some buttons (EQ, Dynamics) not reproducing the exact function they have in Pro Tools.
src/protocol.{hpp,cpp} native MIDI protocol: decode (input) + encode (feedback)
src/surface.hpp Surface interface: device discovery, wake + keepalive, events
src/midi_port.hpp MidiPort interface: raw-bytes duplex port (MCU side)
src/alsa/ ALSA-seq implementations of both (Linux)
src/rtmidi/ RtMidi implementations of both (Windows)
src/macos/ libusb Surface + virtual-CoreMIDI MidiPort (macOS)
src/feedback.{hpp,cpp} normalized (0..1) feedback: faders/meters/rings/LEDs/LCD
src/backend.hpp Backend interface — host integrations subclass this
src/controller.{hpp,cpp} wires Surface -> Backend, normalizes events
src/main.cpp command8-monitor: demo Backend (loopback, no DAW)
src/reaper/ command8-reaper: Reaper OSC bridge (liblo)
src/mackie/ command8-mackie: Mackie Control (MCU) emulation
docs/PROTOCOL.md protocol reverse-engineering evidence (+ raw captures)
quirk/ Linux snd-usb-audio quirk patch (exposes the MIDI input)
reaper/Command8.ReaperOSC Reaper OSC pattern file for command8-reaper
libcommand8 is DAW-agnostic. A Backend receives normalized input
(on_fader(strip, 0..1), on_encoder(strip, ±1), on_select, …) and drives a
Feedback handle (meter, ring_dot, select_led, lcd_channel, …) that
hides the device bit-packing. Host integrations (a Reaper OSC bridge, a
Mackie/HUI translator, a Bitwig backend, …) are Backends built on top — the demo
in main.cpp is one (pure loopback: faders→meters, encoders→pan dot,
select/mute/solo→LEDs).
Requires a C++17 compiler, CMake ≥ 3.16, and libasound2-dev (plus liblo-dev
for the Reaper bridge).
cmake -B build
cmake --build build
ctest --test-dir build # protocol decode/encode unit tests
./build/command8-monitor # loopback demo (needs the device + quirk)
./build/command8-reaper # Reaper OSC bridge (waits for the device)
./build/command8-mackie # MCU bridge (needs snd-virmidi)In Reaper: Preferences → Control/OSC/web → Add → OSC. Set the pattern
config to reaper/Command8.ReaperOSC (installed
packages put it in <prefix>/share/command8/), device receives on 8000,
sends to 9000 — command8-reaper's defaults.
cmake --install build --prefix /usr/local # binaries + systemd user unit + docsOr build distributable packages (a .deb and a .tar.gz) with CPack:
cd build && cpack # -> command8-<ver>-Linux.deb / .tar.gz
sudo apt install ./command8-*-Linux.deb # deps (libasound2, liblo) auto-resolvedEither way, command8-reaper's ExecStart is rewritten to the real install
prefix (/usr/bin for the .deb, /usr/local/bin for a plain install), and the
systemd user unit lands in <prefix>/lib/systemd/user/.
Run command8-reaper as a systemd user service (self-heals on unplug/replug):
systemctl --user daemon-reload
systemctl --user enable --now command8-reaper(Building from source without installing? Copy systemd/command8-reaper.service.in
to ~/.config/systemd/user/command8-reaper.service and set ExecStart to your
build/command8-reaper.)
Requires a C++17 compiler (Xcode command line tools), CMake ≥ 3.16, and
libusb + rtmidi (plus liblo for the Reaper bridge):
brew install cmake ninja libusb rtmidi liblo
cmake -B build -G Ninja
cmake --build build
ctest --test-dir build
./build/command8-monitor # loopback demo
./build/command8-reaper # Reaper OSC bridge (identical OSC setup)
./build/command8-mackie # MCU bridge (no loopback needed)No sudo needed for command8-monitor or command8-reaper: on the machine
this was verified on (macOS 15.6, Intel, Homebrew, libusb 1.0.30)
command8-monitor claims the USB interface and gets live fader/encoder input
and LED feedback as a normal user, and command8-reaper does the same and
additionally reclaims the interface unprivileged after an unplug/replug cycle
(re-tested directly, not inferred). Neither binary ever touches RtMidi/CoreMIDI
(command8-reaper only speaks OSC over liblo, and rebuilds its backend fresh
on every reconnect) — unlike command8-mackie below, which does and pays for
it on replug. If your setup instead reports "device not found" or a claim
failure, it's most likely another process already holding the interface (see
below) or a stricter USB permission policy on your machine — try sudo as a
fallback in that case, and consider a LaunchDaemon if you need it every run.
command8-mackie is different — see below.
If another Command|8 bridge is already running, stop it first: the interface is exclusive.
Nothing to install. command8-mackie publishes a virtual MIDI source and
destination, both named Command|8; point your DAW's Mackie Control
surface at that name for both its input and its output. Rename with
--mcu-recv/--mcu-send if you want something else.
Sudo-free at launch, but not across a replug. Publishing those virtual
ports means this process has a CoreMIDI client for its whole life, and once
that's true, this process's own future claims of the physical device race
CoreMIDI's in-process device-notification handling for the same interface —
and lose. So command8-mackie opens the Command|8 unprivileged fine on first
launch, but if the device is unplugged and replugged while it's running, it
cannot reclaim the interface again without root (verified: this isn't a
race that resolves with more retries or more time, and tearing down and
rebuilding the virtual ports around the reclaim attempt doesn't help either
— it's a standing condition for the rest of that process's life). If you need
replug resilience, run it under sudo from the start; if you don't (or you're
fine restarting it after a reconnect), it's the only one of the three bridges
that's usually unprivileged.
Publishing both endpoints matters: with only a source, a DAW sees an input with no matching output and control-surface support reports that it cannot find a MIDI output.
Requires Visual Studio 2022+ (MSVC), CMake, and vcpkg (all bundled with a Visual Studio install). Dependencies (RtMidi, liblo) come from the vcpkg manifest automatically:
cmake -S . -B build -G Ninja ^
-DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake ^
-DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build build
ctest --test-dir build
build\command8-monitor.exe --list # check the Command|8's ports are visible
build\command8-monitor.exe # loopback demo
build\command8-reaper.exe # Reaper OSC bridge (identical OSC setup)
build\command8-mackie.exe # MCU bridge (see below)(From a plain VS developer prompt, %VCPKG_ROOT% is
%VSINSTALLDIR%VC\vcpkg. The static triplet folds RtMidi, liblo and the MSVC
runtime into the exes; drop it for a plain dynamic dev build.)
Package a portable ZIP of the self-contained exes (command8-<ver>-win64.zip;
unzip anywhere, no vcredist or DLLs needed):
cd build && cpackOn Windows the Command|8 needs either Digidesign/Avid's own driver to expose its
MIDI input and output (Command|8, plus MIDIIN2/3 for the rear MIDI jacks),
or the command8-dongle, which
makes the surface enumerate as an ordinary class-compliant USB-MIDI device with
no driver at all. Either way the ports look the same to this engine.
If another app holds the port, close it first: WinMM ports are exclusive.
In particular, set the Command|8's input and output to disabled in a DAW's
MIDI device list before starting command8-reaper or command8-mackie --
otherwise the engine cannot open the device and simply fails to start. The DAW
talks to the engine (over OSC, or over the MCU loopback pair), never to the
surface directly: the Command|8 speaks a proprietary protocol, so a DAW sending
it generic MIDI just makes the faders twitch and leaves the display Offline.
A dongle built with on-board translation exposes a fourth cable that is a Mackie Control endpoint, so none of the above is needed — no engine process, no loopback pair, no OSC. Point the DAW's Mackie Control support straight at it, input and output, and leave the surface's own port disabled.
Where to find the cable, verified on hardware on all three:
| OS | Port name |
|---|---|
| Linux (ALSA) | Command|8 Bridge Command8 MCU |
| macOS (CoreMIDI) | command8-cpp - Command|8 Bridge - Command8 MCU |
| Windows (WinMM) | MIDIIN4 (Command|8 Bridge) / MIDIOUT4 (…) |
ALSA and CoreMIDI read the per-cable jack strings; WinMM ignores them and names cables positionally, so on Windows it is simply the fourth port.
Verified 2026-08-11: Reaper driving a Command|8 through the dongle on Windows and on macOS, with no host software running on either.
The translation is the same src/mcu/c8_mcu.c this repo unit-tests on the
desktop; the firmware compiles that file directly rather than a copy.
Windows has no app-created virtual MIDI ports, so create a loopback pair once
with Windows MIDI Services (or two loopMIDI cables and
--mcu-recv/--mcu-send):
midi loopback create --name-a "Command8 MCU Bridge" --name-b "Command8 MCU DAW"The ends are named for who owns them. command8-mackie takes Command8 MCU
Bridge by default; point the DAW's Mackie Control input and output at
Command8 MCU DAW. The pair is crossed, so neither end hears its own output.
Note there is no bar in these names, deliberately. Command|8 is how the tools
find the surface itself, by prefix, so a loopback named Command|8 MCU … could
be matched as the device. Keep the loopback pair on the unbarred Command8.
Verified end to end on Windows: with the three Command|8 Bridge device ports
left disabled in Reaper's MIDI Devices, Command8 MCU DAW enabled, and a
Mackie Control Universal surface pointed at it for both input and output.
GPL-3.0-or-later.