#### 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