mirror of
				https://github.com/NixOS/nixos-hardware.git
				synced 2025-11-04 09:17:14 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			391 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{
 | 
						|
  config,
 | 
						|
  lib,
 | 
						|
  pkgs,
 | 
						|
  ...
 | 
						|
}:
 | 
						|
let
 | 
						|
  cfg = config.hardware.framework.laptop13.audioEnhancement;
 | 
						|
in
 | 
						|
{
 | 
						|
  options = {
 | 
						|
    hardware.framework.laptop13.audioEnhancement = {
 | 
						|
      enable = lib.mkOption {
 | 
						|
        type = lib.types.bool;
 | 
						|
        default = false;
 | 
						|
        description = ''
 | 
						|
          Create a new audio device called "Framework Speakers",
 | 
						|
          which applies sound tuning before sending the audio out to the speakers.
 | 
						|
          This option requires PipeWire and WirePlumber.
 | 
						|
 | 
						|
          The filter chain includes the following:
 | 
						|
            - Psychoacoustic bass enhancement
 | 
						|
            - Loudness compensation
 | 
						|
            - Equalizer
 | 
						|
            - Slight compression
 | 
						|
 | 
						|
          This option has been optimised for the Framework Laptop 13 AMD 7040 series, but should work on all models.
 | 
						|
 | 
						|
          Before applying, ensure the speakers are set to 100%,
 | 
						|
          because the volumes compound and the raw speaker device will be hidden by default.
 | 
						|
 | 
						|
          You might also need to re-select the default output device.
 | 
						|
 | 
						|
          In some cases, the added bass will vibrate the keyboard cable leading to a rattling sound,
 | 
						|
          a piece of foam can be used to mitigate this.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      hideRawDevice = lib.mkOption {
 | 
						|
        type = lib.types.bool;
 | 
						|
        default = true;
 | 
						|
        description = ''
 | 
						|
          Hide the raw speaker device.
 | 
						|
          This option is enabled by default, because keeping the raw speaker device can lead to volume conflicts.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      rawDeviceName = lib.mkOption {
 | 
						|
        type = lib.types.str;
 | 
						|
        example = "alsa_output.pci-0000_c1_00.6.analog-stereo";
 | 
						|
        description = ''
 | 
						|
          The name of the raw speaker device. This will vary by device.
 | 
						|
          You can get this by running `pw-dump | grep -C 20 pci-0000`.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  config = lib.mkIf cfg.enable (
 | 
						|
    let
 | 
						|
      outputName = cfg.rawDeviceName;
 | 
						|
      prettyName = "Framework Speakers";
 | 
						|
 | 
						|
      # These are pre-made decibel to linear value conversions, since Nix doesn't have pow().
 | 
						|
      # Use the formula `10 ** (db / 20)` to calculate.
 | 
						|
      db = {
 | 
						|
        "-18.1" = 0.1244514611771385;
 | 
						|
        "-5.48" = 0.5321082592667942;
 | 
						|
        "-4.76" = 0.5780960474057181;
 | 
						|
        "8.1" = 2.5409727055493048;
 | 
						|
        "-36" = 1.5848931924611134e-2;
 | 
						|
      };
 | 
						|
 | 
						|
      json = pkgs.formats.json { };
 | 
						|
 | 
						|
      # The filter chain, heavily inspired by the asahi-audio project: https://github.com/AsahiLinux/asahi-audio
 | 
						|
      filter-chain = json.generate "filter-chain.json" {
 | 
						|
        "node.description" = prettyName;
 | 
						|
        "media.name" = prettyName;
 | 
						|
        "filter.graph" = {
 | 
						|
          nodes = [
 | 
						|
            # Psychoacoustic bass extension,
 | 
						|
            # it creates harmonics of the missing bass to fool our ears into hearing it.
 | 
						|
            {
 | 
						|
              type = "lv2";
 | 
						|
              plugin = "https://chadmed.au/bankstown";
 | 
						|
              name = "bassex";
 | 
						|
              control = {
 | 
						|
                bypass = 0;
 | 
						|
                amt = 1.2;
 | 
						|
                sat_second = 1.3;
 | 
						|
                sat_third = 2.5;
 | 
						|
                blend = 1.0;
 | 
						|
                ceil = 200.0;
 | 
						|
                floor = 20.0;
 | 
						|
              };
 | 
						|
            }
 | 
						|
            # Loudness compensation,
 | 
						|
            # it ensures that the sound profile stays consistent across different volumes.
 | 
						|
            {
 | 
						|
              type = "lv2";
 | 
						|
              plugin = "http://lsp-plug.in/plugins/lv2/loud_comp_stereo";
 | 
						|
              name = "el";
 | 
						|
              control = {
 | 
						|
                enabled = 1;
 | 
						|
                input = 1.0;
 | 
						|
                fft = 4;
 | 
						|
              };
 | 
						|
            }
 | 
						|
            # 8-band equalizer,
 | 
						|
            # it tries to lessen frequencies where the laptop might resonate,
 | 
						|
            # and tries to make the frequency curve more pleasing;
 | 
						|
            # this is the "Lappy McTopface" profile (https://github.com/ceiphr/ee-framework-presets)
 | 
						|
            # further tuned for the Framework Laptop 13 AMD 7040 series
 | 
						|
            # and might need some tuning on other models.
 | 
						|
            {
 | 
						|
              type = "lv2";
 | 
						|
              plugin = "http://lsp-plug.in/plugins/lv2/para_equalizer_x8_lr";
 | 
						|
              name = "fw13eq";
 | 
						|
              control = {
 | 
						|
                mode = 0;
 | 
						|
                react = 0.2;
 | 
						|
                zoom = db."-36";
 | 
						|
 | 
						|
                fl_0 = 101.0;
 | 
						|
                fml_0 = 0;
 | 
						|
                ftl_0 = 5;
 | 
						|
                gl_0 = db."-18.1";
 | 
						|
                huel_0 = 0.0;
 | 
						|
                ql_0 = 4.36;
 | 
						|
                sl_0 = 0;
 | 
						|
                wl_0 = 4.0;
 | 
						|
 | 
						|
                fl_1 = 451.0;
 | 
						|
                fml_1 = 0;
 | 
						|
                ftl_1 = 1;
 | 
						|
                gl_1 = db."-5.48";
 | 
						|
                huel_1 = 3.125e-2;
 | 
						|
                ql_1 = 2.46;
 | 
						|
                sl_1 = 0;
 | 
						|
                wl_1 = 4.0;
 | 
						|
 | 
						|
                fl_2 = 918.0;
 | 
						|
                fml_2 = 0;
 | 
						|
                ftl_2 = 1;
 | 
						|
                gl_2 = db."-4.76";
 | 
						|
                huel_2 = 6.25e-2;
 | 
						|
                ql_2 = 2.44;
 | 
						|
                sl_2 = 0;
 | 
						|
                wl_2 = 4.0;
 | 
						|
 | 
						|
                fl_3 = 9700.0;
 | 
						|
                fml_3 = 0;
 | 
						|
                ftl_3 = 1;
 | 
						|
                gl_3 = db."8.1";
 | 
						|
                huel_3 = 9.375e-2;
 | 
						|
                ql_3 = 2.0;
 | 
						|
                sl_3 = 0;
 | 
						|
                wl_3 = 4.0;
 | 
						|
 | 
						|
                fr_0 = 101.0;
 | 
						|
                fmr_0 = 0;
 | 
						|
                ftr_0 = 5;
 | 
						|
                gr_0 = db."-18.1";
 | 
						|
                huer_0 = 0.0;
 | 
						|
                qr_0 = 4.36;
 | 
						|
                sr_0 = 0;
 | 
						|
                wr_0 = 4.0;
 | 
						|
 | 
						|
                fr_1 = 451.0;
 | 
						|
                fmr_1 = 0;
 | 
						|
                ftr_1 = 1;
 | 
						|
                gr_1 = db."-5.48";
 | 
						|
                huer_1 = 3.125e-2;
 | 
						|
                qr_1 = 2.46;
 | 
						|
                sr_1 = 0;
 | 
						|
                wr_1 = 4.0;
 | 
						|
 | 
						|
                fr_2 = 918.0;
 | 
						|
                fmr_2 = 0;
 | 
						|
                ftr_2 = 1;
 | 
						|
                gr_2 = db."-4.76";
 | 
						|
                huer_2 = 6.25e-2;
 | 
						|
                qr_2 = 2.44;
 | 
						|
                sr_2 = 0;
 | 
						|
                wr_2 = 4.0;
 | 
						|
 | 
						|
                fr_3 = 9700.0;
 | 
						|
                fmr_3 = 0;
 | 
						|
                ftr_3 = 1;
 | 
						|
                gr_3 = db."8.1";
 | 
						|
                huer_3 = 9.375e-2;
 | 
						|
                qr_3 = 2.0;
 | 
						|
                sr_3 = 0;
 | 
						|
                wr_3 = 4.0;
 | 
						|
              };
 | 
						|
            }
 | 
						|
            # Compressors. The settings were taken from the asahi-audio project.
 | 
						|
            {
 | 
						|
              type = "lv2";
 | 
						|
              plugin = "http://lsp-plug.in/plugins/lv2/mb_compressor_stereo";
 | 
						|
              name = "woofer_bp";
 | 
						|
              control = {
 | 
						|
                mode = 0;
 | 
						|
                ce_0 = 1;
 | 
						|
                sla_0 = 5.0;
 | 
						|
                cr_0 = 1.75;
 | 
						|
                al_0 = 0.725;
 | 
						|
                at_0 = 1.0;
 | 
						|
                rt_0 = 100;
 | 
						|
                kn_0 = 0.125;
 | 
						|
                cbe_1 = 1;
 | 
						|
                sf_1 = 200.0;
 | 
						|
                ce_1 = 0;
 | 
						|
                cbe_2 = 0;
 | 
						|
                ce_2 = 0;
 | 
						|
                cbe_3 = 0;
 | 
						|
                ce_3 = 0;
 | 
						|
                cbe_4 = 0;
 | 
						|
                ce_4 = 0;
 | 
						|
                cbe_5 = 0;
 | 
						|
                ce_5 = 0;
 | 
						|
                cbe_6 = 0;
 | 
						|
                ce_6 = 0;
 | 
						|
              };
 | 
						|
            }
 | 
						|
            {
 | 
						|
              type = "lv2";
 | 
						|
              plugin = "http://lsp-plug.in/plugins/lv2/compressor_stereo";
 | 
						|
              name = "woofer_lim";
 | 
						|
              control = {
 | 
						|
                sla = 5.0;
 | 
						|
                al = 1.0;
 | 
						|
                at = 1.0;
 | 
						|
                rt = 100.0;
 | 
						|
                cr = 15.0;
 | 
						|
                kn = 0.5;
 | 
						|
              };
 | 
						|
            }
 | 
						|
          ];
 | 
						|
 | 
						|
          # Now, we're chaining together the modules instantiated above.
 | 
						|
          links = [
 | 
						|
            {
 | 
						|
              output = "bassex:out_l";
 | 
						|
              input = "el:in_l";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "bassex:out_r";
 | 
						|
              input = "el:in_r";
 | 
						|
            }
 | 
						|
 | 
						|
            {
 | 
						|
              output = "el:out_l";
 | 
						|
              input = "fw13eq:in_l";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "el:out_r";
 | 
						|
              input = "fw13eq:in_r";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "fw13eq:out_l";
 | 
						|
              input = "woofer_bp:in_l";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "fw13eq:out_r";
 | 
						|
              input = "woofer_bp:in_r";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "woofer_bp:out_l";
 | 
						|
              input = "woofer_lim:in_l";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              output = "woofer_bp:out_r";
 | 
						|
              input = "woofer_lim:in_r";
 | 
						|
            }
 | 
						|
          ];
 | 
						|
 | 
						|
          inputs = [
 | 
						|
            "bassex:in_l"
 | 
						|
            "bassex:in_r"
 | 
						|
          ];
 | 
						|
          outputs = [
 | 
						|
            "woofer_lim:out_l"
 | 
						|
            "woofer_lim:out_r"
 | 
						|
          ];
 | 
						|
 | 
						|
          # This makes pipewire's volume control actually control the loudness comp module
 | 
						|
          "capture.volumes" = [
 | 
						|
            {
 | 
						|
              control = "el:volume";
 | 
						|
              min = -47.5;
 | 
						|
              max = 0.0;
 | 
						|
              scale = "cubic";
 | 
						|
            }
 | 
						|
          ];
 | 
						|
        };
 | 
						|
        "capture.props" = {
 | 
						|
          "node.name" = "audio_effect.laptop-convolver";
 | 
						|
          "media.class" = "Audio/Sink";
 | 
						|
          "audio.channels" = "2";
 | 
						|
          "audio.position" = [
 | 
						|
            "FL"
 | 
						|
            "FR"
 | 
						|
          ];
 | 
						|
          "audio.allowed-rates" = [
 | 
						|
            44100
 | 
						|
            48000
 | 
						|
            88200
 | 
						|
            96000
 | 
						|
            176400
 | 
						|
            192000
 | 
						|
          ];
 | 
						|
          "device.api" = "dsp";
 | 
						|
          "node.virtual" = "false";
 | 
						|
 | 
						|
          # Lower seems to mean "more preferred",
 | 
						|
          # bluetooth devices seem to be ~1000, speakers seem to be ~2000
 | 
						|
          # since this is between the two, bluetooth devices take over when they connect,
 | 
						|
          # and hand over to this instead of the speakers when they disconnect.
 | 
						|
          "priority.session" = 1500;
 | 
						|
          "priority.driver" = 1500;
 | 
						|
          "state.default-volume" = 0.343;
 | 
						|
          "device.icon-name" = "audio-card-analog-pci";
 | 
						|
        };
 | 
						|
        "playback.props" = {
 | 
						|
          "node.name" = "audio_effect.laptop-convolver";
 | 
						|
          "target.object" = outputName;
 | 
						|
          "node.passive" = "true";
 | 
						|
          "audio.channels" = "2";
 | 
						|
          "audio.allowed-rates" = [
 | 
						|
            44100
 | 
						|
            48000
 | 
						|
            88200
 | 
						|
            96000
 | 
						|
            176400
 | 
						|
            192000
 | 
						|
          ];
 | 
						|
          "audio.position" = [
 | 
						|
            "FL"
 | 
						|
            "FR"
 | 
						|
          ];
 | 
						|
          "device.icon-name" = "audio-card-analog-pci";
 | 
						|
        };
 | 
						|
      };
 | 
						|
 | 
						|
      configPackage =
 | 
						|
        (pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/99-laptop.conf" ''
 | 
						|
          monitor.alsa.rules = [
 | 
						|
            {
 | 
						|
              matches = [{ node.name = "${outputName}" }]
 | 
						|
              actions = {
 | 
						|
                update-props = {
 | 
						|
                  audio.allowed-rates = [44100, 48000, 88200, 96000, 176400, 192000]
 | 
						|
                }
 | 
						|
              }
 | 
						|
            }
 | 
						|
          ]
 | 
						|
 | 
						|
          node.software-dsp.rules = [
 | 
						|
            {
 | 
						|
              matches = [{ node.name = "${outputName}" }]
 | 
						|
              actions = {
 | 
						|
                create-filter = {
 | 
						|
                  filter-path = "${filter-chain}"
 | 
						|
                  hide-parent = ${lib.boolToString cfg.hideRawDevice}
 | 
						|
                }
 | 
						|
              }
 | 
						|
            }
 | 
						|
          ]
 | 
						|
 | 
						|
          wireplumber.profiles = {
 | 
						|
            main = { node.software-dsp = "required" }
 | 
						|
          }
 | 
						|
        '')
 | 
						|
        // {
 | 
						|
          passthru.requiredLv2Packages = with pkgs; [
 | 
						|
            lsp-plugins
 | 
						|
            bankstown-lv2
 | 
						|
          ];
 | 
						|
        };
 | 
						|
    in
 | 
						|
    {
 | 
						|
      services.pipewire.wireplumber.configPackages = [ configPackage ];
 | 
						|
 | 
						|
      # Pipewire is needed for this.
 | 
						|
      services.pipewire.enable = lib.mkDefault true;
 | 
						|
    }
 | 
						|
  );
 | 
						|
}
 |