Use c++ headers
[openal-soft.git] / Alc / effects / echo.cpp
blobcadd62f218c7a4c2ef8b5bd31dad3faab953e9a1
1 /**
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
21 #include "config.h"
23 #include <cmath>
24 #include <cstdlib>
26 #include <algorithm>
28 #include "alMain.h"
29 #include "alcontext.h"
30 #include "alFilter.h"
31 #include "alAuxEffectSlot.h"
32 #include "alError.h"
33 #include "alu.h"
34 #include "filters/biquad.h"
35 #include "vector.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
42 // current offset
43 struct {
44 ALsizei delay{0};
45 } mTap[2];
46 ALsizei mOffset{0};
48 /* The panning gains for the two taps */
49 struct {
50 ALfloat Current[MAX_OUTPUT_CHANNELS]{};
51 ALfloat Target[MAX_OUTPUT_CHANNELS]{};
52 } mGains[2];
54 ALfloat mFeedGain{0.0f};
56 BiquadFilter mFilter;
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)
68 ALuint maxlen;
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);
84 for(auto &e : mGains)
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);
90 return AL_TRUE;
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;
105 else 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};
135 ALfloat z1, z2;
136 ALsizei base;
137 ALsizei c, i;
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];
150 /* First tap */
151 temps[0][i] = delaybuf[(offset-tap1) & mask];
152 /* Second tap */
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;
161 offset++;
164 for(c = 0;c < 2;c++)
165 MixSamples(temps[c], NumChannels, SamplesOut, mGains[c].Current,
166 mGains[c].Target, SamplesToDo-base, base, td);
168 base += td;
170 mFilter.setComponents(z1, z2);
172 mOffset = offset;
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{};
186 return &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;
197 switch(param)
199 case AL_ECHO_DELAY:
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;
203 break;
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;
209 break;
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;
215 break;
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;
221 break;
223 case AL_ECHO_SPREAD:
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;
227 break;
229 default:
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;
243 switch(param)
245 case AL_ECHO_DELAY:
246 *val = props->Echo.Delay;
247 break;
249 case AL_ECHO_LRDELAY:
250 *val = props->Echo.LRDelay;
251 break;
253 case AL_ECHO_DAMPING:
254 *val = props->Echo.Damping;
255 break;
257 case AL_ECHO_FEEDBACK:
258 *val = props->Echo.Feedback;
259 break;
261 case AL_ECHO_SPREAD:
262 *val = props->Echo.Spread;
263 break;
265 default:
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);