134 lines
5.3 KiB
Nix
134 lines
5.3 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.services.motu-m4-combined;
|
|
in
|
|
{
|
|
options.services.motu-m4-combined = {
|
|
enable = lib.mkEnableOption "MOTU M4 combined mono input";
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "User to run the setup service as";
|
|
};
|
|
|
|
devicePrefix = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "alsa_input.usb-MOTU_M4_M4MA0824DV-00.HiFi";
|
|
description = "Device prefix for MOTU M4 inputs";
|
|
};
|
|
|
|
virtualSourceName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "motu_m4_combined";
|
|
description = "Name of the virtual combined source";
|
|
};
|
|
|
|
virtualSourceDescription = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "MOTU M4 All Inputs (Mono)";
|
|
description = "Human-readable description of the virtual source";
|
|
};
|
|
|
|
latencyMs = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 20;
|
|
description = "Loopback latency in milliseconds";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
# Create loopbacks and remap source via systemd user service
|
|
systemd.user.services.motu-m4-combined-setup = {
|
|
description = "Setup MOTU M4 combined mono input loopbacks";
|
|
after = [ "pipewire-pulse.service" ];
|
|
requires = [ "pipewire-pulse.service" ];
|
|
wantedBy = [ "pipewire-pulse.service" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
|
|
ExecStart = pkgs.writeShellScript "motu-m4-setup" ''
|
|
# Wait for pipewire-pulse to be fully ready
|
|
sleep 2
|
|
|
|
# Create null sink for mixing (internal, not visible in normal output lists)
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-null-sink \
|
|
sink_name=motu_mixer \
|
|
sink_properties=device.description="MOTU_Mixer_(Internal)" \
|
|
channels=1 \
|
|
channel_map=mono
|
|
|
|
# Create loopbacks from each M4 input to the mixer (fail gracefully if M4 not connected)
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-loopback \
|
|
source=${cfg.devicePrefix}__Mic1__source \
|
|
sink=motu_mixer channels=1 latency_msec=${toString cfg.latencyMs} \
|
|
source_dont_move=true sink_dont_move=true || true
|
|
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-loopback \
|
|
source=${cfg.devicePrefix}__Mic2__source \
|
|
sink=motu_mixer channels=1 latency_msec=${toString cfg.latencyMs} \
|
|
source_dont_move=true sink_dont_move=true || true
|
|
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-loopback \
|
|
source=${cfg.devicePrefix}__Line3__source \
|
|
sink=motu_mixer channels=1 latency_msec=${toString cfg.latencyMs} \
|
|
source_dont_move=true sink_dont_move=true || true
|
|
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-loopback \
|
|
source=${cfg.devicePrefix}__Line4__source \
|
|
sink=motu_mixer channels=1 latency_msec=${toString cfg.latencyMs} \
|
|
source_dont_move=true sink_dont_move=true || true
|
|
|
|
# Create the virtual source from the mixer monitor
|
|
${pkgs.pulseaudio}/bin/pactl load-module module-remap-source \
|
|
master=motu_mixer.monitor \
|
|
source_name=${cfg.virtualSourceName} \
|
|
source_properties=device.description="${cfg.virtualSourceDescription}" \
|
|
channels=1 \
|
|
channel_map=mono
|
|
'';
|
|
|
|
# Clean up modules on service stop/restart
|
|
ExecStop = pkgs.writeShellScript "motu-m4-teardown" ''
|
|
# Unload loopback modules connected to motu_mixer
|
|
${pkgs.pulseaudio}/bin/pactl list modules short | \
|
|
${pkgs.gnugrep}/bin/grep "module-loopback.*sink=motu_mixer" | \
|
|
${pkgs.coreutils}/bin/cut -f1 | \
|
|
while read id; do
|
|
${pkgs.pulseaudio}/bin/pactl unload-module "$id" 2>/dev/null || true
|
|
done
|
|
|
|
# Unload the remap source module
|
|
${pkgs.pulseaudio}/bin/pactl list modules short | \
|
|
${pkgs.gnugrep}/bin/grep "module-remap-source.*source_name=${cfg.virtualSourceName}" | \
|
|
${pkgs.coreutils}/bin/cut -f1 | \
|
|
while read id; do
|
|
${pkgs.pulseaudio}/bin/pactl unload-module "$id" 2>/dev/null || true
|
|
done
|
|
|
|
# Unload the null sink module
|
|
${pkgs.pulseaudio}/bin/pactl list modules short | \
|
|
${pkgs.gnugrep}/bin/grep "module-null-sink.*sink_name=motu_mixer" | \
|
|
${pkgs.coreutils}/bin/cut -f1 | \
|
|
while read id; do
|
|
${pkgs.pulseaudio}/bin/pactl unload-module "$id" 2>/dev/null || true
|
|
done
|
|
'';
|
|
|
|
# Failsafe: ensure cleanup happens even if ExecStop fails
|
|
ExecStopPost = pkgs.writeShellScript "motu-m4-cleanup-failsafe" ''
|
|
# Cleanup any remaining MOTU-related modules (loopbacks, remap source, and null sink)
|
|
${pkgs.pulseaudio}/bin/pactl list modules short | \
|
|
${pkgs.gnugrep}/bin/grep -E "module-loopback.*motu_mixer|module-remap-source.*${cfg.virtualSourceName}|module-null-sink.*motu_mixer" | \
|
|
${pkgs.coreutils}/bin/cut -f1 | \
|
|
while read id; do
|
|
${pkgs.pulseaudio}/bin/pactl unload-module "$id" 2>/dev/null || true
|
|
done
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
}
|