Clean up some unnecessary includes
[openal-soft.git] / alc / effects / equalizer.cpp
blob929bff14ba87d46d38ba5e191e6b8f5069fd91e2
1 /**
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
21 #include "config.h"
23 #include <cmath>
24 #include <cstdlib>
26 #include <algorithm>
27 #include <functional>
29 #include "al/auxeffectslot.h"
30 #include "alcmain.h"
31 #include "alcontext.h"
32 #include "alu.h"
33 #include "filters/biquad.h"
34 #include "vecmat.h"
37 namespace {
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 *
49 * gains above 1.0. *
50 * *
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 *
55 * *
56 * | | | | *
57 * | | | | *
58 * B -----+ /--+--\ /--+--\ +----- *
59 * O |\ | | | | | | /| *
60 * O | \ - | - - | - / | *
61 * S + | \ | | | | | | / | *
62 * T | | | | | | | | | | *
63 * ---------+---------------+------------------+---------------+-------- *
64 * C | | | | | | | | | | *
65 * U - | / | | | | | | \ | *
66 * T | / - | - - | - \ | *
67 * O |/ | | | | | | \| *
68 * F -----+ \--+--/ \--+--/ +----- *
69 * F | | | | *
70 * | | | | *
71 * *
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. *
75 * *
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 {
82 struct {
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);
109 return AL_TRUE;
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
121 * provided gain.
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);
172 ++chandata;
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)
183 switch(param)
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;
189 break;
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;
195 break;
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;
201 break;
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;
207 break;
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;
213 break;
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;
219 break;
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;
225 break;
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;
231 break;
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;
237 break;
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;
243 break;
245 default:
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)
258 switch(param)
260 case AL_EQUALIZER_LOW_GAIN:
261 *val = props->Equalizer.LowGain;
262 break;
264 case AL_EQUALIZER_LOW_CUTOFF:
265 *val = props->Equalizer.LowCutoff;
266 break;
268 case AL_EQUALIZER_MID1_GAIN:
269 *val = props->Equalizer.Mid1Gain;
270 break;
272 case AL_EQUALIZER_MID1_CENTER:
273 *val = props->Equalizer.Mid1Center;
274 break;
276 case AL_EQUALIZER_MID1_WIDTH:
277 *val = props->Equalizer.Mid1Width;
278 break;
280 case AL_EQUALIZER_MID2_GAIN:
281 *val = props->Equalizer.Mid2Gain;
282 break;
284 case AL_EQUALIZER_MID2_CENTER:
285 *val = props->Equalizer.Mid2Center;
286 break;
288 case AL_EQUALIZER_MID2_WIDTH:
289 *val = props->Equalizer.Mid2Width;
290 break;
292 case AL_EQUALIZER_HIGH_GAIN:
293 *val = props->Equalizer.HighGain;
294 break;
296 case AL_EQUALIZER_HIGH_CUTOFF:
297 *val = props->Equalizer.HighCutoff;
298 break;
300 default:
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
318 EffectProps props{};
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;
329 return props;
332 } // namespace
334 EffectStateFactory *EqualizerStateFactory_getFactory()
336 static EqualizerStateFactory EqualizerFactory{};
337 return &EqualizerFactory;