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.

330 lines
9.6KB

  1. #pragma once
  2. #include <algorithm>
  3. #include "AudioMath.h"
  4. #include "LookupTable.h"
  5. #include "LookupTableFactory.h"
  6. #include "MultiModOsc.h"
  7. #include "ObjectCache.h"
  8. #include "StateVariableFilter.h"
  9. #define _ANORM
  10. /**
  11. * Version 2 - make the math sane.
  12. * was 46
  13. * with mod sub-sample 2 => 26
  14. */
  15. template <class TBase>
  16. class VocalAnimator : public TBase
  17. {
  18. public:
  19. typedef float T;
  20. static const int numTriangle = 4;
  21. static const int numModOutputs = 3;
  22. static const int numFilters = 4;
  23. static const int modulationSubSample = 2; // do at a fraction of the audio sample rate
  24. VocalAnimator(struct Module * module) : TBase(module)
  25. {
  26. }
  27. VocalAnimator() : TBase()
  28. {
  29. }
  30. void setSampleRate(float rate)
  31. {
  32. reciprocalSampleRate = 1 / rate;
  33. modulatorParams.setRateAndSpread(.5, .5, 0, reciprocalSampleRate);
  34. }
  35. enum ParamIds
  36. {
  37. LFO_RATE_PARAM,
  38. FILTER_Q_PARAM,
  39. FILTER_FC_PARAM,
  40. FILTER_MOD_DEPTH_PARAM,
  41. LFO_RATE_TRIM_PARAM,
  42. FILTER_Q_TRIM_PARAM,
  43. FILTER_FC_TRIM_PARAM,
  44. FILTER_MOD_DEPTH_TRIM_PARAM,
  45. BASS_EXP_PARAM,
  46. // tracking:
  47. // 0 = all 1v/octave, mod scaled, no on top
  48. // 1 = mod and cv scaled
  49. // 2 = 1, + top filter gets some mod
  50. TRACK_EXP_PARAM,
  51. // LFO mixing options
  52. // 0 = classic
  53. // 1 = option
  54. // 2 = lf sub
  55. LFO_MIX_PARAM,
  56. NUM_PARAMS
  57. };
  58. enum InputIds
  59. {
  60. AUDIO_INPUT,
  61. LFO_RATE_CV_INPUT,
  62. FILTER_Q_CV_INPUT,
  63. FILTER_FC_CV_INPUT,
  64. FILTER_MOD_DEPTH_CV_INPUT,
  65. NUM_INPUTS
  66. };
  67. enum OutputIds
  68. {
  69. AUDIO_OUTPUT,
  70. LFO0_OUTPUT,
  71. LFO1_OUTPUT,
  72. LFO2_OUTPUT,
  73. NUM_OUTPUTS
  74. };
  75. enum LightIds
  76. {
  77. LFO0_LIGHT,
  78. LFO1_LIGHT,
  79. LFO2_LIGHT,
  80. BASS_LIGHT,
  81. NUM_LIGHTS
  82. };
  83. void init();
  84. void step() override;
  85. void stepModulation();
  86. T modulatorOutput[numModOutputs];
  87. // The frequency inputs to the filters, exposed for testing.
  88. T filterFrequencyLog[numFilters];
  89. const T nominalFilterCenterHz[numFilters] = {522, 1340, 2570, 3700};
  90. const T nominalFilterCenterLog2[numFilters] = {
  91. std::log2(T(522)),
  92. std::log2(T(1340)),
  93. std::log2(T(2570)),
  94. std::log2(T(3700))
  95. };
  96. // 1, .937 .3125
  97. const T nominalModSensitivity[numFilters] = {T(1), T(.937), T(.3125), 0};
  98. // Following are for unit tests.
  99. T normalizedFilterFreq[numFilters];
  100. bool jamModForTest = false;
  101. T modValueForTest = 0;
  102. int modulationSubSampleCounter = 1;
  103. T filterNormalizedBandwidth = .1f;
  104. float reciprocalSampleRate;
  105. using osc = MultiModOsc<T, numTriangle, numModOutputs>;
  106. typename osc::State modulatorState;
  107. typename osc::Params modulatorParams;
  108. StateVariableFilterState<T> filterStates[numFilters];
  109. StateVariableFilterParams<T> filterParams[numFilters];
  110. std::shared_ptr<LookupTableParams<T>> expLookup;
  111. // We need a bunch of scalers to convert knob, CV, trim into the voltage
  112. // range each parameter needs.
  113. AudioMath::ScaleFun<T> scale0_1;
  114. AudioMath::ScaleFun<T> scalem2_2;
  115. AudioMath::ScaleFun<T> scaleQ;
  116. AudioMath::ScaleFun<T> scalen5_5;
  117. };
  118. template <class TBase>
  119. inline void VocalAnimator<TBase>::init()
  120. {
  121. for (int i = 0; i < numFilters; ++i) {
  122. filterParams[i].setMode(StateVariableFilterParams<T>::Mode::BandPass);
  123. filterParams[i].setQ(15); // or should it be 5?
  124. filterParams[i].setFreq(nominalFilterCenterHz[i] * reciprocalSampleRate);
  125. filterFrequencyLog[i] = nominalFilterCenterLog2[i];
  126. normalizedFilterFreq[i] = nominalFilterCenterHz[i] * reciprocalSampleRate;
  127. }
  128. scale0_1 = AudioMath::makeScalerWithBipolarAudioTrim(0, 1); // full CV range -> 0..1
  129. scalem2_2 = AudioMath::makeScalerWithBipolarAudioTrim(-2, 2); // full CV range -> -2..2
  130. scaleQ = AudioMath::makeScalerWithBipolarAudioTrim(.71f, 21);
  131. scalen5_5 = AudioMath::makeScalerWithBipolarAudioTrim(-5, 5);
  132. // make table of 2 ** x
  133. expLookup = ObjectCache<T>::getExp2();
  134. }
  135. template <class TBase>
  136. inline void VocalAnimator<TBase>::step()
  137. {
  138. // printf("step %d\n", modulationSubSampleCounter);
  139. if (--modulationSubSampleCounter <= 0) {
  140. modulationSubSampleCounter = modulationSubSample;
  141. stepModulation();
  142. }
  143. // Now run the filters
  144. T filterMix = 0; // Sum the folder outputs here
  145. const T input = TBase::inputs[AUDIO_INPUT].value;
  146. for (int i = 0; i < numFilters; ++i) {
  147. filterMix += StateVariableFilter<T>::run(input, filterStates[i], filterParams[i]);
  148. }
  149. #ifdef _ANORM
  150. filterMix *= filterNormalizedBandwidth * 2;
  151. #else
  152. filterMix *= T(.3); // attenuate to avoid clip
  153. #endif
  154. TBase::outputs[AUDIO_OUTPUT].value = filterMix;
  155. }
  156. template <class TBase>
  157. inline void VocalAnimator<TBase>::stepModulation()
  158. {
  159. // printf("step mod\n");
  160. const bool bass = TBase::params[BASS_EXP_PARAM].value > .5;
  161. const auto mode = bass ?
  162. StateVariableFilterParams<T>::Mode::LowPass :
  163. StateVariableFilterParams<T>::Mode::BandPass;
  164. for (int i = 0; i < numFilters + 1 - 1; ++i) {
  165. filterParams[i].setMode(mode);
  166. }
  167. // Run the modulators, hold onto their output.
  168. // Raw Modulator outputs put in modulatorOutputs[].
  169. osc::run(modulatorOutput, modulatorState, modulatorParams);
  170. static const OutputIds LEDOutputs[] = {
  171. LFO0_OUTPUT,
  172. LFO1_OUTPUT,
  173. LFO2_OUTPUT,
  174. };
  175. // Light up the LEDs with the unscaled Modulator outputs.
  176. for (int i = LFO0_LIGHT; i <= LFO2_LIGHT; ++i) {
  177. TBase::outputs[LEDOutputs[i]].value = modulatorOutput[i];
  178. TBase::lights[i].value = (modulatorOutput[i]) * .3f;
  179. TBase::outputs[LEDOutputs[i]].value = modulatorOutput[i];
  180. }
  181. // Normalize all the parameters out here
  182. const T qFinal = scaleQ(
  183. TBase::inputs[FILTER_Q_CV_INPUT].value,
  184. TBase::params[FILTER_Q_PARAM].value,
  185. TBase::params[FILTER_Q_TRIM_PARAM].value);
  186. const T fc = scalen5_5(
  187. TBase::inputs[FILTER_FC_CV_INPUT].value,
  188. TBase::params[FILTER_FC_PARAM].value,
  189. TBase::params[FILTER_FC_TRIM_PARAM].value);
  190. // put together a mod depth parameter from all the inputs
  191. // range is 0..1
  192. // cv, knob, trim
  193. const T baseModDepth = scale0_1(
  194. TBase::inputs[FILTER_MOD_DEPTH_CV_INPUT].value,
  195. TBase::params[FILTER_MOD_DEPTH_PARAM].value,
  196. TBase::params[FILTER_MOD_DEPTH_TRIM_PARAM].value);
  197. // tracking:
  198. // 0 = all 1v/octave, mod scaled, no on top
  199. // 1 = mod and cv scaled
  200. // 2 = 1, + top filter gets some mod
  201. int cvScaleMode = 0;
  202. const float cvScaleParam = TBase::params[TRACK_EXP_PARAM].value;
  203. if (cvScaleParam < .5) {
  204. cvScaleMode = 0;
  205. } else if (cvScaleParam < 1.5) {
  206. cvScaleMode = 1;
  207. } else {
  208. cvScaleMode = 2;
  209. assert(cvScaleParam < 2.5);
  210. }
  211. // Just do the Q division once, in the outer loop
  212. filterNormalizedBandwidth = T(1) / qFinal;
  213. for (int i = 0; i < numFilters; ++i) {
  214. T logFreq = nominalFilterCenterLog2[i];
  215. switch (cvScaleMode) {
  216. case 1:
  217. // In this mode (1) CV comes straight through at 1V/8
  218. // Even on the top (fixed) filter
  219. logFreq += fc; // add without attenuation for 1V/octave
  220. break;
  221. case 0:
  222. // In mode (0) CV gets scaled per filter, as in the original design.
  223. // Since nominalModSensitivity[3] == 0, top doesn't track
  224. logFreq += fc * nominalModSensitivity[i];
  225. break;
  226. case 2:
  227. if (fc < 0) {
  228. // Normal scaling for Fc less than nominal
  229. logFreq += fc * nominalModSensitivity[i];
  230. } else {
  231. // above nominal, they all track the high one (so they don't cross)
  232. logFreq += fc * nominalModSensitivity[2];
  233. }
  234. break;
  235. default:
  236. assert(false);
  237. }
  238. // First three filters always modulated,
  239. // (wanted to try modulating 4, but don't have an LFO (yet
  240. const bool modulateThisFilter = (i < 3);
  241. if (modulateThisFilter) {
  242. logFreq += modulatorOutput[i] *
  243. baseModDepth *
  244. nominalModSensitivity[i];
  245. }
  246. logFreq += ((i < 3) ? modulatorOutput[i] : 0) *
  247. baseModDepth *
  248. nominalModSensitivity[i];
  249. filterFrequencyLog[i] = logFreq;
  250. // tell lookup not to assert - we know we can go slightly out of range.
  251. T normFreq = LookupTable<T>::lookup(*expLookup, logFreq, true) * reciprocalSampleRate;
  252. normFreq = std::min(normFreq, T(.2));
  253. normalizedFilterFreq[i] = normFreq;
  254. filterParams[i].setFreq(normFreq);
  255. filterParams[i].setNormalizedBandwidth(filterNormalizedBandwidth);
  256. }
  257. int matrixMode;
  258. float mmParam = TBase::params[LFO_MIX_PARAM].value;
  259. if (mmParam < .5) {
  260. matrixMode = 0;
  261. } else if (mmParam < 1.5) {
  262. matrixMode = 1;
  263. } else {
  264. matrixMode = 2;
  265. assert(mmParam < 2.5);
  266. }
  267. const T spread = T(1.0);
  268. // scale by sub-sample rate to lfo rate sounds right.
  269. const float modRate = modulationSubSample * scalem2_2(
  270. TBase::inputs[LFO_RATE_CV_INPUT].value,
  271. TBase::params[LFO_RATE_PARAM].value,
  272. TBase::params[LFO_RATE_TRIM_PARAM].value);
  273. modulatorParams.setRateAndSpread(
  274. modRate,
  275. spread,
  276. matrixMode,
  277. reciprocalSampleRate);
  278. }