diff --git a/hosts/nixos-pc/default.nix b/hosts/nixos-pc/default.nix index d594649..649aa77 100644 --- a/hosts/nixos-pc/default.nix +++ b/hosts/nixos-pc/default.nix @@ -8,6 +8,7 @@ ../../modules/nvidia.nix ../../modules/gaming.nix ../../modules/tailscale.nix + ../../modules/noise-suppression.nix ]; networking.hostName = "nixos-pc"; diff --git a/modules/noise-suppression.nix b/modules/noise-suppression.nix new file mode 100644 index 0000000..052e0fb --- /dev/null +++ b/modules/noise-suppression.nix @@ -0,0 +1,74 @@ +{ pkgs, ... }: + +# App-independent microphone noise suppression via PipeWire + RNNoise. +# +# Replaces Discord's Krisp (which can't be enabled on current nixpkgs Discord: +# the binary is patchelf'd, and even after bypassing Krisp's signature check the +# newer "THz SDK" stage fails with KRISP_INIT_ERROR_GLOBAL_INIT). Instead we run +# RNNoise as a PipeWire filter-chain that exposes a cleaned virtual microphone. +# +# This works in EVERY app (Discord, browser, OBS, games), survives Discord +# updates, and touches no DRM. In each app's input settings, pick the source +# named "Noise Canceling source" instead of the raw mic. + +{ + # LADSPA plugin providing noise_suppressor_mono/stereo (werman RNNoise). + environment.systemPackages = [ pkgs.rnnoise-plugin ]; + + services.pipewire.extraConfig.pipewire."99-input-denoising" = { + "context.modules" = [ + { + name = "libpipewire-module-filter-chain"; + args = { + "node.description" = "Noise Canceling source"; + "media.name" = "Noise Canceling source"; + "filter.graph" = { + nodes = [ + { + type = "ladspa"; + name = "rnnoise"; + plugin = "${pkgs.rnnoise-plugin}/lib/ladspa/librnnoise_ladspa.so"; + label = "noise_suppressor_mono"; + control = { + # Higher threshold = more aggressive gating of non-voice. + # 50% is a balanced starting point; raise toward 90–95 if a + # noisy background still leaks through, lower if speech clips. + "VAD Threshold (%)" = 50.0; + "VAD Grace Period (ms)" = 200.0; + "Retroactive VAD Grace (ms)" = 0.0; + }; + } + { + # Makeup gain baked into the graph so the source volume is + # reproducible instead of living only in WirePlumber's runtime + # state. Mult = 1.53 matches the 153% level set in the mixer. + type = "builtin"; + name = "gain"; + label = "linear"; + control = { + "Mult" = 1.53; + "Add" = 0.0; + }; + } + ]; + links = [ + { output = "rnnoise:Output"; input = "gain:In"; } + ]; + }; + # The virtual mic apps select. media.class = Audio/Source makes it + # appear as a capture device throughout the desktop. + "capture.props" = { + "node.name" = "capture.rnnoise_source"; + "node.passive" = true; + "audio.rate" = 48000; + }; + "playback.props" = { + "node.name" = "rnnoise_source"; + "media.class" = "Audio/Source"; + "audio.rate" = 48000; + }; + }; + } + ]; + }; +}