{ config, lib, pkgs, ... }: with lib; let cfg = config.services.jack; pcmPlugin = cfg.jackd.enable && cfg.alsa.enable; loopback = cfg.jackd.enable && cfg.loopback.enable; enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsaLib != null; umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12"; bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12"; in { options = { services.jack = { jackd = { enable = mkEnableOption '' JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group ''; package = mkOption { # until jack1 promiscuous mode is fixed internal = true; type = types.package; default = pkgs.jack2; defaultText = "pkgs.jack2"; example = literalExample "pkgs.jack1"; description = '' The JACK package to use. ''; }; extraOptions = mkOption { type = types.listOf types.str; default = [ "-dalsa" ]; example = literalExample '' [ "-dalsa" "--device" "hw:1" ]; ''; description = '' Specifies startup command line arguments to pass to JACK server. ''; }; session = mkOption { type = types.lines; description = '' Commands to run after JACK is started. ''; }; }; alsa = { enable = mkOption { type = types.bool; default = true; description = '' Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin. ''; }; support32Bit = mkOption { type = types.bool; default = false; description = '' Whether to support sound for 32-bit ALSA applications on 64-bit system. ''; }; }; loopback = { enable = mkOption { type = types.bool; default = false; description = '' Create ALSA loopback device, instead of using PCM plugin. Has broader application support (things like Steam will work), but may need fine-tuning for concrete hardware. ''; }; index = mkOption { type = types.int; default = 10; description = '' Index of an ALSA loopback device. ''; }; config = mkOption { type = types.lines; description = '' ALSA config for loopback device. ''; }; dmixConfig = mkOption { type = types.lines; default = ""; example = '' period_size 2048 periods 2 ''; description = '' For music production software that still doesn't support JACK natively you would like to put buffer/period adjustments here to decrease dmix device latency. ''; }; session = mkOption { type = types.lines; description = '' Additional commands to run to setup loopback device. ''; }; }; }; }; config = mkMerge [ (mkIf pcmPlugin { sound.extraConfig = '' pcm_type.jack { libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ; ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"} } pcm.!default { @func getenv vars [ PCM ] default "plug:jack" } ''; }) (mkIf loopback { boot.kernelModules = [ "snd-aloop" ]; boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ]; sound.extraConfig = cfg.loopback.config; }) (mkIf cfg.jackd.enable { services.jack.jackd.session = '' ${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"} ''; # https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06 services.jack.loopback.config = '' pcm.loophw00 { type hw card ${toString cfg.loopback.index} device 0 subdevice 0 } pcm.amix { type dmix ipc_key 219345 slave { pcm loophw00 ${cfg.loopback.dmixConfig} } } pcm.asoftvol { type softvol slave.pcm "amix" control { name Master } } pcm.cloop { type hw card ${toString cfg.loopback.index} device 1 subdevice 0 format S32_LE } pcm.loophw01 { type hw card ${toString cfg.loopback.index} device 0 subdevice 1 } pcm.ploop { type hw card ${toString cfg.loopback.index} device 1 subdevice 1 format S32_LE } pcm.aduplex { type asym playback.pcm "asoftvol" capture.pcm "loophw01" } pcm.!default { type plug slave.pcm aduplex } ''; services.jack.loopback.session = '' alsa_in -j cloop -dcloop & alsa_out -j ploop -dploop & while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done jack_connect cloop:capture_1 system:playback_1 jack_connect cloop:capture_2 system:playback_2 jack_connect system:capture_1 ploop:playback_1 jack_connect system:capture_2 ploop:playback_2 ''; assertions = [ { assertion = !(cfg.alsa.enable && cfg.loopback.enable); message = "For JACK both alsa and loopback options shouldn't be used at the same time."; } ]; users.users.jackaudio = { group = "jackaudio"; extraGroups = [ "audio" ]; description = "JACK Audio system service user"; isSystemUser = true; }; # http://jackaudio.org/faq/linux_rt_config.html security.pam.loginLimits = [ { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; } { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; } ]; users.groups.jackaudio = {}; environment = { systemPackages = [ cfg.jackd.package ]; etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsaPlugins}/etc/alsa/conf.d/50-jack.conf"; variables.JACK_PROMISCUOUS_SERVER = "jackaudio"; }; services.udev.extraRules = '' ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service" ''; systemd.services.jack = { description = "JACK Audio Connection Kit"; serviceConfig = { User = "jackaudio"; ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}"; LimitRTPRIO = 99; LimitMEMLOCK = "infinity"; } // optionalAttrs umaskNeeded { UMask = "007"; }; path = [ cfg.jackd.package ]; environment = { JACK_PROMISCUOUS_SERVER = "jackaudio"; JACK_NO_AUDIO_RESERVATION = "1"; }; restartIfChanged = false; }; systemd.services.jack-session = { description = "JACK session"; script = '' jack_wait -w ${cfg.jackd.session} ${lib.optionalString cfg.loopback.enable cfg.loopback.session} ''; serviceConfig = { RemainAfterExit = true; User = "jackaudio"; StateDirectory = "jack"; LimitRTPRIO = 99; LimitMEMLOCK = "infinity"; }; path = [ cfg.jackd.package ]; environment = { JACK_PROMISCUOUS_SERVER = "jackaudio"; HOME = "/var/lib/jack"; }; wantedBy = [ "jack.service" ]; partOf = [ "jack.service" ]; after = [ "jack.service" ]; restartIfChanged = false; }; }) ]; meta.maintainers = [ maintainers.gnidorah ]; }