From 05fed3ede113dd248e26db02c031259a54c97260 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Mon, 29 Dec 2025 12:01:29 -0800 Subject: [PATCH] Add virtual 4.1 surround sound configuration for zix790prors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a PipeWire virtual surround sink that routes audio to multiple physical outputs: - FL/FR channels → AmazonBasics USB speaker - RL/RR channels → Fosi BT20A PRO Bluetooth speaker - LFE channel → AmazonBasics (duplicated to both channels) Uses loopback modules with systemd services to maintain correct routing, as PipeWire's target.object parameter doesn't auto-connect properly. A timer checks every 10 seconds and fixes incorrect connections. Configuration is machine-specific and isolated in virtual-surround.nix. --- machines/zix790prors/configuration.nix | 8 +- machines/zix790prors/virtual-surround.nix | 132 ++++++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 machines/zix790prors/virtual-surround.nix diff --git a/machines/zix790prors/configuration.nix b/machines/zix790prors/configuration.nix index 93c09d1..928965b 100644 --- a/machines/zix790prors/configuration.nix +++ b/machines/zix790prors/configuration.nix @@ -7,10 +7,10 @@ with lib; { - imports = - [ # Include the results of the hardware scan. - ./hardware-configuration.nix - ]; + imports = [ + ./hardware-configuration.nix + ./virtual-surround.nix + ]; roles = { audio.enable = true; diff --git a/machines/zix790prors/virtual-surround.nix b/machines/zix790prors/virtual-surround.nix new file mode 100644 index 0000000..eed6d69 --- /dev/null +++ b/machines/zix790prors/virtual-surround.nix @@ -0,0 +1,132 @@ +# 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"; + }; + }; +}