{ config, lib, pkgs, ... }: with lib; let cfg = config.services.trivmix; trivmix = pkgs.haskellPackages.callPackage ./trivmix {}; mixerModule = { options = { connectIn = mkOption { type = with types; coercedTo str singleton (listOf str); default = []; description = "Ports to connect mixer input to"; }; connectOut = mkOption { type = with types; coercedTo str singleton (listOf str); default = []; description = "Ports to connect mixer output to"; }; onConnect = mkOption { type = types.lines; default = ""; description = "Additional shell commands to run after connections are set up"; }; adjustable = mkOption { type = types.bool; default = true; description = "Should the volume on this mixer be adjustable?"; }; group = mkOption { type = types.nullOr types.str; default = null; description = "Volumes of mixers that share a group name are synchronised"; }; balance = mkOption { type = types.nullOr types.str; default = null; description = "Volumes of mixers are multiplied with their balances, this is the name of the file that contains the factor for this mixer"; }; initialBalance = mkOption { type = types.str; default = "1"; description = "Initial balance"; }; initial = mkOption { type = types.str; default = "1"; description = "Initial volume"; }; }; }; service = name: mixerCfg: with mixerCfg; let connectScript = pkgs.writeScript "connect" '' #!${pkgs.stdenv.shell} ${concatMapStringsSep "\n" (port: "jack_connect ${port} $1") connectIn} ${concatMapStringsSep "\n" (port: "jack_connect $2 ${port}") connectOut} ${onConnect} ''; connect = (connectOut != []) || (connectIn != []) || (onConnect != []); mixerDeps = filter (x: any (hasPrefix (x + ":")) connectIn || any (hasPrefix (x + ":")) connectOut) (attrNames cfg); trivmixArgs = let dirName = if isNull group then name else group; in [ "--fps" cfg.fps "--interval" cfg.interval "--client" name "--level" initial "--initial-balance" initialBalance ] ++ optionals connect ["--run" connectScript] ++ optionals (adjustable && (! isNull balance)) ["--balance" "/dev/shm/mix/${dirName}/${balance}"] ++ optional (adjustable && isNull group) "/dev/shm/mix/${name}/level" ++ optional (! isNull group) "/dev/shm/mix/${group}/level"; in { wantedBy = [ "sound.target" ]; bindsTo = [ "jack.service" ] ++ map (n: n + ".service") mixerDeps; after = [ "jack.service" ] ++ map (n: n + ".service") mixerDeps; path = with pkgs; [ jack2Full trivmix ]; serviceConfig = { Type = "notify"; User = "jack"; Group = "audio"; Nice = "-10"; LimitRTPRIO = "95:95"; LimitMEMLOCK = "infinity"; WatchdogSec = "2"; }; script = '' exec -a trivmix -- trivmix ${escapeShellArgs trivmixArgs} ''; }; in { options.services.trivmix = { mixers = mkOption { type = types.attrsOf (types.submodule mixerModule); default = {}; description = "Definition of mixers"; }; fps = mkOption { type = types.str; default = "200"; }; interval = mkOption { type = types.str; default = "0.2"; }; }; config = mkIf (cfg.mixers != {}) { environment.systemPackages = [ trivmix ]; systemd.services = mapAttrs service cfg.mixers; }; }