#### RemiAudio
#### Copyright (C) 2022-2024 Remilia Scarlet <remilia@posteo.jp>
####   Based on MVerb
####   Copyright (c) 2010 Martin Eastwood
####
#### This program is free software: you can redistribute it and/or modify it
#### under the terms of the GNU Affero General Public License as published by
#### the Free Software Foundation, either version 3 of the License, or (at your
#### option) any later version.
####
#### This program is distributed in the hope that it will be useful, but WITHOUT
#### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#### FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
#### License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program.  If not, see <https://www.gnu.org/licenses/>.
require "./reverb"

module RemiAudio::DSP
  class MVerb < Reverb
    # Defines a set of parameters for `MVerb`.
    class Preset < Reverb::Preset
      # Applies a filter to the higher frequenices, with 0.0 meaning the filter
      # is fully open, while 1.0 means it is nearly closed.
      getter dampingFreq : Float64 = DEFAULT_DAMPING_FREQ

      # How much feedback gets applied, from 0.0 to 1.0.
      getter density : Float64 = DEFAULT_DENSITY

      # How wide the pre-filter is.  The larger the more frequencies from the
      # original signal are processed.  Likewise, the smaller this is, fewer
      # frequencies are processed and the resulting sound is darker.  Valid
      # range is 0.0 to 1.0.
      getter bandwidthFreq : Float64 = DEFAULT_BANDWIDTH_FREQ

      # How long the reverb lasts, from 0.0 to 1.0.
      getter decay : Float64 = DEFAULT_DECAY

      # how much delay is added between the original signal and the processed
      # signal, between 0.0 and 1.0.
      getter predelay : Float64 = DEFAULT_PREDELAY

      # The size of the virtual room.  Note that larger values adds a slight
      # delay to the early reflections.
      getter size : Float64 = DEFAULT_SIZE

      # How loud the mixed output signal is.
      getter gain : Float64 = DEFAULT_GAIN

      # How much of the processed signal is mixed into the original signal, from
      # 0.0 to 1.0.
      getter mix : Float64 = DEFAULT_MIX

      # How much of the early reflection versus the late reverb tail is mixed
      # into the signal.  A value of 0 means that the signal contains only the
      # early reflections, while a value of 1.0 means the signal contains only
      # the late reverb tail.
      getter earlyLateMix : Float64 = DEFAULT_EARLY_LATE_MIX

      # Creates a new `Preset` instance.
      def initialize
      end

      # :ditto:
      def initialize(*, @dampingFreq : Float64 = DEFAULT_DAMPING_FREQ,
                     @density : Float64 = DEFAULT_DENSITY,
                     @bandwidthFreq : Float64 = DEFAULT_BANDWIDTH_FREQ,
                     @decay : Float64 = DEFAULT_DECAY,
                     @predelay : Float64 = DEFAULT_PREDELAY,
                     @size : Float64 = DEFAULT_SIZE,
                     @gain : Float64 = DEFAULT_GAIN,
                     @mix : Float64 = DEFAULT_MIX,
                     @earlyLateMix : Float64 = DEFAULT_EARLY_LATE_MIX)
        MVerb.checkDampingFreq(@dampingFreq)
        MVerb.checkDensity(@density)
        MVerb.checkBandwidthFreq(@bandwidthFreq)
        MVerb.checkDecay(@decay)
        MVerb.checkPredelay(@predelay)
        MVerb.checkSize(@size)
        MVerb.checkGain(@gain)
        MVerb.checkMix(@mix)
        MVerb.checkEarlyLateMix(@earlyLateMix)
      end

      def dampingFreq=(value : Float64)
        MVerb.checkDampingFreq(value)
        @dampingFreq = value
      end

      def density=(value : Float64)
        MVerb.checkDensity(value)
        @density = value
      end

      def bandwidthFreq=(value : Float64)
        MVerb.checkBandwidthFreq(value)
        @bandwidthFreq = value
      end

      def decay=(value : Float64)
        MVerb.checkDecay(value)
        @decay = value
      end

      def predelay=(value : Float64)
        MVerb.checkPredelay(value)
        @predelay = value
      end

      def size=(value : Float64)
        MVerb.checkSize(value)
        @size = value
      end

      def gain=(value : Float64)
        MVerb.checkGain(value)
        @gain = value
      end

      def mix=(value : Float64)
        MVerb.checkMix(value)
        @mix = value
      end

      def earlyLateMix=(value : Float64)
        MVerb.checkEarlyLateMix(value)
        @earlyLateMix = value
      end
    end

    # Names for the pre-constructed `Preset` instances in `MVerb::PRESETS`.
    enum PresetNames
      # A good default for General MIDI use.
      GmDefault

      # An alternative default for General MIDI use.
      GmDefault2

      # A subtle reverb effect.
      Subtle

      # A reverb similar to a large stadium.
      Stadium

      # A small wooden room or box.
      Cupboard

      # A dark reverb.
      Dark

      # A reverb where most values are set to 0.5.
      Halves

      # A large concert hall.
      Hall

      # A somewhat large concert hall, but slightly more subtle than the `Hall`
      # preset.
      SubtleHall
    end

    # Pre-constructed `Preset` instances for `MVerb` that correspond to the
    # names in `PresetNames`.
    PRESETS = {
      PresetNames::GmDefault => Preset.new(dampingFreq: 0.59,
                                           density: 0.469,
                                           bandwidthFreq: 0.688,
                                           decay: 0.5084,
                                           predelay: 0.0,
                                           size: 0.69,
                                           gain: 0.5,
                                           mix: 1.0,
                                           earlyLateMix: 0.795),

      PresetNames::GmDefault2 => Preset.new(dampingFreq: 0.69,
                                            density: 0.425,
                                            bandwidthFreq: 0.785,
                                            decay: 0.5036,
                                            predelay: 0.0,
                                            size: 0.42,
                                            gain: 0.5,
                                            mix: 1.0,
                                            earlyLateMix: 0.79),

      PresetNames::Subtle => Preset.new(dampingFreq: 0.0,
                                        density: 0.5,
                                        bandwidthFreq: 1.0,
                                        decay: 0.5,
                                        predelay: 0.0,
                                        size: 0.5,
                                        gain: 1.0,
                                        mix: 0.15,
                                        earlyLateMix: 0.75),

      PresetNames::Stadium => Preset.new(dampingFreq: 0.0,
                                         density: 0.5,
                                         bandwidthFreq: 1.0,
                                         decay: 0.5,
                                         predelay: 0.0,
                                         size: 1.0,
                                         gain: 1.0,
                                         mix: 0.35,
                                         earlyLateMix: 0.75),

      PresetNames::Cupboard => Preset.new(dampingFreq: 0.0,
                                          density: 0.5,
                                          bandwidthFreq: 1.0,
                                          decay: 0.5,
                                          predelay: 0.0,
                                          size: 0.25,
                                          gain: 1.0,
                                          mix: 0.35,
                                          earlyLateMix: 0.75),

      PresetNames::Dark => Preset.new(dampingFreq: 0.9,
                                      density: 0.5,
                                      bandwidthFreq: 0.1,
                                      decay: 0.5,
                                      predelay: 0.0,
                                      size: 0.5,
                                      gain: 1.0,
                                      mix: 0.5,
                                      earlyLateMix: 0.75),

      PresetNames::Halves => Preset.new(dampingFreq: 0.5,
                                        density: 0.5,
                                        bandwidthFreq: 0.5,
                                        decay: 0.5,
                                        predelay: 0.5,
                                        size: 0.75,
                                        gain: 1.0,
                                        mix: 0.5,
                                        earlyLateMix: 0.5),

      PresetNames::Hall => Preset.new(dampingFreq: 0.79,
                                      density: 0.785,
                                      bandwidthFreq: 0.555,
                                      decay: 0.675,
                                      predelay: 0.0105,
                                      size: 1.0,
                                      gain: 1.0,
                                      mix: 0.32,
                                      earlyLateMix: 0.69),

      PresetNames::SubtleHall => Preset.new(dampingFreq: 0.59,
                                            density: 0.525,
                                            bandwidthFreq: 0.685,
                                            decay: 0.599,
                                            predelay: 0.007,
                                            size: 0.655,
                                            gain: 1.0,
                                            mix: 0.15,
                                            earlyLateMix: 0.78),
    }
  end
end
