{
  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;

    }
  );
}
