2 * OpenAL cross platform audio library
3 * Copyright (C) 2013 by Mike Gorchak
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
29 #include "al/auxeffectslot.h"
31 #include "alcontext.h"
33 #include "filters/biquad.h"
39 /* The document "Effects Extension Guide.pdf" says that low and high *
40 * frequencies are cutoff frequencies. This is not fully correct, they *
41 * are corner frequencies for low and high shelf filters. If they were *
42 * just cutoff frequencies, there would be no need in cutoff frequency *
43 * gains, which are present. Documentation for "Creative Proteus X2" *
44 * software describes 4-band equalizer functionality in a much better *
45 * way. This equalizer seems to be a predecessor of OpenAL 4-band *
46 * equalizer. With low and high shelf filters we are able to cutoff *
47 * frequencies below and/or above corner frequencies using attenuation *
48 * gains (below 1.0) and amplify all low and/or high frequencies using *
51 * Low-shelf Low Mid Band High Mid Band High-shelf *
52 * corner center center corner *
53 * frequency frequency frequency frequency *
54 * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
58 * B -----+ /--+--\ /--+--\ +----- *
59 * O |\ | | | | | | /| *
60 * O | \ - | - - | - / | *
61 * S + | \ | | | | | | / | *
62 * T | | | | | | | | | | *
63 * ---------+---------------+------------------+---------------+-------- *
64 * C | | | | | | | | | | *
65 * U - | / | | | | | | \ | *
66 * T | / - | - - | - \ | *
67 * O |/ | | | | | | \| *
68 * F -----+ \--+--/ \--+--/ +----- *
72 * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
73 * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
74 * octaves for two mid bands. *
76 * Implementation is based on the "Cookbook formulae for audio EQ biquad *
77 * filter coefficients" by Robert Bristow-Johnson *
78 * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
81 struct EqualizerState final
: public EffectState
{
83 /* Effect parameters */
84 BiquadFilter filter
[4];
86 /* Effect gains for each channel */
87 float CurrentGains
[MAX_OUTPUT_CHANNELS
]{};
88 float TargetGains
[MAX_OUTPUT_CHANNELS
]{};
89 } mChans
[MAX_AMBI_CHANNELS
];
91 FloatBufferLine mSampleBuffer
{};
94 void deviceUpdate(const ALCdevice
*device
) override
;
95 void update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
) override
;
96 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
) override
;
98 DEF_NEWDEL(EqualizerState
)
101 void EqualizerState::deviceUpdate(const ALCdevice
*)
103 for(auto &e
: mChans
)
105 std::for_each(std::begin(e
.filter
), std::end(e
.filter
), std::mem_fn(&BiquadFilter::clear
));
106 std::fill(std::begin(e
.CurrentGains
), std::end(e
.CurrentGains
), 0.0f
);
110 void EqualizerState::update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
)
112 const ALCdevice
*device
{context
->mDevice
.get()};
113 auto frequency
= static_cast<float>(device
->Frequency
);
116 /* Calculate coefficients for the each type of filter. Note that the shelf
117 * and peaking filters' gain is for the centerpoint of the transition band,
118 * while the effect property gains are for the shelf/peak itself. So the
119 * property gains need their dB halved (sqrt of linear gain) for the
120 * shelf/peak to reach the provided gain.
122 gain
= std::sqrt(props
->Equalizer
.LowGain
);
123 f0norm
= props
->Equalizer
.LowCutoff
/ frequency
;
124 mChans
[0].filter
[0].setParamsFromSlope(BiquadType::LowShelf
, f0norm
, gain
, 0.75f
);
126 gain
= std::sqrt(props
->Equalizer
.Mid1Gain
);
127 f0norm
= props
->Equalizer
.Mid1Center
/ frequency
;
128 mChans
[0].filter
[1].setParamsFromBandwidth(BiquadType::Peaking
, f0norm
, gain
,
129 props
->Equalizer
.Mid1Width
);
131 gain
= std::sqrt(props
->Equalizer
.Mid2Gain
);
132 f0norm
= props
->Equalizer
.Mid2Center
/ frequency
;
133 mChans
[0].filter
[2].setParamsFromBandwidth(BiquadType::Peaking
, f0norm
, gain
,
134 props
->Equalizer
.Mid2Width
);
136 gain
= std::sqrt(props
->Equalizer
.HighGain
);
137 f0norm
= props
->Equalizer
.HighCutoff
/ frequency
;
138 mChans
[0].filter
[3].setParamsFromSlope(BiquadType::HighShelf
, f0norm
, gain
, 0.75f
);
140 /* Copy the filter coefficients for the other input channels. */
141 for(size_t i
{1u};i
< slot
->Wet
.Buffer
.size();++i
)
143 mChans
[i
].filter
[0].copyParamsFrom(mChans
[0].filter
[0]);
144 mChans
[i
].filter
[1].copyParamsFrom(mChans
[0].filter
[1]);
145 mChans
[i
].filter
[2].copyParamsFrom(mChans
[0].filter
[2]);
146 mChans
[i
].filter
[3].copyParamsFrom(mChans
[0].filter
[3]);
149 mOutTarget
= target
.Main
->Buffer
;
150 auto set_gains
= [slot
,target
](auto &chan
, al::span
<const float,MAX_AMBI_CHANNELS
> coeffs
)
151 { ComputePanGains(target
.Main
, coeffs
.data(), slot
->Params
.Gain
, chan
.TargetGains
); };
152 SetAmbiPanIdentity(std::begin(mChans
), slot
->Wet
.Buffer
.size(), set_gains
);
155 void EqualizerState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
157 const al::span
<float> buffer
{mSampleBuffer
.data(), samplesToDo
};
158 auto chan
= std::addressof(mChans
[0]);
159 for(const auto &input
: samplesIn
)
161 const al::span
<const float> inbuf
{input
.data(), samplesToDo
};
162 DualBiquad
{chan
->filter
[0], chan
->filter
[1]}.process(inbuf
, buffer
.begin());
163 DualBiquad
{chan
->filter
[2], chan
->filter
[3]}.process(buffer
, buffer
.begin());
165 MixSamples(buffer
, samplesOut
, chan
->CurrentGains
, chan
->TargetGains
, samplesToDo
, 0u);
171 void Equalizer_setParami(EffectProps
*, ALenum param
, int)
172 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
}; }
173 void Equalizer_setParamiv(EffectProps
*, ALenum param
, const int*)
175 throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x",
178 void Equalizer_setParamf(EffectProps
*props
, ALenum param
, float val
)
182 case AL_EQUALIZER_LOW_GAIN
:
183 if(!(val
>= AL_EQUALIZER_MIN_LOW_GAIN
&& val
<= AL_EQUALIZER_MAX_LOW_GAIN
))
184 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer low-band gain out of range"};
185 props
->Equalizer
.LowGain
= val
;
188 case AL_EQUALIZER_LOW_CUTOFF
:
189 if(!(val
>= AL_EQUALIZER_MIN_LOW_CUTOFF
&& val
<= AL_EQUALIZER_MAX_LOW_CUTOFF
))
190 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer low-band cutoff out of range"};
191 props
->Equalizer
.LowCutoff
= val
;
194 case AL_EQUALIZER_MID1_GAIN
:
195 if(!(val
>= AL_EQUALIZER_MIN_MID1_GAIN
&& val
<= AL_EQUALIZER_MAX_MID1_GAIN
))
196 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid1-band gain out of range"};
197 props
->Equalizer
.Mid1Gain
= val
;
200 case AL_EQUALIZER_MID1_CENTER
:
201 if(!(val
>= AL_EQUALIZER_MIN_MID1_CENTER
&& val
<= AL_EQUALIZER_MAX_MID1_CENTER
))
202 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid1-band center out of range"};
203 props
->Equalizer
.Mid1Center
= val
;
206 case AL_EQUALIZER_MID1_WIDTH
:
207 if(!(val
>= AL_EQUALIZER_MIN_MID1_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID1_WIDTH
))
208 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid1-band width out of range"};
209 props
->Equalizer
.Mid1Width
= val
;
212 case AL_EQUALIZER_MID2_GAIN
:
213 if(!(val
>= AL_EQUALIZER_MIN_MID2_GAIN
&& val
<= AL_EQUALIZER_MAX_MID2_GAIN
))
214 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid2-band gain out of range"};
215 props
->Equalizer
.Mid2Gain
= val
;
218 case AL_EQUALIZER_MID2_CENTER
:
219 if(!(val
>= AL_EQUALIZER_MIN_MID2_CENTER
&& val
<= AL_EQUALIZER_MAX_MID2_CENTER
))
220 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid2-band center out of range"};
221 props
->Equalizer
.Mid2Center
= val
;
224 case AL_EQUALIZER_MID2_WIDTH
:
225 if(!(val
>= AL_EQUALIZER_MIN_MID2_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID2_WIDTH
))
226 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer mid2-band width out of range"};
227 props
->Equalizer
.Mid2Width
= val
;
230 case AL_EQUALIZER_HIGH_GAIN
:
231 if(!(val
>= AL_EQUALIZER_MIN_HIGH_GAIN
&& val
<= AL_EQUALIZER_MAX_HIGH_GAIN
))
232 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer high-band gain out of range"};
233 props
->Equalizer
.HighGain
= val
;
236 case AL_EQUALIZER_HIGH_CUTOFF
:
237 if(!(val
>= AL_EQUALIZER_MIN_HIGH_CUTOFF
&& val
<= AL_EQUALIZER_MAX_HIGH_CUTOFF
))
238 throw effect_exception
{AL_INVALID_VALUE
, "Equalizer high-band cutoff out of range"};
239 props
->Equalizer
.HighCutoff
= val
;
243 throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
};
246 void Equalizer_setParamfv(EffectProps
*props
, ALenum param
, const float *vals
)
247 { Equalizer_setParamf(props
, param
, vals
[0]); }
249 void Equalizer_getParami(const EffectProps
*, ALenum param
, int*)
250 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
}; }
251 void Equalizer_getParamiv(const EffectProps
*, ALenum param
, int*)
253 throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x",
256 void Equalizer_getParamf(const EffectProps
*props
, ALenum param
, float *val
)
260 case AL_EQUALIZER_LOW_GAIN
:
261 *val
= props
->Equalizer
.LowGain
;
264 case AL_EQUALIZER_LOW_CUTOFF
:
265 *val
= props
->Equalizer
.LowCutoff
;
268 case AL_EQUALIZER_MID1_GAIN
:
269 *val
= props
->Equalizer
.Mid1Gain
;
272 case AL_EQUALIZER_MID1_CENTER
:
273 *val
= props
->Equalizer
.Mid1Center
;
276 case AL_EQUALIZER_MID1_WIDTH
:
277 *val
= props
->Equalizer
.Mid1Width
;
280 case AL_EQUALIZER_MID2_GAIN
:
281 *val
= props
->Equalizer
.Mid2Gain
;
284 case AL_EQUALIZER_MID2_CENTER
:
285 *val
= props
->Equalizer
.Mid2Center
;
288 case AL_EQUALIZER_MID2_WIDTH
:
289 *val
= props
->Equalizer
.Mid2Width
;
292 case AL_EQUALIZER_HIGH_GAIN
:
293 *val
= props
->Equalizer
.HighGain
;
296 case AL_EQUALIZER_HIGH_CUTOFF
:
297 *val
= props
->Equalizer
.HighCutoff
;
301 throw effect_exception
{AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
};
304 void Equalizer_getParamfv(const EffectProps
*props
, ALenum param
, float *vals
)
305 { Equalizer_getParamf(props
, param
, vals
); }
307 DEFINE_ALEFFECT_VTABLE(Equalizer
);
310 struct EqualizerStateFactory final
: public EffectStateFactory
{
311 EffectState
*create() override
{ return new EqualizerState
{}; }
312 EffectProps
getDefaultProps() const noexcept override
;
313 const EffectVtable
*getEffectVtable() const noexcept override
{ return &Equalizer_vtable
; }
316 EffectProps
EqualizerStateFactory::getDefaultProps() const noexcept
319 props
.Equalizer
.LowCutoff
= AL_EQUALIZER_DEFAULT_LOW_CUTOFF
;
320 props
.Equalizer
.LowGain
= AL_EQUALIZER_DEFAULT_LOW_GAIN
;
321 props
.Equalizer
.Mid1Center
= AL_EQUALIZER_DEFAULT_MID1_CENTER
;
322 props
.Equalizer
.Mid1Gain
= AL_EQUALIZER_DEFAULT_MID1_GAIN
;
323 props
.Equalizer
.Mid1Width
= AL_EQUALIZER_DEFAULT_MID1_WIDTH
;
324 props
.Equalizer
.Mid2Center
= AL_EQUALIZER_DEFAULT_MID2_CENTER
;
325 props
.Equalizer
.Mid2Gain
= AL_EQUALIZER_DEFAULT_MID2_GAIN
;
326 props
.Equalizer
.Mid2Width
= AL_EQUALIZER_DEFAULT_MID2_WIDTH
;
327 props
.Equalizer
.HighCutoff
= AL_EQUALIZER_DEFAULT_HIGH_CUTOFF
;
328 props
.Equalizer
.HighGain
= AL_EQUALIZER_DEFAULT_HIGH_GAIN
;
334 EffectStateFactory
*EqualizerStateFactory_getFactory()
336 static EqualizerStateFactory EqualizerFactory
{};
337 return &EqualizerFactory
;