Don't inline the utf8 converters
[openal-soft.git] / alc / effects / echo.cpp
bloba9213df55f4a71935d146a307f2f48cfbfe78c25
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 "al/auxeffectslot.h"
29 #include "al/filter.h"
30 #include "alcmain.h"
31 #include "alcontext.h"
32 #include "alu.h"
33 #include "filters/biquad.h"
34 #include "vector.h"
37 namespace {
39 struct EchoState final : public EffectState {
40 al::vector<ALfloat,16> mSampleBuffer;
42 // The echo is two tap. The delay is the number of samples from before the
43 // current offset
44 struct {
45 size_t delay{0u};
46 } mTap[2];
47 size_t mOffset{0u};
49 /* The panning gains for the two taps */
50 struct {
51 ALfloat Current[MAX_OUTPUT_CHANNELS]{};
52 ALfloat Target[MAX_OUTPUT_CHANNELS]{};
53 } mGains[2];
55 BiquadFilter mFilter;
56 ALfloat mFeedGain{0.0f};
58 alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
60 ALboolean deviceUpdate(const ALCdevice *device) override;
61 void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
62 void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
64 DEF_NEWDEL(EchoState)
67 ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
69 const auto frequency = static_cast<float>(Device->Frequency);
71 // Use the next power of 2 for the buffer length, so the tap offsets can be
72 // wrapped using a mask instead of a modulo
73 const ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) +
74 float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))};
75 if(maxlen != mSampleBuffer.size())
77 mSampleBuffer.resize(maxlen);
78 mSampleBuffer.shrink_to_fit();
81 std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
82 for(auto &e : mGains)
84 std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
85 std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
88 return AL_TRUE;
91 void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
93 const ALCdevice *device{context->mDevice.get()};
94 const auto frequency = static_cast<ALfloat>(device->Frequency);
96 mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
97 mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
99 const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
100 mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency,
101 mFilter.rcpQFromSlope(gainhf, 1.0f));
103 mFeedGain = props->Echo.Feedback;
105 /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
106 const ALfloat angle{std::asin(props->Echo.Spread)};
108 ALfloat coeffs[2][MAX_AMBI_CHANNELS];
109 CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
110 CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
112 mOutTarget = target.Main->Buffer;
113 ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
114 ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
117 void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
119 const size_t mask{mSampleBuffer.size()-1};
120 ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
121 size_t offset{mOffset};
122 size_t tap1{offset - mTap[0].delay};
123 size_t tap2{offset - mTap[1].delay};
124 ALfloat z1, z2;
126 ASSUME(samplesToDo > 0);
128 const BiquadFilter filter{mFilter};
129 std::tie(z1, z2) = mFilter.getComponents();
130 for(size_t i{0u};i < samplesToDo;)
132 offset &= mask;
133 tap1 &= mask;
134 tap2 &= mask;
136 size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
137 do {
138 /* Feed the delay buffer's input first. */
139 delaybuf[offset] = samplesIn[0][i];
141 /* Get delayed output from the first and second taps. Use the
142 * second tap for feedback.
144 mTempBuffer[0][i] = delaybuf[tap1++];
145 mTempBuffer[1][i] = delaybuf[tap2++];
146 const float feedb{mTempBuffer[1][i++]};
148 /* Add feedback to the delay buffer with damping and attenuation. */
149 delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
150 } while(--td);
152 mFilter.setComponents(z1, z2);
153 mOffset = offset;
155 for(ALsizei c{0};c < 2;c++)
156 MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
157 samplesToDo, 0);
161 void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
162 { context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
163 void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
164 { context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
165 void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
167 switch(param)
169 case AL_ECHO_DELAY:
170 if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
171 SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
172 props->Echo.Delay = val;
173 break;
175 case AL_ECHO_LRDELAY:
176 if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
177 SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
178 props->Echo.LRDelay = val;
179 break;
181 case AL_ECHO_DAMPING:
182 if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
183 SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
184 props->Echo.Damping = val;
185 break;
187 case AL_ECHO_FEEDBACK:
188 if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
189 SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
190 props->Echo.Feedback = val;
191 break;
193 case AL_ECHO_SPREAD:
194 if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
195 SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
196 props->Echo.Spread = val;
197 break;
199 default:
200 context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
203 void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
204 { Echo_setParamf(props, context, param, vals[0]); }
206 void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
207 { context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
208 void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
209 { context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
210 void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
212 switch(param)
214 case AL_ECHO_DELAY:
215 *val = props->Echo.Delay;
216 break;
218 case AL_ECHO_LRDELAY:
219 *val = props->Echo.LRDelay;
220 break;
222 case AL_ECHO_DAMPING:
223 *val = props->Echo.Damping;
224 break;
226 case AL_ECHO_FEEDBACK:
227 *val = props->Echo.Feedback;
228 break;
230 case AL_ECHO_SPREAD:
231 *val = props->Echo.Spread;
232 break;
234 default:
235 context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
238 void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
239 { Echo_getParamf(props, context, param, vals); }
241 DEFINE_ALEFFECT_VTABLE(Echo);
244 struct EchoStateFactory final : public EffectStateFactory {
245 EffectState *create() override { return new EchoState{}; }
246 EffectProps getDefaultProps() const noexcept override;
247 const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
250 EffectProps EchoStateFactory::getDefaultProps() const noexcept
252 EffectProps props{};
253 props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
254 props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
255 props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
256 props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
257 props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
258 return props;
261 } // namespace
263 EffectStateFactory *EchoStateFactory_getFactory()
265 static EchoStateFactory EchoFactory{};
266 return &EchoFactory;