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 ALfloat CurrentGains
[MAX_OUTPUT_CHANNELS
]{};
88 ALfloat TargetGains
[MAX_OUTPUT_CHANNELS
]{};
89 } mChans
[MAX_AMBI_CHANNELS
];
91 ALfloat mSampleBuffer
[BUFFERSIZE
]{};
94 ALboolean
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 ALboolean
EqualizerState::deviceUpdate(const ALCdevice
*)
103 for(auto &e
: mChans
)
105 std::for_each(std::begin(e
.filter
), std::end(e
.filter
),
106 std::mem_fn(&BiquadFilter::clear
));
107 std::fill(std::begin(e
.CurrentGains
), std::end(e
.CurrentGains
), 0.0f
);
112 void EqualizerState::update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
)
114 const ALCdevice
*device
{context
->mDevice
.get()};
115 auto frequency
= static_cast<ALfloat
>(device
->Frequency
);
116 ALfloat gain
, f0norm
;
118 /* Calculate coefficients for the each type of filter. Note that the shelf
119 * and peaking filters' gain is for the centerpoint of the transition band,
120 * meaning its dB needs to be doubled for the shelf or peak to reach the
123 gain
= maxf(std::sqrt(props
->Equalizer
.LowGain
), 0.0625f
); /* Limit -24dB */
124 f0norm
= props
->Equalizer
.LowCutoff
/frequency
;
125 mChans
[0].filter
[0].setParams(BiquadType::LowShelf
, gain
, f0norm
,
126 BiquadFilter::rcpQFromSlope(gain
, 0.75f
));
128 gain
= maxf(std::sqrt(props
->Equalizer
.Mid1Gain
), 0.0625f
);
129 f0norm
= props
->Equalizer
.Mid1Center
/frequency
;
130 mChans
[0].filter
[1].setParams(BiquadType::Peaking
, gain
, f0norm
,
131 BiquadFilter::rcpQFromBandwidth(f0norm
, props
->Equalizer
.Mid1Width
));
133 gain
= maxf(std::sqrt(props
->Equalizer
.Mid2Gain
), 0.0625f
);
134 f0norm
= props
->Equalizer
.Mid2Center
/frequency
;
135 mChans
[0].filter
[2].setParams(BiquadType::Peaking
, gain
, f0norm
,
136 BiquadFilter::rcpQFromBandwidth(f0norm
, props
->Equalizer
.Mid2Width
));
138 gain
= maxf(std::sqrt(props
->Equalizer
.HighGain
), 0.0625f
);
139 f0norm
= props
->Equalizer
.HighCutoff
/frequency
;
140 mChans
[0].filter
[3].setParams(BiquadType::HighShelf
, gain
, f0norm
,
141 BiquadFilter::rcpQFromSlope(gain
, 0.75f
));
143 /* Copy the filter coefficients for the other input channels. */
144 for(size_t i
{1u};i
< slot
->Wet
.Buffer
.size();++i
)
146 mChans
[i
].filter
[0].copyParamsFrom(mChans
[0].filter
[0]);
147 mChans
[i
].filter
[1].copyParamsFrom(mChans
[0].filter
[1]);
148 mChans
[i
].filter
[2].copyParamsFrom(mChans
[0].filter
[2]);
149 mChans
[i
].filter
[3].copyParamsFrom(mChans
[0].filter
[3]);
152 mOutTarget
= target
.Main
->Buffer
;
153 for(size_t i
{0u};i
< slot
->Wet
.Buffer
.size();++i
)
155 auto coeffs
= GetAmbiIdentityRow(i
);
156 ComputePanGains(target
.Main
, coeffs
.data(), slot
->Params
.Gain
, mChans
[i
].TargetGains
);
160 void EqualizerState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
162 auto chandata
= std::addressof(mChans
[0]);
163 for(const auto &input
: samplesIn
)
165 chandata
->filter
[0].process(mSampleBuffer
, input
.data(), samplesToDo
);
166 chandata
->filter
[1].process(mSampleBuffer
, mSampleBuffer
, samplesToDo
);
167 chandata
->filter
[2].process(mSampleBuffer
, mSampleBuffer
, samplesToDo
);
168 chandata
->filter
[3].process(mSampleBuffer
, mSampleBuffer
, samplesToDo
);
170 MixSamples({mSampleBuffer
, samplesToDo
}, samplesOut
, chandata
->CurrentGains
,
171 chandata
->TargetGains
, samplesToDo
, 0);
177 void Equalizer_setParami(EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
)
178 { context
->setError(AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
); }
179 void Equalizer_setParamiv(EffectProps
*, ALCcontext
*context
, ALenum param
, const ALint
*)
180 { context
->setError(AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x", param
); }
181 void Equalizer_setParamf(EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat val
)
185 case AL_EQUALIZER_LOW_GAIN
:
186 if(!(val
>= AL_EQUALIZER_MIN_LOW_GAIN
&& val
<= AL_EQUALIZER_MAX_LOW_GAIN
))
187 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer low-band gain out of range");
188 props
->Equalizer
.LowGain
= val
;
191 case AL_EQUALIZER_LOW_CUTOFF
:
192 if(!(val
>= AL_EQUALIZER_MIN_LOW_CUTOFF
&& val
<= AL_EQUALIZER_MAX_LOW_CUTOFF
))
193 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer low-band cutoff out of range");
194 props
->Equalizer
.LowCutoff
= val
;
197 case AL_EQUALIZER_MID1_GAIN
:
198 if(!(val
>= AL_EQUALIZER_MIN_MID1_GAIN
&& val
<= AL_EQUALIZER_MAX_MID1_GAIN
))
199 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band gain out of range");
200 props
->Equalizer
.Mid1Gain
= val
;
203 case AL_EQUALIZER_MID1_CENTER
:
204 if(!(val
>= AL_EQUALIZER_MIN_MID1_CENTER
&& val
<= AL_EQUALIZER_MAX_MID1_CENTER
))
205 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band center out of range");
206 props
->Equalizer
.Mid1Center
= val
;
209 case AL_EQUALIZER_MID1_WIDTH
:
210 if(!(val
>= AL_EQUALIZER_MIN_MID1_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID1_WIDTH
))
211 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band width out of range");
212 props
->Equalizer
.Mid1Width
= val
;
215 case AL_EQUALIZER_MID2_GAIN
:
216 if(!(val
>= AL_EQUALIZER_MIN_MID2_GAIN
&& val
<= AL_EQUALIZER_MAX_MID2_GAIN
))
217 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band gain out of range");
218 props
->Equalizer
.Mid2Gain
= val
;
221 case AL_EQUALIZER_MID2_CENTER
:
222 if(!(val
>= AL_EQUALIZER_MIN_MID2_CENTER
&& val
<= AL_EQUALIZER_MAX_MID2_CENTER
))
223 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band center out of range");
224 props
->Equalizer
.Mid2Center
= val
;
227 case AL_EQUALIZER_MID2_WIDTH
:
228 if(!(val
>= AL_EQUALIZER_MIN_MID2_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID2_WIDTH
))
229 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band width out of range");
230 props
->Equalizer
.Mid2Width
= val
;
233 case AL_EQUALIZER_HIGH_GAIN
:
234 if(!(val
>= AL_EQUALIZER_MIN_HIGH_GAIN
&& val
<= AL_EQUALIZER_MAX_HIGH_GAIN
))
235 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer high-band gain out of range");
236 props
->Equalizer
.HighGain
= val
;
239 case AL_EQUALIZER_HIGH_CUTOFF
:
240 if(!(val
>= AL_EQUALIZER_MIN_HIGH_CUTOFF
&& val
<= AL_EQUALIZER_MAX_HIGH_CUTOFF
))
241 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer high-band cutoff out of range");
242 props
->Equalizer
.HighCutoff
= val
;
246 context
->setError(AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
);
249 void Equalizer_setParamfv(EffectProps
*props
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
250 { Equalizer_setParamf(props
, context
, param
, vals
[0]); }
252 void Equalizer_getParami(const EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
*)
253 { context
->setError(AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
); }
254 void Equalizer_getParamiv(const EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
*)
255 { context
->setError(AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x", param
); }
256 void Equalizer_getParamf(const EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat
*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 context
->setError(AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
);
304 void Equalizer_getParamfv(const EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
305 { Equalizer_getParamf(props
, context
, 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
;