Pipewire gives many ways to do post-processing of the sound generated by an application. One of the frequent use cases is to add reverb, room acoustics or virtual surround of multichannel audio over 2 headphone channels.
Below is a rather simple way.
Virtual Surround using JConvolver
This approach leverages PipeWire's abilities to act as a JACK daemon and uses convolution to apply impulse responses to simulate a 7.1 surround headphone.
- Install
JConvolver
. - Create a 7 channel PulseAudio sink:
pw-cli create-node adapter '{ factory.name=support.null-audio-sink node.name=hrir-headphones media.class=Audio/Sink object.linger=1 audio.position=FL,BL,SL,FC,FR,BR,SR }'
- Create the
~/virtual-surround/
directory:
mkdir ~/virtual-surround/
- Next, get a surround impulse response of your liking from the HeSuVi project. Let's pick GSX. Copy
gsx.wav
into~/virtual-surround/
directory. - Next create the file
~/virtual-surround/gsx-jconvolver.config
:
# Virtual Surround fro Headphones using JCONVOLVER
# (https://kokkinizita.linuxaudio.org/linuxaudio/)
#
# This JCONVOLVER config downmixes 7 channel audio into 2 headphone
# channels while preserving audio positions as much as possible.
#
# For best results it is recommended to equalize your headphones
# and make them phase-neutral before applying the virtualization
#
# Impulse responses are taken from the HESUVI project.
# https://sourceforge.net/projects/hesuvi/
# -------------------------------------------------------------------------
#
/cd ~/virtual-surround/
#
#
# in out partition maxsize density
# -------------------------------------------------------------------------
/convolver/new 7 2 1024 800000 1.0
#
#
# num port name connect to
# -------------------------------------------------------------------------
/input/name 1 Input.FL "hrir-headphones:monitor_FL"
/input/name 2 Input.BL "hrir-headphones:monitor_BLC" #5.1
/input/name 3 Input.SL "hrir-headphones:monitor_SL" #7.1
/input/name 4 Input.FC "hrir-headphones:monitor_FC" # 3.1
/input/name 5 Input.FR "hrir-headphones:monitor_FR"
/input/name 6 Input.BR "hrir-headphones:monitor_BRC" #5.1
/input/name 7 Input.SR "hrir-headphones:monitor_SR" #7.1
#
/output/name 1 Output.FL "HDA Intel PCH:playback_FL"
/output/name 2 Output.FR "HDA Intel PCH:playback_FR"
#
#
# in out gain delay offset length chan file
# -------------------------------------------------------------------------
#Front-Left
/impulse/read 1 1 1 0 0 0 1 gsx.wav
/impulse/read 1 2 1 0 0 0 2 gsx.wav
#Back-Left
/impulse/read 2 1 1 0 0 0 5 gsx.wav
/impulse/read 2 2 1 0 0 0 6 gsx.wav
#Side-Left
/impulse/read 3 1 1 0 0 0 3 gsx.wav
/impulse/read 3 2 1 0 0 0 4 gsx.wav
#CENTER
/impulse/read 4 1 1 0 0 0 7 gsx.wav
/impulse/read 4 2 1 0 0 0 14 gsx.wav
#Front-Right
/impulse/read 5 1 1 0 0 0 9 gsx.wav
/impulse/read 5 2 1 0 0 0 8 gsx.wav
#Back-Right
/impulse/read 6 1 1 0 0 0 13 gsx.wav
/impulse/read 6 2 1 0 0 0 12 gsx.wav
#Side-Right
/impulse/read 7 1 1 0 0 0 11 gsx.wav
/impulse/read 7 2 1 0 0 0 10 gsx.wav
- Start the convolver:
pw-jack jconvolver -s pipewire-0 ~/virtual-surround/gsx-jconvolver.config
- Open
pavucontrol
(or Gnome Control Center) and redirect the application's and/or systems default audio output tohrir-headphones
. Enjoy virtual surround in your headphones.
Note that you'll need to re-create the PulseAudio sink (step 2) and start jconvolver
(step 6) after every login.
If you want to have that automated, use the following systemd unit definition in ~/.config/systemd/user/surround-headphones.service
:
[Unit]
Description="7 channel Virtual Surround Sink"
After=pipewire-pulse.service
[Service]
Type=simple
StandardOutput=journal
Environment="PIPEWIRE_LATENCY=1024/48000"
ExecStartPre=pw-cli create-node adapter '{ factory.name=support.null-audio-sink node.name=hrir-headphones media.class=Audio/Sink object.linger=1 audio.position=FL,BL,SL,FC,FR,BR,SR }'
ExecStart=pw-jack jconvolver /home/username/virtual-surround/gsx-jconvolver.config
ExecStopPost=/usr/bin/bash -c "pw-cli destroy `pw-cli dump short Node|grep hrir-headphones|awk -F: '{print $1}'`"
RestartSec=5
Restart=on-failure
[Install]
WantedBy=multi-user.target