Skip to content

Instantly share code, notes, and snippets.

@Kirbo
Last active July 29, 2025 15:08
Show Gist options
  • Save Kirbo/5f4e7c9f1508583e94d8d3d5fc2599ad to your computer and use it in GitHub Desktop.
Save Kirbo/5f4e7c9f1508583e94d8d3d5fc2599ad to your computer and use it in GitHub Desktop.
EndeavourOS Virtual Cables A-D
[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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment