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.

543 lines
14KB

  1. #pragma once
  2. #include <algorithm>
  3. #include "AudioMath.h"
  4. #include "IComposite.h"
  5. #include "LookupTableFactory.h"
  6. #include "MultiLag.h"
  7. #include "ObjectCache.h"
  8. #include "poly.h"
  9. #include "SinOscillator.h"
  10. using Osc = SinOscillator<float, true>;
  11. #ifndef _CLAMP
  12. #define _CLAMP
  13. namespace std {
  14. inline float clamp(float v, float lo, float hi)
  15. {
  16. assert(lo < hi);
  17. return std::min(hi, std::max(v, lo));
  18. }
  19. }
  20. #endif
  21. template <class TBase>
  22. class CHBDescription : public IComposite
  23. {
  24. public:
  25. Config getParam(int i) override;
  26. int getNumParams() override;
  27. };
  28. /**
  29. * Composite for Chebyshev module.
  30. *
  31. * Performance measure for 1.0 = 42.44
  32. * reduced polynomial order to what we actually use (10), perf = 39.5
  33. */
  34. template <class TBase>
  35. class CHB : public TBase
  36. {
  37. public:
  38. CHB(struct Module * module) : TBase(module)
  39. {
  40. init();
  41. }
  42. CHB() : TBase()
  43. {
  44. init();
  45. }
  46. enum ParamIds
  47. {
  48. PARAM_TUNE,
  49. PARAM_OCTAVE,
  50. PARAM_EXTGAIN,
  51. PARAM_PITCH_MOD_TRIM,
  52. PARAM_LINEAR_FM_TRIM,
  53. PARAM_EXTGAIN_TRIM,
  54. PARAM_FOLD,
  55. PARAM_SLOPE,
  56. PARAM_MAG_EVEN,
  57. PARAM_MAG_ODD,
  58. PARAM_H0,
  59. PARAM_H1,
  60. PARAM_H2,
  61. PARAM_H3,
  62. PARAM_H4,
  63. PARAM_H5,
  64. PARAM_H6,
  65. PARAM_H7,
  66. PARAM_H8,
  67. PARAM_H9, // up to here is Ver 1.0
  68. PARAM_EXPAND,
  69. PARAM_RISE,
  70. PARAM_FALL,
  71. PARAM_EVEN_TRIM,
  72. PARAM_ODD_TRIM,
  73. PARAM_SLOPE_TRIM,
  74. PARAM_SEMIS,
  75. NUM_PARAMS
  76. };
  77. const int numHarmonics = 1 + PARAM_H9 - PARAM_H0;
  78. enum InputIds
  79. {
  80. CV_INPUT,
  81. PITCH_MOD_INPUT,
  82. LINEAR_FM_INPUT,
  83. ENV_INPUT,
  84. GAIN_INPUT,
  85. AUDIO_INPUT,
  86. SLOPE_INPUT,
  87. H0_INPUT,
  88. H1_INPUT,
  89. H2_INPUT,
  90. H3_INPUT,
  91. H4_INPUT,
  92. H5_INPUT,
  93. H6_INPUT,
  94. H7_INPUT,
  95. H8_INPUT,
  96. H9_INPUT,
  97. H10_INPUT, // up to here V1.0
  98. RISE_INPUT,
  99. FALL_INPUT,
  100. EVEN_INPUT,
  101. ODD_INPUT,
  102. NUM_INPUTS
  103. };
  104. enum OutputIds
  105. {
  106. MIX_OUTPUT,
  107. NUM_OUTPUTS
  108. };
  109. enum LightIds
  110. {
  111. GAIN_GREEN_LIGHT,
  112. GAIN_RED_LIGHT,
  113. NUM_LIGHTS
  114. };
  115. /** Implement IComposite
  116. */
  117. static std::shared_ptr<IComposite> getDescription()
  118. {
  119. return std::make_shared<CHBDescription<TBase>>();
  120. }
  121. /**
  122. * Main processing entry point. Called every sample
  123. */
  124. void step() override;
  125. void onSampleRateChange()
  126. {
  127. knobToFilterL = makeLPFDirectFilterLookup<float>(this->engineGetSampleTime());
  128. }
  129. float _freq = 0;
  130. private:
  131. int cycleCount = 1;
  132. int clipCount = 0;
  133. int signalCount = 0;
  134. const int clipDuration = 4000;
  135. float finalGain = 0;
  136. bool isExternalAudio = false;
  137. static const int polyOrder = 10;
  138. /**
  139. * The waveshaper that is the heart of this module.
  140. * Let's use doubles.
  141. */
  142. Poly<double, polyOrder> poly;
  143. MultiLag<12> lag;
  144. /*
  145. * maps freq multiple to "octave".
  146. * In other words, log base 12.
  147. */
  148. float _octave[polyOrder];
  149. float getOctave(int mult) const;
  150. void init();
  151. // round up to 12, so multi-lag is happy
  152. float _volume[12] = {0};
  153. /**
  154. * Internal sine wave oscillator to drive the waveshaper
  155. */
  156. SinOscillatorParams<float> sinParams;
  157. SinOscillatorState<float> sinState;
  158. // just maps 0..1 to 0..1
  159. std::shared_ptr<LookupTableParams<float>> audioTaper = {ObjectCache<float>::getAudioTaper()};
  160. AudioMath::ScaleFun<float> gainCombiner = AudioMath::makeLinearScaler(0.f, 1.f);
  161. std::function<float(float)> expLookup = ObjectCache<float>::getExp2Ex();
  162. std::shared_ptr<LookupTableParams<float>> db2gain = ObjectCache<float>::getDb2Gain();
  163. std::shared_ptr <LookupTableParams<float>> knobToFilterL;
  164. /**
  165. * Audio taper for the slope.
  166. */
  167. AudioMath::ScaleFun<float> slopeScale =
  168. {AudioMath::makeLinearScaler<float>(-18, 0)};
  169. /**
  170. * do one-time calculations when sample rate changes
  171. */
  172. void internalUpdate();
  173. /**
  174. * Do all the processing to get the input waveform
  175. * that will be fed to the polynomials
  176. */
  177. float getInput();
  178. void calcVolumes(float *);
  179. void checkClipping(float sample);
  180. void updateLagTC();
  181. /**
  182. * Does audio taper
  183. * @param raw = 0..1
  184. * @return 0..1
  185. */
  186. float taper(float raw)
  187. {
  188. return LookupTable<float>::lookup(*audioTaper, raw, false);
  189. }
  190. AudioMath::ScaleFun<float> lin = AudioMath::makeLinearScaler<float>(0, 1);
  191. };
  192. template <class TBase>
  193. inline void CHB<TBase>::init()
  194. {
  195. for (int i = 0; i < polyOrder; ++i) {
  196. _octave[i] = log2(float(i + 1));
  197. }
  198. onSampleRateChange();
  199. lag.setAttack(.1f);
  200. lag.setRelease(.0001f);
  201. }
  202. template <class TBase>
  203. inline float CHB<TBase>::getOctave(int i) const
  204. {
  205. assert(i >= 0 && i < polyOrder);
  206. return _octave[i];
  207. }
  208. template <class TBase>
  209. inline void CHB<TBase>::updateLagTC()
  210. {
  211. const float combinedA = lin(
  212. TBase::inputs[RISE_INPUT].value,
  213. TBase::params[PARAM_RISE].value,
  214. 1);
  215. const float combinedR = lin(
  216. TBase::inputs[FALL_INPUT].value,
  217. TBase::params[PARAM_FALL].value,
  218. 1);
  219. if (combinedA < .1 && combinedR < .1) {
  220. lag.setEnable(false);
  221. } else {
  222. lag.setEnable(true);
  223. const float lA = LookupTable<float>::lookup(*knobToFilterL, combinedA);
  224. lag.setAttackL(lA);
  225. const float lR = LookupTable<float>::lookup(*knobToFilterL, combinedR);
  226. lag.setReleaseL(lR);
  227. }
  228. }
  229. template <class TBase>
  230. inline float CHB<TBase>::getInput()
  231. {
  232. assert(TBase::engineGetSampleTime() > 0);
  233. // Get the frequency from the inputs.
  234. float pitch = 1.0f + roundf(TBase::params[PARAM_OCTAVE].value) +
  235. TBase::params[PARAM_SEMIS].value / 12.0f +
  236. TBase::params[PARAM_TUNE].value / 12.0f;
  237. pitch += TBase::inputs[CV_INPUT].value;
  238. pitch += .25f * TBase::inputs[PITCH_MOD_INPUT].value *
  239. taper(TBase::params[PARAM_PITCH_MOD_TRIM].value);
  240. const float q = float(log2(261.626)); // move up to pitch range of EvenVCO
  241. pitch += q;
  242. _freq = expLookup(pitch);
  243. if (_freq < .01f) {
  244. _freq = .01f;
  245. }
  246. // Multiply in the Linear FM contribution
  247. _freq *= 1.0f + TBase::inputs[LINEAR_FM_INPUT].value * taper(TBase::params[PARAM_LINEAR_FM_TRIM].value);
  248. float time = std::clamp(_freq * TBase::engineGetSampleTime(), -.5f, 0.5f);
  249. Osc::setFrequency(sinParams, time);
  250. if (cycleCount == 0) {
  251. // Get the gain from the envelope generator in
  252. // eGain = {0 .. 10.0f }
  253. float eGain = TBase::inputs[ENV_INPUT].active ? TBase::inputs[ENV_INPUT].value : 10.f;
  254. isExternalAudio = TBase::inputs[AUDIO_INPUT].active;
  255. const float gainKnobValue = TBase::params[PARAM_EXTGAIN].value;
  256. const float gainCVValue = TBase::inputs[GAIN_INPUT].value;
  257. const float gainTrimValue = TBase::params[PARAM_EXTGAIN_TRIM].value;
  258. const float combinedGain = gainCombiner(gainCVValue, gainKnobValue, gainTrimValue);
  259. // tapered gain {0 .. 0.5}
  260. const float taperedGain = .5f * taper(combinedGain);
  261. // final gain 0..5
  262. finalGain = taperedGain * eGain;
  263. }
  264. float input = finalGain * (isExternalAudio ?
  265. TBase::inputs[AUDIO_INPUT].value :
  266. Osc::run(sinState, sinParams));
  267. checkClipping(input);
  268. // Now clip or fold to keep in -1...+1
  269. if (TBase::params[PARAM_FOLD].value > .5) {
  270. input = AudioMath::fold(input);
  271. } else {
  272. input = std::max(input, -1.f);
  273. input = std::min(input, 1.f);
  274. }
  275. return input;
  276. }
  277. /**
  278. * Desired behavior:
  279. * If we clip, led goes red and stays red for clipDuration
  280. * if not red, sign present goes green
  281. * nothing - turn off
  282. */
  283. template <class TBase>
  284. inline void CHB<TBase>::checkClipping(float input)
  285. {
  286. if (input > 1) {
  287. // if clipping, go red
  288. clipCount = clipDuration;
  289. TBase::lights[GAIN_RED_LIGHT].value = 10;
  290. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  291. } else if (clipCount) {
  292. // If red,run down the clock
  293. clipCount--;
  294. if (clipCount <= 0) {
  295. TBase::lights[GAIN_RED_LIGHT].value = 0;
  296. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  297. }
  298. } else if (input > .3f) {
  299. // if signal present
  300. signalCount = clipDuration;
  301. TBase::lights[GAIN_GREEN_LIGHT].value = 10;
  302. } else if (signalCount) {
  303. signalCount--;
  304. if (signalCount <= 0) {
  305. TBase::lights[GAIN_GREEN_LIGHT].value = 0;
  306. }
  307. }
  308. }
  309. template <class TBase>
  310. inline void CHB<TBase>::calcVolumes(float * volumes)
  311. {
  312. // first get the harmonics knobs, and scale them
  313. for (int i = 0; i < numHarmonics; ++i) {
  314. float val = taper(TBase::params[i + PARAM_H0].value); // apply taper to the knobs
  315. // If input connected, scale and multiply with knob value
  316. if (TBase::inputs[i + H0_INPUT].active) {
  317. const float inputCV = TBase::inputs[i + H0_INPUT].value * .1f;
  318. val *= std::max(inputCV, 0.f);
  319. }
  320. volumes[i] = val;
  321. }
  322. // Second: apply the even and odd knobs
  323. {
  324. const float evenCombined = gainCombiner(
  325. TBase::inputs[EVEN_INPUT].value,
  326. TBase::params[PARAM_MAG_EVEN].value,
  327. TBase::params[PARAM_EVEN_TRIM].value);
  328. const float oddCombined = gainCombiner(
  329. TBase::inputs[ODD_INPUT].value,
  330. TBase::params[PARAM_MAG_ODD].value,
  331. TBase::params[PARAM_ODD_TRIM].value);
  332. const float even = taper(evenCombined);
  333. const float odd = taper(oddCombined);
  334. for (int i = 1; i < polyOrder; ++i) {
  335. const float mul = (i & 1) ? even : odd; // 0 = fundamental, 1=even, 2=odd....
  336. volumes[i] *= mul;
  337. }
  338. }
  339. // Third: slope
  340. {
  341. const float slope = slopeScale(
  342. TBase::inputs[SLOPE_INPUT].value,
  343. TBase::params[PARAM_SLOPE].value,
  344. TBase::params[PARAM_SLOPE_TRIM].value);
  345. for (int i = 0; i < polyOrder; ++i) {
  346. float slopeAttenDb = slope * getOctave(i);
  347. float slopeAtten = LookupTable<float>::lookup(*db2gain, slopeAttenDb);
  348. volumes[i] *= slopeAtten;
  349. }
  350. }
  351. }
  352. template <class TBase>
  353. inline void CHB<TBase>::step()
  354. {
  355. if (--cycleCount < 0) {
  356. cycleCount = 3;
  357. }
  358. // do all the processing to get the carrier signal
  359. // Does the pitch every cycle, vol every 4
  360. const float input = getInput();
  361. if (cycleCount == 0) {
  362. updateLagTC(); // TODO: could do at reduced rate
  363. calcVolumes(_volume); // now _volume has all 10 harmonic volumes
  364. lag.step(_volume); // TODO: we could run lag at full rate.
  365. for (int i = 0; i < polyOrder; ++i) {
  366. //poly.setGain(i, _volume[i]);
  367. poly.setGain(i, lag.get(i));
  368. }
  369. }
  370. float output = poly.run(input, std::min(finalGain, 1.f));
  371. TBase::outputs[MIX_OUTPUT].value = 5.0f * output;
  372. }
  373. template <class TBase>
  374. int CHBDescription<TBase>::getNumParams()
  375. {
  376. return CHB<TBase>::NUM_PARAMS;
  377. }
  378. template <class TBase>
  379. inline IComposite::Config CHBDescription<TBase>::getParam(int i)
  380. {
  381. Config ret(0, 1, 0, "");
  382. const float defaultGainParam = .63108f;
  383. switch (i) {
  384. case CHB<TBase>::PARAM_TUNE:
  385. ret = {-1.0f, 1.0f, 0, "Fine Tune"};
  386. break;
  387. case CHB<TBase>::PARAM_OCTAVE:
  388. ret = {-5.0f, 4.0f, 0.f, "Octave"};
  389. break;
  390. case CHB<TBase>::PARAM_EXTGAIN:
  391. ret = {-5.0f, 5.0f, defaultGainParam, "External Gain"};
  392. break;
  393. case CHB<TBase>::PARAM_PITCH_MOD_TRIM:
  394. ret = {0, 1.0f, 0.0f, "Pitch mod trim"};
  395. break;
  396. case CHB<TBase>::PARAM_LINEAR_FM_TRIM:
  397. ret = {0, 1.0f, 0.0f, "Linear FM trim"};
  398. break;
  399. case CHB<TBase>::PARAM_EXTGAIN_TRIM:
  400. ret = {-1, 1, 0, "External gain trim"};
  401. break;
  402. case CHB<TBase>::PARAM_FOLD:
  403. ret = {0.0f, 1.0f, 0.0f, "Fold/Clip"};
  404. break;
  405. case CHB<TBase>::PARAM_SLOPE:
  406. ret = {-5, 5, 5, "Harmonic slope"};
  407. break;
  408. case CHB<TBase>::PARAM_MAG_EVEN:
  409. ret = {-5, 5, 5, "Even harmonic volume"};
  410. break;
  411. case CHB<TBase>::PARAM_MAG_ODD:
  412. ret = {-5, 5, 5, "Odd harmonic volume"};
  413. break;
  414. case CHB<TBase>::PARAM_H0:
  415. ret = {0.0f, 1.0f, 1, "Fundamental level"};
  416. break;
  417. case CHB<TBase>::PARAM_H1:
  418. ret = {0.0f, 1.0f, 0, "Harmonic 1 level"};
  419. break;
  420. case CHB<TBase>::PARAM_H2:
  421. ret = {0.0f, 1.0f, 0, "Harmonic 2 level"};
  422. break;
  423. case CHB<TBase>::PARAM_H3:
  424. ret = {0.0f, 1.0f, 0, "Harmonic 3 level"};
  425. break;
  426. case CHB<TBase>::PARAM_H4:
  427. ret = {0.0f, 1.0f, 0, "Harmonic 4 level"};
  428. break;
  429. case CHB<TBase>::PARAM_H5:
  430. ret = {0.0f, 1.0f, 0, "Harmonic 5 level"};
  431. break;
  432. case CHB<TBase>::PARAM_H6:
  433. ret = {0.0f, 1.0f, 0, "Harmonic 6 level"};
  434. break;
  435. case CHB<TBase>::PARAM_H7:
  436. ret = {0.0f, 1.0f, 0, "Harmonic 7 level"};
  437. break;
  438. case CHB<TBase>::PARAM_H8:
  439. ret = {0.0f, 1.0f, 0, "Harmonic 8 level"};
  440. break;
  441. case CHB<TBase>::PARAM_H9: // up to here is Ver 1.0
  442. ret = {0.0f, 1.0f, 0, "Harmonic 9 level"};
  443. break;
  444. case CHB<TBase>::PARAM_EXPAND:
  445. ret = {0, 1, 0, "unused"};
  446. break;
  447. case CHB<TBase>::PARAM_RISE:
  448. ret = {-5.f, 5.f, 0.f, "Rise time (harmonic volume CV)"};
  449. break;
  450. case CHB<TBase>::PARAM_FALL:
  451. ret = {-5.f, 5.f, 0.f, "Fall time (harmonic volume CV)"};
  452. break;
  453. case CHB<TBase>::PARAM_EVEN_TRIM:
  454. ret = {-1.0f, 1.0f, 0, "Even Harmonic CV trim"};
  455. break;
  456. case CHB<TBase>::PARAM_ODD_TRIM:
  457. ret = {-1.0f, 1.0f, 0, "Odd Harmonic CV trim"};
  458. break;
  459. case CHB<TBase>::PARAM_SLOPE_TRIM:
  460. ret = {-1.0f, 1.0f, 0, "Harmonic slope CV trim"};
  461. break;
  462. case CHB<TBase>::PARAM_SEMIS:
  463. ret = {-11.0f, 11.0f, 0.f, "Semitone offset"};
  464. break;
  465. default:
  466. assert(false);
  467. }
  468. return ret;
  469. }