mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-24 18:16:21 +03:00
459 lines
14 KiB
Nix
459 lines
14 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.hardware.alsa;
|
|
|
|
quote = x: ''"${lib.escape [ "\"" ] x}"'';
|
|
|
|
alsactl = lib.getExe' pkgs.alsa-utils "alsactl";
|
|
|
|
# Creates a volume control
|
|
mkControl = name: opts: ''
|
|
pcm.${name} {
|
|
type softvol
|
|
slave.pcm ${quote opts.device}
|
|
control.name ${quote (if opts.name != null then opts.name else name)}
|
|
control.card ${quote opts.card}
|
|
max_dB ${toString opts.maxVolume}
|
|
}
|
|
'';
|
|
|
|
# modprobe.conf for naming sound cards
|
|
cardsConfig =
|
|
let
|
|
# Reverse the mapping from card name→driver to card driver→name
|
|
drivers = lib.unique (lib.mapAttrsToList (n: v: v.driver) cfg.cardAliases);
|
|
options = lib.forEach drivers (
|
|
drv:
|
|
let
|
|
byDriver = lib.filterAttrs (n: v: v.driver == drv);
|
|
ids = lib.mapAttrs (n: v: v.id) (byDriver cfg.cardAliases);
|
|
in
|
|
{
|
|
driver = drv;
|
|
names = lib.attrNames ids;
|
|
ids = lib.attrValues ids;
|
|
}
|
|
);
|
|
toList = x: lib.concatStringsSep "," (map toString x);
|
|
in
|
|
lib.forEach options (i: "options ${i.driver} index=${toList i.ids} id=${toList i.names}");
|
|
|
|
defaultDeviceVars = {
|
|
"ALSA_AUDIO_OUT" = cfg.defaultDevice.playback;
|
|
"ALSA_AUDIO_IN" = cfg.defaultDevice.capture;
|
|
};
|
|
|
|
in
|
|
|
|
{
|
|
imports = [
|
|
(lib.mkRemovedOptionModule [ "sound" "enable" ] ''
|
|
The option was heavily overloaded and can be removed from most configurations.
|
|
To specifically configure the user space part of ALSA, see `hardware.alsa`.
|
|
'')
|
|
(lib.mkRemovedOptionModule [ "sound" "mediaKeys" ] ''
|
|
The media keys can be configured with any hotkey daemon (that better
|
|
integrates with your desktop setup). To continue using the actkbd daemon
|
|
(which was used up to NixOS 24.05), add these lines to your configuration:
|
|
|
|
services.actkbd.enable = true;
|
|
services.actkbd.bindings = [
|
|
# Mute
|
|
{ keys = [ 113 ]; events = [ "key" ];
|
|
command = "''${pkgs.alsa-utils}/bin/amixer -q set Master toggle";
|
|
}
|
|
# Volume down
|
|
{ keys = [ 114 ]; events = [ "key" "rep" ];
|
|
command = "''${pkgs.alsa-utils}/bin/amixer -q set Master 1- unmute";
|
|
}
|
|
# Volume up
|
|
{ keys = [ 115 ]; events = [ "key" "rep" ];
|
|
command = "''${pkgs.alsa-utils}/bin/amixer -q set Master 1+ unmute";
|
|
}
|
|
# Mic Mute
|
|
{ keys = [ 190 ]; events = [ "key" ];
|
|
command = "''${pkgs.alsa-utils}/bin/amixer -q set Capture toggle";
|
|
}
|
|
];
|
|
'')
|
|
(lib.mkRenamedOptionModule
|
|
[ "sound" "enableOSSEmulation" ]
|
|
[ "hardware" "alsa" "enableOSSEmulation" ]
|
|
)
|
|
(lib.mkRenamedOptionModule [ "sound" "extraConfig" ] [ "hardware" "alsa" "config" ])
|
|
];
|
|
|
|
options.hardware.alsa = {
|
|
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to set up the user space part of the Advanced Linux Sound Architecture (ALSA)
|
|
|
|
::: {.warning}
|
|
Enable this option only if you want to use ALSA as your main sound system,
|
|
not if you're using a sound server (e.g. PulseAudio or Pipewire).
|
|
:::
|
|
'';
|
|
};
|
|
|
|
enableOSSEmulation = lib.mkEnableOption "the OSS emulation";
|
|
|
|
enableRecorder = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to set up a loopback device that continuously records and
|
|
allows to play back audio from the computer.
|
|
|
|
The loopback device is named `pcm.recorder`, audio can be saved
|
|
by capturing from this device as with any microphone.
|
|
|
|
::: {.note}
|
|
By default the output is duplicated to the recorder assuming stereo
|
|
audio, for a more complex layout you have to override the pcm.splitter
|
|
device using `hardware.alsa.config`.
|
|
See the generated /etc/asound.conf for its definition.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
defaultDevice.playback = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
example = "dmix:CARD=1,DEV=0";
|
|
description = ''
|
|
The default playback device.
|
|
Leave empty to let ALSA pick the default automatically.
|
|
|
|
::: {.note}
|
|
The device can be changed at runtime by setting the ALSA_AUDIO_OUT
|
|
environment variables (but only before starting a program).
|
|
:::
|
|
'';
|
|
};
|
|
|
|
defaultDevice.capture = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
example = "dsnoop:CARD=0,DEV=2";
|
|
description = ''
|
|
The default capture device (i.e. microphone).
|
|
Leave empty to let ALSA pick the default automatically.
|
|
|
|
::: {.note}
|
|
The device can be changed at runtime by setting the ALSA_AUDIO_IN
|
|
environment variables (but only before starting a program).
|
|
:::
|
|
'';
|
|
};
|
|
|
|
controls = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.submodule ({
|
|
options.name = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
Name of the control, as it appears in `alsamixer`.
|
|
If null it will be the same as the softvol device name.
|
|
'';
|
|
};
|
|
options.device = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "default";
|
|
description = ''
|
|
Name of the PCM device to control (slave).
|
|
'';
|
|
};
|
|
options.card = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "default";
|
|
description = ''
|
|
Name of the PCM card to control (slave).
|
|
'';
|
|
};
|
|
options.maxVolume = lib.mkOption {
|
|
type = lib.types.float;
|
|
default = 0.0;
|
|
description = ''
|
|
The maximum volume in dB.
|
|
'';
|
|
};
|
|
})
|
|
);
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
firefox = { device = "front"; maxVolume = -25.0; };
|
|
mpv = { device = "front"; maxVolume = -25.0; };
|
|
# and run programs with `env ALSA_AUDIO_OUT=<name>`
|
|
}
|
|
'';
|
|
description = ''
|
|
Virtual volume controls (softvols) to add to a sound card.
|
|
These can be used to control the volume of specific applications
|
|
or a digital output device (HDMI video card).
|
|
'';
|
|
};
|
|
|
|
cardAliases = lib.mkOption {
|
|
type = lib.types.attrsOf (
|
|
lib.types.submodule ({
|
|
options.driver = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Name of the kernel module that provides the card.
|
|
'';
|
|
};
|
|
options.id = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = "default";
|
|
description = ''
|
|
The ID of the sound card
|
|
'';
|
|
};
|
|
})
|
|
);
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
soundchip = { driver = "snd_intel_hda"; id = 0; };
|
|
videocard = { driver = "snd_intel_hda"; id = 1; };
|
|
usb = { driver = "snd_usb_audio"; id = 2; };
|
|
}
|
|
'';
|
|
description = ''
|
|
Assign custom names and reorder the sound cards.
|
|
|
|
::: {.note}
|
|
You can find the card ids by looking at `/proc/asound/cards`.
|
|
:::
|
|
'';
|
|
};
|
|
|
|
deviceAliases = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
hdmi1 = "hw:CARD=videocard,DEV=5";
|
|
hdmi2 = "hw:CARD=videocard,DEV=6";
|
|
}
|
|
'';
|
|
description = ''
|
|
Assign custom names to sound cards.
|
|
'';
|
|
};
|
|
|
|
config = lib.mkOption {
|
|
type = lib.types.lines;
|
|
default = "";
|
|
example = lib.literalExpression ''
|
|
# Send audio to a remote host via SSH
|
|
pcm.remote {
|
|
@args [ HOSTNAME ]
|
|
@args.HOSTNAME { type string }
|
|
type file
|
|
format raw
|
|
slave.pcm pcm.null
|
|
file {
|
|
@func concat
|
|
strings [
|
|
"| ''${lib.getExec pkgs.openssh} -C "
|
|
$HOSTNAME
|
|
" aplay -f %f -c %c -r %r -"
|
|
]
|
|
}
|
|
}
|
|
'';
|
|
description = ''
|
|
The content of the system-wide ALSA configuration (/etc/asound.conf).
|
|
|
|
Documentation of the configuration language and examples can be found
|
|
in the unofficial ALSA wiki: https://alsa.opensrc.org/Asoundrc
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
options.hardware.alsa.enablePersistence = lib.mkOption {
|
|
type = lib.types.bool;
|
|
defaultText = lib.literalExpression "config.hardware.alsa.enable";
|
|
default = config.hardware.alsa.enable;
|
|
description = ''
|
|
Whether to enable ALSA sound card state saving on shutdown.
|
|
This is generally not necessary if you're using an external sound server.
|
|
'';
|
|
};
|
|
|
|
config = lib.mkMerge [
|
|
|
|
(lib.mkIf cfg.enable {
|
|
# Disable sound servers enabled by default and,
|
|
# if the user enabled one manually, cause a conflict.
|
|
services.pipewire.enable = false;
|
|
services.pulseaudio.enable = false;
|
|
|
|
hardware.alsa.config =
|
|
let
|
|
conf = [
|
|
''
|
|
pcm.!default fromenv
|
|
|
|
# Read the capture and playback device from
|
|
# the ALSA_AUDIO_IN, ALSA_AUDIO_OUT variables
|
|
pcm.fromenv {
|
|
type asym
|
|
playback.pcm {
|
|
type plug
|
|
slave.pcm {
|
|
@func getenv
|
|
vars [ ALSA_AUDIO_OUT ]
|
|
default pcm.sysdefault
|
|
}
|
|
}
|
|
capture.pcm {
|
|
type plug
|
|
slave.pcm {
|
|
@func getenv
|
|
vars [ ALSA_AUDIO_IN ]
|
|
default pcm.sysdefault
|
|
}
|
|
}
|
|
}
|
|
''
|
|
(lib.optional cfg.enableRecorder ''
|
|
pcm.!default "splitter:fromenv,recorder"
|
|
|
|
# Send audio to two stereo devices
|
|
pcm.splitter {
|
|
@args [ A B ]
|
|
@args.A.type string
|
|
@args.B.type string
|
|
type asym
|
|
playback.pcm {
|
|
type plug
|
|
route_policy "duplicate"
|
|
slave.pcm {
|
|
type multi
|
|
slaves.a.pcm $A
|
|
slaves.b.pcm $B
|
|
slaves.a.channels 2
|
|
slaves.b.channels 2
|
|
bindings [
|
|
{ slave a channel 0 }
|
|
{ slave a channel 1 }
|
|
{ slave b channel 0 }
|
|
{ slave b channel 1 }
|
|
]
|
|
}
|
|
}
|
|
capture.pcm $A
|
|
}
|
|
|
|
# Device which records and plays back audio
|
|
pcm.recorder {
|
|
type asym
|
|
capture.pcm {
|
|
type dsnoop
|
|
ipc_key 9165218
|
|
ipc_perm 0666
|
|
slave.pcm "hw:loopback,1,0"
|
|
slave.period_size 1024
|
|
slave.buffer_size 8192
|
|
}
|
|
playback.pcm {
|
|
type dmix
|
|
ipc_key 6181923
|
|
ipc_perm 0666
|
|
slave.pcm "hw:loopback,0,0"
|
|
slave.period_size 1024
|
|
slave.buffer_size 8192
|
|
}
|
|
}
|
|
'')
|
|
(lib.mapAttrsToList mkControl cfg.controls)
|
|
(lib.mapAttrsToList (n: v: "pcm.${n} ${quote v}") cfg.deviceAliases)
|
|
];
|
|
in
|
|
lib.mkBefore (lib.concatStringsSep "\n" (lib.flatten conf));
|
|
|
|
hardware.alsa.cardAliases = lib.mkIf cfg.enableRecorder {
|
|
loopback.driver = "snd_aloop";
|
|
loopback.id = 2;
|
|
};
|
|
|
|
# Set default PCM devices
|
|
environment.sessionVariables = defaultDeviceVars;
|
|
systemd.globalEnvironment = defaultDeviceVars;
|
|
|
|
environment.etc."asound.conf".text = cfg.config;
|
|
|
|
boot.kernelModules =
|
|
[ ]
|
|
++ lib.optionals cfg.enableOSSEmulation [
|
|
"snd_pcm_oss"
|
|
"snd_mixer_oss"
|
|
]
|
|
++ lib.optionals cfg.enableRecorder [ "snd_aloop" ];
|
|
|
|
# Assign names to the sound cards
|
|
boot.extraModprobeConfig = lib.concatStringsSep "\n" cardsConfig;
|
|
|
|
# Provide alsamixer, aplay, arecord, etc.
|
|
environment.systemPackages = [ pkgs.alsa-utils ];
|
|
})
|
|
|
|
(lib.mkIf config.hardware.alsa.enablePersistence {
|
|
|
|
# Install udev rules for restoring card settings on boot
|
|
services.udev.extraRules = ''
|
|
ACTION=="add", SUBSYSTEM=="sound", KERNEL=="controlC*", KERNELS!="card*", GOTO="alsa_restore_go"
|
|
GOTO="alsa_restore_end"
|
|
|
|
LABEL="alsa_restore_go"
|
|
TEST!="/etc/alsa/state-daemon.conf", RUN+="${alsactl} restore -gU $attr{device/number}"
|
|
TEST=="/etc/alsa/state-daemon.conf", RUN+="${alsactl} nrestore -gU $attr{device/number}"
|
|
LABEL="alsa_restore_end"
|
|
'';
|
|
|
|
# Service to store/restore the sound card settings
|
|
systemd.services.alsa-store = {
|
|
description = "Store Sound Card State";
|
|
wantedBy = [ "multi-user.target" ];
|
|
restartIfChanged = false;
|
|
unitConfig = {
|
|
RequiresMountsFor = "/var/lib/alsa";
|
|
ConditionVirtualization = "!systemd-nspawn";
|
|
};
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
StateDirectory = "alsa";
|
|
# Note: the service should never be restated, otherwise any
|
|
# setting changed between the last `store` and now will be lost.
|
|
# To prevent NixOS from starting it in case it has failed we
|
|
# expand the exit codes considered successful
|
|
SuccessExitStatus = [
|
|
0
|
|
99
|
|
];
|
|
ExecStart = "${alsactl} restore -gU";
|
|
ExecStop = "${alsactl} store -gU";
|
|
};
|
|
};
|
|
})
|
|
|
|
];
|
|
|
|
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
|
|
|
}
|