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
27 #include "alcontext.h"
29 #include "alAuxEffectSlot.h"
32 #include "filters/defs.h"
35 struct ALechoState final
: public ALeffectState
{
36 ALfloat
*SampleBuffer
;
39 // The echo is two tap. The delay is the number of samples from before the
46 /* The panning gains for the two taps */
48 ALfloat Current
[MAX_OUTPUT_CHANNELS
];
49 ALfloat Target
[MAX_OUTPUT_CHANNELS
];
57 static ALvoid
ALechoState_Destruct(ALechoState
*state
);
58 static ALboolean
ALechoState_deviceUpdate(ALechoState
*state
, ALCdevice
*Device
);
59 static ALvoid
ALechoState_update(ALechoState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
);
60 static ALvoid
ALechoState_process(ALechoState
*state
, ALsizei SamplesToDo
, const ALfloat (*RESTRICT SamplesIn
)[BUFFERSIZE
], ALfloat (*RESTRICT SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
);
61 DECLARE_DEFAULT_ALLOCATORS(ALechoState
)
63 DEFINE_ALEFFECTSTATE_VTABLE(ALechoState
);
66 static void ALechoState_Construct(ALechoState
*state
)
68 new (state
) ALechoState
{};
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
));
87 state
->~ALechoState();
90 static ALboolean
ALechoState_deviceUpdate(ALechoState
*state
, ALCdevice
*Device
)
94 // Use the next power of 2 for the buffer length, so the tap offsets can be
95 // wrapped using a mask instead of a modulo
96 maxlen
= float2int(AL_ECHO_MAX_DELAY
*Device
->Frequency
+ 0.5f
) +
97 float2int(AL_ECHO_MAX_LRDELAY
*Device
->Frequency
+ 0.5f
);
98 maxlen
= NextPowerOf2(maxlen
);
99 if(maxlen
<= 0) return AL_FALSE
;
101 if(maxlen
!= state
->BufferLength
)
103 void *temp
= al_calloc(16, maxlen
* sizeof(ALfloat
));
104 if(!temp
) return AL_FALSE
;
106 al_free(state
->SampleBuffer
);
107 state
->SampleBuffer
= static_cast<float*>(temp
);
108 state
->BufferLength
= maxlen
;
111 memset(state
->SampleBuffer
, 0, state
->BufferLength
*sizeof(ALfloat
));
112 memset(state
->Gains
, 0, sizeof(state
->Gains
));
117 static ALvoid
ALechoState_update(ALechoState
*state
, const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
)
119 const ALCdevice
*device
= context
->Device
;
120 ALuint frequency
= device
->Frequency
;
121 ALfloat coeffs
[MAX_AMBI_COEFFS
];
122 ALfloat gainhf
, lrpan
, spread
;
124 state
->Tap
[0].delay
= maxi(float2int(props
->Echo
.Delay
*frequency
+ 0.5f
), 1);
125 state
->Tap
[1].delay
= float2int(props
->Echo
.LRDelay
*frequency
+ 0.5f
);
126 state
->Tap
[1].delay
+= state
->Tap
[0].delay
;
128 spread
= props
->Echo
.Spread
;
129 if(spread
< 0.0f
) lrpan
= -1.0f
;
131 /* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
132 * spread (where 0 = point, tau = omni).
134 spread
= asinf(1.0f
- fabsf(spread
))*4.0f
;
136 state
->FeedGain
= props
->Echo
.Feedback
;
138 gainhf
= maxf(1.0f
- props
->Echo
.Damping
, 0.0625f
); /* Limit -24dB */
139 BiquadFilter_setParams(&state
->Filter
, BiquadType_HighShelf
,
140 gainhf
, LOWPASSFREQREF
/frequency
, calc_rcpQ_from_slope(gainhf
, 1.0f
)
143 /* First tap panning */
144 CalcAngleCoeffs(-F_PI_2
*lrpan
, 0.0f
, spread
, coeffs
);
145 ComputePanGains(&device
->Dry
, coeffs
, slot
->Params
.Gain
, state
->Gains
[0].Target
);
147 /* Second tap panning */
148 CalcAngleCoeffs( F_PI_2
*lrpan
, 0.0f
, spread
, coeffs
);
149 ComputePanGains(&device
->Dry
, coeffs
, slot
->Params
.Gain
, state
->Gains
[1].Target
);
152 static ALvoid
ALechoState_process(ALechoState
*state
, ALsizei SamplesToDo
, const ALfloat (*RESTRICT SamplesIn
)[BUFFERSIZE
], ALfloat (*RESTRICT SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
)
154 const ALsizei mask
= state
->BufferLength
-1;
155 const ALsizei tap1
= state
->Tap
[0].delay
;
156 const ALsizei tap2
= state
->Tap
[1].delay
;
157 ALfloat
*RESTRICT delaybuf
= state
->SampleBuffer
;
158 ALsizei offset
= state
->Offset
;
159 ALfloat z1
, z2
, in
, out
;
163 z1
= state
->Filter
.z1
;
164 z2
= state
->Filter
.z2
;
165 for(base
= 0;base
< SamplesToDo
;)
167 alignas(16) ALfloat temps
[2][128];
168 ALsizei td
= mini(128, SamplesToDo
-base
);
170 for(i
= 0;i
< td
;i
++)
172 /* Feed the delay buffer's input first. */
173 delaybuf
[offset
&mask
] = SamplesIn
[0][i
+base
];
176 temps
[0][i
] = delaybuf
[(offset
-tap1
) & mask
];
178 temps
[1][i
] = delaybuf
[(offset
-tap2
) & mask
];
180 /* Apply damping to the second tap, then add it to the buffer with
181 * feedback attenuation.
184 out
= in
*state
->Filter
.b0
+ z1
;
185 z1
= in
*state
->Filter
.b1
- out
*state
->Filter
.a1
+ z2
;
186 z2
= in
*state
->Filter
.b2
- out
*state
->Filter
.a2
;
188 delaybuf
[offset
&mask
] += out
* state
->FeedGain
;
193 MixSamples(temps
[c
], NumChannels
, SamplesOut
, state
->Gains
[c
].Current
,
194 state
->Gains
[c
].Target
, SamplesToDo
-base
, base
, td
);
198 state
->Filter
.z1
= z1
;
199 state
->Filter
.z2
= z2
;
201 state
->Offset
= offset
;
205 struct EchoStateFactory final
: public EffectStateFactory
{
206 ALeffectState
*create() override
;
209 ALeffectState
*EchoStateFactory::create()
212 NEW_OBJ0(state
, ALechoState
)();
216 EffectStateFactory
*EchoStateFactory_getFactory(void)
218 static EchoStateFactory EchoFactory
{};
223 void ALecho_setParami(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
UNUSED(val
))
224 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
225 void ALecho_setParamiv(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, const ALint
*UNUSED(vals
))
226 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
227 void ALecho_setParamf(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat val
)
229 ALeffectProps
*props
= &effect
->Props
;
233 if(!(val
>= AL_ECHO_MIN_DELAY
&& val
<= AL_ECHO_MAX_DELAY
))
234 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo delay out of range");
235 props
->Echo
.Delay
= val
;
238 case AL_ECHO_LRDELAY
:
239 if(!(val
>= AL_ECHO_MIN_LRDELAY
&& val
<= AL_ECHO_MAX_LRDELAY
))
240 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo LR delay out of range");
241 props
->Echo
.LRDelay
= val
;
244 case AL_ECHO_DAMPING
:
245 if(!(val
>= AL_ECHO_MIN_DAMPING
&& val
<= AL_ECHO_MAX_DAMPING
))
246 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo damping out of range");
247 props
->Echo
.Damping
= val
;
250 case AL_ECHO_FEEDBACK
:
251 if(!(val
>= AL_ECHO_MIN_FEEDBACK
&& val
<= AL_ECHO_MAX_FEEDBACK
))
252 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo feedback out of range");
253 props
->Echo
.Feedback
= val
;
257 if(!(val
>= AL_ECHO_MIN_SPREAD
&& val
<= AL_ECHO_MAX_SPREAD
))
258 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo spread out of range");
259 props
->Echo
.Spread
= val
;
263 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
266 void ALecho_setParamfv(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
267 { ALecho_setParamf(effect
, context
, param
, vals
[0]); }
269 void ALecho_getParami(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(val
))
270 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
271 void ALecho_getParamiv(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(vals
))
272 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
273 void ALecho_getParamf(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*val
)
275 const ALeffectProps
*props
= &effect
->Props
;
279 *val
= props
->Echo
.Delay
;
282 case AL_ECHO_LRDELAY
:
283 *val
= props
->Echo
.LRDelay
;
286 case AL_ECHO_DAMPING
:
287 *val
= props
->Echo
.Damping
;
290 case AL_ECHO_FEEDBACK
:
291 *val
= props
->Echo
.Feedback
;
295 *val
= props
->Echo
.Spread
;
299 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
302 void ALecho_getParamfv(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
303 { ALecho_getParamf(effect
, context
, param
, vals
); }
305 DEFINE_ALEFFECT_VTABLE(ALecho
);