🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
15 KiB

  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 "alMain.h"
  26. #include "alcontext.h"
  27. #include "alAuxEffectSlot.h"
  28. #include "alError.h"
  29. #include "alu.h"
  30. #include "filters/biquad.h"
  31. #include "vecmat.h"
  32. namespace {
  33. /* The document "Effects Extension Guide.pdf" says that low and high *
  34. * frequencies are cutoff frequencies. This is not fully correct, they *
  35. * are corner frequencies for low and high shelf filters. If they were *
  36. * just cutoff frequencies, there would be no need in cutoff frequency *
  37. * gains, which are present. Documentation for "Creative Proteus X2" *
  38. * software describes 4-band equalizer functionality in a much better *
  39. * way. This equalizer seems to be a predecessor of OpenAL 4-band *
  40. * equalizer. With low and high shelf filters we are able to cutoff *
  41. * frequencies below and/or above corner frequencies using attenuation *
  42. * gains (below 1.0) and amplify all low and/or high frequencies using *
  43. * gains above 1.0. *
  44. * *
  45. * Low-shelf Low Mid Band High Mid Band High-shelf *
  46. * corner center center corner *
  47. * frequency frequency frequency frequency *
  48. * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
  49. * *
  50. * | | | | *
  51. * | | | | *
  52. * B -----+ /--+--\ /--+--\ +----- *
  53. * O |\ | | | | | | /| *
  54. * O | \ - | - - | - / | *
  55. * S + | \ | | | | | | / | *
  56. * T | | | | | | | | | | *
  57. * ---------+---------------+------------------+---------------+-------- *
  58. * C | | | | | | | | | | *
  59. * U - | / | | | | | | \ | *
  60. * T | / - | - - | - \ | *
  61. * O |/ | | | | | | \| *
  62. * F -----+ \--+--/ \--+--/ +----- *
  63. * F | | | | *
  64. * | | | | *
  65. * *
  66. * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
  67. * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
  68. * octaves for two mid bands. *
  69. * *
  70. * Implementation is based on the "Cookbook formulae for audio EQ biquad *
  71. * filter coefficients" by Robert Bristow-Johnson *
  72. * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
  73. struct EqualizerState final : public EffectState {
  74. struct {
  75. /* Effect parameters */
  76. BiquadFilter filter[4];
  77. /* Effect gains for each channel */
  78. ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
  79. ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
  80. } mChans[MAX_AMBI_CHANNELS];
  81. ALfloat mSampleBuffer[BUFFERSIZE]{};
  82. ALboolean deviceUpdate(const ALCdevice *device) override;
  83. void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
  84. void process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput) override;
  85. DEF_NEWDEL(EqualizerState)
  86. };
  87. ALboolean EqualizerState::deviceUpdate(const ALCdevice *UNUSED(device))
  88. {
  89. for(auto &e : mChans)
  90. {
  91. std::for_each(std::begin(e.filter), std::end(e.filter),
  92. std::mem_fn(&BiquadFilter::clear));
  93. std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
  94. }
  95. return AL_TRUE;
  96. }
  97. void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
  98. {
  99. const ALCdevice *device = context->Device;
  100. auto frequency = static_cast<ALfloat>(device->Frequency);
  101. ALfloat gain, f0norm;
  102. /* Calculate coefficients for the each type of filter. Note that the shelf
  103. * filters' gain is for the reference frequency, which is the centerpoint
  104. * of the transition band.
  105. */
  106. gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
  107. f0norm = props->Equalizer.LowCutoff/frequency;
  108. mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm,
  109. calc_rcpQ_from_slope(gain, 0.75f));
  110. gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
  111. f0norm = props->Equalizer.Mid1Center/frequency;
  112. mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm,
  113. calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid1Width));
  114. gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
  115. f0norm = props->Equalizer.Mid2Center/frequency;
  116. mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm,
  117. calc_rcpQ_from_bandwidth(f0norm, props->Equalizer.Mid2Width));
  118. gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
  119. f0norm = props->Equalizer.HighCutoff/frequency;
  120. mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm,
  121. calc_rcpQ_from_slope(gain, 0.75f));
  122. /* Copy the filter coefficients for the other input channels. */
  123. for(ALsizei i{1};i < slot->Wet.NumChannels;++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. mOutBuffer = target.Main->Buffer;
  131. mOutChannels = target.Main->NumChannels;
  132. for(ALsizei i{0};i < slot->Wet.NumChannels;++i)
  133. {
  134. auto coeffs = GetAmbiIdentityRow(i);
  135. ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
  136. }
  137. }
  138. void EqualizerState::process(ALsizei samplesToDo, const ALfloat (*RESTRICT samplesIn)[BUFFERSIZE], const ALsizei numInput, ALfloat (*RESTRICT samplesOut)[BUFFERSIZE], const ALsizei numOutput)
  139. {
  140. ASSUME(numInput > 0);
  141. for(ALsizei c{0};c < numInput;c++)
  142. {
  143. mChans[c].filter[0].process(mSampleBuffer, samplesIn[c], samplesToDo);
  144. mChans[c].filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  145. mChans[c].filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  146. mChans[c].filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo);
  147. MixSamples(mSampleBuffer, numOutput, samplesOut, mChans[c].CurrentGains,
  148. mChans[c].TargetGains, samplesToDo, 0, samplesToDo);
  149. }
  150. }
  151. void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
  152. { alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
  153. void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
  154. { alSetError(context, 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. alSetError(context, 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. { alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
  217. void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
  218. { alSetError(context, 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. alSetError(context, 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. }