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.

822 lines
26KB

  1. #include "plugin.hpp"
  2. // TODO: Remove these DSP classes after released in Rack SDK
  3. /** Approximates sin(2pi x) over [0, 1] with the form
  4. $ t (t^2 - 1) (c_0 + t^2 (c_1 + t^2 (c_2 + t^2 c_3))) $ where $ t = 2x - 1 $
  5. Optimized coefficients for lowest max absolute error 6.5e-06, THD -103.7 dB.
  6. */
  7. template <typename T>
  8. inline T sin_2pi_9(T x) {
  9. // Shift argument to [-1, 1]
  10. x = T(2) * x - T(1);
  11. T x2 = x * x;
  12. return x * (x2 - T(1)) * (T(3.1415211942925003) + x2 * (T(-2.0247734792732333) + x2 * (T(0.51749274223813413) + x2 * T(-0.063691093590695858))));
  13. }
  14. /** One-pole low-pass filter, exponential moving average.
  15. -6 dB/octave slope.
  16. Useful for leaky integrators and smoothing control signals.
  17. Has a pole at (1 - alpha) and a zero at 0.
  18. */
  19. struct OnePoleLowpass {
  20. float alpha = 0.f;
  21. /** Sets alpha using the matched-z / impulse invariance transform.
  22. Preserves the analog time constant, so step response reaches ~63.2% after t = 1/(2pi f) seconds.
  23. The -3 dB point is approximately f for low frequencies.
  24. f = f_c / f_s, the normalized frequency in the range [0, 0.5].
  25. */
  26. void setCutoffMatchedZ(float f) {
  27. alpha = 1.f - std::exp(float(-2 * M_PI) * f);
  28. }
  29. /** Sets alpha using the bilinear transform.
  30. Guarantees exact -3 dB gain at frequency f, but warps other frequencies.
  31. f = f_c / f_s, the normalized frequency in the range [0, 0.5].
  32. */
  33. void setCutoffBilinear(float f) {
  34. float w = std::tan(float(M_PI) * f);
  35. alpha = w / (1.f + w);
  36. }
  37. /** Sets alpha using a rational approximation.
  38. Approximates the bilinear transform for low frequencies (f << 0.1).
  39. f = f_c / f_s, the normalized frequency in the range [0, 0.5].
  40. */
  41. void setCutoffApprox(float f) {
  42. float w = float(2 * M_PI) * f;
  43. alpha = w / (1.f + w);
  44. }
  45. /** Computes the frequency response at normalized frequency f. */
  46. std::complex<float> getResponse(float f) const {
  47. float omega = float(2 * M_PI) * f;
  48. std::complex<float> z = std::exp(std::complex<float>(0.f, omega));
  49. return alpha * z / (z - (1.f - alpha));
  50. }
  51. float getMagnitude(float f) const {
  52. return std::abs(getResponse(f));
  53. }
  54. float getPhase(float f) const {
  55. return std::arg(getResponse(f));
  56. }
  57. template <typename T = float>
  58. struct State {
  59. T y = 0.f;
  60. };
  61. /** Advances the state with input x. Returns the output. */
  62. template <typename T>
  63. T process(State<T>& s, T x) const {
  64. s.y += alpha * (x - s.y);
  65. return s.y;
  66. }
  67. };
  68. /** One-pole high-pass filter.
  69. 6 dB/octave slope.
  70. Useful for DC-blocking.
  71. Has a pole at (1 - alpha) and a zero at 1.
  72. */
  73. struct OnePoleHighpass : OnePoleLowpass {
  74. std::complex<float> getResponse(float f) const {
  75. return 1.f - OnePoleLowpass::getResponse(f);
  76. }
  77. float getMagnitude(float f) const {
  78. return std::abs(getResponse(f));
  79. }
  80. float getPhase(float f) const {
  81. return std::arg(getResponse(f));
  82. }
  83. template <typename T>
  84. T process(State<T>& s, T x) const {
  85. return x - OnePoleLowpass::process(s, x);
  86. }
  87. };
  88. /** Simple radix-2 Cooley-Tukey FFT. n must be a power of 2. */
  89. template <typename T>
  90. void fft(std::complex<T>* x, int n, bool inverse = false) {
  91. // Bit reversal permutation
  92. for (int i = 1, j = 0; i < n; i++) {
  93. int bit = n >> 1;
  94. for (; j & bit; bit >>= 1)
  95. j ^= bit;
  96. j ^= bit;
  97. if (i < j)
  98. std::swap(x[i], x[j]);
  99. }
  100. // Cooley-Tukey butterflies
  101. for (int len = 2; len <= n; len <<= 1) {
  102. T ang = (inverse ? 1 : -1) * 2 * M_PI / len;
  103. std::complex<T> wn(std::cos(ang), std::sin(ang));
  104. for (int i = 0; i < n; i += len) {
  105. std::complex<T> w(1);
  106. for (int j = 0; j < len / 2; j++) {
  107. std::complex<T> u = x[i + j];
  108. std::complex<T> v = x[i + j + len / 2] * w;
  109. x[i + j] = u + v;
  110. x[i + j + len / 2] = u - v;
  111. w *= wn;
  112. }
  113. }
  114. }
  115. if (inverse) {
  116. for (int i = 0; i < n; i++)
  117. x[i] /= T(n);
  118. }
  119. }
  120. template <typename T>
  121. inline T blackmanHarris(T p) {
  122. return
  123. + T(0.35875)
  124. - T(0.48829) * simd::cos(T(2 * M_PI) * p)
  125. + T(0.14128) * simd::cos(T(4 * M_PI) * p)
  126. - T(0.01168) * simd::cos(T(6 * M_PI) * p);
  127. }
  128. /** Computes the minimum-phase bandlimited step (minBLEP), an impulse step response that rises from 0 to 1.
  129. Z: number of zero-crossings on each side of the original symmetric sync signal
  130. O: oversample factor
  131. output: must be length `(2 * Z) * O`.
  132. Algorithm from "Hard Sync Without Aliasing" by Eli Brandt (2001).
  133. https://www.cs.cmu.edu/~eli/papers/icmc01-hardsync.pdf
  134. */
  135. inline void minBlepImpulse(int z, int o, float* output) {
  136. // Symmetric sinc impulse with `z` zero-crossings on each side
  137. int n = 2 * z * o;
  138. std::complex<double>* x = new std::complex<double>[n];
  139. for (int i = 0; i < n; i++) {
  140. double p = (double) i / o - z;
  141. x[i] = (p == 0.0) ? 1.0 : std::sin(M_PI * p) / (M_PI * p);
  142. }
  143. // Apply window
  144. for (int i = 0; i < n; i++) {
  145. x[i] *= blackmanHarris(i / (double) (n - 1));
  146. }
  147. #if 1
  148. // Reconstruct impulse response with minimum phase
  149. fft(x, n);
  150. // Take log of magnitude and set phase to zero.
  151. // Limit frequency bin to e^-10
  152. for (int i = 0; i < n; i++) {
  153. x[i] = std::log(std::abs(x[i]));
  154. }
  155. // Transform to cepstral domain.
  156. fft(x, n, true);
  157. // Apply minimum-phase window in cepstral domain
  158. // Double positive quefrencies, zero negative quefrencies
  159. for (int i = 1; i < n / 2; i++) {
  160. x[i] *= 2.0;
  161. }
  162. for (int i = n / 2; i < n; i++) {
  163. x[i] = 0.0;
  164. }
  165. // Transform back to frequency domain
  166. fft(x, n);
  167. // Take complex exponential of each bin
  168. for (int i = 0; i < n; i++) {
  169. x[i] = std::exp(x[i]);
  170. }
  171. // Transform to time domain
  172. fft(x, n, true);
  173. #endif
  174. // Integrate. First sample x[0] should be 0.
  175. double total = 0.0;
  176. for (int i = 0; i < n; i++) {
  177. output[i] = total;
  178. total += x[i].real() / o;
  179. }
  180. // Renormalize so last virtual sample x[n] should be exactly 1
  181. for (int i = 0; i < n; i++) {
  182. output[i] /= total;
  183. }
  184. delete[] x;
  185. }
  186. /** Holds a precomputed minBLEP impulse response, reordered to efficiently insert into an audio buffer.
  187. */
  188. template <int Z, int O>
  189. struct MinBlep {
  190. /** Reordered impulse response for linear interpolation, minus 1.0.
  191. Z dimension has +4 padding at end for SIMD and o+1 wrap.
  192. */
  193. float impulseReordered[O][2 * Z + 4] = {};
  194. float rampReordered[O][2 * Z + 4] = {};
  195. MinBlep() {
  196. float impulse[2 * Z * O];
  197. minBlepImpulse(Z, O, impulse);
  198. // Subtract 1 so our step impulse goes from -1 to 0.
  199. for (int i = 0; i < 2*Z*O; i++) {
  200. impulse[i] -= 1.f;
  201. }
  202. // Integrate minBLEP to obtain minBLAMP
  203. float ramp[2 * Z * O];
  204. double total = 0.0;
  205. for (int i = 0; i < 2*Z*O; i++) {
  206. ramp[i] = total;
  207. total += impulse[i] / O;
  208. }
  209. // Subtract ideal ramp so ramp[0] and the virtual ramp[n] are 0
  210. for (int i = 0; i < 2*Z*O; i++) {
  211. ramp[i] -= (float) i / (2*Z*O) * total;
  212. }
  213. // FILE* f = fopen("plugins/Fundamental/minblep.txt", "w");
  214. // for (int i = 0; i < 2*Z*O; i++) {
  215. // fprintf(f, "%.12f\n", ramp[i]);
  216. // }
  217. // fclose(f);
  218. // Transpose samples by making z values contiguous for each o
  219. for (int o = 0; o < O; o++) {
  220. for (int z = 0; z < 2 * Z; z++) {
  221. impulseReordered[o][z] = impulse[z * O + o];
  222. }
  223. }
  224. for (int o = 0; o < O; o++) {
  225. for (int z = 0; z < 2 * Z; z++) {
  226. rampReordered[o][z] = ramp[z * O + o];
  227. }
  228. }
  229. }
  230. /** Places a discontinuity at 0 < subsample <= 1 relative to the current frame.
  231. `out` must have enough space to write 2*Z floats, spaced by `stride` floats.
  232. `subsample` is the subsample position to insert a discontinuity of `magnitude`.
  233. For example if a square wave will jump from 1 to -1 in 0.1 frames, use insertDiscontinuity(out, 1, 0.1f, -2.f).
  234. Note: In the deprecated MinBlepGenerator, `subsample` was in the range (-1, 0], so add 1 to subsample if updating to MinBlep.
  235. */
  236. void insertDiscontinuity(float subsample, float magnitude, float* out, int stride = 1) const {
  237. insert(impulseReordered, subsample, magnitude, out, stride);
  238. }
  239. void insertSlopeDiscontinuity(float subsample, float magnitude, float* out, int stride = 1) const {
  240. insert(rampReordered, subsample, magnitude, out, stride);
  241. }
  242. private:
  243. void insert(const float table[O][2 * Z + 4], float subsample, float magnitude, float* out, int stride = 1) const {
  244. if (!(0.f < subsample && subsample <= 1.f))
  245. return;
  246. // Calculate impulse array index and fractional part
  247. float t = (1.f - subsample) * O;
  248. int o = (int) t;
  249. t -= o;
  250. // For each zero crossing, linearly interpolate impulse response from oversample points
  251. for (int z = 0; z < 2 * Z; z += 4) {
  252. using simd::float_4;
  253. float_4 y1 = float_4::load(&table[o][z]);
  254. int o2 = (o + 1) % O;
  255. int z2 = z + (o + 1) / O;
  256. float_4 y2 = float_4::load(&table[o2][z2]);
  257. float_4 y = y1 + t * (y2 - y1);
  258. y *= magnitude;
  259. // Write all 4 samples to buffer
  260. for (int zi = 0; zi < 4; zi++) {
  261. out[(z + zi) * stride] += y[zi];
  262. }
  263. }
  264. }
  265. };
  266. /** Buffer that allows reading/writing up to N future elements contiguously.
  267. */
  268. template <int N, typename T>
  269. struct MinBlepBuffer {
  270. T buffer[2 * N] = {};
  271. int32_t bufferIndex = 0;
  272. T* startData() {
  273. return &buffer[bufferIndex];
  274. }
  275. /** Returns the current element and advances the buffer.
  276. */
  277. T shift() {
  278. T v = buffer[bufferIndex];
  279. bufferIndex++;
  280. if (bufferIndex >= N) {
  281. // Move second half of buffer to beginning
  282. std::memcpy(buffer, buffer + N, N * sizeof(T));
  283. std::memset(buffer + N, 0, N * sizeof(T));
  284. bufferIndex = 0;
  285. }
  286. return v;
  287. }
  288. /** Copies `n` elements to `out` and advances the buffer.
  289. */
  290. void shiftBuffer(T* out, size_t n) {
  291. std::memcpy(out, buffer + bufferIndex, n * sizeof(T));
  292. bufferIndex += n;
  293. if (bufferIndex >= N) {
  294. std::memcpy(buffer, buffer + N, N * sizeof(T));
  295. std::memset(buffer + N, 0, N * sizeof(T));
  296. bufferIndex = 0;
  297. }
  298. }
  299. };
  300. static const MinBlep<16, 16>& getMinBlep() {
  301. static MinBlep<16, 16> minBlep;
  302. return minBlep;
  303. }
  304. template <typename T>
  305. struct VCOProcessor {
  306. T phase = 0.f;
  307. /** 1 for forward, -1 for backward. */
  308. T syncDirection = 1.f;
  309. T lastSync = 0.f;
  310. /** 1 or -1 */
  311. T lastSqrState = 1.f;
  312. OnePoleHighpass dcFilter;
  313. OnePoleHighpass::State<T> dcFilterStateSqr;
  314. OnePoleHighpass::State<T> dcFilterStateSaw;
  315. OnePoleHighpass::State<T> dcFilterStateTri;
  316. OnePoleHighpass::State<T> dcFilterStateSin;
  317. MinBlepBuffer<16*2, T> sqrMinBlep;
  318. MinBlepBuffer<16*2, T> sawMinBlep;
  319. MinBlepBuffer<16*2, T> triMinBlep;
  320. MinBlepBuffer<16*2, T> sinMinBlep;
  321. void setSampleTime(float sampleTime) {
  322. dcFilter.setCutoffApprox(std::min(0.4f, 20.f * sampleTime));
  323. }
  324. struct Frame {
  325. /** Number of channels valid in SIMD type
  326. For optimizing serial operations.
  327. */
  328. uint8_t channels = 0;
  329. bool soft = false;
  330. bool syncEnabled = false;
  331. bool sqrEnabled = false;
  332. bool sawEnabled = false;
  333. bool triEnabled = false;
  334. bool sinEnabled = false;
  335. T pulseWidth = 0.5f;
  336. T sync = 0.f;
  337. T freq = 0.f;
  338. // Outputs
  339. T sqr = 0.f;
  340. T saw = 0.f;
  341. T tri = 0.f;
  342. T sin = 0.f;
  343. };
  344. void process(Frame& frame, float sampleTime) {
  345. // Compute deltaPhase for full frame
  346. T deltaPhase = simd::clamp(frame.freq * sampleTime, 0.f, 0.49f);
  347. if (frame.soft) {
  348. deltaPhase *= syncDirection;
  349. }
  350. else {
  351. syncDirection = 1.f;
  352. }
  353. T prevPhase = phase;
  354. const float pwMin = 0.01f;
  355. T pulseWidth = simd::clamp(frame.pulseWidth, pwMin, 1.f - pwMin);
  356. // Inserts minBLEP for each channel where mask is true.
  357. // Serial but does nothing if mask is all zero.
  358. auto insertDiscontinuity = [&](T mask, T subsample, T magnitude, MinBlepBuffer<16*2, T>& buffer) {
  359. int m = simd::movemask(mask);
  360. if (!m)
  361. return;
  362. for (int i = 0; i < frame.channels; i++) {
  363. if (m & (1 << i)) {
  364. float* x = (float*) buffer.startData();
  365. getMinBlep().insertDiscontinuity(subsample[i], magnitude[i], &x[i], 4);
  366. }
  367. }
  368. };
  369. auto insertSlopeDiscontinuity = [&](T mask, T subsample, T magnitude, MinBlepBuffer<16*2, T>& buffer) {
  370. int m = simd::movemask(mask);
  371. if (!m)
  372. return;
  373. for (int i = 0; i < frame.channels; i++) {
  374. if (m & (1 << i)) {
  375. float* x = (float*) buffer.startData();
  376. getMinBlep().insertSlopeDiscontinuity(subsample[i], magnitude[i], &x[i], 4);
  377. }
  378. }
  379. };
  380. // Computes subsample time where phase crosses threshold.
  381. auto getCrossing = [](T thresholdPhase, T startPhase, T endPhase, T startSubsample, T endSubsample) -> T {
  382. T delta = endPhase - startPhase;
  383. T diff = thresholdPhase - startPhase;
  384. // Forward: wrap thresholdPhase to (startPhase, startPhase+1]
  385. // Backward: wrap thresholdPhase to [startPhase-1, startPhase)
  386. thresholdPhase -= simd::ifelse(delta >= 0.f, simd::floor(diff), simd::ceil(diff));
  387. T p = (thresholdPhase - startPhase) / delta;
  388. return startSubsample + p * (endSubsample - startSubsample);
  389. };
  390. // Processes wrap/pulse/saw crossings between startPhase and endPhase.
  391. // startSubsample and endSubsample define the time range within the frame.
  392. // channelMask limits processing to specific channels.
  393. auto processCrossings = [&](T startPhase, T endPhase, T startSubsample, T endSubsample, T channelMask) {
  394. if (frame.sqrEnabled) {
  395. // Insert minBLEP to square when phase crosses 0 (mod 1)
  396. T wrapSubsample = getCrossing(1.f, startPhase, endPhase, startSubsample, endSubsample);
  397. T mask = channelMask & (startSubsample < wrapSubsample) & (wrapSubsample <= endSubsample);
  398. insertDiscontinuity(mask, wrapSubsample, 2.f * syncDirection, sqrMinBlep);
  399. // Insert minBLEP to square when phase crosses pulse width
  400. T pulseSubsample = getCrossing(pulseWidth, startPhase, endPhase, startSubsample, endSubsample);
  401. mask = channelMask & (startSubsample < pulseSubsample) & (pulseSubsample <= endSubsample);
  402. insertDiscontinuity(mask, pulseSubsample, -2.f * syncDirection, sqrMinBlep);
  403. }
  404. if (frame.sawEnabled) {
  405. // Insert minBLEP to saw when crossing 0.5
  406. T sawSubsample = getCrossing(0.5f, startPhase, endPhase, startSubsample, endSubsample);
  407. T mask = channelMask & (startSubsample < sawSubsample) & (sawSubsample <= endSubsample);
  408. insertDiscontinuity(mask, sawSubsample, -2.f * syncDirection, sawMinBlep);
  409. }
  410. if (frame.triEnabled) {
  411. // Insert minBLAMP to tri when crossing 0.25
  412. T triSubsample = getCrossing(0.25f, startPhase, endPhase, startSubsample, endSubsample);
  413. T mask = channelMask & (startSubsample < triSubsample) & (triSubsample <= endSubsample);
  414. // Slope goes from +4 to -4, so slope jump is -8 * abs(deltaPhase)
  415. insertSlopeDiscontinuity(mask, triSubsample, -8.f * deltaPhase * syncDirection, triMinBlep);
  416. // Insert minBLAMP to tri when crossing 0.75, slope from -4 to +4
  417. triSubsample = getCrossing(0.75f, startPhase, endPhase, startSubsample, endSubsample);
  418. mask = channelMask & (startSubsample < triSubsample) & (triSubsample <= endSubsample);
  419. // Slope goes from -4 to +4, so slope jump is 8 * abs(deltaPhase)
  420. insertSlopeDiscontinuity(mask, triSubsample, 8.f * deltaPhase * syncDirection, triMinBlep);
  421. }
  422. };
  423. // Check if square value changed due to pulseWidth changing since last frame
  424. if (frame.sqrEnabled) {
  425. T sqrState = sqr(prevPhase, pulseWidth);
  426. T magnitude = sqrState - lastSqrState;
  427. T changed = (magnitude != 0.f);
  428. insertDiscontinuity(changed, 1e-6f, magnitude, sqrMinBlep);
  429. }
  430. if (!frame.syncEnabled) {
  431. // No sync. Process full frame
  432. T endPhase = prevPhase + deltaPhase;
  433. processCrossings(prevPhase, endPhase, 0.f, 1.f, T::mask());
  434. phase = endPhase;
  435. }
  436. else {
  437. // Compute sync subsample position
  438. T deltaSync = frame.sync - lastSync;
  439. T syncSubsample = -lastSync / deltaSync;
  440. lastSync = frame.sync;
  441. // Check if sync rises through 0
  442. T syncOccurred = (0.f < syncSubsample) & (syncSubsample <= 1.f) & (deltaSync >= 0.f);
  443. T noSync = ~syncOccurred;
  444. if (simd::movemask(noSync)) {
  445. // No sync for these channels. Process full frame
  446. T endPhase = prevPhase + deltaPhase;
  447. processCrossings(prevPhase, endPhase, 0.f, 1.f, noSync);
  448. phase = simd::ifelse(noSync, endPhase, phase);
  449. }
  450. if (simd::movemask(syncOccurred)) {
  451. // Process crossings before sync
  452. T syncPhase = prevPhase + deltaPhase * syncSubsample;
  453. processCrossings(prevPhase, syncPhase, 0.f, syncSubsample, syncOccurred);
  454. // Wrap sync phase
  455. syncPhase -= simd::floor(syncPhase);
  456. if (frame.soft) {
  457. // Soft sync: Reverse direction, continue from syncPhase
  458. if (frame.sawEnabled) {
  459. // Saw slope reverses
  460. // +2 slope becomes -2 if deltaPhase > 0
  461. // -2 slope becomes +2 if deltaPhase < 0
  462. insertSlopeDiscontinuity(syncOccurred, syncSubsample, -4.f * deltaPhase, sawMinBlep);
  463. }
  464. if (frame.triEnabled) {
  465. // Tri slope reverses
  466. // -4 slope becomes +4 if 0.25 < phase < 0.75
  467. // -4 slope becomes +4 otherwise
  468. T descending = (0.25f < syncPhase) & (syncPhase < 0.75f);
  469. T slopeJump = simd::ifelse(descending, 8.f, -8.f);
  470. insertSlopeDiscontinuity(syncOccurred, syncSubsample, slopeJump * deltaPhase, triMinBlep);
  471. }
  472. syncDirection = simd::ifelse(syncOccurred, -syncDirection, syncDirection);
  473. deltaPhase = simd::ifelse(syncOccurred, -deltaPhase, deltaPhase);
  474. T endPhase = syncPhase + deltaPhase * (1.f - syncSubsample);
  475. processCrossings(syncPhase, endPhase, syncSubsample, 1.f, syncOccurred);
  476. phase = simd::ifelse(syncOccurred, endPhase, phase);
  477. }
  478. else {
  479. // Hard sync: Reset phase from syncPhase to 0 at syncSubsample, insert discontinuities
  480. if (frame.sqrEnabled) {
  481. // Check if square jumps from -1 to +1
  482. T sqrJump = (syncPhase >= pulseWidth);
  483. insertDiscontinuity(syncOccurred & sqrJump, syncSubsample, 2.f, sqrMinBlep);
  484. }
  485. if (frame.sawEnabled) {
  486. // Saw jumps from saw(syncPhase) to saw(0) = 0
  487. insertDiscontinuity(syncOccurred, syncSubsample, -saw(syncPhase), sawMinBlep);
  488. }
  489. if (frame.triEnabled) {
  490. // Tri jumps from tri(syncPhase) to tri(0) = 0
  491. insertDiscontinuity(syncOccurred, syncSubsample, -tri(syncPhase), triMinBlep);
  492. // If descending slope (-4), reset to ascending slope (+4)
  493. T wasDescending = (0.25f < syncPhase) & (syncPhase < 0.75f);
  494. insertSlopeDiscontinuity(syncOccurred & wasDescending, syncSubsample, 8.f * deltaPhase, triMinBlep);
  495. }
  496. if (frame.sinEnabled) {
  497. // sin jumps from sin(syncPhase) to sin(0) = 0
  498. insertDiscontinuity(syncOccurred, syncSubsample, -sin(syncPhase), sinMinBlep);
  499. // Slope changes from sinDerivative(syncPhase) to sinDerivative(0) = 2pi
  500. // T slopeJump = T(2 * M_PI) - sinDerivative(syncPhase);
  501. // insertSlopeDiscontinuity(syncOccurred, syncSubsample, slopeJump * deltaPhase, sinMinBlep);
  502. }
  503. // Process crossings after sync (starting from phase 0)
  504. T endPhase = deltaPhase * (1.f - syncSubsample);
  505. processCrossings(0.f, endPhase, syncSubsample, 1.f, syncOccurred);
  506. phase = simd::ifelse(syncOccurred, endPhase, phase);
  507. }
  508. }
  509. }
  510. // Wrap phase to [0, 1)
  511. phase -= simd::floor(phase);
  512. // Generate outputs
  513. if (frame.sawEnabled) {
  514. frame.saw = saw(phase);
  515. frame.saw += sawMinBlep.shift();
  516. frame.saw = dcFilter.process(dcFilterStateSaw, frame.saw);
  517. }
  518. if (frame.sqrEnabled) {
  519. frame.sqr = sqr(phase, pulseWidth);
  520. lastSqrState = frame.sqr;
  521. frame.sqr += sqrMinBlep.shift();
  522. frame.sqr = dcFilter.process(dcFilterStateSqr, frame.sqr);
  523. }
  524. if (frame.triEnabled) {
  525. frame.tri = tri(phase);
  526. frame.tri += triMinBlep.shift();
  527. frame.tri = dcFilter.process(dcFilterStateTri, frame.tri);
  528. }
  529. if (frame.sinEnabled) {
  530. frame.sin = sin(phase);
  531. frame.sin += sinMinBlep.shift();
  532. frame.sin = dcFilter.process(dcFilterStateSin, frame.sin);
  533. }
  534. }
  535. T light() const {
  536. return sin(phase);
  537. }
  538. static T sqr(T phase, T pulseWidth) {
  539. return simd::ifelse(phase < pulseWidth, 1.f, -1.f);
  540. }
  541. static T saw(T phase) {
  542. T x = phase + 0.5f;
  543. x -= simd::trunc(x);
  544. return 2 * x - 1;
  545. }
  546. static T tri(T phase) {
  547. return 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), 1.25f - phase);
  548. }
  549. static T sin(T phase) {
  550. return sin_2pi_9(phase);
  551. }
  552. static T sinDerivative(T phase) {
  553. // Shift and wrap cosine
  554. phase += 0.25f;
  555. phase -= simd::floor(phase);
  556. return T(2 * M_PI) * sin_2pi_9(phase);
  557. }
  558. };
  559. using simd::float_4;
  560. struct VCO : Module {
  561. enum ParamIds {
  562. MODE_PARAM, // removed
  563. SYNC_PARAM,
  564. FREQ_PARAM,
  565. FINE_PARAM, // removed
  566. FM_PARAM,
  567. PW_PARAM,
  568. PW_CV_PARAM,
  569. // new in 2.0
  570. LINEAR_PARAM,
  571. NUM_PARAMS
  572. };
  573. enum InputIds {
  574. PITCH_INPUT,
  575. FM_INPUT,
  576. SYNC_INPUT,
  577. PW_INPUT,
  578. NUM_INPUTS
  579. };
  580. enum OutputIds {
  581. SIN_OUTPUT,
  582. TRI_OUTPUT,
  583. SAW_OUTPUT,
  584. SQR_OUTPUT,
  585. NUM_OUTPUTS
  586. };
  587. enum LightIds {
  588. ENUMS(PHASE_LIGHT, 3),
  589. LINEAR_LIGHT,
  590. SOFT_LIGHT,
  591. NUM_LIGHTS
  592. };
  593. VCOProcessor<float_4> processors[4];
  594. dsp::ClockDivider lightDivider;
  595. VCO() {
  596. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  597. configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "FM mode", {"1V/octave", "Linear"});
  598. configSwitch(SYNC_PARAM, 0.f, 1.f, 1.f, "Sync mode", {"Soft", "Hard"});
  599. configParam(FREQ_PARAM, -76.f, 76.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
  600. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  601. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  602. configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f);
  603. configParam(PW_CV_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
  604. getParamQuantity(PW_CV_PARAM)->randomizeEnabled = false;
  605. configInput(PITCH_INPUT, "1V/octave pitch");
  606. configInput(FM_INPUT, "Frequency modulation");
  607. configInput(SYNC_INPUT, "Sync");
  608. configInput(PW_INPUT, "Pulse width modulation");
  609. configOutput(SIN_OUTPUT, "Sine");
  610. configOutput(TRI_OUTPUT, "Triangle");
  611. configOutput(SAW_OUTPUT, "Sawtooth");
  612. configOutput(SQR_OUTPUT, "Square");
  613. lightDivider.setDivision(16);
  614. }
  615. void onSampleRateChange(const SampleRateChangeEvent& e) override {
  616. for (int c = 0; c < 16; c += 4) {
  617. processors[c / 4].setSampleTime(e.sampleTime);
  618. }
  619. }
  620. void process(const ProcessArgs& args) override {
  621. VCOProcessor<float_4>::Frame frame;
  622. float freqParam = params[FREQ_PARAM].getValue() / 12.f;
  623. float fmParam = params[FM_PARAM].getValue();
  624. float pwParam = params[PW_PARAM].getValue();
  625. float pwCvParam = params[PW_CV_PARAM].getValue();
  626. bool linear = params[LINEAR_PARAM].getValue() > 0.f;
  627. frame.soft = params[SYNC_PARAM].getValue() <= 0.f;
  628. frame.syncEnabled = inputs[SYNC_INPUT].isConnected();
  629. frame.sqrEnabled = outputs[SQR_OUTPUT].isConnected();
  630. frame.sawEnabled = outputs[SAW_OUTPUT].isConnected();
  631. frame.triEnabled = outputs[TRI_OUTPUT].isConnected();
  632. frame.sinEnabled = outputs[SIN_OUTPUT].isConnected();
  633. int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
  634. for (int c = 0; c < channels; c += 4) {
  635. frame.channels = std::min(channels - c, 4);
  636. // Get frequency
  637. float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
  638. float_4 freq;
  639. if (!linear) {
  640. pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  641. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  642. }
  643. else {
  644. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  645. freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  646. }
  647. frame.freq = clamp(freq, 0.f, args.sampleRate / 2.f);
  648. // Get pulse width
  649. frame.pulseWidth = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwCvParam;
  650. frame.sync = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
  651. processors[c / 4].process(frame, args.sampleTime);
  652. // Set output
  653. outputs[SQR_OUTPUT].setVoltageSimd(5.f * frame.sqr, c);
  654. outputs[SAW_OUTPUT].setVoltageSimd(5.f * frame.saw, c);
  655. outputs[TRI_OUTPUT].setVoltageSimd(5.f * frame.tri, c);
  656. outputs[SIN_OUTPUT].setVoltageSimd(5.f * frame.sin, c);
  657. }
  658. outputs[SIN_OUTPUT].setChannels(channels);
  659. outputs[TRI_OUTPUT].setChannels(channels);
  660. outputs[SAW_OUTPUT].setChannels(channels);
  661. outputs[SQR_OUTPUT].setChannels(channels);
  662. // Light
  663. if (lightDivider.process()) {
  664. if (channels == 1) {
  665. float lightValue = processors[0].light()[0];
  666. lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
  667. lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
  668. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  669. }
  670. else {
  671. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  672. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  673. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  674. }
  675. lights[LINEAR_LIGHT].setBrightness(linear);
  676. lights[SOFT_LIGHT].setBrightness(frame.soft);
  677. }
  678. }
  679. };
  680. struct VCOWidget : ModuleWidget {
  681. VCOWidget(VCO* module) {
  682. setModule(module);
  683. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO.svg"), asset::plugin(pluginInstance, "res/VCO-dark.svg")));
  684. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  685. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  686. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  687. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  688. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.905, 29.808)), module, VCO::FREQ_PARAM));
  689. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.862, 56.388)), module, VCO::PW_PARAM));
  690. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.607, 80.603)), module, VCO::FM_PARAM));
  691. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.444, 80.603)), module, VCO::LINEAR_PARAM, VCO::LINEAR_LIGHT));
  692. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(28.282, 80.603)), module, VCO::SYNC_PARAM, VCO::SOFT_LIGHT));
  693. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.118, 80.603)), module, VCO::PW_CV_PARAM));
  694. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 96.859)), module, VCO::FM_INPUT));
  695. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 96.859)), module, VCO::PITCH_INPUT));
  696. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 96.859)), module, VCO::SYNC_INPUT));
  697. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.15, 96.859)), module, VCO::PW_INPUT));
  698. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 113.115)), module, VCO::SIN_OUTPUT));
  699. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 113.115)), module, VCO::TRI_OUTPUT));
  700. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 113.115)), module, VCO::SAW_OUTPUT));
  701. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 113.115)), module, VCO::SQR_OUTPUT));
  702. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(31.089, 16.428)), module, VCO::PHASE_LIGHT));
  703. }
  704. };
  705. Model* modelVCO = createModel<VCO, VCOWidget>("VCO");