Remove extraneous parenthesis
[openal-soft.git] / alc / effects / equalizer.cpp
blob8f002f8cf9b6589d445fc78ae8d0561b1b045b2d
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 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);
114 float gain, f0norm;
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);
166 ++chan;
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",
176 param};
178 void Equalizer_setParamf(EffectProps *props, ALenum param, float val)
180 switch(param)
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;
186 break;
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;
192 break;
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;
198 break;
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;
204 break;
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;
210 break;
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;
216 break;
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;
222 break;
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;
228 break;
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;
234 break;
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;
240 break;
242 default:
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",
254 param};
256 void Equalizer_getParamf(const EffectProps *props, ALenum param, float *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 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
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;