{ config, lib, pkgs, ... }: with lib; let cfg = config.services.ezpw; in { options.services.ezpw = { enable = mkEnableOption "Enable pipewire"; lowLatency = mkOption { type = types.bool; description = "Enable low latency configuration"; default = true; }; usbSoundcard = mkOption { type = types.bool; description = "Doubles the audio rate for alsa outputs in low latency mode"; default = false; }; periodSize = mkOption { type = types.int; description = "Pipewire period size in low latency mode (\"tweak by trial-and-error\")"; default = 32; }; rate = mkOption { type = types.int; description = "Pipewire rate in low latency mode"; default = 48000; }; quantum = mkOption { type = types.int; description = "Pipewire quantum in low latency mode"; default = 64; }; }; config = let qr = "${toString cfg.quantum}/${toString cfg.rate}"; in mkIf cfg.enable { hardware.pulseaudio.enable = lib.mkDefault false; sound.enable = lib.mkDefault false; services.pipewire = { enable = lib.mkDefault true; jack.enable = lib.mkDefault true; alsa.enable = lib.mkDefault true; alsa.support32Bit = lib.mkDefault true; pulse.enable = lib.mkDefault true; media-session.enable = lib.mkDefault false; wireplumber.enable = lib.mkDefault true; config = mkIf cfg.lowLatency { pipewire = { "context.properties" = { "default.clock.rate" = cfg.rate; "default.clock.quantum" = cfg.quantum; "default.clock.min-quantum" = cfg.quantum; }; "context.modules" = [ { name = "libpipewire-module-rtkit"; args = { "nice.level" = -11; "rt.prio" = 88; "rt.time.soft" = 200000; "rt.time.hard" = 200000; }; flags = [ "ifexists" "nofail" ]; } { name = "libpipewire-module-protocol-native"; } { name = "libpipewire-module-profiler"; } { name = "libpipewire-module-metadata"; } { name = "libpipewire-module-spa-device-factory"; } { name = "libpipewire-module-spa-node-factory"; } { name = "libpipewire-module-client-node"; } { name = "libpipewire-module-client-device"; } { name = "libpipewire-module-portal"; flags = [ "ifexists" "nofail" ]; } { name = "libpipewire-module-access"; args = { }; } { name = "libpipewire-module-adapter"; } { name = "libpipewire-module-link-factory"; } { name = "libpipewire-module-session-manager"; } ]; }; pipewire-pulse = { "context.modules" = [ { name = "libpipewire-module-rtkit"; args = { "nice.level" = -11; "rt.prio" = 88; "rt.time.soft" = 200000; "rt.time.hard" = 200000; }; flags = [ "ifexists" "nofail" ]; } { name = "libpipewire-module-protocol-native"; } { name = "libpipewire-module-client-node"; } { name = "libpipewire-module-adapter"; } { name = "libpipewire-module-metadata"; } { name = "libpipewire-module-protocol-pulse"; args = { "pulse.min.req" = qr; "pulse.default.req" = qr; "pulse.min.quantum" = qr; "server.address" = [ "unix:native" ]; }; } ]; "stream.properties" = { "node.latency" = qr; "resample.quality" = 1; }; }; }; media-session.config.alsa-monitor = mkIf cfg.lowLatency { rules = [ { matches = [{ node.name = "alsa_output.*"; }]; actions = { update-props = { "audio.format" = "S32LE"; "audio.rate" = cfg.rate * (if cfg.usbSoundcard then 2 else 1); "api.alsa.period-size" = cfg.periodSize; }; }; } ]; }; media-session.config.bluez-monitor = { properties = { "bluez5.codecs" = [ "sbc" "aac" "ldac" "aptx" "aptx_hd" ]; "bluez5.mdbc-support" = true; }; rules = [ { actions = { update-props = { "bluez5.auto-connect" = [ "hsp_hs" "hfp_hf" "a2dp_sink" ]; "bluez5.hw-volume" = [ "hsp_ag" "hfp_ag" "a2dp_source" "a2dp_sink" ]; "bluez5.autoswitch-profile" = true; }; }; matches = [{ "device.name" = "~bluez_card.*"; }]; } { actions = { update-props = { "node.pause-on-idle" = false; }; }; matches = [ { "node.name" = "~bluez_input.*"; } { "node.name" = "~bluez_output.*"; } ]; } ]; }; }; environment.systemPackages = with pkgs; [ # pactl is required for pipewire-pulse pulseaudio ]; environment.etc."wireplumber/main.lua.d/51-alsa-config.lua".text = mkIf cfg.lowLatency '' alsa_monitor.properties = { ["audio.rate"] = ${toString (cfg.rate * (if cfg.usbSoundcard then 2 else 1))}, ["audio.format"] = "S32LE", ["api.alsa.headroom"] = 512, ["api.alsa.period-size"] = ${toString cfg.periodSize} } ''; environment.etc."wireplumber/bluetooth.lua.d/51-bluez-config.lua".text = '' bluez_monitor.properties = { ["bluez5.enable-sbc-xq"] = true, ["bluez5.enable-msbc"] = true, ["bluez5.enable-hw-volume"] = true, ["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]" } ''; xdg.portal.enable = lib.mkDefault true; xdg.portal.gtkUsePortal = lib.mkDefault true; xdg.portal.extraPortals = lib.mkDefault (with pkgs; [ xdg-desktop-portal-wlr xdg-desktop-portal-gtk ]); }; }