#### RemiAudio
#### Copyright (C) 2022-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### 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 "./paraeq"

module RemiAudio::DSP
  # A stereo parametric EQ.  This is identical to using two `ParaEQ` instances,
  # and is provided simply as a convenience wrapper.
  class ParaEQStereo
    # Direct access to the underlying `ParaEQ` for the left channel.
    getter eqL : ParaEQ

    # Direct access to the underlying `ParaEQ` for the right channel.
    getter eqR : ParaEQ

    # Creates a new `Para5EQ` instance with the default settings.
    def initialize(numBands, newSampleRate)
      @eqL = ParaEQ.new(numBands, newSampleRate)
      @eqR = ParaEQ.new(numBands, newSampleRate)
    end

    # The sample rate of the equalizer.
    @[AlwaysInline]
    def sampleRate : Float64
      @eqL.sampleRate
    end

    # The amount of gain to apply after the EQ, in decibels.
    def postGain : Float64
      @eqL.postGain
    end

    # The amount of gain to apply after the EQ, in decibels.
    def postGain=(value : Float64) : Float64
      @eqL.postGain = value
      @eqR.postGain = value
      @eqL.postGain
    end

    # When true, this equalizer will process audio, otherwise it will pass any
    # audio through unmodified.
    @[AlwaysInline]
    def active? : Bool
      @eqL.active?
    end

    # When true, this equalizer will process audio, otherwise it will pass any
    # audio through unmodified.
    @[AlwaysInline]
    def active=(value : Bool) : Bool
      @eqL.active = value
    end

    # Sets the frequency/band/width for the given band.  `band` must be between
    # 0 and 4, inclusive.
    #
    # If `freq` is greater than 49.9% of the sampling frequency, that band will
    # be disabled.
    @[AlwaysInline]
    def setBand(band : Int, freq : Float64, gain : Float64, width : Float64) : Nil
      @eqL.setBand(band, freq, gain, width)
      @eqR.setBand(band, freq, gain, width)
    end

    {% begin %}
      {% types = [:Float32, :Float64] %}
      {% for typ in types %}
        # Processes a single left-channel sample and a single right-channel sample
        # of audio with the equalizer, returning two processed samples where the 0th
        # is the new left sample.
        @[AlwaysInline]
        def process(sampleL : {{typ.id}}, sampleR : {{typ.id}}) : Tuple({{typ.id}}, {{typ.id}})
          {@eqL.process(sampleL), @eqR.process(sampleR)}
        end

        {% for klass in [:Array, :Slice] %}
          # Processes a block of audio with the equalizer.  The block is assumed to
          # hold interleaved stereo audio data.
          @[AlwaysInline]
          def process(block : {{klass.id}}({{typ.id}})) : Nil
            if @eqL.active?
              i = 0
              while i < block.size
                block.unsafe_put(i,     @eqL.process(block.unsafe_fetch(i)))
                block.unsafe_put(i + 1, @eqR.process(block.unsafe_fetch(i + 1)))
                i += 2
              end
            end
          end

          # Processes two channels of stereo audio with the equalizer.
          @[AlwaysInline]
          def process(blockL : {{klass.id}}({{typ.id}}), blockR : {{klass.id}}({{typ.id}})) : Nil
            {% unless flag?(:remiaudio_wd40) %}
              unless blockL.size == blockR.size
                raise RemiAudioError.new("Block sizes differ")
              end
            {% end %}
            if @eqL.active?
              @eqL.process(blockL)
              @eqR.process(blockR)
            end
          end
        {% end %}
      {% end %}
    {% end %}

    # Resets all bands in the equalizer.
    @[AlwaysInline]
    def reset : Nil
      @eqL.reset
      @eqR.reset
    end

    # Generates a string that can be passed to a program to plot this equalizer
    # on a graph.  The `PlotType` dictates what kind of script is generated.
    def plot(pt : PlotType) : String
      @eqL.plot
    end
  end
end
