summary refs log tree commit diff
path: root/nixos/modules/services/audio/jack.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/audio/jack.nix')
-rw-r--r--nixos/modules/services/audio/jack.nix294
1 files changed, 294 insertions, 0 deletions
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
new file mode 100644
index 00000000000..84fc9957b87
--- /dev/null
+++ b/nixos/modules/services/audio/jack.nix
@@ -0,0 +1,294 @@
+{ 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.alsa-lib != 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 = literalExpression "pkgs.jack2";
+          example = literalExpression "pkgs.jack1";
+          description = ''
+            The JACK package to use.
+          '';
+        };
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [
+            "-dalsa"
+          ];
+          example = literalExpression ''
+            [ "-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.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
+          ${lib.optionalString enable32BitAlsaPlugins
+          "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/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.alsa-plugins}/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";
+          SupplementaryGroups = lib.optional
+            (config.hardware.pulseaudio.enable
+            && !config.hardware.pulseaudio.systemWide) "users";
+          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 = [ ];
+}