Skip to content

Commit

Permalink
nixos/jack: init
Browse files Browse the repository at this point in the history
  • Loading branch information
gnidorah committed May 30, 2019
1 parent bc94dcf commit ea82b7f
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 0 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
./services/amqp/activemq/default.nix
./services/amqp/rabbitmq.nix
./services/audio/alsa.nix
./services/audio/jack.nix
./services/audio/icecast.nix
./services/audio/liquidsoap.nix
./services/audio/mpd.nix
Expand Down
275 changes: 275 additions & 0 deletions nixos/modules/services/audio/jack.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
{ 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.
'';
};

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
}
}
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";
};
# 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 ];
}

0 comments on commit ea82b7f

Please sign in to comment.