238 lines
6.4 KiB
Nix
238 lines
6.4 KiB
Nix
{
|
|
pkgs,
|
|
config,
|
|
lib,
|
|
...
|
|
}:
|
|
with lib;
|
|
let
|
|
cfg = config.xyno.desktop.audio;
|
|
json = pkgs.formats.json { };
|
|
toFloat = builtins.fromJSON;
|
|
readFilterTxt =
|
|
text:
|
|
let
|
|
lines = splitString "\n" text;
|
|
consumeLine =
|
|
line:
|
|
let
|
|
parts = splitString " " line;
|
|
content = foldl (
|
|
props: str:
|
|
if props.lastNode == "Filter" then
|
|
props
|
|
// {
|
|
nr = toInt (removeSuffix ":" str);
|
|
lastNode = str;
|
|
}
|
|
else if props.lastNode == "Preamp:" then
|
|
props
|
|
// {
|
|
nr = 0;
|
|
gain = toFloat str;
|
|
label = "HSC";
|
|
lastNode = str;
|
|
freq = 0;
|
|
q = 1.0;
|
|
}
|
|
else if props.lastNode == "ON" then
|
|
props
|
|
// {
|
|
label = str;
|
|
lastNode = str;
|
|
}
|
|
else if props.lastNode == "Fc" then
|
|
props
|
|
// {
|
|
freq = toInt str;
|
|
lastNode = str;
|
|
}
|
|
else if props.lastNode == "Gain" then
|
|
props
|
|
// {
|
|
gain = toFloat str;
|
|
lastNode = str;
|
|
}
|
|
else if props.lastNode == "Q" then
|
|
props
|
|
// {
|
|
q = toFloat str;
|
|
lastNode = str;
|
|
}
|
|
else
|
|
|
|
props // { lastNode = str; }
|
|
) { lastNode = null; } parts;
|
|
in
|
|
content;
|
|
in
|
|
filter (x: x ? nr) (map consumeLine lines);
|
|
genFilterGraph = filters: {
|
|
nodes = (
|
|
map (filter: {
|
|
type = "builtin";
|
|
name = "eq_band_${toString (filter.nr + 1)}";
|
|
label =
|
|
if filter.label == "HSC" then
|
|
"bq_highshelf"
|
|
else if filter.label == "LSC" then
|
|
"bq_lowshelf"
|
|
else
|
|
"bq_peaking";
|
|
control = {
|
|
Freq = filter.freq;
|
|
Q = filter.q;
|
|
Gain = filter.gain;
|
|
};
|
|
|
|
}) filters
|
|
);
|
|
links = map (x: {
|
|
output = "eq_band_${toString x}:Out";
|
|
input = "eq_band_${toString (x + 1)}:In";
|
|
}) (range 1 ((builtins.length filters) - 1));
|
|
};
|
|
genfilterChain =
|
|
cfg':
|
|
json.generate "filter-chain.json" (
|
|
recursiveUpdate {
|
|
"node.description" = cfg'.displayName;
|
|
"media.name" = cfg'.displayName;
|
|
"filter.graph" = genFilterGraph (readFilterTxt cfg'.filterText);
|
|
"audio.channels" = 2;
|
|
"audio.position" = [
|
|
"FL"
|
|
"FR"
|
|
];
|
|
"capture.props" = {
|
|
"node.name" = "effect_input.${cfg'.internalName}";
|
|
"media.class" = "Audio/Sink";
|
|
};
|
|
"playback.props" = {
|
|
"node.name" = "effect_output.${cfg'.internalName}";
|
|
"node.passive" = true;
|
|
"target.object" = cfg'.hwAddr;
|
|
};
|
|
} cfg'.extraConfig
|
|
);
|
|
eqPkg =
|
|
cfg':
|
|
(pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/99-${cfg'.internalName}.conf" ''
|
|
node.software-dsp.rules = [
|
|
{
|
|
matches = [{ node.name = "${cfg'.hwAddr}" }]
|
|
actions = {
|
|
create-filter = {
|
|
filter-path = "${genfilterChain cfg'}"
|
|
hide-parent = ${boolToString cfg'.hideParent}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
wireplumber.profiles = {
|
|
main = { node.software-dsp = "required" }
|
|
}
|
|
'')
|
|
// {
|
|
passthru.requiredLv2Packages = with pkgs; [
|
|
lsp-plugins
|
|
bankstown-lv2
|
|
];
|
|
|
|
};
|
|
|
|
eqSubmodule =
|
|
{ name, ... }:
|
|
{
|
|
options = {
|
|
internalName = mkOption {
|
|
type = types.str;
|
|
default = name;
|
|
internal = true;
|
|
};
|
|
displayName = mkOption { type = types.str; };
|
|
hwAddr = mkOption { type = types.str; };
|
|
filterText = mkOption { type = types.str; };
|
|
hideParent = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
extraConfig = mkOption {
|
|
type = types.attrsOf types.anything;
|
|
default = { };
|
|
};
|
|
};
|
|
};
|
|
|
|
in
|
|
{
|
|
options.xyno.desktop.audio.enable = mkEnableOption "enable pipewire and stuff";
|
|
options.xyno.desktop.audio.eq = mkOption {
|
|
type = types.attrsOf (types.submodule eqSubmodule);
|
|
default = { };
|
|
};
|
|
config = mkIf cfg.enable {
|
|
# https://github.com/jaakkopasanen/AutoEq/blob/master/results/oratory1990/in-ear/Shure%20SE215/Shure%20SE215%20ParametricEQ.txt
|
|
xyno.desktop.audio.eq.se215 = {
|
|
displayName = "Shure SE215";
|
|
hwAddr = "alsa_output.usb-Synaptics_CX31993_384Khz_HIFI_AUDIO-00.analog-stereo";
|
|
# hideParent = false;
|
|
extraConfig."capture.props"."device.icon-name" = "audio-headphones";
|
|
filterText = ''
|
|
Preamp: -5.7 dB
|
|
Filter 1: ON LSC Fc 105 Hz Gain 0.3 dB Q 0.70
|
|
Filter 2: ON PK Fc 174 Hz Gain -4.6 dB Q 0.68
|
|
Filter 3: ON PK Fc 811 Hz Gain 3.1 dB Q 1.42
|
|
Filter 4: ON PK Fc 3430 Hz Gain 3.8 dB Q 4.37
|
|
Filter 5: ON PK Fc 6943 Hz Gain 5.4 dB Q 2.76
|
|
Filter 6: ON HSC Fc 10000 Hz Gain 3.8 dB Q 0.70
|
|
Filter 7: ON PK Fc 5725 Hz Gain 8.1 dB Q 1.13
|
|
Filter 8: ON PK Fc 5056 Hz Gain -10.9 dB Q 2.93
|
|
Filter 9: ON PK Fc 7269 Hz Gain -6.3 dB Q 3.47
|
|
Filter 10: ON PK Fc 2278 Hz Gain -2.1 dB Q 2.94
|
|
'';
|
|
};
|
|
services.pipewire = {
|
|
enable = true;
|
|
# raopOpenFirewall = true; # airplay
|
|
pulse.enable = true;
|
|
extraConfig.pipewire = {
|
|
"9-clock-allow-higher" = {
|
|
"context.properties" = {
|
|
"default.clock.allowed-rates" = [
|
|
"44100"
|
|
"48000"
|
|
"88200"
|
|
"96000"
|
|
"176400"
|
|
"192000"
|
|
];
|
|
};
|
|
};
|
|
# "10-raop-discover" = {
|
|
# "context.modules" = [
|
|
# {
|
|
# name = "libpipewire-module-raop-discover";
|
|
# args = { };
|
|
# }
|
|
# ];
|
|
# };
|
|
};
|
|
extraConfig.pipewire-pulse = {
|
|
"10-zeroconf" = {
|
|
"pulse.cmd" = [
|
|
{
|
|
cmd = "load-module";
|
|
args = "module-zeroconf-discover";
|
|
}
|
|
];
|
|
};
|
|
};
|
|
wireplumber.extraConfig."98-bluetooth"."wireplumber.settings"."bluetooth.autoswitch-to-headset-profile" =
|
|
false;
|
|
wireplumber.configPackages = mapAttrsToList (n: v: eqPkg v) cfg.eq;
|
|
};
|
|
|
|
};
|
|
}
|