autowah.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2018 by Raul Herraiz.
  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 "alcmain.h"
  26. #include "alcontext.h"
  27. #include "alu.h"
  28. #include "filters/biquad.h"
  29. #include "vecmat.h"
  30. namespace {
  31. #define MIN_FREQ 20.0f
  32. #define MAX_FREQ 2500.0f
  33. #define Q_FACTOR 5.0f
  34. struct AutowahState final : public EffectState {
  35. /* Effect parameters */
  36. ALfloat mAttackRate;
  37. ALfloat mReleaseRate;
  38. ALfloat mResonanceGain;
  39. ALfloat mPeakGain;
  40. ALfloat mFreqMinNorm;
  41. ALfloat mBandwidthNorm;
  42. ALfloat mEnvDelay;
  43. /* Filter components derived from the envelope. */
  44. struct {
  45. ALfloat cos_w0;
  46. ALfloat alpha;
  47. } mEnv[BUFFERSIZE];
  48. struct {
  49. /* Effect filters' history. */
  50. struct {
  51. ALfloat z1, z2;
  52. } Filter;
  53. /* Effect gains for each output channel */
  54. ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
  55. ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
  56. } mChans[MAX_AMBI_CHANNELS];
  57. /* Effects buffers */
  58. alignas(16) ALfloat mBufferOut[BUFFERSIZE];
  59. ALboolean deviceUpdate(const ALCdevice *device) override;
  60. void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
  61. void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
  62. DEF_NEWDEL(AutowahState)
  63. };
  64. ALboolean AutowahState::deviceUpdate(const ALCdevice*)
  65. {
  66. /* (Re-)initializing parameters and clear the buffers. */
  67. mAttackRate = 1.0f;
  68. mReleaseRate = 1.0f;
  69. mResonanceGain = 10.0f;
  70. mPeakGain = 4.5f;
  71. mFreqMinNorm = 4.5e-4f;
  72. mBandwidthNorm = 0.05f;
  73. mEnvDelay = 0.0f;
  74. for(auto &e : mEnv)
  75. {
  76. e.cos_w0 = 0.0f;
  77. e.alpha = 0.0f;
  78. }
  79. for(auto &chan : mChans)
  80. {
  81. std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
  82. chan.Filter.z1 = 0.0f;
  83. chan.Filter.z2 = 0.0f;
  84. }
  85. return AL_TRUE;
  86. }
  87. void AutowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
  88. {
  89. const ALCdevice *device{context->mDevice.get()};
  90. const auto frequency = static_cast<float>(device->Frequency);
  91. const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
  92. mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
  93. mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
  94. /* 0-20dB Resonance Peak gain */
  95. mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
  96. mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
  97. mFreqMinNorm = MIN_FREQ / frequency;
  98. mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / frequency;
  99. mOutTarget = target.Main->Buffer;
  100. for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
  101. {
  102. auto coeffs = GetAmbiIdentityRow(i);
  103. ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
  104. }
  105. }
  106. void AutowahState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
  107. {
  108. const ALfloat attack_rate = mAttackRate;
  109. const ALfloat release_rate = mReleaseRate;
  110. const ALfloat res_gain = mResonanceGain;
  111. const ALfloat peak_gain = mPeakGain;
  112. const ALfloat freq_min = mFreqMinNorm;
  113. const ALfloat bandwidth = mBandwidthNorm;
  114. ALfloat env_delay{mEnvDelay};
  115. for(size_t i{0u};i < samplesToDo;i++)
  116. {
  117. ALfloat w0, sample, a;
  118. /* Envelope follower described on the book: Audio Effects, Theory,
  119. * Implementation and Application.
  120. */
  121. sample = peak_gain * std::fabs(samplesIn[0][i]);
  122. a = (sample > env_delay) ? attack_rate : release_rate;
  123. env_delay = lerp(sample, env_delay, a);
  124. /* Calculate the cos and alpha components for this sample's filter. */
  125. w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
  126. mEnv[i].cos_w0 = cosf(w0);
  127. mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
  128. }
  129. mEnvDelay = env_delay;
  130. auto chandata = std::addressof(mChans[0]);
  131. for(const auto &insamples : samplesIn)
  132. {
  133. /* This effectively inlines BiquadFilter_setParams for a peaking
  134. * filter and BiquadFilter_processC. The alpha and cosine components
  135. * for the filter coefficients were previously calculated with the
  136. * envelope. Because the filter changes for each sample, the
  137. * coefficients are transient and don't need to be held.
  138. */
  139. ALfloat z1{chandata->Filter.z1};
  140. ALfloat z2{chandata->Filter.z2};
  141. for(size_t i{0u};i < samplesToDo;i++)
  142. {
  143. const ALfloat alpha = mEnv[i].alpha;
  144. const ALfloat cos_w0 = mEnv[i].cos_w0;
  145. ALfloat input, output;
  146. ALfloat a[3], b[3];
  147. b[0] = 1.0f + alpha*res_gain;
  148. b[1] = -2.0f * cos_w0;
  149. b[2] = 1.0f - alpha*res_gain;
  150. a[0] = 1.0f + alpha/res_gain;
  151. a[1] = -2.0f * cos_w0;
  152. a[2] = 1.0f - alpha/res_gain;
  153. input = insamples[i];
  154. output = input*(b[0]/a[0]) + z1;
  155. z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
  156. z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
  157. mBufferOut[i] = output;
  158. }
  159. chandata->Filter.z1 = z1;
  160. chandata->Filter.z2 = z2;
  161. /* Now, mix the processed sound data to the output. */
  162. MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
  163. chandata->TargetGains, samplesToDo, 0);
  164. ++chandata;
  165. }
  166. }
  167. void Autowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
  168. {
  169. switch(param)
  170. {
  171. case AL_AUTOWAH_ATTACK_TIME:
  172. if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
  173. SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
  174. props->Autowah.AttackTime = val;
  175. break;
  176. case AL_AUTOWAH_RELEASE_TIME:
  177. if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
  178. SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
  179. props->Autowah.ReleaseTime = val;
  180. break;
  181. case AL_AUTOWAH_RESONANCE:
  182. if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
  183. SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
  184. props->Autowah.Resonance = val;
  185. break;
  186. case AL_AUTOWAH_PEAK_GAIN:
  187. if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
  188. SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
  189. props->Autowah.PeakGain = val;
  190. break;
  191. default:
  192. context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
  193. }
  194. }
  195. void Autowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
  196. { Autowah_setParamf(props, context, param, vals[0]); }
  197. void Autowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
  198. { context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
  199. void Autowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
  200. { context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
  201. void Autowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
  202. {
  203. switch(param)
  204. {
  205. case AL_AUTOWAH_ATTACK_TIME:
  206. *val = props->Autowah.AttackTime;
  207. break;
  208. case AL_AUTOWAH_RELEASE_TIME:
  209. *val = props->Autowah.ReleaseTime;
  210. break;
  211. case AL_AUTOWAH_RESONANCE:
  212. *val = props->Autowah.Resonance;
  213. break;
  214. case AL_AUTOWAH_PEAK_GAIN:
  215. *val = props->Autowah.PeakGain;
  216. break;
  217. default:
  218. context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
  219. }
  220. }
  221. void Autowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
  222. { Autowah_getParamf(props, context, param, vals); }
  223. void Autowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  224. { context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
  225. void Autowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  226. { context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
  227. DEFINE_ALEFFECT_VTABLE(Autowah);
  228. struct AutowahStateFactory final : public EffectStateFactory {
  229. EffectState *create() override { return new AutowahState{}; }
  230. EffectProps getDefaultProps() const noexcept override;
  231. const EffectVtable *getEffectVtable() const noexcept override { return &Autowah_vtable; }
  232. };
  233. EffectProps AutowahStateFactory::getDefaultProps() const noexcept
  234. {
  235. EffectProps props{};
  236. props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
  237. props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
  238. props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
  239. props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
  240. return props;
  241. }
  242. } // namespace
  243. EffectStateFactory *AutowahStateFactory_getFactory()
  244. {
  245. static AutowahStateFactory AutowahFactory{};
  246. return &AutowahFactory;
  247. }