nixpkgs/nixos/modules/services/audio/jack.nix
2019-10-12 22:25:28 +02:00

292 lines
8.1 KiB
Nix

{ 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 ];
}