Control an RME Fireface 400 mixer on modern macOS without TotalMix — through the ASFW DriverKit user client.
ASFW restores FireWire audio on macOS Tahoe (26+) by driving the OHCI
controller directly from a DriverKit extension. It streams audio to the
FF400 but does not (yet) expose RME's interactive mixer. This project fills
the gap by issuing the same asynchronous FireWire register writes that
TotalMix/FFADO use, through ASFW's raw AsyncWrite user-client method.
┌─────────────┐ IOConnectCallMethod ┌──────────────┐ IEEE-1394 ┌───────────┐
│ ffctl / │ ──────────────────────► │ ASFWDriver │ ────────────► │ Fireface │
│ FFTray.app │ asyncWrite (sel. 9) │ (OHCI dext) │ quadlet write│ 400 mixer │
└─────────────┘ └──────────────┘ └───────────┘
| Piece | Description |
|---|---|
FFTray.app |
Menu-bar app: 18×18 input→output and playback→output routing matrix, output faders, mic/line preamp gains. ASFW "Studio Dark" style. |
ffctl |
CLI for scripting the same operations. |
fftests |
Self-contained unit-test runner (no Xcode needed). |
- macOS with ASFW installed and its
net.asfw.driversystem extension activated (tested on ASFW 0.6.0 / macOS 26). - RME Fireface 400 on the bus.
- No Xcode required — Command Line Tools (
swift) is enough.
Grab ff400ctl-<ver>.dmg from Releases, drag FFTray.app to
Applications, and optionally sudo cp ffctl /usr/local/bin/.
Gatekeeper: the binaries aren't notarized, so first launch is blocked. Right-click FFTray.app → Open, or approve it in System Settings → Privacy & Security → Open Anyway, or clear quarantine:
xattr -dr com.apple.quarantine /Applications/FFTray.app
xattr -d com.apple.quarantine /usr/local/bin/ffctl # if you installed the CLIEntitlement caveat: the binaries are ad-hoc signed and claim
com.apple.developer.driverkit.userclient-access for net.asfw.driver.
Verified working on the author's Mac (custom SIP configuration, OpenCore
Legacy Patcher); on a stock-security Mac, macOS may refuse to launch a
binary claiming that restricted entitlement without a provisioning
profile — untested, reports welcome. The planned fix is ASFW's driver
extension adopting
com.apple.developer.driverkit.allow-any-userclient-access, after which
no entitlement is needed client-side.
make # builds release ffctl + FFTray.app, ad-hoc signs both
make test # 124 unit checks: address math, dB conversion, state codec
make install # ffctl -> /usr/local/bin, FFTray.app -> /ApplicationsBoth binaries must be signed with
com.apple.developer.driverkit.userclient-access = [net.asfw.driver]
(Resources/entitlements.plist). Ad-hoc signing (codesign -s -) is
sufficient on the machine where ASFW runs — the Makefile does it for you.
Menu-bar slider icon → click:
- MAIN — one prominent volume slider for a stereo output pair (picker for which pair, -60…+6 dB travel, mute = bottom), with a mute button and click-to-type dB entry.
- Inputs / Playback segmented tabs — one matrix row per source channel, one column per output channel (1–6 = Analog 1-6, 7–8 = Phones, 9–10 = SPDIF, 11–18 = ADAT). Click a cell to toggle the crosspoint between mute and 0 dB.
- INPUTS → bus — per-input send faders into the selected monitor-bus pair, with M (mute), S (exclusive solo) and Ø (phase invert) chips per channel, and a MONO switch that sums the pair to (L+R)/2 (inputs and playback, non-destructive).
- OUTPUTS — 18 master faders in stereo pairs with per-channel M (they also sync the FF400's hardware amp-gain register, like TotalMix does). The ST chip links a pair into one fader — TotalMix's stereo button.
- Mic 1/2 — preamp gain 0–65 dB + 48V phantom toggles;
In 3/4 — line gain + PAD/INST toggles.
All four write the 3-quadlet device config block at
0x80100514. - Launch at login — footer toggle registers/unregisters the app as a login item (SMAppService, macOS 13+).
- Auto re-apply — a CoreAudio device-list watcher notices when the FF400 reappears after a power cycle and pushes the saved mix again (mixer RAM is volatile); footer toggle, on by default. Relies on ASFW republishing the CoreAudio device when the unit returns.
- Re-apply mix — rewrites all tracked cells (mixer RAM is volatile — the unit restores its flash-stored mix on every power cycle); output amp gains are re-derived from the faders rather than replayed raw.
- Save state — persists the intended mix to
~/Library/Application Support/ff400ctl/state.json. Writes auto-save (coalesced) and the file is watched, soffctlchanges show up in the tray live.
Full reference: CLI.md — every command, channel numbering, gain syntax, automation recipes and file locations. Quick taste:
ffctl mon 0 0 1 # input 1 -> analog outs 1,2 at 0 dB
ffctl mix 0 0 -6dB # one input fader cell
ffctl outvol 0 -3dB # output master fader (relative step)
ffctl phantom 1 on # +48V on mic input 1
ffctl mute 0 on # mute input 1 on all outputs
ffctl save vocal # snapshot the whole mix state
ffctl load vocal # restore + apply it
ffctl batch script.txt # run many commands over one session
ffctl state --json # machine-readable state for scriptsThe FF400 has no AV/C or vendor-commands for mixing — it exposes raw
register RAM. Protocol recovered from the open-source
FFADO drivers (src/rme/fireface_hw.cpp), which RME
historically cooperated with:
| Register | Address | Purpose |
|---|---|---|
| mixer RAM | 0x80080000 |
live matrix, one quadlet per crosspoint |
| output faders | 0x80080f80 |
18 master faders inside mixer RAM |
| gain register | 0x801c0180 |
hardware amp gains (index<<16)|devval |
| channel mute | 0x801c0000 |
28-quadlet PCM fetch mask |
| output rec | 0x801c0080 |
28-quadlet output record flags |
| config block | 0x80100514 |
3 quadlets, write-only: phantom, pads, inst, levels, clock |
| meter block | 0x80100000 |
~648-byte level readback (needs working reads — see below) |
Config block (0x80100514, 3 quadlets, block write / selector 45):
phantom q0 bits 0x01/0x80 (mic 1/2), pads 0x100/0x02 (in 3/4),
instrument 0x200/0x04, in/out/phones reference levels in q0/q1,
clock + sync + SPDIF opts + fixed 0x1e|0x80000000|0x04000000 in q2.
Encoding ported from FFADO set_hardware_params / ASFW EncodeConfig.
Channel flags (TotalMix semantics, software-side like FFADO):
mute writes 0 but keeps the tracked fader value; phase invert writes the
negative of the gain (with FFADO's 0 → -1 glitch workaround); solo is
exclusive and applies to the monitor bus pair only; mono is a true L+R
sum — both cells of the monitor pair carry the (L+R)/2 average (inputs
and playback), and it's non-destructive: stored cells are never modified,
so switching mono off restores the original stereo image.
Matrix addressing (FF400, block = 0x48):
input src→dest: 0x80080000 + dest*0x90 + src*4
playback src→dest: 0x80080000 + dest*0x90 + src*4 + 0x48
output ch fader: 0x80080f80 + ch*4
gain value: 0x8000 = 0 dB, 0x10000 = +6 dB, 0 = mute
(dB = 20·log10(val/32768))
Wire format: registers are little-endian quadlets written to node
0xffc0 at 48-bit offset 0x0000:xxxxxxxx — plain quadlet async writes
through IOConnectCallMethod selector 9 (kMethodAsyncWrite), after the
same open handshake the ASFW app performs (map memory type 0 + register
status listener, selector 10).
- Mixer RAM is write-only in hardware. User-client async reads work
fine — the device acks every read — but mixer cells (
0x80080000) and the config register (0x80100514) return filler (0xffffffff/0) no matter what was written; the RAM simply offers no read-back. So this tool tracks mixer state in software, like FFADO's GUI does. Readable regions: Config ROM, status0x801c0000, revision0x80100290, flash status0x80100520— and the meter block0x80100000, a ~648-byte block read returning all 54 channel levels (inputs/stream/outputs, −90.03…0 dBFS, mask0x7fffff00, overload in the low byte) per Takashi Sakamoto's ALSAfirewire-fireface-protocols.ffctl metersand the FFTray fader bars use it. - Multiple OHCI services are possible;
ffctl/FFTraywrite on everyASFWDriverconnection and treat the write as applied if any transaction completes. The stray writes on the wrong bus fail withevt_missing_ackharmlessly. - Don't flood transactions. Rapid read bursts wedge the FF400's async response engine (driver Configure then times out → audio stalls until power cycle). Keep write bursts small; this tool writes ≤ ~40 transactions per operation with verification polling in between.
- Mixer RAM is volatile — power cycle reverts to the flash-stored mix.
- A proper integration would be an RME control provider inside ASFW's
Device Controls model (see
documentation/controls-rollout.md), sharing this register map — happy to upstream the protocol bits.
Writes go to live mixer RAM. A wrong value can blast an input at 0 dB into
an output or mute playback — recoverable, but start with ffctl mon at a
sensible level. Nothing here writes the unit's flash, so a power cycle
always restores the stored configuration.
- Protocol: FFADO project
src/rme/(J. Woithe et al.) - Driver path: ASFireWire by mrmidi
- UI style: ASFW Audio Analyzer "Studio Dark" theme
Apache-2.0, matching ASFW. Register map and protocol details were derived from FFADO and the Linux ALSA fireface driver (hardware facts; no code copied).
