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
27 #include "alAuxEffectSlot.h"
30 #include "filters/defs.h"
33 /* The document "Effects Extension Guide.pdf" says that low and high *
34 * frequencies are cutoff frequencies. This is not fully correct, they *
35 * are corner frequencies for low and high shelf filters. If they were *
36 * just cutoff frequencies, there would be no need in cutoff frequency *
37 * gains, which are present. Documentation for "Creative Proteus X2" *
38 * software describes 4-band equalizer functionality in a much better *
39 * way. This equalizer seems to be a predecessor of OpenAL 4-band *
40 * equalizer. With low and high shelf filters we are able to cutoff *
41 * frequencies below and/or above corner frequencies using attenuation *
42 * gains (below 1.0) and amplify all low and/or high frequencies using *
45 * Low-shelf Low Mid Band High Mid Band High-shelf *
46 * corner center center corner *
47 * frequency frequency frequency frequency *
48 * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
52 * B -----+ /--+--\ /--+--\ +----- *
53 * O |\ | | | | | | /| *
54 * O | \ - | - - | - / | *
55 * S + | \ | | | | | | / | *
56 * T | | | | | | | | | | *
57 * ---------+---------------+------------------+---------------+-------- *
58 * C | | | | | | | | | | *
59 * U - | / | | | | | | \ | *
60 * T | / - | - - | - \ | *
61 * O |/ | | | | | | \| *
62 * F -----+ \--+--/ \--+--/ +----- *
66 * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
67 * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
68 * octaves for two mid bands. *
70 * Implementation is based on the "Cookbook formulae for audio EQ biquad *
71 * filter coefficients" by Robert Bristow-Johnson *
72 * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
75 typedef struct ALequalizerState
{
76 DERIVE_FROM_TYPE(ALeffectState
);
79 /* Effect parameters */
80 BiquadFilter filter
[4];
82 /* Effect gains for each channel */
83 ALfloat CurrentGains
[MAX_OUTPUT_CHANNELS
];
84 ALfloat TargetGains
[MAX_OUTPUT_CHANNELS
];
85 } Chans
[MAX_EFFECT_CHANNELS
];
87 ALfloat SampleBuffer
[MAX_EFFECT_CHANNELS
][BUFFERSIZE
];
90 static ALvoid
ALequalizerState_Destruct(ALequalizerState
*state
);
91 static ALboolean
ALequalizerState_deviceUpdate(ALequalizerState
*state
, ALCdevice
*device
);
92 static ALvoid
ALequalizerState_update(ALequalizerState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
);
93 static ALvoid
ALequalizerState_process(ALequalizerState
*state
, ALsizei SamplesToDo
, const ALfloat (*restrict SamplesIn
)[BUFFERSIZE
], ALfloat (*restrict SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
);
94 DECLARE_DEFAULT_ALLOCATORS(ALequalizerState
)
96 DEFINE_ALEFFECTSTATE_VTABLE(ALequalizerState
);
99 static void ALequalizerState_Construct(ALequalizerState
*state
)
101 ALeffectState_Construct(STATIC_CAST(ALeffectState
, state
));
102 SET_VTABLE2(ALequalizerState
, ALeffectState
, state
);
105 static ALvoid
ALequalizerState_Destruct(ALequalizerState
*state
)
107 ALeffectState_Destruct(STATIC_CAST(ALeffectState
,state
));
110 static ALboolean
ALequalizerState_deviceUpdate(ALequalizerState
*state
, ALCdevice
*UNUSED(device
))
114 for(i
= 0; i
< MAX_EFFECT_CHANNELS
;i
++)
117 BiquadFilter_clear(&state
->Chans
[i
].filter
[j
]);
118 for(j
= 0;j
< MAX_OUTPUT_CHANNELS
;j
++)
119 state
->Chans
[i
].CurrentGains
[j
] = 0.0f
;
124 static ALvoid
ALequalizerState_update(ALequalizerState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
)
126 const ALCdevice
*device
= context
->Device
;
127 ALfloat frequency
= (ALfloat
)device
->Frequency
;
128 ALfloat gain
, f0norm
;
131 /* Calculate coefficients for the each type of filter. Note that the shelf
132 * filters' gain is for the reference frequency, which is the centerpoint
133 * of the transition band.
135 gain
= maxf(sqrtf(props
->Equalizer
.LowGain
), 0.0625f
); /* Limit -24dB */
136 f0norm
= props
->Equalizer
.LowCutoff
/frequency
;
137 BiquadFilter_setParams(&state
->Chans
[0].filter
[0], BiquadType_LowShelf
,
138 gain
, f0norm
, calc_rcpQ_from_slope(gain
, 0.75f
)
141 gain
= maxf(props
->Equalizer
.Mid1Gain
, 0.0625f
);
142 f0norm
= props
->Equalizer
.Mid1Center
/frequency
;
143 BiquadFilter_setParams(&state
->Chans
[0].filter
[1], BiquadType_Peaking
,
144 gain
, f0norm
, calc_rcpQ_from_bandwidth(
145 f0norm
, props
->Equalizer
.Mid1Width
149 gain
= maxf(props
->Equalizer
.Mid2Gain
, 0.0625f
);
150 f0norm
= props
->Equalizer
.Mid2Center
/frequency
;
151 BiquadFilter_setParams(&state
->Chans
[0].filter
[2], BiquadType_Peaking
,
152 gain
, f0norm
, calc_rcpQ_from_bandwidth(
153 f0norm
, props
->Equalizer
.Mid2Width
157 gain
= maxf(sqrtf(props
->Equalizer
.HighGain
), 0.0625f
);
158 f0norm
= props
->Equalizer
.HighCutoff
/frequency
;
159 BiquadFilter_setParams(&state
->Chans
[0].filter
[3], BiquadType_HighShelf
,
160 gain
, f0norm
, calc_rcpQ_from_slope(gain
, 0.75f
)
163 /* Copy the filter coefficients for the other input channels. */
164 for(i
= 1;i
< MAX_EFFECT_CHANNELS
;i
++)
166 BiquadFilter_copyParams(&state
->Chans
[i
].filter
[0], &state
->Chans
[0].filter
[0]);
167 BiquadFilter_copyParams(&state
->Chans
[i
].filter
[1], &state
->Chans
[0].filter
[1]);
168 BiquadFilter_copyParams(&state
->Chans
[i
].filter
[2], &state
->Chans
[0].filter
[2]);
169 BiquadFilter_copyParams(&state
->Chans
[i
].filter
[3], &state
->Chans
[0].filter
[3]);
172 STATIC_CAST(ALeffectState
,state
)->OutBuffer
= device
->FOAOut
.Buffer
;
173 STATIC_CAST(ALeffectState
,state
)->OutChannels
= device
->FOAOut
.NumChannels
;
174 for(i
= 0;i
< MAX_EFFECT_CHANNELS
;i
++)
175 ComputePanGains(&device
->FOAOut
, IdentityMatrixf
.m
[i
], slot
->Params
.Gain
,
176 state
->Chans
[i
].TargetGains
);
179 static ALvoid
ALequalizerState_process(ALequalizerState
*state
, ALsizei SamplesToDo
, const ALfloat (*restrict SamplesIn
)[BUFFERSIZE
], ALfloat (*restrict SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
)
181 ALfloat (*restrict temps
)[BUFFERSIZE
] = state
->SampleBuffer
;
184 for(c
= 0;c
< MAX_EFFECT_CHANNELS
;c
++)
186 BiquadFilter_process(&state
->Chans
[c
].filter
[0], temps
[0], SamplesIn
[c
], SamplesToDo
);
187 BiquadFilter_process(&state
->Chans
[c
].filter
[1], temps
[1], temps
[0], SamplesToDo
);
188 BiquadFilter_process(&state
->Chans
[c
].filter
[2], temps
[2], temps
[1], SamplesToDo
);
189 BiquadFilter_process(&state
->Chans
[c
].filter
[3], temps
[3], temps
[2], SamplesToDo
);
191 MixSamples(temps
[3], NumChannels
, SamplesOut
,
192 state
->Chans
[c
].CurrentGains
, state
->Chans
[c
].TargetGains
,
193 SamplesToDo
, 0, SamplesToDo
199 typedef struct EqualizerStateFactory
{
200 DERIVE_FROM_TYPE(EffectStateFactory
);
201 } EqualizerStateFactory
;
203 ALeffectState
*EqualizerStateFactory_create(EqualizerStateFactory
*UNUSED(factory
))
205 ALequalizerState
*state
;
207 NEW_OBJ0(state
, ALequalizerState
)();
208 if(!state
) return NULL
;
210 return STATIC_CAST(ALeffectState
, state
);
213 DEFINE_EFFECTSTATEFACTORY_VTABLE(EqualizerStateFactory
);
215 EffectStateFactory
*EqualizerStateFactory_getFactory(void)
217 static EqualizerStateFactory EqualizerFactory
= { { GET_VTABLE2(EqualizerStateFactory
, EffectStateFactory
) } };
219 return STATIC_CAST(EffectStateFactory
, &EqualizerFactory
);
223 void ALequalizer_setParami(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
UNUSED(val
))
224 { alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
); }
225 void ALequalizer_setParamiv(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, const ALint
*UNUSED(vals
))
226 { alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x", param
); }
227 void ALequalizer_setParamf(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat val
)
229 ALeffectProps
*props
= &effect
->Props
;
232 case AL_EQUALIZER_LOW_GAIN
:
233 if(!(val
>= AL_EQUALIZER_MIN_LOW_GAIN
&& val
<= AL_EQUALIZER_MAX_LOW_GAIN
))
234 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer low-band gain out of range");
235 props
->Equalizer
.LowGain
= val
;
238 case AL_EQUALIZER_LOW_CUTOFF
:
239 if(!(val
>= AL_EQUALIZER_MIN_LOW_CUTOFF
&& val
<= AL_EQUALIZER_MAX_LOW_CUTOFF
))
240 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer low-band cutoff out of range");
241 props
->Equalizer
.LowCutoff
= val
;
244 case AL_EQUALIZER_MID1_GAIN
:
245 if(!(val
>= AL_EQUALIZER_MIN_MID1_GAIN
&& val
<= AL_EQUALIZER_MAX_MID1_GAIN
))
246 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band gain out of range");
247 props
->Equalizer
.Mid1Gain
= val
;
250 case AL_EQUALIZER_MID1_CENTER
:
251 if(!(val
>= AL_EQUALIZER_MIN_MID1_CENTER
&& val
<= AL_EQUALIZER_MAX_MID1_CENTER
))
252 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band center out of range");
253 props
->Equalizer
.Mid1Center
= val
;
256 case AL_EQUALIZER_MID1_WIDTH
:
257 if(!(val
>= AL_EQUALIZER_MIN_MID1_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID1_WIDTH
))
258 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid1-band width out of range");
259 props
->Equalizer
.Mid1Width
= val
;
262 case AL_EQUALIZER_MID2_GAIN
:
263 if(!(val
>= AL_EQUALIZER_MIN_MID2_GAIN
&& val
<= AL_EQUALIZER_MAX_MID2_GAIN
))
264 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band gain out of range");
265 props
->Equalizer
.Mid2Gain
= val
;
268 case AL_EQUALIZER_MID2_CENTER
:
269 if(!(val
>= AL_EQUALIZER_MIN_MID2_CENTER
&& val
<= AL_EQUALIZER_MAX_MID2_CENTER
))
270 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band center out of range");
271 props
->Equalizer
.Mid2Center
= val
;
274 case AL_EQUALIZER_MID2_WIDTH
:
275 if(!(val
>= AL_EQUALIZER_MIN_MID2_WIDTH
&& val
<= AL_EQUALIZER_MAX_MID2_WIDTH
))
276 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer mid2-band width out of range");
277 props
->Equalizer
.Mid2Width
= val
;
280 case AL_EQUALIZER_HIGH_GAIN
:
281 if(!(val
>= AL_EQUALIZER_MIN_HIGH_GAIN
&& val
<= AL_EQUALIZER_MAX_HIGH_GAIN
))
282 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer high-band gain out of range");
283 props
->Equalizer
.HighGain
= val
;
286 case AL_EQUALIZER_HIGH_CUTOFF
:
287 if(!(val
>= AL_EQUALIZER_MIN_HIGH_CUTOFF
&& val
<= AL_EQUALIZER_MAX_HIGH_CUTOFF
))
288 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Equalizer high-band cutoff out of range");
289 props
->Equalizer
.HighCutoff
= val
;
293 alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
);
296 void ALequalizer_setParamfv(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
297 { ALequalizer_setParamf(effect
, context
, param
, vals
[0]); }
299 void ALequalizer_getParami(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(val
))
300 { alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer integer property 0x%04x", param
); }
301 void ALequalizer_getParamiv(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(vals
))
302 { alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer integer-vector property 0x%04x", param
); }
303 void ALequalizer_getParamf(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*val
)
305 const ALeffectProps
*props
= &effect
->Props
;
308 case AL_EQUALIZER_LOW_GAIN
:
309 *val
= props
->Equalizer
.LowGain
;
312 case AL_EQUALIZER_LOW_CUTOFF
:
313 *val
= props
->Equalizer
.LowCutoff
;
316 case AL_EQUALIZER_MID1_GAIN
:
317 *val
= props
->Equalizer
.Mid1Gain
;
320 case AL_EQUALIZER_MID1_CENTER
:
321 *val
= props
->Equalizer
.Mid1Center
;
324 case AL_EQUALIZER_MID1_WIDTH
:
325 *val
= props
->Equalizer
.Mid1Width
;
328 case AL_EQUALIZER_MID2_GAIN
:
329 *val
= props
->Equalizer
.Mid2Gain
;
332 case AL_EQUALIZER_MID2_CENTER
:
333 *val
= props
->Equalizer
.Mid2Center
;
336 case AL_EQUALIZER_MID2_WIDTH
:
337 *val
= props
->Equalizer
.Mid2Width
;
340 case AL_EQUALIZER_HIGH_GAIN
:
341 *val
= props
->Equalizer
.HighGain
;
344 case AL_EQUALIZER_HIGH_CUTOFF
:
345 *val
= props
->Equalizer
.HighCutoff
;
349 alSetError(context
, AL_INVALID_ENUM
, "Invalid equalizer float property 0x%04x", param
);
352 void ALequalizer_getParamfv(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
353 { ALequalizer_getParamf(effect
, context
, param
, vals
); }
355 DEFINE_ALEFFECT_VTABLE(ALequalizer
);