equalizer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2013 by Mike Gorchak
  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 <functional>
  25. #include "al/auxeffectslot.h"
  26. #include "alcmain.h"
  27. #include "alcontext.h"
  28. #include "alu.h"
  29. #include "filters/biquad.h"
  30. #include "vecmat.h"
  31. namespace {
  32. /* The document "Effects Extension Guide.pdf" says that low and high *
  33. * frequencies are cutoff frequencies. This is not fully correct, they *
  34. * are corner frequencies for low and high shelf filters. If they were *
  35. * just cutoff frequencies, there would be no need in cutoff frequency *
  36. * gains, which are present. Documentation for "Creative Proteus X2" *
  37. * software describes 4-band equalizer functionality in a much better *
  38. * way. This equalizer seems to be a predecessor of OpenAL 4-band *
  39. * equalizer. With low and high shelf filters we are able to cutoff *
  40. * frequencies below and/or above corner frequencies using attenuation *
  41. * gains (below 1.0) and amplify all low and/or high frequencies using *
  42. * gains above 1.0. *
  43. * *
  44. * Low-shelf Low Mid Band High Mid Band High-shelf *
  45. * corner center center corner *
  46. * frequency frequency frequency frequency *
  47. * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
  48. * *
  49. * | | | | *
  50. * | | | | *
  51. * B -----+ /--+--\ /--+--\ +----- *
  52. * O |\ | | | | | | /| *
  53. * O | \ - | - - | - / | *
  54. * S + | \ | | | | | | / | *
  55. * T | | | | | | | | | | *
  56. * ---------+---------------+------------------+---------------+-------- *
  57. * C | | | | | | | | | | *
  58. * U - | / | | | | | | \ | *
  59. * T | / - | - - | - \ | *
  60. * O |/ | | | | | | \| *
  61. * F -----+ \--+--/ \--+--/ +----- *
  62. * F | | | | *
  63. * | | | | *
  64. * *
  65. * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
  66. * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
  67. * octaves for two mid bands. *
  68. * *
  69. * Implementation is based on the "Cookbook formulae for audio EQ biquad *
  70. * filter coefficients" by Robert Bristow-Johnson *
  71. * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
  72. struct EqualizerState final : public EffectState {
  73. struct {
  74. /* Effect parameters */
  75. BiquadFilter filter[4];
  76. /* Effect gains for each channel */
  77. ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
  78. ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
  79. } mChans[MAX_AMBI_CHANNELS];
  80. ALfloat mSampleBuffer[BUFFERSIZE]{};
  81. ALboolean deviceUpdate(const ALCdevice *device) override;
  82. void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
  83. void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
  84. DEF_NEWDEL(EqualizerState)
  85. };
  86. ALboolean EqualizerState::deviceUpdate(const ALCdevice*)
  87. {
  88. for(auto &e : mChans)
  89. {
  90. std::for_each(std::begin(e.filter), std::end(e.filter),
  91. std::mem_fn(&BiquadFilter::clear));
  92. std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
  93. }
  94. return AL_TRUE;
  95. }
  96. void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
  97. {
  98. const ALCdevice *device{context->mDevice.get()};
  99. auto frequency = static_cast<ALfloat>(device->Frequency);
  100. ALfloat gain, f0norm;
  101. /* Calculate coefficients for the each type of filter. Note that the shelf
  102. * and peaking filters' gain is for the centerpoint of the transition band,
  103. * meaning its dB needs to be doubled for the shelf or peak to reach the
  104. * provided gain.
  105. */
  106. gain = maxf(std::sqrt(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
  107. f0norm = props->Equalizer.LowCutoff/frequency;
  108. mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm,
  109. BiquadFilter::rcpQFromSlope(gain, 0.75f));
  110. gain = maxf(std::sqrt(props->Equalizer.Mid1Gain), 0.0625f);
  111. f0norm = props->Equalizer.Mid1Center/frequency;
  112. mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm,
  113. BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid1Width));
  114. gain = maxf(std::sqrt(props->Equalizer.Mid2Gain), 0.0625f);
  115. f0norm = props->Equalizer.Mid2Center/frequency;
  116. mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm,
  117. BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid2Width));
  118. gain = maxf(std::sqrt(props->Equalizer.HighGain), 0.0625f);
  119. f0norm = props->Equalizer.HighCutoff/frequency;
  120. mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm,
  121. BiquadFilter::rcpQFromSlope(gain, 0.75f));
  122. /* Copy the filter coefficients for the other input channels. */
  123. for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
  124. {
  125. mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
  126. mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
  127. mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
  128. mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
  129. }
  130. mOutTarget = target.Main->Buffer;
  131. for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
  132. {
  133. auto coeffs = GetAmbiIdentityRow(i);
  134. ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
  135. }
  136. }
  137. void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
  138. {
  139. auto chandata = std::addressof(mChans[0]);
  140. for(const auto &input : samplesIn)
  141. {
  142. chandata->filter[0].process(mSampleBuffer, input.data(), samplesToDo);
  143. chandata->filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  144. chandata->filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  145. chandata->filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  146. MixSamples({mSampleBuffer, samplesToDo}, samplesOut, chandata->CurrentGains,
  147. chandata->TargetGains, samplesToDo, 0);
  148. ++chandata;
  149. }
  150. }
  151. void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
  152. { context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
  153. void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
  154. { context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
  155. void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
  156. {
  157. switch(param)
  158. {
  159. case AL_EQUALIZER_LOW_GAIN:
  160. if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
  161. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
  162. props->Equalizer.LowGain = val;
  163. break;
  164. case AL_EQUALIZER_LOW_CUTOFF:
  165. if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
  166. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
  167. props->Equalizer.LowCutoff = val;
  168. break;
  169. case AL_EQUALIZER_MID1_GAIN:
  170. if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
  171. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
  172. props->Equalizer.Mid1Gain = val;
  173. break;
  174. case AL_EQUALIZER_MID1_CENTER:
  175. if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
  176. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
  177. props->Equalizer.Mid1Center = val;
  178. break;
  179. case AL_EQUALIZER_MID1_WIDTH:
  180. if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
  181. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
  182. props->Equalizer.Mid1Width = val;
  183. break;
  184. case AL_EQUALIZER_MID2_GAIN:
  185. if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
  186. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
  187. props->Equalizer.Mid2Gain = val;
  188. break;
  189. case AL_EQUALIZER_MID2_CENTER:
  190. if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
  191. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
  192. props->Equalizer.Mid2Center = val;
  193. break;
  194. case AL_EQUALIZER_MID2_WIDTH:
  195. if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
  196. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
  197. props->Equalizer.Mid2Width = val;
  198. break;
  199. case AL_EQUALIZER_HIGH_GAIN:
  200. if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
  201. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
  202. props->Equalizer.HighGain = val;
  203. break;
  204. case AL_EQUALIZER_HIGH_CUTOFF:
  205. if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
  206. SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
  207. props->Equalizer.HighCutoff = val;
  208. break;
  209. default:
  210. context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
  211. }
  212. }
  213. void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
  214. { Equalizer_setParamf(props, context, param, vals[0]); }
  215. void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  216. { context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
  217. void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  218. { context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
  219. void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
  220. {
  221. switch(param)
  222. {
  223. case AL_EQUALIZER_LOW_GAIN:
  224. *val = props->Equalizer.LowGain;
  225. break;
  226. case AL_EQUALIZER_LOW_CUTOFF:
  227. *val = props->Equalizer.LowCutoff;
  228. break;
  229. case AL_EQUALIZER_MID1_GAIN:
  230. *val = props->Equalizer.Mid1Gain;
  231. break;
  232. case AL_EQUALIZER_MID1_CENTER:
  233. *val = props->Equalizer.Mid1Center;
  234. break;
  235. case AL_EQUALIZER_MID1_WIDTH:
  236. *val = props->Equalizer.Mid1Width;
  237. break;
  238. case AL_EQUALIZER_MID2_GAIN:
  239. *val = props->Equalizer.Mid2Gain;
  240. break;
  241. case AL_EQUALIZER_MID2_CENTER:
  242. *val = props->Equalizer.Mid2Center;
  243. break;
  244. case AL_EQUALIZER_MID2_WIDTH:
  245. *val = props->Equalizer.Mid2Width;
  246. break;
  247. case AL_EQUALIZER_HIGH_GAIN:
  248. *val = props->Equalizer.HighGain;
  249. break;
  250. case AL_EQUALIZER_HIGH_CUTOFF:
  251. *val = props->Equalizer.HighCutoff;
  252. break;
  253. default:
  254. context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
  255. }
  256. }
  257. void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
  258. { Equalizer_getParamf(props, context, param, vals); }
  259. DEFINE_ALEFFECT_VTABLE(Equalizer);
  260. struct EqualizerStateFactory final : public EffectStateFactory {
  261. EffectState *create() override { return new EqualizerState{}; }
  262. EffectProps getDefaultProps() const noexcept override;
  263. const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
  264. };
  265. EffectProps EqualizerStateFactory::getDefaultProps() const noexcept
  266. {
  267. EffectProps props{};
  268. props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
  269. props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
  270. props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
  271. props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
  272. props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
  273. props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
  274. props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
  275. props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
  276. props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
  277. props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
  278. return props;
  279. }
  280. } // namespace
  281. EffectStateFactory *EqualizerStateFactory_getFactory()
  282. {
  283. static EqualizerStateFactory EqualizerFactory{};
  284. return &EqualizerFactory;
  285. }