Blogshit

Nice blog where can I subscribe

Remapping left channel ONLY from a stereo source to mono in PipeWire

17 July 2021

I recently upgraded to Fedora 34, which meant switching from PulseAudio to PipeWire (precisely the reason I kept putting the upgrade off).

I have a certain circumstance in my audio setup: my audio interface has two inputs (for microphones, instruments, etc.) which are recognized by ALSA as a single stereo input. I want a mono input that only has my microphone on it and not the second channel. Granted, the second channel is almost always empty, but mixing stereo to mono involves something called the pan law (which isn’t a law, just an audio engineering convention), which, to make a long story short, ends up reducing my microphone’s maximum volume by up to 6 decibels as it gets downmixed to mono.

In PulseAudio I did this with the following line in ~/.config/pulse/default.pa: pactl load-module module-remap-source master=alsa_input.usb-BEHRINGER_UMC204HD_192k-00.analog-stereo master_channel_map=front-left channel_map=mono rate=48000 format=s24le

This would create a virtual microphone device that only had the left channel of input on it, and avoided the -6dB pan “law” effect.

Enter PipeWire

Doing this on PipeWire was a little harder, because the PipeWire documentation is… well, let’s say it’s a work in progress. I did not want to use the PulseAudio compatibility functionality of PipeWire (which would let me just use the Pulse command from above) because I suspect it has worse performance than the native PipeWire solution, and instead wanted to use the native solution.

Reading the relevant PipeWire documentation on Virtual Devices I managed to do something like what I wanted with the following command:

pw-loopback --capture-props='audio.position=[FL, FL]' --playback-props='media.class=Audio/Source node.name=testsource audio.position=[mono]'

However, this had two issues: (1) it did not persist through restarts, and (2) if I selected the virtual interface as my default input, PipeWire would try to be smart and re-link it to itself, causing it to stop working. This is to my understanding not a bug but a feature, as the documentation page says:

Streams are normally automatically linked to devices by the session manager. This means that coupled streams don’t need any manual intervention to get linked and can also be moved around with tools like pavucontrol or pw-metadata.

I wanted a solution that persisted through restarts, and one that I could just set as my system-wide default microphone input and forget about.

The solution

First, copy a default PipeWire session manager config file to your ~/.config (or under /etc/pipewire/ if you want it to be system-wide):

cp /usr/share/pipewire/media-session.d/media-session.conf ~/.config/pipewire/media-session.d

Then, open up that config file and insert the following at the bottom of the context.modules section. Mind your braces and square brackets; humans are second-class citizens in the PipeWire config format.

    {   name = libpipewire-module-loopback
            args = {
                capture.props = {
                    audio.position = [FL, FL]
                    node.target = alsa_input.usb-BEHRINGER_UMC204HD_192k-00.analog-stereo
                }
                playback.props = {
                    media.class = Audio/Source
                    node.name = mono-microphone
                    node.description = "Behringer Left"
                    audio.position = [mono]
                }
            }
    }

This must go in the session manager config (something that took me a long time to figure out), and PipeWire is very fussy about the parameters; specifying media.class for the capture.props section will break everything, as does node.name and what have you.

The configuration here works for me, and I hope it works for the future me who came back here to remind himself of how to configure this, and I hope it works for you. If it doesn’t, god help you (or me).

Home