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.

637 lines
25KB

  1. #ifndef LABSEVEN_3340_VCO_H
  2. #define LABSEVEN_3340_VCO_H
  3. #include <fstream>
  4. #include <float.h>
  5. #define _USE_MATH_DEFINES
  6. using namespace std;
  7. namespace LabSeven
  8. {
  9. namespace LS3340
  10. {
  11. //Blackman Harris function
  12. inline double wfBlackmanHarris64bit(double nbr_points, double t, double *parameters = NULL)
  13. {
  14. double _2_pi_by_N_1_times_t = (2.0 * M_PI / (nbr_points - 1))*t;
  15. return 0.35875
  16. - 0.48829 * cos(1.0 * _2_pi_by_N_1_times_t)
  17. + 0.14128 * cos(2.0 * _2_pi_by_N_1_times_t)
  18. - 0.01168 * cos(3.0 * _2_pi_by_N_1_times_t);
  19. }
  20. //SINC function
  21. inline double sinc(double t)
  22. {
  23. if(t == 0.0)
  24. {
  25. return 1.0;
  26. }
  27. else
  28. {
  29. double M_PI_t;
  30. M_PI_t = M_PI * t;
  31. return sin(M_PI_t) / M_PI_t;
  32. }
  33. }
  34. double *makeOversampledUnintegrated3340ImpulseDbl(int widthSamples = 8, int ovrsampling = 10000)
  35. {
  36. //SH pulse specifications:
  37. //@192kHz: width 8 samples, integrator leakage 1.0-0.27
  38. //norm 0.27 (coincidence?)
  39. int nbrSamples = widthSamples*ovrsampling;
  40. double *pulse = new double[nbrSamples];
  41. double sum;
  42. double norm = 0.27; //magic number!
  43. sum = 0.0;
  44. for (int i = 0; i < nbrSamples; i++)
  45. {
  46. pulse[i] = wfBlackmanHarris64bit(nbrSamples, i) - wfBlackmanHarris64bit(nbrSamples, 0);
  47. sum += pulse[i];
  48. }
  49. for (int i = 0; i < nbrSamples; i++)
  50. {
  51. pulse[i] *= norm * (double)ovrsampling / sum;
  52. }
  53. return pulse;
  54. //integration to complete 3340 pulse: s[i] += s[i - 1] * (1.0 - 0.27 / oversampling);
  55. }
  56. struct TLS3340VCOFrame
  57. {
  58. double square;
  59. double sawtooth;
  60. double subosc;
  61. double noise;
  62. double triangle;
  63. };
  64. struct TLS3340ImpulseParameters
  65. {
  66. size_t position;
  67. double offset;
  68. double phaseStep;
  69. double scaling;
  70. };
  71. struct TLS3340VCOImpulseLUT
  72. {
  73. //use high oversampling factor + next neighbour interpolation for performance
  74. size_t impulseLengthFrames;
  75. double oversamplingFactor;
  76. size_t lutSize;
  77. double *lut;
  78. //auxiliary
  79. double posOversampled;
  80. TLS3340VCOImpulseLUT()
  81. {
  82. impulseLengthFrames = 8;
  83. oversamplingFactor = 10000;
  84. lutSize = impulseLengthFrames * (size_t)oversamplingFactor;
  85. lut = makeOversampledUnintegrated3340ImpulseDbl(impulseLengthFrames,oversamplingFactor);
  86. }
  87. ~TLS3340VCOImpulseLUT()
  88. {
  89. delete lut;
  90. }
  91. inline double getValAt(double position)//position without oversampling
  92. {
  93. //position with oversampling
  94. posOversampled = position*oversamplingFactor;
  95. //return 0 if position is out of bounds
  96. if (posOversampled < 0.0 || posOversampled > lutSize-1) //
  97. {
  98. return 0;
  99. }
  100. //next neigbour interpolation
  101. return lut[(size_t)round(posOversampled)];
  102. }
  103. };
  104. struct TLS3340VCOImpulseTrain
  105. {
  106. const static int maxNbrImpulsesPerTrain = 50; //depends on impulse length!
  107. TLS3340ImpulseParameters train[maxNbrImpulsesPerTrain];
  108. //runtime stuff
  109. int trainIndex;
  110. int nbrActiveImpulses;
  111. int currentIndex;
  112. double impulseCounter;
  113. //needed for the leaky integrator that turns the lut impulse into the 340 impulse
  114. double integrationBuffer;
  115. TLS3340VCOImpulseTrain()
  116. {
  117. trainIndex = 0;
  118. nbrActiveImpulses = 0;
  119. currentIndex = 0;
  120. integrationBuffer = 0.0;
  121. }
  122. inline void addImpulse(double offset, double phaseStep, double scaling = 1.0)
  123. {
  124. //increment/wrap trainIndex
  125. trainIndex++;
  126. if (trainIndex >= maxNbrImpulsesPerTrain) trainIndex = 0;
  127. //initialize new impulse
  128. train[trainIndex].position = 0;
  129. train[trainIndex].offset = offset;
  130. train[trainIndex].phaseStep = phaseStep;
  131. train[trainIndex].scaling = scaling;
  132. //increment/limit nbrActiveImpulses
  133. nbrActiveImpulses++;
  134. if (nbrActiveImpulses > maxNbrImpulsesPerTrain)
  135. nbrActiveImpulses = maxNbrImpulsesPerTrain;
  136. }
  137. inline void getNextSumOfImpulsesAndSawtoothSlope(TLS3340VCOImpulseLUT *lut,
  138. double &currentSumOfImpulses)//,double &currentSumOfSlopes)
  139. {
  140. impulseCounter = 0.0;
  141. currentSumOfImpulses = 0.0;
  142. currentIndex = trainIndex;
  143. for (int i = 0; i < nbrActiveImpulses; i++)
  144. {
  145. //add contribution of current impulse
  146. if (train[currentIndex].phaseStep > 0.0)
  147. {
  148. currentSumOfImpulses += train[currentIndex].scaling *
  149. lut->getValAt(train[currentIndex].position +
  150. train[currentIndex].offset/train[currentIndex].phaseStep);
  151. }
  152. //deactivate impulse if it has been completely put out
  153. //(can be done this way because this impulse is the oldest active one;
  154. //therefore, this happens only while i == nbrActiveImpulses-1)
  155. train[currentIndex].position++;
  156. if (train[currentIndex].position > lut->impulseLengthFrames-1)
  157. {
  158. nbrActiveImpulses--;
  159. }
  160. //prepare currentIndex for next impulse
  161. currentIndex--;
  162. if (currentIndex < 0) currentIndex += maxNbrImpulsesPerTrain;
  163. }
  164. //integration
  165. currentSumOfImpulses += integrationBuffer * (1.0 - 0.27);
  166. integrationBuffer = currentSumOfImpulses;
  167. }
  168. };
  169. struct TLS3340NoiseSource
  170. {
  171. private:
  172. static const size_t dataLengthSamples = 960527;
  173. float LS3340Noise[dataLengthSamples];
  174. unsigned long currentPosition;
  175. public:
  176. TLS3340NoiseSource()
  177. {
  178. //initialize noise array
  179. for (size_t i=0;i<dataLengthSamples;i++)
  180. {
  181. LS3340Noise[i] = 0.0;
  182. }
  183. //LOAD NOISE SAMPLE
  184. ifstream f;
  185. f.open(assetPlugin(plugin, "res/LabSeven_3340_noise.pcm"), ios::binary);
  186. f.read((char*)&(LS3340Noise[0]),dataLengthSamples*sizeof(float));
  187. f.close();
  188. //randomize start position
  189. //makes phasing unlikely in case multiple 3340s are used together
  190. currentPosition = (unsigned long)round(((double) rand() / (RAND_MAX))*(double)(dataLengthSamples-1));
  191. }
  192. inline float getNextNoiseSample()
  193. {
  194. if (dataLengthSamples == 0)
  195. {
  196. return 0.0;
  197. }
  198. else
  199. {
  200. //move/wrap currentPosition
  201. currentPosition++;
  202. if (currentPosition >= dataLengthSamples)
  203. currentPosition -= dataLengthSamples;
  204. return LS3340Noise[currentPosition];
  205. }
  206. }
  207. };
  208. struct TLS3340VCOSINCLUT
  209. {
  210. private:
  211. int sincTableSize;
  212. float *sincTable;
  213. double overSampling;
  214. double zeroCrossings;
  215. double posOversampled;
  216. double middle;
  217. public:
  218. inline float getValAt(double posRelMiddleNotOversampled)
  219. {
  220. posOversampled = posRelMiddleNotOversampled * overSampling;
  221. int pos = (size_t)round(middle+posOversampled);
  222. if (pos >= 0 && pos < sincTableSize) //TODO: This should always be the case but it is not!
  223. {
  224. return sincTable[pos];
  225. }
  226. else return 0.0f;
  227. }
  228. inline int size(){return sincTableSize;}
  229. TLS3340VCOSINCLUT(int zeroCrossings, int overSampling)
  230. {
  231. this->overSampling = overSampling;
  232. this->zeroCrossings = zeroCrossings;
  233. sincTableSize = (zeroCrossings * 2 * overSampling) + 1;
  234. middle = floor(sincTableSize/2.0);
  235. sincTable = new float[sincTableSize];
  236. // BEGIN calculate sinc lut
  237. // Based on code by Daniel Werner https://www.experimentalscene.com/articles/minbleps.php
  238. double a = (double)-zeroCrossings;
  239. double b = (double)zeroCrossings;
  240. double r;
  241. for(int i = 0; i < sincTableSize; i++)
  242. {
  243. r = ((double)i) / ((double)(sincTableSize - 1));
  244. sincTable[i] = (float)sinc(a + (r * (b - a)));
  245. sincTable[i] *= (float)sinc(-1.0 + (r * (2.0))); //Window function is sinc, too!
  246. }
  247. // END calculate sinc lut
  248. //normalize sinc lut
  249. double sum = 0.0;
  250. for(int i = 0; i < sincTableSize; i++)
  251. {
  252. sum += sincTable[i];
  253. }
  254. sum /= overSampling;
  255. sum = 1.0/sum;
  256. for(int i = 0; i < sincTableSize; i++)
  257. {
  258. sincTable[i] *= sum;
  259. }
  260. }
  261. ~TLS3340VCOSINCLUT()
  262. {
  263. delete sincTable;
  264. }
  265. };
  266. struct TLS3340VCO
  267. {
  268. private:
  269. TLS3340VCOFrame lastFrame;
  270. TLS3340VCOImpulseLUT *impulse;
  271. TLS3340VCOImpulseTrain zeroPhaseImpulses;//at the beginnig of a vco cycle
  272. TLS3340VCOImpulseTrain pwmImpulses;//for square pwm
  273. TLS3340VCOImpulseTrain suboscillatorImpulses;
  274. TLS3340VCOImpulseTrain sawImpulses;
  275. TLS3340NoiseSource noise;
  276. double vcoFrequencyHz;
  277. double sampleRateHzInternal;
  278. double sampleRateHzExternal;
  279. double sampleTimeS; //time per sample, 1/f
  280. double currentPhaseStep;
  281. double phase;
  282. double currentSumOfZeroPhaseImpulses, currentSumOfPWMImpulses,currentSumOfSawImpulses,currentSumOfSuboscImpulses;
  283. double sawtoothSlope;
  284. double leakyIntegratorFactor,leakyIntegratorFactorSaw;
  285. int suboscillatorCounter;
  286. bool squareSwitch,sawSwitch;
  287. double pwmCoefficient; //range: -0.5 to +0.5
  288. int suboscillatorMode;
  289. int noiseIndex;
  290. double lastPitch = 261.6256;
  291. //for sinc interpolation (todo: Move iterpolation to struct or class)
  292. const int sincZeroCrossings = 3; //increase for even better anti aliasing
  293. const int sincOversampling = 10000;
  294. LabSeven::LS3340::TLS3340VCOSINCLUT *sincOversampled;
  295. //nbrInternalSamples is much larger than neccessary
  296. //so that sincZeroCrossings can be increased without problems
  297. const int nbrInternalSamples = 1000;
  298. TLS3340VCOFrame internalSamples[1000];
  299. int nbrInternalSamplesInUse = 0;
  300. int internalSamplesIndex = 0;
  301. //depend on external samplerate
  302. int radius;
  303. double sampleStep;
  304. double stretch;
  305. int nbrSamplesRequired;
  306. //runtime stuff
  307. int internalSamplesPointer;
  308. double sincPosition;
  309. double samplePhase = 0.0;
  310. double currentSincCoeff;
  311. double triangleTemp = 0.0;
  312. inline void setSamplerateInternal(double sampleRateHz)
  313. {
  314. sampleRateHzInternal = sampleRateHz;
  315. this->sampleTimeS = 1.0/sampleRateHzInternal;
  316. currentPhaseStep = vcoFrequencyHz * sampleTimeS;
  317. sawtoothSlope = currentPhaseStep;//vcoFrequencyHz * sampleTimeS;
  318. }
  319. public:
  320. inline void setSamplerateExternal(double sampleRateHz)
  321. {
  322. sampleRateHzExternal = sampleRateHz;
  323. //recalulate frequency dependent sinc interpolation parameters
  324. sampleStep = sampleRateHzInternal/sampleRateHzExternal;
  325. stretch = 0.8*sampleRateHzExternal/sampleRateHzInternal;
  326. if (sampleRateHz <= 48000)
  327. {
  328. stretch = 0.8*sampleRateHzExternal/sampleRateHzInternal;
  329. }
  330. else if (sampleRateHz <= 96000)
  331. {
  332. stretch = 0.6*sampleRateHzExternal/sampleRateHzInternal;
  333. }
  334. else
  335. {
  336. stretch = 0.5*sampleRateHzExternal/sampleRateHzInternal;
  337. }
  338. radius = floor((double)sincZeroCrossings/stretch);
  339. nbrSamplesRequired = radius+1;
  340. nbrInternalSamplesInUse = 2*radius+1;
  341. }
  342. inline void setFrequency(double frequencyHz)
  343. {
  344. this->vcoFrequencyHz = frequencyHz;
  345. currentPhaseStep = vcoFrequencyHz * sampleTimeS;
  346. sawtoothSlope = currentPhaseStep;//vcoFrequencyHz * sampleTimeS;
  347. }
  348. inline void setPwmCoefficient(double pwmCoefficient)
  349. {
  350. if (pwmCoefficient > 0.4)
  351. this->pwmCoefficient = 0.4;
  352. else if (pwmCoefficient < -0.4)
  353. this->pwmCoefficient = -0.4;
  354. else
  355. this->pwmCoefficient = pwmCoefficient;
  356. }
  357. inline void setSuboscillatorMode(int mode)
  358. {
  359. if (mode == 0)
  360. this->suboscillatorMode = 0;
  361. else if (mode == 1)
  362. this->suboscillatorMode = 1;
  363. else if (mode == 2)
  364. this->suboscillatorMode = 2;
  365. else
  366. this->suboscillatorMode = 1;
  367. }
  368. //can output several samples at a time
  369. inline void getNextBlock(TLS3340VCOFrame *block, size_t nbrFramesInBlock)
  370. {
  371. for (size_t i=0; i<nbrFramesInBlock; i++)
  372. {
  373. //manage phase and pulse creation
  374. phase += currentPhaseStep;
  375. if (phase > 1.0)
  376. {
  377. //wrap phase for periodicity
  378. phase -= 1.0;
  379. //add new positive impulse
  380. zeroPhaseImpulses.addImpulse(phase,currentPhaseStep);
  381. //inc and wrap suboscillatorCounter
  382. suboscillatorCounter++;
  383. if (suboscillatorCounter > 3) suboscillatorCounter = 0;
  384. switch (suboscillatorMode)
  385. {
  386. case 0: switch (suboscillatorCounter)
  387. {
  388. case 0: case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep, 1.0);break;
  389. case 1: case 3: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break;
  390. default: suboscillatorCounter=suboscillatorCounter;
  391. }
  392. break;
  393. case 1: switch (suboscillatorCounter)
  394. {
  395. case 0: suboscillatorImpulses.addImpulse(phase,currentPhaseStep, 1.0);break;
  396. case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break;
  397. default: suboscillatorCounter=suboscillatorCounter;
  398. }
  399. break;
  400. case 2: switch (suboscillatorCounter)
  401. {
  402. //amplitude set to 1.255 on purpose
  403. case 0: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,1.255); break;
  404. case 1: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-0.25); break;
  405. case 2: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-0.005); break;
  406. case 3: suboscillatorImpulses.addImpulse(phase,currentPhaseStep,-1.0);break;
  407. default: suboscillatorCounter=suboscillatorCounter;
  408. }
  409. }
  410. //tell squareSwitch that a new period has begun
  411. squareSwitch = true;
  412. sawSwitch = true;
  413. }
  414. else
  415. {
  416. //pwmCoefficient is in [-0.4,+0.4] für 10% to 90% pwm range
  417. if (squareSwitch && phase > 0.5+pwmCoefficient)
  418. {
  419. //add new negative impulse
  420. pwmImpulses.addImpulse(phase-(0.5+pwmCoefficient),currentPhaseStep,-1.0);
  421. squareSwitch = false;
  422. }
  423. if (sawSwitch && phase > 0.5)
  424. {
  425. //add new negative impulse
  426. sawImpulses.addImpulse(phase-0.5,currentPhaseStep,-1.0);
  427. sawSwitch = false;
  428. }
  429. }
  430. //get current sums of impulses
  431. zeroPhaseImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfZeroPhaseImpulses);
  432. pwmImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfPWMImpulses);
  433. sawImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse, currentSumOfSawImpulses);
  434. suboscillatorImpulses.getNextSumOfImpulsesAndSawtoothSlope(impulse,currentSumOfSuboscImpulses);
  435. //put together the waveforms
  436. leakyIntegratorFactor = 0.9998;
  437. leakyIntegratorFactorSaw = 0.9998 - 0.05*2.0*vcoFrequencyHz*sampleTimeS;
  438. block[i].sawtooth = currentSumOfSawImpulses + sawtoothSlope + leakyIntegratorFactorSaw * lastFrame.sawtooth;
  439. block[i].square = currentSumOfZeroPhaseImpulses + currentSumOfPWMImpulses + leakyIntegratorFactor * lastFrame.square;
  440. block[i].subosc = currentSumOfSuboscImpulses + leakyIntegratorFactorSaw * lastFrame.subosc;
  441. block[i].noise = noise.getNextNoiseSample();
  442. triangleTemp = currentSumOfZeroPhaseImpulses + currentSumOfSawImpulses + leakyIntegratorFactor * triangleTemp;
  443. block[i].triangle = 4.9*sawtoothSlope*triangleTemp + leakyIntegratorFactorSaw * lastFrame.triangle;
  444. lastFrame.sawtooth = block[i].sawtooth;
  445. lastFrame.square = block[i].square;
  446. lastFrame.triangle = block[i].triangle;
  447. lastFrame.subosc = block[i].subosc;
  448. lastPitch = vcoFrequencyHz;
  449. }
  450. }
  451. //output with sinc interpolation
  452. inline void getNextFrameAtExternalSampleRateSinc(TLS3340VCOFrame *f)
  453. {
  454. //get the necessary number of new internal samples
  455. for (int j = 0; j < nbrSamplesRequired; j++)
  456. {
  457. getNextBlock(&(internalSamples[internalSamplesIndex]),1);
  458. internalSamplesIndex++;
  459. if (internalSamplesIndex >= nbrInternalSamples)
  460. internalSamplesIndex -= nbrInternalSamples;
  461. }
  462. //calculate interpolation
  463. internalSamplesPointer = internalSamplesIndex;
  464. f->square = 0.0;
  465. f->sawtooth = 0.0;
  466. f->subosc = 0.0;
  467. f->noise = 0.0;
  468. f->triangle = 0.0;
  469. sincPosition = ((double)(nbrInternalSamplesInUse-(radius+1))-samplePhase)*stretch;
  470. for (int j = 0; j < nbrInternalSamplesInUse; j++)
  471. {
  472. currentSincCoeff = sincOversampled->getValAt(sincPosition);
  473. f->square += internalSamples[internalSamplesPointer].square * currentSincCoeff;
  474. f->sawtooth += internalSamples[internalSamplesPointer].sawtooth * currentSincCoeff;
  475. f->subosc += internalSamples[internalSamplesPointer].subosc * currentSincCoeff;
  476. f->noise += internalSamples[internalSamplesPointer].noise * currentSincCoeff;
  477. f->triangle += internalSamples[internalSamplesPointer].triangle * currentSincCoeff;
  478. sincPosition -= stretch;
  479. internalSamplesPointer--;
  480. if (internalSamplesPointer < 0)
  481. internalSamplesPointer += nbrInternalSamples;
  482. }
  483. //rescale
  484. f->square *= stretch;
  485. f->sawtooth *= stretch;
  486. f->subosc *= stretch;
  487. f->noise *= stretch;
  488. f->triangle *= stretch;
  489. //prepare parameters for next interpolation
  490. samplePhase += sampleStep;
  491. nbrSamplesRequired = (int)floor(samplePhase);
  492. samplePhase -= (double)nbrSamplesRequired;
  493. }
  494. inline void getNextFrameAtExternalSampleRateCubic(TLS3340VCOFrame *f)
  495. {
  496. //get the necessary number of new internal samples
  497. for (int j = 0; j < nbrSamplesRequired; j++)
  498. {
  499. getNextBlock(&(internalSamples[internalSamplesIndex]),1);
  500. internalSamplesIndex++;
  501. if (internalSamplesIndex >= nbrInternalSamples)
  502. internalSamplesIndex -= nbrInternalSamples;
  503. }
  504. //calculate interpolation
  505. f->square = 0.0;
  506. f->sawtooth = 0.0;
  507. f->subosc = 0.0;
  508. f->noise = 0.0;
  509. f->triangle = 0.0;
  510. //Next neighbour interpolation as a stub
  511. //TODO: Replace by cubic interpolation and add a 'quality: std/hgh' switch to GUI
  512. *f = internalSamples[internalSamplesIndex];
  513. //prepare parameters for next interpolation
  514. samplePhase += sampleStep;
  515. nbrSamplesRequired = (int)floor(samplePhase);
  516. samplePhase -= (double)nbrSamplesRequired;
  517. }
  518. TLS3340VCO()
  519. {
  520. //defaults
  521. setSamplerateInternal(192000); //192k covers the complete bandwidth of the original impulse. Do not change!
  522. setSamplerateExternal(44100); //to be changed/updated by host software
  523. setFrequency(261.6256);
  524. //generate fitted LS3340 impulse and sinc resampler lookup tables
  525. impulse = new TLS3340VCOImpulseLUT();
  526. sincOversampled = new LabSeven::LS3340::TLS3340VCOSINCLUT(sincZeroCrossings,sincOversampling);
  527. //some initializations
  528. lastFrame.sawtooth=-0.7;
  529. suboscillatorCounter = 0;
  530. squareSwitch = false;
  531. pwmCoefficient = 0;
  532. suboscillatorMode = 1;
  533. noiseIndex = 0;
  534. currentSumOfSuboscImpulses = 0.0;
  535. currentSumOfSawImpulses = 0.0;
  536. currentSumOfZeroPhaseImpulses = 0.0;
  537. currentSumOfPWMImpulses = 0.0;
  538. }
  539. ~TLS3340VCO()
  540. {
  541. delete impulse;
  542. delete sincOversampled;
  543. }
  544. };
  545. };
  546. };
  547. #endif // LABSEVEN_3340_VCO_H