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.

383 lines
9.0KB

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