2 * OpenAL cross platform audio library
3 * Copyright (C) 2009 by Chris Robinson.
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
28 #include "alAuxEffectSlot.h"
31 #include "filters/defs.h"
34 typedef struct ALechoState
{
35 DERIVE_FROM_TYPE(ALeffectState
);
37 ALfloat
*SampleBuffer
;
40 // The echo is two tap. The delay is the number of samples from before the
47 /* The panning gains for the two taps */
49 ALfloat Current
[MAX_OUTPUT_CHANNELS
];
50 ALfloat Target
[MAX_OUTPUT_CHANNELS
];
58 static ALvoid
ALechoState_Destruct(ALechoState
*state
);
59 static ALboolean
ALechoState_deviceUpdate(ALechoState
*state
, ALCdevice
*Device
);
60 static ALvoid
ALechoState_update(ALechoState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
);
61 static ALvoid
ALechoState_process(ALechoState
*state
, ALsizei SamplesToDo
, const ALfloat (*restrict SamplesIn
)[BUFFERSIZE
], ALfloat (*restrict SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
);
62 DECLARE_DEFAULT_ALLOCATORS(ALechoState
)
64 DEFINE_ALEFFECTSTATE_VTABLE(ALechoState
);
67 static void ALechoState_Construct(ALechoState
*state
)
69 ALeffectState_Construct(STATIC_CAST(ALeffectState
, state
));
70 SET_VTABLE2(ALechoState
, ALeffectState
, state
);
72 state
->BufferLength
= 0;
73 state
->SampleBuffer
= NULL
;
75 state
->Tap
[0].delay
= 0;
76 state
->Tap
[1].delay
= 0;
79 BiquadFilter_clear(&state
->Filter
);
82 static ALvoid
ALechoState_Destruct(ALechoState
*state
)
84 al_free(state
->SampleBuffer
);
85 state
->SampleBuffer
= NULL
;
86 ALeffectState_Destruct(STATIC_CAST(ALeffectState
,state
));
89 static ALboolean
ALechoState_deviceUpdate(ALechoState
*state
, ALCdevice
*Device
)
93 // Use the next power of 2 for the buffer length, so the tap offsets can be
94 // wrapped using a mask instead of a modulo
95 maxlen
= float2int(AL_ECHO_MAX_DELAY
*Device
->Frequency
+ 0.5f
) +
96 float2int(AL_ECHO_MAX_LRDELAY
*Device
->Frequency
+ 0.5f
);
97 maxlen
= NextPowerOf2(maxlen
);
98 if(maxlen
<= 0) return AL_FALSE
;
100 if(maxlen
!= state
->BufferLength
)
102 void *temp
= al_calloc(16, maxlen
* sizeof(ALfloat
));
103 if(!temp
) return AL_FALSE
;
105 al_free(state
->SampleBuffer
);
106 state
->SampleBuffer
= temp
;
107 state
->BufferLength
= maxlen
;
110 memset(state
->SampleBuffer
, 0, state
->BufferLength
*sizeof(ALfloat
));
111 memset(state
->Gains
, 0, sizeof(state
->Gains
));
116 static ALvoid
ALechoState_update(ALechoState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
)
118 const ALCdevice
*device
= context
->Device
;
119 ALuint frequency
= device
->Frequency
;
120 ALfloat coeffs
[MAX_AMBI_COEFFS
];
121 ALfloat gainhf
, lrpan
, spread
;
123 state
->Tap
[0].delay
= maxi(float2int(props
->Echo
.Delay
*frequency
+ 0.5f
), 1);
124 state
->Tap
[1].delay
= float2int(props
->Echo
.LRDelay
*frequency
+ 0.5f
);
125 state
->Tap
[1].delay
+= state
->Tap
[0].delay
;
127 spread
= props
->Echo
.Spread
;
128 if(spread
< 0.0f
) lrpan
= -1.0f
;
130 /* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
131 * spread (where 0 = point, tau = omni).
133 spread
= asinf(1.0f
- fabsf(spread
))*4.0f
;
135 state
->FeedGain
= props
->Echo
.Feedback
;
137 gainhf
= maxf(1.0f
- props
->Echo
.Damping
, 0.0625f
); /* Limit -24dB */
138 BiquadFilter_setParams(&state
->Filter
, BiquadType_HighShelf
,
139 gainhf
, LOWPASSFREQREF
/frequency
, calc_rcpQ_from_slope(gainhf
, 1.0f
)
142 /* First tap panning */
143 CalcAngleCoeffs(-F_PI_2
*lrpan
, 0.0f
, spread
, coeffs
);
144 ComputePanGains(&device
->Dry
, coeffs
, slot
->Params
.Gain
, state
->Gains
[0].Target
);
146 /* Second tap panning */
147 CalcAngleCoeffs( F_PI_2
*lrpan
, 0.0f
, spread
, coeffs
);
148 ComputePanGains(&device
->Dry
, coeffs
, slot
->Params
.Gain
, state
->Gains
[1].Target
);
151 static ALvoid
ALechoState_process(ALechoState
*state
, ALsizei SamplesToDo
, const ALfloat (*restrict SamplesIn
)[BUFFERSIZE
], ALfloat (*restrict SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
)
153 const ALsizei mask
= state
->BufferLength
-1;
154 const ALsizei tap1
= state
->Tap
[0].delay
;
155 const ALsizei tap2
= state
->Tap
[1].delay
;
156 ALfloat
*restrict delaybuf
= state
->SampleBuffer
;
157 ALsizei offset
= state
->Offset
;
158 ALfloat z1
, z2
, in
, out
;
162 z1
= state
->Filter
.z1
;
163 z2
= state
->Filter
.z2
;
164 for(base
= 0;base
< SamplesToDo
;)
166 alignas(16) ALfloat temps
[2][128];
167 ALsizei td
= mini(128, SamplesToDo
-base
);
169 for(i
= 0;i
< td
;i
++)
171 /* Feed the delay buffer's input first. */
172 delaybuf
[offset
&mask
] = SamplesIn
[0][i
+base
];
175 temps
[0][i
] = delaybuf
[(offset
-tap1
) & mask
];
177 temps
[1][i
] = delaybuf
[(offset
-tap2
) & mask
];
179 /* Apply damping to the second tap, then add it to the buffer with
180 * feedback attenuation.
183 out
= in
*state
->Filter
.b0
+ z1
;
184 z1
= in
*state
->Filter
.b1
- out
*state
->Filter
.a1
+ z2
;
185 z2
= in
*state
->Filter
.b2
- out
*state
->Filter
.a2
;
187 delaybuf
[offset
&mask
] += out
* state
->FeedGain
;
192 MixSamples(temps
[c
], NumChannels
, SamplesOut
, state
->Gains
[c
].Current
,
193 state
->Gains
[c
].Target
, SamplesToDo
-base
, base
, td
);
197 state
->Filter
.z1
= z1
;
198 state
->Filter
.z2
= z2
;
200 state
->Offset
= offset
;
204 typedef struct EchoStateFactory
{
205 DERIVE_FROM_TYPE(EffectStateFactory
);
208 ALeffectState
*EchoStateFactory_create(EchoStateFactory
*UNUSED(factory
))
212 NEW_OBJ0(state
, ALechoState
)();
213 if(!state
) return NULL
;
215 return STATIC_CAST(ALeffectState
, state
);
218 DEFINE_EFFECTSTATEFACTORY_VTABLE(EchoStateFactory
);
220 EffectStateFactory
*EchoStateFactory_getFactory(void)
222 static EchoStateFactory EchoFactory
= { { GET_VTABLE2(EchoStateFactory
, EffectStateFactory
) } };
224 return STATIC_CAST(EffectStateFactory
, &EchoFactory
);
228 void ALecho_setParami(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
UNUSED(val
))
229 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
230 void ALecho_setParamiv(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, const ALint
*UNUSED(vals
))
231 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
232 void ALecho_setParamf(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat val
)
234 ALeffectProps
*props
= &effect
->Props
;
238 if(!(val
>= AL_ECHO_MIN_DELAY
&& val
<= AL_ECHO_MAX_DELAY
))
239 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo delay out of range");
240 props
->Echo
.Delay
= val
;
243 case AL_ECHO_LRDELAY
:
244 if(!(val
>= AL_ECHO_MIN_LRDELAY
&& val
<= AL_ECHO_MAX_LRDELAY
))
245 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo LR delay out of range");
246 props
->Echo
.LRDelay
= val
;
249 case AL_ECHO_DAMPING
:
250 if(!(val
>= AL_ECHO_MIN_DAMPING
&& val
<= AL_ECHO_MAX_DAMPING
))
251 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo damping out of range");
252 props
->Echo
.Damping
= val
;
255 case AL_ECHO_FEEDBACK
:
256 if(!(val
>= AL_ECHO_MIN_FEEDBACK
&& val
<= AL_ECHO_MAX_FEEDBACK
))
257 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo feedback out of range");
258 props
->Echo
.Feedback
= val
;
262 if(!(val
>= AL_ECHO_MIN_SPREAD
&& val
<= AL_ECHO_MAX_SPREAD
))
263 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo spread out of range");
264 props
->Echo
.Spread
= val
;
268 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
271 void ALecho_setParamfv(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
272 { ALecho_setParamf(effect
, context
, param
, vals
[0]); }
274 void ALecho_getParami(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(val
))
275 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
276 void ALecho_getParamiv(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(vals
))
277 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
278 void ALecho_getParamf(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*val
)
280 const ALeffectProps
*props
= &effect
->Props
;
284 *val
= props
->Echo
.Delay
;
287 case AL_ECHO_LRDELAY
:
288 *val
= props
->Echo
.LRDelay
;
291 case AL_ECHO_DAMPING
:
292 *val
= props
->Echo
.Damping
;
295 case AL_ECHO_FEEDBACK
:
296 *val
= props
->Echo
.Feedback
;
300 *val
= props
->Echo
.Spread
;
304 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
307 void ALecho_getParamfv(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
308 { ALecho_getParamf(effect
, context
, param
, vals
); }
310 DEFINE_ALEFFECT_VTABLE(ALecho
);