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.

360 lines
8.7KB

  1. #pragma once
  2. #include <algorithm>
  3. #include "AudioMath.h"
  4. #include "poly.h"
  5. #include "ObjectCache.h"
  6. #include "SinOscillator.h"
  7. using Osc = SinOscillator<float, true>;
  8. #ifndef _CLAMP
  9. #define _CLAMP
  10. namespace std {
  11. inline float clamp(float v, float lo, float hi)
  12. {
  13. assert(lo < hi);
  14. return std::min(hi, std::max(v, lo));
  15. }
  16. }
  17. #endif
  18. /**
  19. * Composite for Chebyshev module.
  20. *
  21. * Performance measure for 1.0 = 42.44
  22. * reduced polynomial order to what we actually use (10), perf = 39.5
  23. */
  24. template <class TBase>
  25. class CHBg : public TBase
  26. {
  27. public:
  28. CHBg(struct Module * module) : TBase(module)
  29. {
  30. init();
  31. }
  32. CHBg() : TBase()
  33. {
  34. init();
  35. }
  36. enum ParamIds
  37. {
  38. PARAM_TUNE,
  39. PARAM_OCTAVE,
  40. PARAM_EXTGAIN,
  41. PARAM_PITCH_MOD_TRIM,
  42. PARAM_LINEAR_FM_TRIM,
  43. PARAM_EXTGAIN_TRIM,
  44. PARAM_FOLD,
  45. PARAM_SLOPE,
  46. PARAM_MAG_EVEN,
  47. PARAM_MAG_ODD,
  48. PARAM_H0,
  49. PARAM_H1,
  50. PARAM_H2,
  51. PARAM_H3,
  52. PARAM_H4,
  53. PARAM_H5,
  54. PARAM_H6,
  55. PARAM_H7,
  56. PARAM_H8,
  57. PARAM_H9,
  58. NUM_PARAMS
  59. };
  60. const int numHarmonics = 1 + PARAM_H9 - PARAM_H0;
  61. enum InputIds
  62. {
  63. CV_INPUT,
  64. PITCH_MOD_INPUT,
  65. LINEAR_FM_INPUT,
  66. ENV_INPUT,
  67. GAIN_INPUT,
  68. AUDIO_INPUT,
  69. SLOPE_INPUT,
  70. H0_INPUT,
  71. H1_INPUT,
  72. H2_INPUT,
  73. H3_INPUT,
  74. H4_INPUT,
  75. H5_INPUT,
  76. H6_INPUT,
  77. H7_INPUT,
  78. H8_INPUT,
  79. H9_INPUT,
  80. H10_INPUT,
  81. NUM_INPUTS
  82. };
  83. enum OutputIds
  84. {
  85. MIX_OUTPUT,
  86. NUM_OUTPUTS
  87. };
  88. enum LightIds
  89. {
  90. GAIN_GREEN_LIGHT,
  91. GAIN_RED_LIGHT,
  92. NUM_LIGHTS
  93. };
  94. /**
  95. * Main processing entry point. Called every sample
  96. */
  97. void step() override;
  98. float _freq = 0;
  99. private:
  100. bool economyMode = true; // let's default to economy mode
  101. int cycleCount = 1;
  102. int clipCount = 0;
  103. int signalCount = 0;
  104. const int clipDuration = 4000;
  105. float finalGain = 0;
  106. bool isExternalAudio = false;
  107. static const int polyOrder = 10;
  108. /**
  109. * The waveshaper that is the heart of this module.
  110. * Let's use doubles.
  111. */
  112. Poly<double, polyOrder> poly;
  113. /*
  114. * maps freq multiple to "octave".
  115. * In other words, log base 12.
  116. */
  117. float _octave[polyOrder];
  118. float getOctave(int mult) const;
  119. void init();
  120. float _volume[polyOrder] = {0};
  121. /**
  122. * Internal sine wave oscillator to drive the waveshaper
  123. */
  124. SinOscillatorParams<float> sinParams;
  125. SinOscillatorState<float> sinState;
  126. // just maps 0..1 to 0..1
  127. std::shared_ptr<LookupTableParams<float>> audioTaper = {ObjectCache<float>::getAudioTaper()};
  128. AudioMath::ScaleFun<float> gainCombiner = AudioMath::makeLinearScaler(0.f, 1.f);
  129. std::function<float(float)> expLookup = ObjectCache<float>::getExp2Ex();
  130. std::shared_ptr<LookupTableParams<float>> db2gain = ObjectCache<float>::getDb2Gain();
  131. /**
  132. * Audio taper for the slope.
  133. */
  134. AudioMath::ScaleFun<float> slopeScale =
  135. {AudioMath::makeLinearScaler<float>(-18, 0)};
  136. /**
  137. * do one-time calculations when sample rate changes
  138. */
  139. void internalUpdate();
  140. /**
  141. * Do all the processing to get the input waveform
  142. * that will be fed to the polynomials
  143. */
  144. float getInput();
  145. void calcVolumes(float *);
  146. void checkClipping(float sample);
  147. /**
  148. * Does audio taper
  149. * @param raw = 0..1
  150. * @return 0..1
  151. */
  152. float taper(float raw)
  153. {
  154. return LookupTable<float>::lookup(*audioTaper, raw, false);
  155. }
  156. };
  157. template <class TBase>
  158. inline void CHBg<TBase>::init()
  159. {
  160. for (int i = 0; i < polyOrder; ++i) {
  161. _octave[i] = log2(float(i + 1));
  162. }
  163. }
  164. template <class TBase>
  165. inline float CHBg<TBase>::getOctave(int i) const
  166. {
  167. assert(i >= 0 && i < polyOrder);
  168. return _octave[i];
  169. }
  170. template <class TBase>
  171. inline float CHBg<TBase>::getInput()
  172. {
  173. assert(TBase::engineGetSampleTime() > 0);
  174. // Get the frequency from the inputs.
  175. float pitch = 1.0f + roundf(TBase::params[PARAM_OCTAVE].value) + TBase::params[PARAM_TUNE].value / 12.0f;
  176. pitch += TBase::inputs[CV_INPUT].value;
  177. pitch += .25f * TBase::inputs[PITCH_MOD_INPUT].value *
  178. taper(TBase::params[PARAM_PITCH_MOD_TRIM].value);
  179. const float q = float(log2(261.626)); // move up to pitch range of EvenVCO
  180. pitch += q;
  181. _freq = expLookup(pitch);
  182. if (_freq < .01f) {
  183. _freq = .01f;
  184. }
  185. // Multiply in the Linear FM contribution
  186. _freq *= 1.0f + TBase::inputs[LINEAR_FM_INPUT].value * taper(TBase::params[PARAM_LINEAR_FM_TRIM].value);
  187. float time = std::clamp(_freq * TBase::engineGetSampleTime(), -.5f, 0.5f);
  188. Osc::setFrequency(sinParams, time);
  189. if (cycleCount == 0) {
  190. // Get the gain from the envelope generator in
  191. // eGain = {0 .. 10.0f }
  192. float eGain = TBase::inputs[ENV_INPUT].active ? TBase::inputs[ENV_INPUT].value : 10.f;
  193. isExternalAudio = TBase::inputs[AUDIO_INPUT].active;
  194. const float gainKnobValue = TBase::params[PARAM_EXTGAIN].value;
  195. const float gainCVValue = TBase::inputs[GAIN_INPUT].value;
  196. const float gainTrimValue = TBase::params[PARAM_EXTGAIN_TRIM].value;
  197. const float combinedGain = gainCombiner(gainCVValue, gainKnobValue, gainTrimValue);
  198. // tapered gain {0 .. 0.5}
  199. const float taperedGain = .5f * taper(combinedGain);
  200. // final gain 0..5
  201. finalGain = taperedGain * eGain;
  202. }
  203. float input = finalGain * (isExternalAudio ?
  204. TBase::inputs[AUDIO_INPUT].value :
  205. Osc::run(sinState, sinParams));
  206. checkClipping(input);
  207. // Now clip or fold to keep in -1...+1
  208. if (TBase::params[PARAM_FOLD].value > .5) {
  209. input = AudioMath::fold(input);
  210. } else {
  211. input = std::max(input, -1.f);
  212. input = std::min(input, 1.f);
  213. }
  214. return input;
  215. }
  216. /**
  217. * Desired behavior:
  218. * If we clip, led goes red and stays red for clipDuration
  219. * if not red, sign present goes green
  220. * nothing - turn off
  221. */
  222. template <class TBase>
  223. inline void CHBg<TBase>::checkClipping(float input)
  224. {
  225. if (input > 1) {
  226. // if clipping, go red
  227. clipCount = clipDuration;
  228. TBase::lights[GAIN_RED_LIGHT].value = 10;
  229. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  230. } else if (clipCount) {
  231. // If red,run down the clock
  232. clipCount--;
  233. if (clipCount <= 0) {
  234. TBase::lights[GAIN_RED_LIGHT].value = 0;
  235. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  236. }
  237. } else if (input > .3f) {
  238. // if signal present
  239. signalCount = clipDuration;
  240. TBase::lights[GAIN_GREEN_LIGHT].value = 10;
  241. } else if (signalCount) {
  242. signalCount--;
  243. if (signalCount <= 0) {
  244. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  245. }
  246. }
  247. }
  248. template <class TBase>
  249. inline void CHBg<TBase>::calcVolumes(float * volumes)
  250. {
  251. // first get the harmonics knobs, and scale them
  252. for (int i = 0; i < numHarmonics; ++i) {
  253. float val = taper(TBase::params[i + PARAM_H0].value); // apply taper to the knobs
  254. // If input connected, scale and multiply with knob value
  255. if (TBase::inputs[i + H0_INPUT].active) {
  256. const float inputCV = TBase::inputs[i + H0_INPUT].value * .1f;
  257. val *= std::max(inputCV, 0.f);
  258. }
  259. volumes[i] = val;
  260. }
  261. // Second: apply the even and odd knobs
  262. {
  263. const float even = taper(TBase::params[PARAM_MAG_EVEN].value);
  264. const float odd = taper(TBase::params[PARAM_MAG_ODD].value);
  265. for (int i = 1; i < polyOrder; ++i) {
  266. const float mul = (i & 1) ? even : odd; // 0 = fundamental, 1=even, 2=odd....
  267. volumes[i] *= mul;
  268. }
  269. }
  270. // Third: slope
  271. {
  272. const float slope = slopeScale(TBase::params[PARAM_SLOPE].value, TBase::inputs[SLOPE_INPUT].value, 1);
  273. for (int i = 0; i < polyOrder; ++i) {
  274. float slopeAttenDb = slope * getOctave(i);
  275. float slopeAtten = LookupTable<float>::lookup(*db2gain, slopeAttenDb);
  276. volumes[i] *= slopeAtten;
  277. }
  278. }
  279. }
  280. template <class TBase>
  281. inline void CHBg<TBase>::step()
  282. {
  283. if (economyMode) {
  284. if (--cycleCount < 0) {
  285. cycleCount = 3;
  286. }
  287. } else {
  288. cycleCount = 0;
  289. }
  290. // do all the processing to get the carrier signal
  291. const float input = getInput();
  292. if (cycleCount == 0) {
  293. calcVolumes(_volume);
  294. for (int i = 0; i < polyOrder; ++i) {
  295. poly.setGain(i, _volume[i]);
  296. }
  297. }
  298. float output = poly.run(input, std::min(finalGain, 1.f));
  299. TBase::outputs[MIX_OUTPUT].value = 5.0f * output;
  300. }