PipeWire and a Mackie mixer as a desktop sound card

TL;DR: The Mackie ProFX works nicely as a Linux desktop sound card. To make it more useful, there is a trick to split the audio into two stereo outputs, instead of using it as a bare 4 channel audio output.

I use a Mackie ProFX mixer as the audio interface on my desktop. It is not a complicated studio setup. I want normal desktop audio, reliable headphones, and speakers that can be controlled separately without having to remember which physical USB channel pair does what. In addition to that, I have a separate PC with a Traktor DJ controller that uses the mixer as it’s output. Everything connects to a pair of KRK Rokit 5 speakers.

The issue with using this mixer on my desktop PC, is Linux detects the sound card as a single 4-channel output instead of separating them as 2 2-channel outputs. The Windows drivers support this by default. In Linux, we of course have to do a bit more tinkering.

Mackie ProFX mixer The Mackie ProFX mixer provides the USB audio interface and the physical controls.

The mixer shows up as a USB audio device with four output channels. That is technically correct, but not intiutive enough for me to easily switch between. I do not want every application to know about front-left, front-right, rear-left, and rear-right. I want applications to see a normal headphone output and a normal speaker output.

PipeWire makes that easy with two loopback devices.

The PipeWire split

The relevant config lives in ~/.config/pipewire/pipewire.conf.d/10-split-channels.conf:

context.modules = [
    # Create a virtual stereo output that desktop applications can choose.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Headphones"
            # This is the visible sink shown in pavucontrol.
            capture.props = {
                # Keep volume controls ready before the first playback stream.
                node.always-process = true
                state.restore-props = true
                node.name = "headphones_virtual"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
                device.icon-name = "audio-headphones"
            }
            # This hidden stream forwards the virtual sink to the real Mackie channels.
            playback.props = {
                node.name = "headphones_real_link"
                media.class = "Stream/Output/Audio/Internal"
                node.hidden = true
                target.object = "alsa_output.usb-LOUD_Technologies_Inc._ProFX-00.analog-surround-40"
                audio.position = [ FL FR ]
                # Wait for the Mackie instead of disappearing or choosing another sink.
                node.dont-reconnect = false
                node.dont-fallback = true
                node.linger = true
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
    # Create another virtual stereo output for the speaker channel pair.
    {   name = libpipewire-module-loopback
        args = {
            node.description = "Speakers"
            # This is the second visible sink shown in pavucontrol.
            capture.props = {
                # Keep volume controls ready before the first playback stream.
                node.always-process = true
                state.restore-props = true
                node.name = "speakers_virtual"
                media.class = "Audio/Sink"
                audio.position = [ FL FR ]
                device.icon-name = "audio-speakers"
            }
            # This hidden stream forwards audio to the rear channel pair on the Mackie.
            playback.props = {
                node.name = "speakers_real_link"
                media.class = "Stream/Output/Audio/Internal"
                node.hidden = true
                target.object = "alsa_output.usb-LOUD_Technologies_Inc._ProFX-00.analog-surround-40"
                audio.position = [ RL RR ]
                # Wait for the Mackie instead of disappearing or choosing another sink.
                node.dont-reconnect = false
                node.dont-fallback = true
                node.linger = true
                stream.dont-remix = true
                node.passive = true
            }
        }
    }
]

The first loopback creates a virtual stereo sink called Headphones and sends it to the front-left/front-right channels on the Mackie device. The second creates Speakers and sends it to rear-left/rear-right.

The important part is that both virtual sinks look like ordinary stereo outputs to desktop applications. The config makes sure to route them to the correct outputs on the sound card.

pavucontrol showing Headphones and Speakers as separate outputs Really nice output selection list: Headphones and Speakers. Nothing else.

Update: surviving login and remembering volume

23 October 2026: The original configuration had two annoying habits: after the first login following a boot, only Headphones was selectable until I restarted PipeWire, and volume would often return to 100%. The settings in the example above solve both issues. No login script or automatic service restart is involved.

This configuration is for Debian 13 with PipeWire 1.4.2 and WirePlumber 0.5.8. Keep your own backend name in target.object; the one above is specific to my Mackie and its four-channel profile.

The playback side needs three settings together:

  • node.dont-reconnect = false allows the stream to reconnect when the backend returns.
  • node.dont-fallback = true prevents it from choosing another output while the Mackie is missing.
  • node.linger = true tells WirePlumber to wait when that specific target is unavailable, rather than destroy the stream. Without this, disabling fallback can make a missing target take the virtual output with it.

stream.dont-remix = true keeps the requested channel layout intact: Headphones links to FL/FR and Speakers to RL/RR. The PipeWire loopback documentation has a similar rear-channel example.

For volume, the important change in this setup was node.always-process = true on each visible sink. Without it, volume adjustments on an unused output could fail to stick, and volume could reset to 100%. Keeping the virtual sinks processing lets volume changes take effect before playback starts and allows WirePlumber to restore the previous levels after a restart. state.restore-props = true explicitly keeps native state restoration enabled; it was already enabled by default. See the PipeWire property reference for the scheduling and state settings. Keeping virtual nodes processing can increase idle audio processing, so this is a deliberate tradeoff for this desktop setup.

Apply the revised configuration with systemctl --user restart pipewire.service. The restart briefly interrupts audio.

Stable sample rate

There is one more small config file, ~/.config/pipewire/pipewire.conf.d/50-rate.conf:

context.properties = {
    default.clock.rate = 48000
    default.clock.allowed-rates = [ 48000 ]

    default.clock.quantum = 1024
    default.clock.max-quantum = 2048
}

This keeps the graph at 48 kHz and uses a conservative quantum. This is a conservative setting, best suitable for a normal desktop and gaming environment. Lower latency and higher clock rate is definitely possible but I noticed some issues where I had to restart the pipewire service to get my sound back to normal when doing so.

*NiX iS SeXy

Technical notes, systems troubleshooting, Linux fixes, infrastructure notes, and other operational scratchpad material.


Splitting a Mackie ProFX USB mixer into practical headphone and speaker outputs with PipeWire loopbacks.

By Daniel Akulenok, 2026-06-29