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
<float,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 float Current
[MAX_OUTPUT_CHANNELS
]{};
52 float Target
[MAX_OUTPUT_CHANNELS
]{};
56 float mFeedGain
{0.0f
};
58 alignas(16) float mTempBuffer
[2][BUFFERSIZE
];
60 void 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 void 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())
76 al::vector
<float,16>(maxlen
).swap(mSampleBuffer
);
78 std::fill(mSampleBuffer
.begin(), mSampleBuffer
.end(), 0.0f
);
81 std::fill(std::begin(e
.Current
), std::end(e
.Current
), 0.0f
);
82 std::fill(std::begin(e
.Target
), std::end(e
.Target
), 0.0f
);
86 void EchoState::update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
)
88 const ALCdevice
*device
{context
->mDevice
.get()};
89 const auto frequency
= static_cast<float>(device
->Frequency
);
91 mTap
[0].delay
= maxu(float2uint(props
->Echo
.Delay
*frequency
+ 0.5f
), 1);
92 mTap
[1].delay
= float2uint(props
->Echo
.LRDelay
*frequency
+ 0.5f
) + mTap
[0].delay
;
94 const float gainhf
{maxf(1.0f
- props
->Echo
.Damping
, 0.0625f
)}; /* Limit -24dB */
95 mFilter
.setParamsFromSlope(BiquadType::HighShelf
, LOWPASSFREQREF
/frequency
, gainhf
, 1.0f
);
97 mFeedGain
= props
->Echo
.Feedback
;
99 /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
100 const float angle
{std::asin(props
->Echo
.Spread
)};
102 const auto coeffs0
= CalcAngleCoeffs(-angle
, 0.0f
, 0.0f
);
103 const auto coeffs1
= CalcAngleCoeffs( angle
, 0.0f
, 0.0f
);
105 mOutTarget
= target
.Main
->Buffer
;
106 ComputePanGains(target
.Main
, coeffs0
.data(), slot
->Params
.Gain
, mGains
[0].Target
);
107 ComputePanGains(target
.Main
, coeffs1
.data(), slot
->Params
.Gain
, mGains
[1].Target
);
110 void EchoState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
112 const size_t mask
{mSampleBuffer
.size()-1};
113 float *RESTRICT delaybuf
{mSampleBuffer
.data()};
114 size_t offset
{mOffset
};
115 size_t tap1
{offset
- mTap
[0].delay
};
116 size_t tap2
{offset
- mTap
[1].delay
};
119 ASSUME(samplesToDo
> 0);
121 const BiquadFilter filter
{mFilter
};
122 std::tie(z1
, z2
) = mFilter
.getComponents();
123 for(size_t i
{0u};i
< samplesToDo
;)
129 size_t td
{minz(mask
+1 - maxz(offset
, maxz(tap1
, tap2
)), samplesToDo
-i
)};
131 /* Feed the delay buffer's input first. */
132 delaybuf
[offset
] = samplesIn
[0][i
];
134 /* Get delayed output from the first and second taps. Use the
135 * second tap for feedback.
137 mTempBuffer
[0][i
] = delaybuf
[tap1
++];
138 mTempBuffer
[1][i
] = delaybuf
[tap2
++];
139 const float feedb
{mTempBuffer
[1][i
++]};
141 /* Add feedback to the delay buffer with damping and attenuation. */
142 delaybuf
[offset
++] += filter
.processOne(feedb
, z1
, z2
) * mFeedGain
;
145 mFilter
.setComponents(z1
, z2
);
148 for(ALsizei c
{0};c
< 2;c
++)
149 MixSamples({mTempBuffer
[c
], samplesToDo
}, samplesOut
, mGains
[c
].Current
, mGains
[c
].Target
,
154 void Echo_setParami(EffectProps
*, ALenum param
, int)
155 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
}; }
156 void Echo_setParamiv(EffectProps
*, ALenum param
, const int*)
157 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
}; }
158 void Echo_setParamf(EffectProps
*props
, ALenum param
, float val
)
163 if(!(val
>= AL_ECHO_MIN_DELAY
&& val
<= AL_ECHO_MAX_DELAY
))
164 throw effect_exception
{AL_INVALID_VALUE
, "Echo delay out of range"};
165 props
->Echo
.Delay
= val
;
168 case AL_ECHO_LRDELAY
:
169 if(!(val
>= AL_ECHO_MIN_LRDELAY
&& val
<= AL_ECHO_MAX_LRDELAY
))
170 throw effect_exception
{AL_INVALID_VALUE
, "Echo LR delay out of range"};
171 props
->Echo
.LRDelay
= val
;
174 case AL_ECHO_DAMPING
:
175 if(!(val
>= AL_ECHO_MIN_DAMPING
&& val
<= AL_ECHO_MAX_DAMPING
))
176 throw effect_exception
{AL_INVALID_VALUE
, "Echo damping out of range"};
177 props
->Echo
.Damping
= val
;
180 case AL_ECHO_FEEDBACK
:
181 if(!(val
>= AL_ECHO_MIN_FEEDBACK
&& val
<= AL_ECHO_MAX_FEEDBACK
))
182 throw effect_exception
{AL_INVALID_VALUE
, "Echo feedback out of range"};
183 props
->Echo
.Feedback
= val
;
187 if(!(val
>= AL_ECHO_MIN_SPREAD
&& val
<= AL_ECHO_MAX_SPREAD
))
188 throw effect_exception
{AL_INVALID_VALUE
, "Echo spread out of range"};
189 props
->Echo
.Spread
= val
;
193 throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
};
196 void Echo_setParamfv(EffectProps
*props
, ALenum param
, const float *vals
)
197 { Echo_setParamf(props
, param
, vals
[0]); }
199 void Echo_getParami(const EffectProps
*, ALenum param
, int*)
200 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo integer property 0x%04x", param
}; }
201 void Echo_getParamiv(const EffectProps
*, ALenum param
, int*)
202 { throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo integer-vector property 0x%04x", param
}; }
203 void Echo_getParamf(const EffectProps
*props
, ALenum param
, float *val
)
208 *val
= props
->Echo
.Delay
;
211 case AL_ECHO_LRDELAY
:
212 *val
= props
->Echo
.LRDelay
;
215 case AL_ECHO_DAMPING
:
216 *val
= props
->Echo
.Damping
;
219 case AL_ECHO_FEEDBACK
:
220 *val
= props
->Echo
.Feedback
;
224 *val
= props
->Echo
.Spread
;
228 throw effect_exception
{AL_INVALID_ENUM
, "Invalid echo float property 0x%04x", param
};
231 void Echo_getParamfv(const EffectProps
*props
, ALenum param
, float *vals
)
232 { Echo_getParamf(props
, param
, vals
); }
234 DEFINE_ALEFFECT_VTABLE(Echo
);
237 struct EchoStateFactory final
: public EffectStateFactory
{
238 EffectState
*create() override
{ return new EchoState
{}; }
239 EffectProps
getDefaultProps() const noexcept override
;
240 const EffectVtable
*getEffectVtable() const noexcept override
{ return &Echo_vtable
; }
243 EffectProps
EchoStateFactory::getDefaultProps() const noexcept
246 props
.Echo
.Delay
= AL_ECHO_DEFAULT_DELAY
;
247 props
.Echo
.LRDelay
= AL_ECHO_DEFAULT_LRDELAY
;
248 props
.Echo
.Damping
= AL_ECHO_DEFAULT_DAMPING
;
249 props
.Echo
.Feedback
= AL_ECHO_DEFAULT_FEEDBACK
;
250 props
.Echo
.Spread
= AL_ECHO_DEFAULT_SPREAD
;
256 EffectStateFactory
*EchoStateFactory_getFactory()
258 static EchoStateFactory EchoFactory
{};