# Virtual 4.1 surround sound setup # Routes FL/FR to AmazonBasics USB speaker, RL/RR to Fosi BT20A PRO Bluetooth speaker { pkgs, ... }: { services.pipewire.extraConfig.pipewire."10-virtual-surround" = { "context.objects" = [ { factory = "adapter"; args = { "factory.name" = "support.null-audio-sink"; "node.name" = "virtual_surround_sink"; "node.description" = "Virtual 4.1 Surround (AmazonBasics + Fosi)"; "media.class" = "Audio/Sink"; "audio.position" = [ "FL" "FR" "RL" "RR" "LFE" ]; "monitor.channel-volumes" = true; }; } ]; "context.modules" = [ { name = "libpipewire-module-loopback"; args = { "node.description" = "Route Front to AmazonBasics"; "capture.props" = { "node.name" = "route_front_capture"; "audio.position" = [ "FL" "FR" ]; "stream.dont-remix" = true; "node.passive" = true; }; "playback.props" = { "node.name" = "route_front_playback"; "node.target" = "alsa_output.usb-C-Media_Electronics_Inc._AmazonBasics_Professional_Mic_2-00.analog-stereo"; "audio.position" = [ "FL" "FR" ]; "stream.dont-remix" = true; }; }; } { name = "libpipewire-module-loopback"; args = { "node.description" = "Route Rear to Fosi Audio"; "capture.props" = { "node.name" = "route_rear_capture"; "audio.position" = [ "RL" "RR" ]; "stream.dont-remix" = true; "node.passive" = true; }; "playback.props" = { "node.name" = "route_rear_playback"; "node.target" = "bluez_output.F4_4E_FD_FB_58_62.1"; "audio.position" = [ "FL" "FR" ]; "stream.dont-remix" = true; }; }; } { name = "libpipewire-module-loopback"; args = { "node.description" = "Route Subwoofer to AmazonBasics"; "capture.props" = { "node.name" = "route_lfe_capture"; "audio.position" = [ "LFE" ]; "stream.dont-remix" = true; "node.passive" = true; }; "playback.props" = { "node.name" = "route_lfe_playback"; "node.target" = "alsa_output.usb-C-Media_Electronics_Inc._AmazonBasics_Professional_Mic_2-00.analog-stereo"; "audio.position" = [ "MONO" ]; "stream.dont-remix" = false; }; }; } ]; }; # Systemd services to fix PipeWire loopback routing for virtual surround systemd.user.services.pipewire-surround-link = { description = "Link virtual surround sink to loopback captures"; after = [ "pipewire.service" "wireplumber.service" ]; requires = [ "pipewire.service" "wireplumber.service" ]; wantedBy = [ "pipewire.service" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = false; ExecStart = pkgs.writeShellScript "surround-link" '' sleep 2 # Disconnect wrong connections ${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_front_capture:input_FL 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX1 route_front_capture:input_FR 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_rear_capture:input_RL 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX1 route_rear_capture:input_RR 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link -d alsa_input.pci-0000_00_1f.3.pro-input-2:capture_AUX0 route_lfe_capture:input_LFE 2>/dev/null || true # Create correct connections ${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_FL route_front_capture:input_FL 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_FR route_front_capture:input_FR 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_RL route_rear_capture:input_RL 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_RR route_rear_capture:input_RR 2>/dev/null || true ${pkgs.pipewire}/bin/pw-link virtual_surround_sink:monitor_LFE route_lfe_capture:input_LFE 2>/dev/null || true ''; }; }; systemd.user.services.pipewire-surround-link-check = { description = "Check and fix surround sink links"; after = [ "pipewire.service" "wireplumber.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = pkgs.writeShellScript "surround-link-check" '' if ${pkgs.pipewire}/bin/pw-cli ls Node 2>/dev/null | grep -q "bluez_output.F4_4E_FD_FB_58_62"; then if ${pkgs.pipewire}/bin/pw-link -l 2>/dev/null | grep -q "route_front_capture:input_FL.*alsa_input"; then ${pkgs.systemd}/bin/systemctl --user start pipewire-surround-link.service fi if ! ${pkgs.pipewire}/bin/pw-link -l 2>/dev/null | grep -q "virtual_surround_sink:monitor_FL.*route_front_capture"; then ${pkgs.systemd}/bin/systemctl --user start pipewire-surround-link.service fi fi ''; }; }; systemd.user.timers.pipewire-surround-link-check = { description = "Periodically check surround sink links"; wantedBy = [ "default.target" ]; timerConfig = { OnStartupSec = "10s"; OnUnitActiveSec = "10s"; Unit = "pipewire-surround-link-check.service"; }; }; }