diff --git a/hosts/herugrim/default.nix b/hosts/herugrim/default.nix index f4f6269..5cc148c 100644 --- a/hosts/herugrim/default.nix +++ b/hosts/herugrim/default.nix @@ -29,6 +29,9 @@ hostname = "herugrim"; username = "itsdrike"; + sound.enable = true; + bluetooth.enable = true; + impermanence = { root = { enable = true; diff --git a/options/system/default.nix b/options/system/default.nix index 70204e3..25cbfc2 100644 --- a/options/system/default.nix +++ b/options/system/default.nix @@ -17,5 +17,13 @@ in type = types.str; description = "Username for the primary admin account for this system"; }; + + sound = { + enable = mkEnableOption "sound related programs and audio-dependent programs"; + }; + + bluetooth = { + enable = mkEnableOption "bluetooth modules, drivers and configuration program(s)"; + }; }; } diff --git a/system/shared/default.nix b/system/shared/default.nix index a93ddac..849efc8 100644 --- a/system/shared/default.nix +++ b/system/shared/default.nix @@ -7,6 +7,7 @@ _: { ./environment ./impermanence ./security + ./multimedia ./programs.nix ./system.nix ./network.nix diff --git a/system/shared/multimedia/default.nix b/system/shared/multimedia/default.nix new file mode 100644 index 0000000..8da82bb --- /dev/null +++ b/system/shared/multimedia/default.nix @@ -0,0 +1,5 @@ +_: { + imports = [ + ./sound + ]; +} diff --git a/system/shared/multimedia/sound/default.nix b/system/shared/multimedia/sound/default.nix new file mode 100644 index 0000000..9e25e37 --- /dev/null +++ b/system/shared/multimedia/sound/default.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf mkDefault; + cfg = config.myOptions.system.sound; +in { + imports = [ ./pipewire.nix ]; + config = mkIf cfg.enable { + sound = { + enable = mkDefault false; # this just enables ALSA, which we don't really care abouyt + mediaKeys.enable = true; + }; + }; +} + diff --git a/system/shared/multimedia/sound/pipewire.nix b/system/shared/multimedia/sound/pipewire.nix new file mode 100644 index 0000000..44ea9e6 --- /dev/null +++ b/system/shared/multimedia/sound/pipewire.nix @@ -0,0 +1,126 @@ +{ + config, + pkgs, + lib, + ... +}: let + inherit (lib.modules) mkIf; + inherit (lib.lists) optionals; + inherit (lib.generators) toLua; + + cfg = config.myOptions.system.sound; + cfgBluetooth = config.myOptions.system.bluetooth; +in { + config = mkIf cfg.enable { + # in case pipewire was force disabled for whatever reason, fall + # back to PulseAudio to ensure that we still have audio. I do not + # like PA, but bad audio is better than no audio. Though we should + # always use PipeWire where available + hardware.pulseaudio.enable = !config.services.pipewire.enable; + + # able to change scheduling policies, e.g. to SCHED_RR + # sounds server use RealtimeKit (rtkti) to acquire + # realtime priority + security.rtkit.enable = config.services.pipewire.enable; + + # enable pipewire and configure it for low latency + # the below configuration may not fit every use case + # and you are recommended to experiment with the values + # in order to find the perfect configuration + services = { + pipewire = let + quantum = 64; + rate = 48000; + qr = "${toString quantum}/${toString rate}"; + in { + enable = true; + + # emulation layers + audio.enable = true; + pulse.enable = true; # PA server emulation + jack.enable = true; # JACK audio emulation + alsa.enable = true; # Alsa emulation + + extraConfig.pipewire."99-lowlatency" = { + context = { + properties.default.clock.min-quantum = quantum; + modules = [ + { + name = "libpipewire-module-rtkit"; + flags = ["ifexists" "nofail"]; + args = { + nice.level = -15; + rt = { + prio = 88; + time.soft = 200000; + time.hard = 200000; + }; + }; + } + { + name = "libpipewire-module-protocol-pulse"; + args = { + server.address = ["unix:native"]; + pulse.min = { + req = qr; + quantum = qr; + frag = qr; + }; + }; + } + ]; + + stream.properties = { + node.latency = qr; + resample.quality = 1; + }; + }; + }; + + wireplumber = { + enable = true; + configPackages = let + # generate "matches" section of the rules + matches = toLua { + multiline = false; # looks better while inline + indent = false; + } [[["node.name" "matches" "alsa_output.*"]]]; # nested lists are to produce `{{{ }}}` in the output + + # generate "apply_properties" section of the rules + apply_properties = toLua {} { + "audio.format" = "S32LE"; + "audio.rate" = rate * 2; + "api.alsa.period-size" = 2; + }; + in + [ + (pkgs.writeTextDir "share/lowlatency.lua.d/99-alsa-lowlatency.lua" '' + alsa_monitor.rules = { + { + matches = ${matches}; + apply_properties = ${apply_properties}; + } + } + '') + ] + ++ optionals cfgBluetooth.enable [ + (pkgs.writeTextDir "share/bluetooth.lua.d/51-bluez-config.lua" '' + 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 ]" + } + '') + ]; + }; + }; + }; + + systemd.user.services = { + pipewire.wantedBy = ["default.target"]; + pipewire-pulse.wantedBy = ["default.target"]; + }; + }; +} +