at 25.11-pre 8.7 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.jack; 9 10 pcmPlugin = cfg.jackd.enable && cfg.alsa.enable; 11 loopback = cfg.jackd.enable && cfg.loopback.enable; 12 13 enable32BitAlsaPlugins = 14 cfg.alsa.support32Bit && pkgs.stdenv.hostPlatform.isx86_64 && pkgs.pkgsi686Linux.alsa-lib != null; 15 16 umaskNeeded = lib.versionOlder cfg.jackd.package.version "1.9.12"; 17 bridgeNeeded = lib.versionAtLeast cfg.jackd.package.version "1.9.12"; 18in 19{ 20 options = { 21 services.jack = { 22 jackd = { 23 enable = lib.mkEnableOption '' 24 JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group 25 ''; 26 27 package = 28 lib.mkPackageOption pkgs "jack2" { 29 example = "jack1"; 30 } 31 // { 32 # until jack1 promiscuous mode is fixed 33 internal = true; 34 }; 35 36 extraOptions = lib.mkOption { 37 type = lib.types.listOf lib.types.str; 38 default = [ 39 "-dalsa" 40 ]; 41 example = lib.literalExpression '' 42 [ "-dalsa" "--device" "hw:1" ]; 43 ''; 44 description = '' 45 Specifies startup command line arguments to pass to JACK server. 46 ''; 47 }; 48 49 session = lib.mkOption { 50 type = lib.types.lines; 51 description = '' 52 Commands to run after JACK is started. 53 ''; 54 }; 55 56 }; 57 58 alsa = { 59 enable = lib.mkOption { 60 type = lib.types.bool; 61 default = true; 62 description = '' 63 Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin. 64 ''; 65 }; 66 67 support32Bit = lib.mkOption { 68 type = lib.types.bool; 69 default = false; 70 description = '' 71 Whether to support sound for 32-bit ALSA applications on 64-bit system. 72 ''; 73 }; 74 }; 75 76 loopback = { 77 enable = lib.mkOption { 78 type = lib.types.bool; 79 default = false; 80 description = '' 81 Create ALSA loopback device, instead of using PCM plugin. Has broader 82 application support (things like Steam will work), but may need fine-tuning 83 for concrete hardware. 84 ''; 85 }; 86 87 index = lib.mkOption { 88 type = lib.types.int; 89 default = 10; 90 description = '' 91 Index of an ALSA loopback device. 92 ''; 93 }; 94 95 config = lib.mkOption { 96 type = lib.types.lines; 97 description = '' 98 ALSA config for loopback device. 99 ''; 100 }; 101 102 dmixConfig = lib.mkOption { 103 type = lib.types.lines; 104 default = ""; 105 example = '' 106 period_size 2048 107 periods 2 108 ''; 109 description = '' 110 For music production software that still doesn't support JACK natively you 111 would like to put buffer/period adjustments here 112 to decrease dmix device latency. 113 ''; 114 }; 115 116 session = lib.mkOption { 117 type = lib.types.lines; 118 description = '' 119 Additional commands to run to setup loopback device. 120 ''; 121 }; 122 }; 123 124 }; 125 126 }; 127 128 config = lib.mkMerge [ 129 130 (lib.mkIf pcmPlugin { 131 environment.etc."alsa/conf.d/98-jack.conf".text = '' 132 pcm_type.jack { 133 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ; 134 ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"} 135 } 136 pcm.!default { 137 @func getenv 138 vars [ PCM ] 139 default "plug:jack" 140 } 141 ''; 142 }) 143 144 (lib.mkIf loopback { 145 boot.kernelModules = [ "snd-aloop" ]; 146 boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ]; 147 environment.etc."alsa/conf.d/99-jack-loopback.conf".text = cfg.loopback.config; 148 }) 149 150 (lib.mkIf cfg.jackd.enable { 151 services.jack.jackd.session = '' 152 ${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"} 153 ''; 154 # https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06 155 services.jack.loopback.config = '' 156 pcm.loophw00 { 157 type hw 158 card ${toString cfg.loopback.index} 159 device 0 160 subdevice 0 161 } 162 pcm.amix { 163 type dmix 164 ipc_key 219345 165 slave { 166 pcm loophw00 167 ${cfg.loopback.dmixConfig} 168 } 169 } 170 pcm.asoftvol { 171 type softvol 172 slave.pcm "amix" 173 control { name Master } 174 } 175 pcm.cloop { 176 type hw 177 card ${toString cfg.loopback.index} 178 device 1 179 subdevice 0 180 format S32_LE 181 } 182 pcm.loophw01 { 183 type hw 184 card ${toString cfg.loopback.index} 185 device 0 186 subdevice 1 187 } 188 pcm.ploop { 189 type hw 190 card ${toString cfg.loopback.index} 191 device 1 192 subdevice 1 193 format S32_LE 194 } 195 pcm.aduplex { 196 type asym 197 playback.pcm "asoftvol" 198 capture.pcm "loophw01" 199 } 200 pcm.!default { 201 type plug 202 slave.pcm aduplex 203 } 204 ''; 205 services.jack.loopback.session = '' 206 alsa_in -j cloop -dcloop & 207 alsa_out -j ploop -dploop & 208 while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done 209 jack_connect cloop:capture_1 system:playback_1 210 jack_connect cloop:capture_2 system:playback_2 211 jack_connect system:capture_1 ploop:playback_1 212 jack_connect system:capture_2 ploop:playback_2 213 ''; 214 215 assertions = [ 216 { 217 assertion = !(cfg.alsa.enable && cfg.loopback.enable); 218 message = "For JACK both alsa and loopback options shouldn't be used at the same time."; 219 } 220 ]; 221 222 users.users.jackaudio = { 223 group = "jackaudio"; 224 extraGroups = [ "audio" ]; 225 description = "JACK Audio system service user"; 226 isSystemUser = true; 227 }; 228 # https://jackaudio.org/faq/linux_rt_config.html 229 security.pam.loginLimits = [ 230 { 231 domain = "@jackaudio"; 232 type = "-"; 233 item = "rtprio"; 234 value = "99"; 235 } 236 { 237 domain = "@jackaudio"; 238 type = "-"; 239 item = "memlock"; 240 value = "unlimited"; 241 } 242 ]; 243 users.groups.jackaudio = { }; 244 245 environment = { 246 systemPackages = [ cfg.jackd.package ]; 247 etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsa-plugins}/etc/alsa/conf.d/50-jack.conf"; 248 variables.JACK_PROMISCUOUS_SERVER = "jackaudio"; 249 }; 250 251 services.udev.extraRules = '' 252 ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service" 253 ''; 254 255 systemd.services.jack = { 256 description = "JACK Audio Connection Kit"; 257 serviceConfig = 258 { 259 User = "jackaudio"; 260 SupplementaryGroups = lib.optional ( 261 config.services.pulseaudio.enable && !config.services.pulseaudio.systemWide 262 ) "users"; 263 ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}"; 264 LimitRTPRIO = 99; 265 LimitMEMLOCK = "infinity"; 266 } 267 // lib.optionalAttrs umaskNeeded { 268 UMask = "007"; 269 }; 270 path = [ cfg.jackd.package ]; 271 environment = { 272 JACK_PROMISCUOUS_SERVER = "jackaudio"; 273 JACK_NO_AUDIO_RESERVATION = "1"; 274 }; 275 restartIfChanged = false; 276 }; 277 systemd.services.jack-session = { 278 description = "JACK session"; 279 script = '' 280 ${pkgs.jack-example-tools}/bin/jack_wait -w 281 ${cfg.jackd.session} 282 ${lib.optionalString cfg.loopback.enable cfg.loopback.session} 283 ''; 284 serviceConfig = { 285 RemainAfterExit = true; 286 User = "jackaudio"; 287 StateDirectory = "jack"; 288 LimitRTPRIO = 99; 289 LimitMEMLOCK = "infinity"; 290 }; 291 path = [ cfg.jackd.package ]; 292 environment = { 293 JACK_PROMISCUOUS_SERVER = "jackaudio"; 294 HOME = "/var/lib/jack"; 295 }; 296 wantedBy = [ "jack.service" ]; 297 partOf = [ "jack.service" ]; 298 after = [ "jack.service" ]; 299 restartIfChanged = false; 300 }; 301 }) 302 303 ]; 304 305 meta.maintainers = [ ]; 306}