From 9c9dcdc1966a82fa1a34c3d4018d4013bc6abb18 Mon Sep 17 00:00:00 2001 From: Geir Okkenhaug Jerstad Date: Wed, 18 Jun 2025 20:57:39 +0200 Subject: [PATCH] Add comprehensive PipeWire audio configuration with noise suppression - Add modules/sound/pipewire.nix with full PipeWire stack - Include RNNoise AI-powered noise suppression - Add EasyEffects with pre-configured presets for mic and speakers - Include multiple GUI applications (pavucontrol, helvum, qpwgraph, pwvucontrol) - Add helper scripts: audio-setup, microphone-test, validate-audio - Optimize for low-latency real-time audio processing - Enable auto-start and desktop integration - Remove duplicate PipeWire configs from hardware-co.nix and users/common.nix - Import sound module through desktop/common.nix for all desktop machines --- machines/congenital-optimist/hardware-co.nix | 6 - modules/desktop/common.nix | 4 + modules/sound/README.md | 273 ++++++++++ modules/sound/audio-desktop-integration.nix | 212 +++++++ modules/sound/easyeffects-presets.nix | 546 +++++++++++++++++++ modules/sound/pipewire.nix | 177 ++++++ modules/sound/validate-audio.sh | 204 +++++++ modules/users/common.nix | 7 - 8 files changed, 1416 insertions(+), 13 deletions(-) create mode 100644 modules/sound/README.md create mode 100644 modules/sound/audio-desktop-integration.nix create mode 100644 modules/sound/easyeffects-presets.nix create mode 100644 modules/sound/pipewire.nix create mode 100755 modules/sound/validate-audio.sh diff --git a/machines/congenital-optimist/hardware-co.nix b/machines/congenital-optimist/hardware-co.nix index 3928085..a50e3a0 100644 --- a/machines/congenital-optimist/hardware-co.nix +++ b/machines/congenital-optimist/hardware-co.nix @@ -19,10 +19,4 @@ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - # Audio system (PipeWire) - services.pipewire = { - enable = true; - alsa.enable = true; - pulse.enable = true; - }; } diff --git a/modules/desktop/common.nix b/modules/desktop/common.nix index f8274ac..dd6d6bc 100644 --- a/modules/desktop/common.nix +++ b/modules/desktop/common.nix @@ -3,6 +3,10 @@ pkgs, ... }: { + imports = [ + ../sound/pipewire.nix + ]; + # Common desktop configuration shared across all environments # XDG Portal configuration for Wayland/X11 compatibility diff --git a/modules/sound/README.md b/modules/sound/README.md new file mode 100644 index 0000000..1e66904 --- /dev/null +++ b/modules/sound/README.md @@ -0,0 +1,273 @@ +# PipeWire with WirePlumber Configuration + +This module provides a comprehensive PipeWire setup with WirePlumber session management, noise suppression, and GUI tools for audio management. + +## Features + +### Core Audio Stack + +- **PipeWire**: Modern audio server with low latency +- **WirePlumber**: Session manager for device management and routing +- **ALSA/PulseAudio/JACK Compatibility**: Works with all major audio APIs +- **Real-time Processing**: RTKit integration for optimal performance + +### Noise Suppression + +- **RNNoise Plugin**: AI-powered noise suppression for microphones +- **EasyEffects Integration**: GUI for managing audio effects +- **Automatic Filter Chain**: Pre-configured noise suppression pipeline + +### GUI Applications Included + +- **EasyEffects**: Modern audio effects processor with noise suppression +- **PulseAudio Volume Control (pavucontrol)**: Volume and device management +- **Helvum**: Graphical PipeWire patchbay for routing +- **qpwgraph**: Qt-based PipeWire graph manager +- **pwvucontrol**: Native PipeWire volume control + +## Quick Start + +### 1. Import the Module + +Add to your NixOS configuration: + +```nix +imports = [ + ./modules/sound/pipewire.nix +]; +``` + +### 2. Rebuild System + +```bash +sudo nixos-rebuild switch +``` + +### 3. Verify Installation + +```bash +# Check if PipeWire is running +systemctl --user status pipewire + +# Launch the audio setup helper +audio-setup + +# Test microphone with noise suppression +microphone-test +``` + +## Using Noise Suppression + +### Method 1: EasyEffects (Recommended) + +1. Launch EasyEffects: `easyeffects` or use the application menu +2. Go to the "Input" tab +3. Load the pre-configured "Microphone_Noise_Suppression" preset +4. Enable the RNNoise effect +5. Adjust the "VAD Threshold" (Voice Activity Detection) as needed + +### Method 2: PipeWire Filter Chain (Automatic) + +The configuration includes an automatic RNNoise filter chain that creates a "Noise Canceling Source" device. This appears as a separate microphone input in audio applications. + +## GUI Applications Usage + +### EasyEffects + +- **Purpose**: Real-time audio effects and noise suppression +- **Launch**: `easyeffects` or from application menu +- **Features**: RNNoise, equalizer, compressor, limiter, gate +- **Auto-start**: Configured to start with desktop session + +### Volume Controls + +- **pavucontrol**: Traditional PulseAudio-style interface +- **pwvucontrol**: Native PipeWire interface +- **Usage**: Control volumes, switch devices, manage streams + +### Audio Routing + +- **Helvum**: Visual patchbay for connecting audio streams +- **qpwgraph**: Advanced graph-based routing interface +- **Usage**: Route audio between applications and devices + +## Command-Line Tools + +### System Status + +```bash +# PipeWire status overview +wpctl status + +# Real-time monitoring +pw-top + +# Inspect audio objects +pw-dump | jq '.' + +# Show metadata +pw-metadata +``` + +### Device Management + +```bash +# List devices +wpctl status + +# Set default sink +wpctl set-default SINK_ID + +# Set volume +wpctl set-volume SOURCE_ID 80% + +# Mute/unmute +wpctl set-mute SOURCE_ID toggle +``` + +### Testing + +```bash +# Test microphone +microphone-test + +# Record and playback test +arecord -d 5 -f cd test.wav && aplay test.wav +``` + +## Configuration Details + +### Audio Quality Settings + +- **Sample Rate**: 48kHz (professional audio standard) +- **Buffer Size**: 1024 samples (balanced latency/stability) +- **Resampling Quality**: High (level 4) +- **Channels**: Stereo support with spatial audio capabilities + +### Noise Suppression Settings + +- **RNNoise VAD Threshold**: 50% (adjustable) +- **VAD Grace Period**: 200ms +- **Noise Reduction**: 80% wet signal +- **Processing**: Real-time with minimal latency + +### Performance Optimizations + +- **Real-time Scheduling**: RTKit enabled +- **Memory Locking**: Enabled for critical processes +- **CPU Affinity**: Configurable per audio thread +- **Quantum Settings**: Optimized for low latency + +## Troubleshooting + +### Common Issues + +#### No Audio Output + +```bash +# Check PipeWire is running +systemctl --user restart pipewire pipewire-pulse wireplumber + +# Check default devices +wpctl status +``` + +#### Microphone Not Working + +```bash +# Test microphone detection +arecord -l + +# Check permissions +groups $USER | grep audio +``` + +#### High CPU Usage + +```bash +# Monitor PipeWire performance +pw-top + +# Check buffer settings +pw-metadata | grep quantum +``` + +#### Noise Suppression Not Working + +1. Verify RNNoise plugin is loaded: `ladspa-ls | grep -i noise` +2. Check EasyEffects preset is loaded +3. Ensure correct input device is selected +4. Adjust VAD threshold in EasyEffects + +### Reset Configuration + +```bash +# Reset user PipeWire configuration +rm -rf ~/.config/pipewire ~/.config/easyeffects +systemctl --user restart pipewire pipewire-pulse wireplumber +``` + +## Advanced Configuration + +### Custom Filter Chains + +Edit `/etc/pipewire/pipewire.conf.d/10-noise-suppression.conf` to modify the RNNoise filter chain parameters. + +### Device-Specific Settings + +Add rules to `/etc/wireplumber/wireplumber.conf.d/51-noise-suppression.conf` for specific audio devices. + +### EasyEffects Presets + +Custom presets are stored in `/etc/easyeffects/` and can be modified or extended. + +## Integration with Applications + +### Discord/Zoom/Teams + +1. Set default microphone to "Noise Canceling Source" in application settings +2. Or use EasyEffects on the regular microphone input +3. Adjust noise gate and compressor settings as needed + +### OBS Studio + +1. Add "Application Audio Capture" source +2. Select the noise-suppressed microphone device +3. Or use OBS's built-in noise suppression with the processed audio + +### Music Production (JACK) + +```bash +# Start JACK mode if needed +pw-jack your-daw-application +``` + +## Updates and Maintenance + +### Updating Configuration + +After modifying the Nix configuration: + +```bash +sudo nixos-rebuild switch +systemctl --user restart pipewire pipewire-pulse wireplumber +``` + +### Monitoring Performance + +Regular checks recommended: + +```bash +# Weekly performance check +pw-top + +# Monthly configuration review +audio-setup +``` + +## See Also + +- [PipeWire Documentation](https://docs.pipewire.org/) +- [WirePlumber Documentation](https://pipewire.pages.freedesktop.org/wireplumber/) +- [EasyEffects Documentation](https://github.com/wwmm/easyeffects) +- [RNNoise Project](https://jmvalin.ca/demo/rnnoise/) diff --git a/modules/sound/audio-desktop-integration.nix b/modules/sound/audio-desktop-integration.nix new file mode 100644 index 0000000..8d7c871 --- /dev/null +++ b/modules/sound/audio-desktop-integration.nix @@ -0,0 +1,212 @@ +{ + config, + lib, + pkgs, + ... +}: { + # Desktop entries for quick audio management access + environment.etc = { + # Desktop entry for EasyEffects + "xdg/autostart/easyeffects.desktop".text = '' + [Desktop Entry] + Name=EasyEffects + Comment=Audio effects for PipeWire applications + Icon=easyeffects + Exec=easyeffects --gapplication-service + Terminal=false + Type=Application + Categories=AudioVideo;Audio; + StartupNotify=true + X-GNOME-Autostart-enabled=true + ''; + + # Custom desktop entry for audio control center + "applications/audio-control-center.desktop".text = '' + [Desktop Entry] + Version=1.0 + Type=Application + Name=Audio Control Center + Comment=Centralized audio management + Icon=audio-volume-high + Categories=AudioVideo;Audio;Settings; + Keywords=audio;sound;volume;pipewire;pulseaudio; + StartupNotify=true + Terminal=false + Exec=sh -c 'if command -v easyeffects >/dev/null 2>&1; then easyeffects; elif command -v pavucontrol >/dev/null 2>&1; then pavucontrol; elif command -v pwvucontrol >/dev/null 2>&1; then pwvucontrol; else helvum; fi' + ''; + }; + + # Create a script for easy audio management + environment.systemPackages = with pkgs; [ + (writeShellScriptBin "audio-setup" '' + #!/bin/bash + + echo "đŸŽĩ Audio Control Center" + echo "======================" + echo "" + echo "Available audio applications:" + echo "" + + if command -v easyeffects >/dev/null 2>&1; then + echo " 1. EasyEffects - Audio effects and noise suppression" + fi + + if command -v pavucontrol >/dev/null 2>&1; then + echo " 2. PulseAudio Volume Control - Volume and device management" + fi + + if command -v pwvucontrol >/dev/null 2>&1; then + echo " 3. PipeWire Volume Control - Native PipeWire control" + fi + + if command -v helvum >/dev/null 2>&1; then + echo " 4. Helvum - PipeWire patchbay" + fi + + if command -v qpwgraph >/dev/null 2>&1; then + echo " 5. qpwgraph - Qt PipeWire graph manager" + fi + + echo "" + echo " 🔧 Audio Tools:" + echo " â€ĸ pw-top - Monitor PipeWire performance" + echo " â€ĸ pw-dump - Inspect PipeWire objects" + echo " â€ĸ pw-metadata - View/set PipeWire metadata" + echo " â€ĸ wpctl - WirePlumber control utility" + echo "" + + echo "Choose an option (1-5) or press Enter for EasyEffects:" + read -r choice + + case $choice in + 1|"") + if command -v easyeffects >/dev/null 2>&1; then + echo "Starting EasyEffects..." + easyeffects + else + echo "EasyEffects not found!" + fi + ;; + 2) + if command -v pavucontrol >/dev/null 2>&1; then + echo "Starting PulseAudio Volume Control..." + pavucontrol + else + echo "pavucontrol not found!" + fi + ;; + 3) + if command -v pwvucontrol >/dev/null 2>&1; then + echo "Starting PipeWire Volume Control..." + pwvucontrol + else + echo "pwvucontrol not found!" + fi + ;; + 4) + if command -v helvum >/dev/null 2>&1; then + echo "Starting Helvum..." + helvum + else + echo "Helvum not found!" + fi + ;; + 5) + if command -v qpwgraph >/dev/null 2>&1; then + echo "Starting qpwgraph..." + qpwgraph + else + echo "qpwgraph not found!" + fi + ;; + *) + echo "Invalid choice" + ;; + esac + '') + + (writeShellScriptBin "microphone-test" '' + #!/bin/bash + + echo "🎤 Microphone Test & Setup" + echo "==========================" + echo "" + + # Check if PipeWire is running + if ! pgrep -x pipewire >/dev/null; then + echo "❌ PipeWire is not running!" + exit 1 + fi + + echo "✅ PipeWire is running" + + # Check for RNNoise + if ls /nix/store/*/lib/ladspa/librnnoise_ladspa.so >/dev/null 2>&1; then + echo "✅ RNNoise plugin is available" + else + echo "âš ī¸ RNNoise plugin not found" + fi + + # List audio sources + echo "" + echo "đŸ“ē Available audio sources:" + wpctl status | grep -A 20 "Audio Sources" + + echo "" + echo "🔊 Available audio sinks:" + wpctl status | grep -A 20 "Audio Sinks" + + echo "" + echo "Would you like to:" + echo " 1. Test microphone input" + echo " 2. Open EasyEffects for noise suppression setup" + echo " 3. Show detailed audio device information" + echo " 4. Monitor audio levels" + echo "" + read -p "Choose an option (1-4): " choice + + case $choice in + 1) + echo "Recording 5 seconds of audio for playback test..." + echo "Speak into your microphone now!" + if command -v arecord >/dev/null 2>&1 && command -v aplay >/dev/null 2>&1; then + arecord -d 5 -f cd /tmp/mic_test.wav && echo "Playing back recording..." && aplay /tmp/mic_test.wav + rm -f /tmp/mic_test.wav + else + echo "❌ ALSA utilities not available" + fi + ;; + 2) + if command -v easyeffects >/dev/null 2>&1; then + echo "Opening EasyEffects..." + easyeffects & + else + echo "❌ EasyEffects not found" + fi + ;; + 3) + echo "" + echo "🔍 Detailed audio information:" + echo "" + pw-dump | jq '.[] | select(.info.props."media.class" == "Audio/Source" or .info.props."media.class" == "Audio/Sink") | {id: .id, name: .info.props."node.name", description: .info.props."node.description", class: .info.props."media.class"}' + ;; + 4) + echo "Monitoring audio levels (Ctrl+C to stop)..." + if command -v pw-top >/dev/null 2>&1; then + pw-top + else + echo "Monitoring with wpctl..." + while true; do + clear + wpctl status + sleep 2 + done + fi + ;; + *) + echo "Invalid choice" + ;; + esac + '') + ]; +} diff --git a/modules/sound/easyeffects-presets.nix b/modules/sound/easyeffects-presets.nix new file mode 100644 index 0000000..9a0b7bb --- /dev/null +++ b/modules/sound/easyeffects-presets.nix @@ -0,0 +1,546 @@ +{ + config, + lib, + pkgs, + ... +}: { + # Create EasyEffects configuration directory and presets + environment.etc = { + # Input preset for microphone noise suppression + "easyeffects/input/Microphone_Noise_Suppression.json".text = builtins.toJSON { + input = { + blocklist = []; + equalizer = { + balance = 0.0; + bypass = false; + input-gain = 0.0; + left = { + band0 = { + frequency = 29.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band1 = { + frequency = 59.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band2 = { + frequency = 119.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band3 = { + frequency = 237.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band4 = { + frequency = 474.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band5 = { + frequency = 947.0; + gain = 2.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band6 = { + frequency = 1889.0; + gain = 1.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band7 = { + frequency = 3770.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band8 = { + frequency = 7523.0; + gain = 0.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band9 = { + frequency = 15011.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + }; + mode = "IIR"; + num-bands = 10; + output-gain = 0.0; + pitch-left = 0.0; + pitch-right = 0.0; + right = { + band0 = { + frequency = 29.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band1 = { + frequency = 59.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band2 = { + frequency = 119.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band3 = { + frequency = 237.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band4 = { + frequency = 474.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band5 = { + frequency = 947.0; + gain = 2.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band6 = { + frequency = 1889.0; + gain = 1.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band7 = { + frequency = 3770.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band8 = { + frequency = 7523.0; + gain = 0.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band9 = { + frequency = 15011.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + }; + split-channels = false; + }; + filter = { + balance = 0.0; + bypass = false; + frequency = 2000.0; + gain = 0.0; + mode = "12dB/oct Highpass"; + quality = 0.7071; + slope = "x1"; + }; + gate = { + attack = 20.0; + bypass = false; + curve-threshold = -24.0; + curve-zone = 2.0; + hpf-frequency = 10.0; + hpf-mode = "12dB/oct Highpass"; + input-gain = 0.0; + knee = 2.5; + lpf-frequency = 20000.0; + lpf-mode = "12dB/oct Lowpass"; + makeup = 0.0; + ratio = 2.0; + release = 250.0; + sidechain = { + lookahead = 0.0; + mode = "RMS"; + preamp = 0.0; + reactivity = 10.0; + source = "Middle"; + }; + threshold = -18.0; + }; + limiter = { + alr = false; + alr-attack = 5.0; + alr-knee = 0.0; + alr-release = 50.0; + attack = 5.0; + bypass = false; + dithering = "None"; + external-sidechain = false; + gain-boost = true; + input-gain = 0.0; + lookahead = 5.0; + mode = "Herm Thin"; + output-gain = 0.0; + oversampling = "None"; + release = 5.0; + sidechain-preamp = 0.0; + stereo-link = 100.0; + threshold = 0.0; + }; + plugins_order = [ + "filter" + "gate" + "equalizer" + "rnnoise" + "limiter" + ]; + rnnoise = { + bypass = false; + enable-vad = true; + input-gain = 0.0; + model-path = ""; + output-gain = 0.0; + release = 20.0; + vad-thres = 50.0; + wet = 80.0; + }; + }; + }; + + # Output preset for speakers/headphones + "easyeffects/output/Speakers_Enhanced.json".text = builtins.toJSON { + output = { + blocklist = []; + equalizer = { + balance = 0.0; + bypass = false; + input-gain = 0.0; + left = { + band0 = { + frequency = 29.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band1 = { + frequency = 59.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band2 = { + frequency = 119.0; + gain = 0.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band3 = { + frequency = 237.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band4 = { + frequency = 474.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band5 = { + frequency = 947.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band6 = { + frequency = 1889.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band7 = { + frequency = 3770.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band8 = { + frequency = 7523.0; + gain = 2.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band9 = { + frequency = 15011.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + }; + mode = "IIR"; + num-bands = 10; + output-gain = 0.0; + pitch-left = 0.0; + pitch-right = 0.0; + right = { + band0 = { + frequency = 29.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band1 = { + frequency = 59.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band2 = { + frequency = 119.0; + gain = 0.5; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band3 = { + frequency = 237.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band4 = { + frequency = 474.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band5 = { + frequency = 947.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band6 = { + frequency = 1889.0; + gain = 0.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band7 = { + frequency = 3770.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band8 = { + frequency = 7523.0; + gain = 2.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + band9 = { + frequency = 15011.0; + gain = 1.0; + mode = "RLC (BT)"; + mute = false; + q = 4.36; + slope = "x1"; + solo = false; + type = "Bell"; + }; + }; + split-channels = false; + }; + limiter = { + alr = false; + alr-attack = 5.0; + alr-knee = 0.0; + alr-release = 50.0; + attack = 5.0; + bypass = false; + dithering = "None"; + external-sidechain = false; + gain-boost = true; + input-gain = 0.0; + lookahead = 5.0; + mode = "Herm Thin"; + output-gain = 0.0; + oversampling = "None"; + release = 5.0; + sidechain-preamp = 0.0; + stereo-link = 100.0; + threshold = -1.0; + }; + plugins_order = [ + "equalizer" + "limiter" + ]; + }; + }; + }; +} diff --git a/modules/sound/pipewire.nix b/modules/sound/pipewire.nix new file mode 100644 index 0000000..0d37a5a --- /dev/null +++ b/modules/sound/pipewire.nix @@ -0,0 +1,177 @@ +{ + config, + lib, + pkgs, + ... +}: { + imports = [ + ./easyeffects-presets.nix + ./audio-desktop-integration.nix + ]; + # Enable PipeWire with full audio stack + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + jack.enable = true; + + # Enable WirePlumber session manager + wireplumber.enable = true; + + # Add noise suppression and audio processing packages + extraPackages = with pkgs; [ + rnnoise-plugin # RNNoise noise suppression + easyeffects # Modern audio effects and filters + ]; + }; + + # Install audio management and GUI applications + environment.systemPackages = with pkgs; [ + # Audio control and monitoring + pavucontrol # PulseAudio volume control (works with PipeWire) + helvum # Graphical patchbay for PipeWire + qpwgraph # Qt-based PipeWire graph manager + easyeffects # Audio effects and noise suppression GUI + pwvucontrol # Native PipeWire volume control + + # Audio utilities + wireplumber # WirePlumber session manager + pipewire-pulse # PulseAudio compatibility + pipecontrol # PipeWire control utility + alsa-utils # ALSA utilities for testing + + # Validation script + (writeShellScriptBin "validate-audio" (builtins.readFile ./validate-audio.sh)) + + # Optional: Professional audio tools + # qjackctl # JACK control GUI (for JACK applications) + # carla # Audio plugin host + ]; + + # System-wide PipeWire configuration + environment.etc = { + # Main PipeWire configuration + "pipewire/pipewire.conf.d/10-noise-suppression.conf".text = '' + context.properties = { + default.clock.rate = 48000 + default.clock.quantum = 1024 + default.clock.min-quantum = 32 + default.clock.max-quantum = 2048 + } + + 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_stereo + control = { + "VAD Threshold (%)" = 50.0 + "VAD Grace Period (ms)" = 200 + "Retroactive VAD Grace (ms)" = 0 + } + } + ] + } + 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 + } + } + } + ] + ''; + + # WirePlumber configuration for noise suppression + "wireplumber/wireplumber.conf.d/51-noise-suppression.conf".text = '' + monitor.alsa.rules = [ + { + matches = [ + { + device.name = "~alsa_card.*" + } + ] + actions = { + update-props = { + device.profile-set = "auto" + device.auto-profile = true + } + } + } + ] + + monitor.bluez.rules = [ + { + matches = [ + { + device.name = "~bluez_card.*" + } + ] + actions = { + update-props = { + bluez5.auto-connect = [ "hfp_hf" "hsp_hs" "a2dp_sink" ] + bluez5.hw-volume = [ "hfp_hf" "hsp_hs" "a2dp_sink" ] + } + } + } + ] + ''; + + # Audio quality and latency optimization + "pipewire/pipewire-pulse.conf.d/10-audio-quality.conf".text = '' + pulse.properties = { + pulse.min.req = 32/48000 + pulse.default.req = 1024/48000 + pulse.min.quantum = 32/48000 + pulse.max.quantum = 2048/48000 + } + + stream.properties = { + node.latency = 1024/48000 + resample.quality = 4 + channelmix.normalize = false + channelmix.mix-lfe = false + session.suspend-timeout-seconds = 0 + } + ''; + }; + + # Enable real-time audio processing + security.rtkit.enable = true; + + # Audio group for users + users.groups.audio = {}; + + # Set environment variables for better audio performance + environment.variables = { + # PipeWire environment variables + PIPEWIRE_LATENCY = "1024/48000"; + # Ensure applications use PipeWire + PULSE_RUNTIME_PATH = "/run/user/$UID/pulse"; + }; + + # Enable additional audio-related services + services = { + # Enable udev rules for audio devices + udev.packages = with pkgs; [ + alsa-utils + ]; + }; + + # User session configuration for audio + systemd.user.services.pipewire-pulse.wantedBy = ["default.target"]; +} diff --git a/modules/sound/validate-audio.sh b/modules/sound/validate-audio.sh new file mode 100755 index 0000000..a1d146b --- /dev/null +++ b/modules/sound/validate-audio.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash + +# Audio Configuration Validation Script +# This script helps validate that PipeWire with noise suppression is working correctly + +set -euo pipefail + +echo "đŸŽĩ PipeWire Audio Configuration Validator" +echo "========================================" +echo "" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +success() { + echo -e "${GREEN}✅ $1${NC}" +} + +warning() { + echo -e "${YELLOW}âš ī¸ $1${NC}" +} + +error() { + echo -e "${RED}❌ $1${NC}" +} + +info() { + echo -e "${BLUE}â„šī¸ $1${NC}" +} + +# Check if PipeWire is running +echo "1. Checking PipeWire service status..." +if systemctl --user is-active pipewire >/dev/null 2>&1; then + success "PipeWire service is running" +else + error "PipeWire service is not running" + echo " Try: systemctl --user start pipewire" + exit 1 +fi + +# Check WirePlumber +echo "" +echo "2. Checking WirePlumber session manager..." +if systemctl --user is-active wireplumber >/dev/null 2>&1; then + success "WirePlumber is running" +else + warning "WirePlumber is not running" + echo " Try: systemctl --user start wireplumber" +fi + +# Check PipeWire-Pulse +echo "" +echo "3. Checking PipeWire-Pulse compatibility..." +if systemctl --user is-active pipewire-pulse >/dev/null 2>&1; then + success "PipeWire-Pulse is running" +else + warning "PipeWire-Pulse is not running" + echo " Try: systemctl --user start pipewire-pulse" +fi + +# Check for RNNoise plugin +echo "" +echo "4. Checking for RNNoise noise suppression plugin..." +if find /nix/store -name "librnnoise_ladspa.so" 2>/dev/null | head -1 | grep -q .; then + success "RNNoise plugin found" + RNNOISE_PATH=$(find /nix/store -name "librnnoise_ladspa.so" 2>/dev/null | head -1) + info "Located at: $RNNOISE_PATH" +else + error "RNNoise plugin not found" + echo " This might indicate the package is not installed correctly" +fi + +# Check audio devices +echo "" +echo "5. Checking available audio devices..." +if command -v wpctl >/dev/null 2>&1; then + SOURCES=$(wpctl status | grep -A 10 "Audio Sources" | grep -c "│" || echo "0") + SINKS=$(wpctl status | grep -A 10 "Audio Sinks" | grep -c "│" || echo "0") + + if [ "$SOURCES" -gt 0 ]; then + success "Found $SOURCES audio source(s)" + else + warning "No audio sources found" + fi + + if [ "$SINKS" -gt 0 ]; then + success "Found $SINKS audio sink(s)" + else + warning "No audio sinks found" + fi +else + error "wpctl command not found" +fi + +# Check for GUI applications +echo "" +echo "6. Checking GUI audio applications..." + +if command -v easyeffects >/dev/null 2>&1; then + success "EasyEffects available" +else + warning "EasyEffects not found" +fi + +if command -v pavucontrol >/dev/null 2>&1; then + success "PulseAudio Volume Control available" +else + warning "pavucontrol not found" +fi + +if command -v helvum >/dev/null 2>&1; then + success "Helvum patchbay available" +else + warning "Helvum not found" +fi + +# Check configuration files +echo "" +echo "7. Checking configuration files..." + +CONFIG_FILES=( + "/etc/pipewire/pipewire.conf.d/10-noise-suppression.conf" + "/etc/wireplumber/wireplumber.conf.d/51-noise-suppression.conf" + "/etc/pipewire/pipewire-pulse.conf.d/10-audio-quality.conf" +) + +for config in "${CONFIG_FILES[@]}"; do + if [ -f "$config" ]; then + success "$(basename "$config") exists" + else + warning "$(basename "$config") not found" + fi +done + +# Check for noise canceling source +echo "" +echo "8. Checking for noise canceling source..." +if command -v pw-dump >/dev/null 2>&1; then + if pw-dump | jq -r '.[] | select(.info.props."node.name" == "rnnoise_source") | .info.props."node.description"' 2>/dev/null | grep -q "Noise Canceling"; then + success "Noise Canceling Source device found" + else + warning "Noise Canceling Source device not found" + info "This is normal if no microphone is connected" + fi +else + warning "pw-dump not available, cannot check for noise canceling source" +fi + +# Performance check +echo "" +echo "9. Checking audio performance..." +if command -v pw-top >/dev/null 2>&1; then + info "You can monitor real-time performance with: pw-top" +else + warning "pw-top not available for performance monitoring" +fi + +# Summary +echo "" +echo "đŸŽ¯ Quick Start Commands:" +echo "========================" +echo "" +echo "Start audio setup wizard: audio-setup" +echo "Test microphone: microphone-test" +echo "Launch EasyEffects: easyeffects" +echo "Control volumes: pavucontrol" +echo "Audio routing: helvum" +echo "Monitor performance: pw-top" +echo "Device status: wpctl status" +echo "" + +# Check if user wants to run a test +echo "Would you like to run a quick microphone test? (y/N)" +read -r response +if [[ "$response" =~ ^[Yy]$ ]]; then + echo "" + info "Starting microphone test..." + if command -v microphone-test >/dev/null 2>&1; then + microphone-test + else + echo "Recording 3 seconds of audio..." + if command -v arecord >/dev/null 2>&1 && command -v aplay >/dev/null 2>&1; then + arecord -d 3 -f cd /tmp/audio_test.wav 2>/dev/null && \ + echo "Playing back..." && \ + aplay /tmp/audio_test.wav 2>/dev/null && \ + rm -f /tmp/audio_test.wav + success "Microphone test completed" + else + error "Audio testing tools not available" + fi + fi +fi + +echo "" +success "Audio configuration validation completed!" +echo "" +info "If you encounter issues, try:" +echo " â€ĸ systemctl --user restart pipewire pipewire-pulse wireplumber" +echo " â€ĸ Check the README.md for detailed troubleshooting" +echo " â€ĸ Run 'audio-setup' for interactive configuration" diff --git a/modules/users/common.nix b/modules/users/common.nix index 4465bd9..731385f 100644 --- a/modules/users/common.nix +++ b/modules/users/common.nix @@ -69,13 +69,6 @@ X11Forwarding = true; # For GUI applications over SSH }; }; - - # Enable sound - pipewire = { - enable = true; - alsa.enable = true; - pulse.enable = true; - }; }; # XDG portal for desktop integration