echo.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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.
  8. *
  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.
  13. *
  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
  19. */
  20. #include "config.h"
  21. #include <cmath>
  22. #include <cstdlib>
  23. #include <algorithm>
  24. #include "al/auxeffectslot.h"
  25. #include "al/filter.h"
  26. #include "alcmain.h"
  27. #include "alcontext.h"
  28. #include "alu.h"
  29. #include "filters/biquad.h"
  30. #include "vector.h"
  31. namespace {
  32. struct EchoState final : public EffectState {
  33. al::vector<ALfloat,16> mSampleBuffer;
  34. // The echo is two tap. The delay is the number of samples from before the
  35. // current offset
  36. struct {
  37. size_t delay{0u};
  38. } mTap[2];
  39. size_t mOffset{0u};
  40. /* The panning gains for the two taps */
  41. struct {
  42. ALfloat Current[MAX_OUTPUT_CHANNELS]{};
  43. ALfloat Target[MAX_OUTPUT_CHANNELS]{};
  44. } mGains[2];
  45. BiquadFilter mFilter;
  46. ALfloat mFeedGain{0.0f};
  47. alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
  48. ALboolean deviceUpdate(const ALCdevice *device) override;
  49. void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
  50. void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
  51. DEF_NEWDEL(EchoState)
  52. };
  53. ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
  54. {
  55. const auto frequency = static_cast<float>(Device->Frequency);
  56. // Use the next power of 2 for the buffer length, so the tap offsets can be
  57. // wrapped using a mask instead of a modulo
  58. const ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) +
  59. float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))};
  60. if(maxlen != mSampleBuffer.size())
  61. {
  62. mSampleBuffer.resize(maxlen);
  63. mSampleBuffer.shrink_to_fit();
  64. }
  65. std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
  66. for(auto &e : mGains)
  67. {
  68. std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
  69. std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
  70. }
  71. return AL_TRUE;
  72. }
  73. void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
  74. {
  75. const ALCdevice *device{context->mDevice.get()};
  76. const auto frequency = static_cast<ALfloat>(device->Frequency);
  77. mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
  78. mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
  79. const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
  80. mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency,
  81. mFilter.rcpQFromSlope(gainhf, 1.0f));
  82. mFeedGain = props->Echo.Feedback;
  83. /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
  84. const ALfloat angle{std::asin(props->Echo.Spread)};
  85. ALfloat coeffs[2][MAX_AMBI_CHANNELS];
  86. CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
  87. CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
  88. mOutTarget = target.Main->Buffer;
  89. ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
  90. ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
  91. }
  92. void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
  93. {
  94. const size_t mask{mSampleBuffer.size()-1};
  95. ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
  96. size_t offset{mOffset};
  97. size_t tap1{offset - mTap[0].delay};
  98. size_t tap2{offset - mTap[1].delay};
  99. ALfloat z1, z2;
  100. ASSUME(samplesToDo > 0);
  101. const BiquadFilter filter{mFilter};
  102. std::tie(z1, z2) = mFilter.getComponents();
  103. for(size_t i{0u};i < samplesToDo;)
  104. {
  105. offset &= mask;
  106. tap1 &= mask;
  107. tap2 &= mask;
  108. size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
  109. do {
  110. /* Feed the delay buffer's input first. */
  111. delaybuf[offset] = samplesIn[0][i];
  112. /* Get delayed output from the first and second taps. Use the
  113. * second tap for feedback.
  114. */
  115. mTempBuffer[0][i] = delaybuf[tap1++];
  116. mTempBuffer[1][i] = delaybuf[tap2++];
  117. const float feedb{mTempBuffer[1][i++]};
  118. /* Add feedback to the delay buffer with damping and attenuation. */
  119. delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
  120. } while(--td);
  121. }
  122. mFilter.setComponents(z1, z2);
  123. mOffset = offset;
  124. for(ALsizei c{0};c < 2;c++)
  125. MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
  126. samplesToDo, 0);
  127. }
  128. void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
  129. { context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
  130. void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
  131. { context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
  132. void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
  133. {
  134. switch(param)
  135. {
  136. case AL_ECHO_DELAY:
  137. if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
  138. SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
  139. props->Echo.Delay = val;
  140. break;
  141. case AL_ECHO_LRDELAY:
  142. if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
  143. SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
  144. props->Echo.LRDelay = val;
  145. break;
  146. case AL_ECHO_DAMPING:
  147. if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
  148. SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
  149. props->Echo.Damping = val;
  150. break;
  151. case AL_ECHO_FEEDBACK:
  152. if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
  153. SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
  154. props->Echo.Feedback = val;
  155. break;
  156. case AL_ECHO_SPREAD:
  157. if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
  158. SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
  159. props->Echo.Spread = val;
  160. break;
  161. default:
  162. context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
  163. }
  164. }
  165. void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
  166. { Echo_setParamf(props, context, param, vals[0]); }
  167. void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  168. { context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
  169. void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  170. { context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
  171. void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
  172. {
  173. switch(param)
  174. {
  175. case AL_ECHO_DELAY:
  176. *val = props->Echo.Delay;
  177. break;
  178. case AL_ECHO_LRDELAY:
  179. *val = props->Echo.LRDelay;
  180. break;
  181. case AL_ECHO_DAMPING:
  182. *val = props->Echo.Damping;
  183. break;
  184. case AL_ECHO_FEEDBACK:
  185. *val = props->Echo.Feedback;
  186. break;
  187. case AL_ECHO_SPREAD:
  188. *val = props->Echo.Spread;
  189. break;
  190. default:
  191. context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
  192. }
  193. }
  194. void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
  195. { Echo_getParamf(props, context, param, vals); }
  196. DEFINE_ALEFFECT_VTABLE(Echo);
  197. struct EchoStateFactory final : public EffectStateFactory {
  198. EffectState *create() override { return new EchoState{}; }
  199. EffectProps getDefaultProps() const noexcept override;
  200. const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
  201. };
  202. EffectProps EchoStateFactory::getDefaultProps() const noexcept
  203. {
  204. EffectProps props{};
  205. props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
  206. props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
  207. props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
  208. props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
  209. props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
  210. return props;
  211. }
  212. } // namespace
  213. EffectStateFactory *EchoStateFactory_getFactory()
  214. {
  215. static EchoStateFactory EchoFactory{};
  216. return &EchoFactory;
  217. }