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
29 #include "alcontext.h"
31 #include "alAuxEffectSlot.h"
34 #include "filters/biquad.h"
38 struct ALechoState final
: public EffectState
{
39 al::vector
<ALfloat
,16> mSampleBuffer
;
41 // The echo is two tap. The delay is the number of samples from before the
48 /* The panning gains for the two taps */
50 ALfloat Current
[MAX_OUTPUT_CHANNELS
]{};
51 ALfloat Target
[MAX_OUTPUT_CHANNELS
]{};
54 ALfloat mFeedGain
{0.0f
};
59 ALboolean
deviceUpdate(const ALCdevice
*device
) override
;
60 void update(const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
, const EffectTarget target
) override
;
61 void process(ALsizei samplesToDo
, const ALfloat (*RESTRICT samplesIn
)[BUFFERSIZE
], ALfloat (*RESTRICT samplesOut
)[BUFFERSIZE
], ALsizei numChannels
) override
;
63 DEF_NEWDEL(ALechoState
)
66 ALboolean
ALechoState::deviceUpdate(const ALCdevice
*Device
)
70 // Use the next power of 2 for the buffer length, so the tap offsets can be
71 // wrapped using a mask instead of a modulo
72 maxlen
= float2int(AL_ECHO_MAX_DELAY
*Device
->Frequency
+ 0.5f
) +
73 float2int(AL_ECHO_MAX_LRDELAY
*Device
->Frequency
+ 0.5f
);
74 maxlen
= NextPowerOf2(maxlen
);
75 if(maxlen
<= 0) return AL_FALSE
;
77 if(maxlen
!= mSampleBuffer
.size())
79 mSampleBuffer
.resize(maxlen
);
80 mSampleBuffer
.shrink_to_fit();
83 std::fill(mSampleBuffer
.begin(), mSampleBuffer
.end(), 0.0f
);
86 std::fill(std::begin(e
.Current
), std::end(e
.Current
), 0.0f
);
87 std::fill(std::begin(e
.Target
), std::end(e
.Target
), 0.0f
);
93 void ALechoState::update(const ALCcontext
*context
, const ALeffectslot
*slot
, const ALeffectProps
*props
, const EffectTarget target
)
95 const ALCdevice
*device
= context
->Device
;
96 ALuint frequency
= device
->Frequency
;
97 ALfloat gainhf
, lrpan
, spread
;
99 mTap
[0].delay
= maxi(float2int(props
->Echo
.Delay
*frequency
+ 0.5f
), 1);
100 mTap
[1].delay
= float2int(props
->Echo
.LRDelay
*frequency
+ 0.5f
);
101 mTap
[1].delay
+= mTap
[0].delay
;
103 spread
= props
->Echo
.Spread
;
104 if(spread
< 0.0f
) lrpan
= -1.0f
;
106 /* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
107 * spread (where 0 = point, tau = omni).
109 spread
= asinf(1.0f
- fabsf(spread
))*4.0f
;
111 mFeedGain
= props
->Echo
.Feedback
;
113 gainhf
= maxf(1.0f
- props
->Echo
.Damping
, 0.0625f
); /* Limit -24dB */
114 mFilter
.setParams(BiquadType::HighShelf
, gainhf
, LOWPASSFREQREF
/frequency
,
115 calc_rcpQ_from_slope(gainhf
, 1.0f
)
118 ALfloat coeffs
[2][MAX_AMBI_COEFFS
];
119 CalcAngleCoeffs(al::MathDefs
<float>::Pi()*-0.5f
*lrpan
, 0.0f
, spread
, coeffs
[0]);
120 CalcAngleCoeffs(al::MathDefs
<float>::Pi()* 0.5f
*lrpan
, 0.0f
, spread
, coeffs
[1]);
122 mOutBuffer
= target
.Main
->Buffer
;
123 mOutChannels
= target
.Main
->NumChannels
;
124 ComputePanGains(target
.Main
, coeffs
[0], slot
->Params
.Gain
, mGains
[0].Target
);
125 ComputePanGains(target
.Main
, coeffs
[1], slot
->Params
.Gain
, mGains
[1].Target
);
128 void ALechoState::process(ALsizei SamplesToDo
, const ALfloat (*RESTRICT SamplesIn
)[BUFFERSIZE
], ALfloat (*RESTRICT SamplesOut
)[BUFFERSIZE
], ALsizei NumChannels
)
130 const auto mask
= static_cast<ALsizei
>(mSampleBuffer
.size()-1);
131 const ALsizei tap1
{mTap
[0].delay
};
132 const ALsizei tap2
{mTap
[1].delay
};
133 ALfloat
*RESTRICT delaybuf
{mSampleBuffer
.data()};
134 ALsizei offset
{mOffset
};
139 std::tie(z1
, z2
) = mFilter
.getComponents();
140 for(base
= 0;base
< SamplesToDo
;)
142 alignas(16) ALfloat temps
[2][128];
143 ALsizei td
= mini(128, SamplesToDo
-base
);
145 for(i
= 0;i
< td
;i
++)
147 /* Feed the delay buffer's input first. */
148 delaybuf
[offset
&mask
] = SamplesIn
[0][i
+base
];
151 temps
[0][i
] = delaybuf
[(offset
-tap1
) & mask
];
153 temps
[1][i
] = delaybuf
[(offset
-tap2
) & mask
];
155 /* Apply damping to the second tap, then add it to the buffer with
156 * feedback attenuation.
158 float out
{mFilter
.processOne(temps
[1][i
], z1
, z2
)};
160 delaybuf
[offset
&mask
] += out
* mFeedGain
;
165 MixSamples(temps
[c
], NumChannels
, SamplesOut
, mGains
[c
].Current
,
166 mGains
[c
].Target
, SamplesToDo
-base
, base
, td
);
170 mFilter
.setComponents(z1
, z2
);
176 struct EchoStateFactory final
: public EffectStateFactory
{
177 EffectState
*create() override
;
180 EffectState
*EchoStateFactory::create()
181 { return new ALechoState
{}; }
183 EffectStateFactory
*EchoStateFactory_getFactory(void)
185 static EchoStateFactory EchoFactory
{};
190 void ALecho_setParami(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
UNUSED(val
))
191 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
192 void ALecho_setParamiv(ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, const ALint
*UNUSED(vals
))
193 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
194 void ALecho_setParamf(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat val
)
196 ALeffectProps
*props
= &effect
->Props
;
200 if(!(val
>= AL_ECHO_MIN_DELAY
&& val
<= AL_ECHO_MAX_DELAY
))
201 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo delay out of range");
202 props
->Echo
.Delay
= val
;
205 case AL_ECHO_LRDELAY
:
206 if(!(val
>= AL_ECHO_MIN_LRDELAY
&& val
<= AL_ECHO_MAX_LRDELAY
))
207 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo LR delay out of range");
208 props
->Echo
.LRDelay
= val
;
211 case AL_ECHO_DAMPING
:
212 if(!(val
>= AL_ECHO_MIN_DAMPING
&& val
<= AL_ECHO_MAX_DAMPING
))
213 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo damping out of range");
214 props
->Echo
.Damping
= val
;
217 case AL_ECHO_FEEDBACK
:
218 if(!(val
>= AL_ECHO_MIN_FEEDBACK
&& val
<= AL_ECHO_MAX_FEEDBACK
))
219 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo feedback out of range");
220 props
->Echo
.Feedback
= val
;
224 if(!(val
>= AL_ECHO_MIN_SPREAD
&& val
<= AL_ECHO_MAX_SPREAD
))
225 SETERR_RETURN(context
, AL_INVALID_VALUE
,, "Echo spread out of range");
226 props
->Echo
.Spread
= val
;
230 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
233 void ALecho_setParamfv(ALeffect
*effect
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
234 { ALecho_setParamf(effect
, context
, param
, vals
[0]); }
236 void ALecho_getParami(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(val
))
237 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
); }
238 void ALecho_getParamiv(const ALeffect
*UNUSED(effect
), ALCcontext
*context
, ALenum param
, ALint
*UNUSED(vals
))
239 { alSetError(context
, AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
); }
240 void ALecho_getParamf(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*val
)
242 const ALeffectProps
*props
= &effect
->Props
;
246 *val
= props
->Echo
.Delay
;
249 case AL_ECHO_LRDELAY
:
250 *val
= props
->Echo
.LRDelay
;
253 case AL_ECHO_DAMPING
:
254 *val
= props
->Echo
.Damping
;
257 case AL_ECHO_FEEDBACK
:
258 *val
= props
->Echo
.Feedback
;
262 *val
= props
->Echo
.Spread
;
266 alSetError(context
, AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
);
269 void ALecho_getParamfv(const ALeffect
*effect
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
270 { ALecho_getParamf(effect
, context
, param
, vals
); }
272 DEFINE_ALEFFECT_VTABLE(ALecho
);