[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/pipewire/pipewire.conf.d/splitter.conf
context.objects = [
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "Splitter"
node.description = "Splitter Node"
media.class = "Audio/Sink"
}
}
]
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/pipewire/pipewire.conf.d/virtual-cables.conf
context.objects = [
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "Virtual_Cable_A"
node.description = "Virtual Cable A"
media.class = "Audio/Sink"
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "Virtual_Cable_B"
node.description = "Virtual Cable B"
media.class = "Audio/Sink"
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "Virtual_Cable_C"
node.description = "Virtual Cable C"
media.class = "Audio/Sink"
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "Virtual_Cable_D"
node.description = "Virtual Cable D"
media.class = "Audio/Sink"
}
}
]
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/wireplumber/main.conf.d/10-alsa-disable-suspend.conf
{
"alsa_monitor.properties": {
"session.suspend-timeout-seconds": 0
}
}
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/wireplumber/main.conf.d/20-default-policy.conf
restore-default-targets = true
restore-stream = true
restore-device = true
restore-node = true
Obsolete/non-working wireplumber config for routes
This one is kind of unnecessary, as it didn't work for me at least. That's why I had to create the ~/.local/bin/pipewire-route.sh
script and ~/.config/systemd/user/pipewire-route.service
systemd service
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/wireplumber/main.conf.d/30-virtual-cables-routing.conf
{
"rules": [
{
"matches": [
{"node.name": "Virtual_Cable_A"},
{"node.name": "Virtual_Cable_B"},
{"node.name": "Virtual_Cable_C"},
{"node.name": "Virtual_Cable_D"}
],
"actions": {
"create-link": [
{
"output": "{node.name}:monitor_FL",
"input": "Splitter:playback_FL",
"link-factory": "si-standard-link",
"condition": { "input.exists": true }
},
{
"output": "{node.name}:monitor_FR",
"input": "Splitter:playback_FR",
"link-factory": "si-standard-link",
"condition": { "input.exists": true }
}
]
}
},
{
"matches": [{"node.name": "Splitter"}],
"actions": {
"create-link": [
{
"output": "Splitter:monitor_FL",
"input": "easyeffects_sink:playback_FL",
"link-factory": "si-standard-link",
"condition": {"input.exists": true}
},
{
"output": "Splitter:monitor_FR",
"input": "easyeffects_sink:playback_FR",
"link-factory": "si-standard-link",
"condition": {"input.exists": true}
},
{
"output": "Splitter:monitor_FL",
"input": "alsa_output.pci-0000_73_00.6.analog-stereo:playback_FL",
"link-factory": "si-standard-link",
"condition": {"input.exists": true}
},
{
"output": "Splitter:monitor_FR",
"input": "alsa_output.pci-0000_73_00.6.analog-stereo:playback_FR",
"link-factory": "si-standard-link",
"condition": {"input.exists": true}
}
]
}
}
]
}
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.local/bin/pipewire-route.sh
#!/usr/bin/env bash
set -euo pipefail
get_node_id() {
local name="$1"
pw-cli list-objects Node | awk -v n="$name" '
/^[ \t]*id [0-9]+,/ {
id = ""
match($0, /id ([0-9]+),/, arr)
id = arr[1]
next
}
/^[ \t]*node.name = "/ {
match($0, /node.name = "([^"]+)"/, arr)
if (arr[1] == n) {
print id
exit
}
}
'
}
retry_get_node_id() {
local name="$1"
local max_attempts=10
local attempt=1
local id=""
while (( attempt <= max_attempts )); do
id=$(get_node_id "$name")
if [[ -n "$id" ]]; then
echo "$id"
return 0
fi
echo "Waiting for node $name to appear (attempt $attempt/$max_attempts)..." >&2
sleep 1
((attempt++))
done
return 1
}
SPLITTER_ID=$(retry_get_node_id "Splitter") || true
echo "SPLITTER_ID=$SPLITTER_ID" >&2
EE_SINK_ID=$(retry_get_node_id "easyeffects_sink") || true
echo "EE_SINK_ID=$EE_SINK_ID" >&2
MOTHERBOARD_ID=$(retry_get_node_id "alsa_output.pci-0000_73_00.6.analog-stereo") || true
echo "MOTHERBOARD_ID=$MOTHERBOARD_ID" >&2
VIRTUAL_CABLES=(
"Virtual_Cable_A"
"Virtual_Cable_B"
"Virtual_Cable_C"
"Virtual_Cable_D"
)
if [[ -z "$SPLITTER_ID" || -z "$EE_SINK_ID" || -z "$MOTHERBOARD_ID" ]]; then
echo "Failed to find required nodes:"
echo "Splitter ID: $SPLITTER_ID"
echo "EasyEffects ID: $EE_SINK_ID"
echo "Motherboard ID: $MOTHERBOARD_ID"
exit 1
fi
echo "Found nodes:"
echo " Splitter ID: $SPLITTER_ID"
echo " EasyEffects ID: $EE_SINK_ID"
echo " Motherboard ID: $MOTHERBOARD_ID"
for vc_name in "${VIRTUAL_CABLES[@]}"; do
VC_ID=$(retry_get_node_id "$vc_name") || {
echo "Virtual Cable $vc_name not found, skipping."
continue
}
echo "Linking $vc_name (ID $VC_ID) monitor_FL -> Splitter playback_FL"
pw-link "${VC_ID}:monitor_FL" "${SPLITTER_ID}:playback_FL" || true
echo "Linking $vc_name (ID $VC_ID) monitor_FR -> Splitter playback_FR"
pw-link "${VC_ID}:monitor_FR" "${SPLITTER_ID}:playback_FR" || true
done
echo "Linking Splitter monitor_FL -> EasyEffects playback_FL and Motherboard playback_FL"
pw-link "${SPLITTER_ID}:monitor_FL" "${EE_SINK_ID}:playback_FL" || true
pw-link "${SPLITTER_ID}:monitor_FL" "${MOTHERBOARD_ID}:playback_FL" || true
echo "Linking Splitter monitor_FR -> EasyEffects playback_FR and Motherboard playback_FR"
pw-link "${SPLITTER_ID}:monitor_FR" "${EE_SINK_ID}:playback_FR" || true
pw-link "${SPLITTER_ID}:monitor_FR" "${MOTHERBOARD_ID}:playback_FR" || true
echo "PipeWire routing set up."
[kirbo@Kirbo-EndeavourOS ~]$ cat ~/.config/systemd/user/pipewire-route.service
[Unit]
Description=PipeWire custom routing
After=pipewire.service
Wants=pipewire.service
[Service]
Type=oneshot
ExecStart=%h/.local/bin/pipewire-route.sh
Restart=no
[Install]
WantedBy=default.target
mkdir -p ~/.local/bin ~/.config/wireplumber/main.conf.d ~/.config/pipewire/pipewire.conf.d/
# create files above
chmod +x ~/.local/bin/pipewire-route.sh
systemctl --user daemon-reload
systemctl --user enable --now pipewire-route.service
systemctl --user restart pipewire pipewire-pulse wireplumber