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 "al/auxeffectslot.h"
29 #include "al/filter.h"
31 #include "alcontext.h"
33 #include "filters/biquad.h"
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
49 /* The panning gains for the two taps */
51 ALfloat Current
[MAX_OUTPUT_CHANNELS
]{};
52 ALfloat Target
[MAX_OUTPUT_CHANNELS
]{};
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
;
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
);
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
);
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
};
126 ASSUME(samplesToDo
> 0);
128 const BiquadFilter filter
{mFilter
};
129 std::tie(z1
, z2
) = mFilter
.getComponents();
130 for(size_t i
{0u};i
< samplesToDo
;)
136 size_t td
{minz(mask
+1 - maxz(offset
, maxz(tap1
, tap2
)), samplesToDo
-i
)};
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
;
152 mFilter
.setComponents(z1
, z2
);
155 for(ALsizei c
{0};c
< 2;c
++)
156 MixSamples({mTempBuffer
[c
], samplesToDo
}, samplesOut
, mGains
[c
].Current
, mGains
[c
].Target
,
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
)
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
;
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
;
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
;
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
;
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
;
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
)
215 *val
= props
->Echo
.Delay
;
218 case AL_ECHO_LRDELAY
:
219 *val
= props
->Echo
.LRDelay
;
222 case AL_ECHO_DAMPING
:
223 *val
= props
->Echo
.Damping
;
226 case AL_ECHO_FEEDBACK
:
227 *val
= props
->Echo
.Feedback
;
231 *val
= props
->Echo
.Spread
;
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
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
;
263 EffectStateFactory
*EchoStateFactory_getFactory()
265 static EchoStateFactory EchoFactory
{};