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.

324 lines
7.9KB

  1. #pragma once
  2. #include "ButterworthFilterDesigner.h"
  3. #include "Decimator.h"
  4. #include "GraphicEq.h"
  5. #include "LowpassFilter.h"
  6. #include "BiquadParams.h"
  7. #include "BiquadState.h"
  8. #include "BiquadFilter.h"
  9. #include "ObjectCache.h"
  10. #include "IComposite.h"
  11. #include <random>
  12. /**
  13. * Noise generator feeding a graphic equalizer.
  14. * Calculated at very low sample rate, then re-sampled
  15. * up to audio rate.
  16. *
  17. * Below assumes 44k SR. TODO: other rates.
  18. *
  19. * We first design the EQ around bands of 100, 200, 400, 800,
  20. * 1600. EQ gets noise.
  21. *
  22. * Then output of EQ is re-sampled up by a factor of 100
  23. * to bring the first band down to 1hz.
  24. * or : decimation factor = 100 * (fs) / 44100.
  25. *
  26. * A butterworth lowpass then removes the re-sampling artifacts.
  27. * Otherwise these images bring in high frequencies that we
  28. * don't want.
  29. *
  30. * Cutoff for the filter can be as low as the top of the eq,
  31. * which is 3.2khz. 44k/3.2k is about 10,
  32. * so fc/fs can be 1/1000.
  33. *
  34. * or : fc = (fs / 44100) / 1000;
  35. *
  36. * (had been using fc/fs = float(1.0 / (44 * 100.0)));)
  37. *
  38. * Design for R = root freq (was 1 Hz, above)
  39. * EQ first band at E (was 100 Hz, above)
  40. *
  41. * Decimation divider = E / R
  42. *
  43. * Imaging filter fc = 3.2khz / decimation-divider
  44. * fc/fs = 3200 * (reciprocal sr) / decimation-divider.
  45. *
  46. * Experiment: let's use those values and compare to what we had been using.
  47. * result: not too far off.
  48. *
  49. * make a range/base control. map -5 to +5 into 1/10 Hz to 2 Hz rate. Can use regular
  50. * functions, since we won't calc that often.
  51. */
  52. template <class TBase>
  53. class LFNDescription : public IComposite
  54. {
  55. public:
  56. Config getParam(int i) override;
  57. int getNumParams() override;
  58. };
  59. template <class TBase>
  60. class LFN : public TBase
  61. {
  62. public:
  63. LFN(struct Module * module) : TBase(module)
  64. {
  65. }
  66. LFN() : TBase()
  67. {
  68. }
  69. /** Implement IComposite
  70. */
  71. static std::shared_ptr<IComposite> getDescription()
  72. {
  73. return std::make_shared<LFNDescription<TBase>>();
  74. }
  75. void setSampleTime(float time)
  76. {
  77. reciprocalSampleRate = time;
  78. updateLPF();
  79. }
  80. /**
  81. * re-calc everything that changes with sample
  82. * rate. Also everything that depends on baseFrequency.
  83. *
  84. * Only needs to be called once.
  85. */
  86. void init();
  87. enum ParamIds
  88. {
  89. EQ0_PARAM,
  90. EQ1_PARAM,
  91. EQ2_PARAM,
  92. EQ3_PARAM,
  93. EQ4_PARAM,
  94. FREQ_RANGE_PARAM,
  95. XLFN_PARAM,
  96. NUM_PARAMS
  97. };
  98. enum InputIds
  99. {
  100. EQ0_INPUT,
  101. EQ1_INPUT,
  102. EQ2_INPUT,
  103. EQ3_INPUT,
  104. EQ4_INPUT,
  105. NUM_INPUTS
  106. };
  107. enum OutputIds
  108. {
  109. OUTPUT,
  110. NUM_OUTPUTS
  111. };
  112. enum LightIds
  113. {
  114. NUM_LIGHTS
  115. };
  116. /**
  117. * Main processing entry point. Called every sample
  118. */
  119. void step() override;
  120. float getBaseFrequency() const
  121. {
  122. return baseFrequency;
  123. }
  124. bool isXLFN() const
  125. {
  126. return TBase::params[XLFN_PARAM].value > .5;
  127. }
  128. /**
  129. * This lets the butterworth get re-calculated on the UI thread.
  130. * We can't do it on the audio thread, because it calls malloc.
  131. */
  132. void pollForChangeOnUIThread();
  133. private:
  134. float reciprocalSampleRate = 0;
  135. ::Decimator decimator;
  136. GraphicEq2<5> geq;
  137. /**
  138. * Template type for butterworth reconstruction filter
  139. * Tried double for best low frequency performance. It's
  140. * probably overkill, but calculates plenty fast.
  141. */
  142. using TButter = double;
  143. BiquadParams<TButter, 2> lpfParams;
  144. BiquadState<TButter, 2> lpfState;
  145. /**
  146. * Frequency, in Hz, of the lowest band in the graphic EQ
  147. */
  148. float baseFrequency = 1;
  149. /**
  150. * The last value baked by the LPF filter calculation
  151. * done on the UI thread.
  152. */
  153. float lastBaseFrequencyParamValue = -100;
  154. float lastXLFMParamValue = -1;
  155. std::default_random_engine generator{57};
  156. std::normal_distribution<double> distribution{-1.0, 1.0};
  157. float noise()
  158. {
  159. return (float) distribution(generator);
  160. }
  161. int controlUpdateCount = 0;
  162. /**
  163. * Must be called after baseFrequency is updated.
  164. * re-calculates the butterworth lowpass.
  165. */
  166. void updateLPF();
  167. /**
  168. * scaling function for the range / base frequency knob
  169. * map knob range from .1 Hz to 2.0 Hz
  170. */
  171. std::function<double(double)> rangeFunc =
  172. {AudioMath::makeFunc_Exp(-5, 5, .1, 2)};
  173. /**
  174. * Audio taper for the EQ gains. Arbitrary max value selected
  175. * to give "good" output level.
  176. */
  177. AudioMath::SimpleScaleFun<float> gainScale =
  178. {AudioMath::makeSimpleScalerAudioTaper(0, 35)};
  179. };
  180. template <class TBase>
  181. int LFNDescription<TBase>::getNumParams()
  182. {
  183. return LFN<TBase>::NUM_PARAMS;
  184. }
  185. template <class TBase>
  186. inline IComposite::Config LFNDescription<TBase>::getParam(int i)
  187. {
  188. const float gmin = -5;
  189. const float gmax = 5;
  190. const float gdef = 0;
  191. Config ret(0, 1, 0, "");
  192. switch(i) {
  193. case LFN<TBase>::EQ0_PARAM:
  194. ret = { gmin, gmax, gdef, "Low freq mix"};
  195. break;
  196. case LFN<TBase>::EQ1_PARAM:
  197. ret = { gmin, gmax, gdef, "Mid-low freq fix"};
  198. break;
  199. case LFN<TBase>::EQ2_PARAM:
  200. ret = { gmin, gmax, gdef, "Mid freq mix"};
  201. break;
  202. case LFN<TBase>::EQ3_PARAM:
  203. ret = { gmin, gmax, gdef, "Mid-high freq mix"};
  204. break;
  205. case LFN<TBase>::EQ4_PARAM:
  206. ret = { gmin, gmax, gdef, "High freq mix"};
  207. break;
  208. case LFN<TBase>::FREQ_RANGE_PARAM:
  209. ret = { -5, 5, 0, "Base frequency"};
  210. break;
  211. case LFN<TBase>::XLFN_PARAM:
  212. ret = { 0, 1, 0, "Extra low frequency"};
  213. break;
  214. default:
  215. assert(false);
  216. }
  217. return ret;
  218. }
  219. template <class TBase>
  220. inline void LFN<TBase>::pollForChangeOnUIThread()
  221. {
  222. if ((lastBaseFrequencyParamValue != TBase::params[FREQ_RANGE_PARAM].value) ||
  223. (lastXLFMParamValue != TBase::params[XLFN_PARAM].value)) {
  224. lastBaseFrequencyParamValue = TBase::params[FREQ_RANGE_PARAM].value;
  225. lastXLFMParamValue = TBase::params[XLFN_PARAM].value;
  226. baseFrequency = float(rangeFunc(lastBaseFrequencyParamValue));
  227. if (TBase::params[XLFN_PARAM].value > .5f) {
  228. baseFrequency /= 10.f;
  229. }
  230. updateLPF(); // now get the filters updated
  231. }
  232. }
  233. template <class TBase>
  234. inline void LFN<TBase>::init()
  235. {
  236. updateLPF();
  237. }
  238. template <class TBase>
  239. inline void LFN<TBase>::updateLPF()
  240. {
  241. assert(reciprocalSampleRate > 0);
  242. // decimation must be 100hz (what our EQ is designed at)
  243. // divided by base.
  244. float decimationDivider = float(100.0 / baseFrequency);
  245. decimator.setDecimationRate(decimationDivider);
  246. // calculate lpFc ( Fc / sr)
  247. // Imaging filter fc = 3.2khz / decimation-divider
  248. // fc/fs = 3200 * (reciprocal sr) / decimation-divider.
  249. const float lpFc = 3200 * reciprocalSampleRate / decimationDivider;
  250. ButterworthFilterDesigner<TButter>::designThreePoleLowpass(
  251. lpfParams, lpFc);
  252. }
  253. template <class TBase>
  254. inline void LFN<TBase>::step()
  255. {
  256. // Let's only check the inputs every 4 samples. Still plenty fast, but
  257. // get the CPU usage down really far.
  258. if (controlUpdateCount++ > 4) {
  259. controlUpdateCount = 0;
  260. const int numEqStages = geq.getNumStages();
  261. for (int i = 0; i < numEqStages; ++i) {
  262. auto paramNum = i + EQ0_PARAM;
  263. auto cvNum = i + EQ0_INPUT;
  264. const float gainParamKnob = TBase::params[paramNum].value;
  265. const float gainParamCV = TBase::inputs[cvNum].value;
  266. const float gain = gainScale(gainParamKnob, gainParamCV);
  267. geq.setGain(i, gain);
  268. }
  269. }
  270. bool needsData;
  271. TButter x = decimator.clock(needsData);
  272. x = BiquadFilter<TButter>::run(x, lpfState, lpfParams);
  273. if (needsData) {
  274. const float z = geq.run(noise());
  275. decimator.acceptData(z);
  276. }
  277. TBase::outputs[OUTPUT].value = (float) x;
  278. }