File src/remiaudio/dsp/paraeqstereo.cr from the latest check-in


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
#### 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