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.

618 lines
20KB

  1. #include "plugin.hpp"
  2. // TODO: Remove these DSP classes after released in Rack SDK
  3. /** Evaluates sin(pi x) for x in [-1, 1].
  4. 9th order polynomial with roots at 0, -1, and 1.
  5. Optimized coefficients for best THD (-103.7 dB) and max absolute error (6.68e-06).
  6. */
  7. template <typename T>
  8. inline T sin_pi_9(T x) {
  9. T x2 = x * x;
  10. return x * (T(1) - x2) * (T(3.141521108990) + x2 * (T(-2.024773594411) + x2 * (T(0.517493161073) + x2 * T(-0.063691343423))));
  11. }
  12. /** Catmull-Rom cubic interpolation
  13. t is the fractional position between y1 and y2.
  14. */
  15. template <typename T>
  16. T catmullRomInterpolate(T y0, T y1, T y2, T y3, T t) {
  17. T t2 = t * t;
  18. T t3 = t2 * t;
  19. return y1 + T(0.5) * t * (y2 - y0)
  20. + t2 * (y0 - T(2.5)*y1 + T(2)*y2 - T(0.5)*y3)
  21. + t3 * (T(-0.5)*y0 + T(1.5)*y1 - T(1.5)*y2 + T(0.5)*y3);
  22. }
  23. /** One-pole low-pass filter, exponential moving average.
  24. -6 dB/octave slope.
  25. Useful for leaky integrators and smoothing control signals.
  26. Has a pole at (1 - alpha) and a zero at 0.
  27. */
  28. struct OnePoleLowpass {
  29. float alpha = 0.f;
  30. /** Sets the cutoff frequency where gain is -3 dB.
  31. f = f_c / f_s, the normalized frequency in the range [0, 0.5].
  32. */
  33. void setCutoff(float f) {
  34. alpha = 1.f - std::exp(-2.f * M_PI * f);
  35. }
  36. template <typename T = float>
  37. struct State {
  38. T y = 0.f;
  39. };
  40. /** Advances the state with input x. Returns the output. */
  41. template <typename T>
  42. T process(State<T>& s, T x) const {
  43. s.y += alpha * (x - s.y);
  44. return s.y;
  45. }
  46. /** Computes the frequency response at normalized frequency f. */
  47. std::complex<float> getResponse(float f) const {
  48. float omega = 2.f * M_PI * f;
  49. std::complex<float> z = std::exp(std::complex<float>(0.f, omega));
  50. return alpha * z / (z - (1.f - alpha));
  51. }
  52. float getMagnitude(float f) const {
  53. return std::abs(getResponse(f));
  54. }
  55. float getPhase(float f) const {
  56. return std::arg(getResponse(f));
  57. }
  58. };
  59. /** One-pole high-pass filter.
  60. 6 dB/octave slope.
  61. Useful for DC-blocking.
  62. Has a pole at (1 - alpha) and a zero at 1.
  63. */
  64. struct OnePoleHighpass : OnePoleLowpass {
  65. template <typename T>
  66. T process(State<T>& s, T x) const {
  67. return x - OnePoleLowpass::process(s, x);
  68. }
  69. std::complex<float> getResponse(float f) const {
  70. return 1.f - OnePoleLowpass::getResponse(f);
  71. }
  72. float getMagnitude(float f) const {
  73. return std::abs(getResponse(f));
  74. }
  75. float getPhase(float f) const {
  76. return std::arg(getResponse(f));
  77. }
  78. };
  79. /** Holds a precomputed minBLEP impulse response, reordered to efficiently insert into an audio buffer.
  80. */
  81. template <int Z, int O>
  82. struct MinBlep {
  83. /** Reordered impulse response for cubic interpolation, minus 1.0.
  84. Z dimension has +1 padding at start (for z-1 wrap) and +4 at end (for SIMD + z+1 wrap).
  85. Access via getImpulse() which handles O wrapping and Z offset.
  86. */
  87. float impulseReordered[O][1 + 2 * Z + 4] = {};
  88. MinBlep() {
  89. float impulse[2 * Z][O];
  90. dsp::minBlepImpulse(Z, O, &impulse[0][0]);
  91. // Fill impulseReordered with transposed and pre-subtracted data
  92. // Storage index = z + 1 (to allow z = -1 access at index 0)
  93. for (int o = 0; o < O; o++) {
  94. // z = -1: before impulse starts
  95. impulseReordered[o][0] = -1.f;
  96. // z = 0 to 2*Z-1: actual impulse data
  97. for (int z = 0; z < 2 * Z; z++) {
  98. impulseReordered[o][1 + z] = impulse[z][o] - 1.f;
  99. }
  100. // z = 2*Z to 2*Z+3: after impulse ends (for SIMD padding)
  101. for (int z = 2 * Z; z < 2 * Z + 4; z++) {
  102. impulseReordered[o][1 + z] = 0.f;
  103. }
  104. }
  105. }
  106. /** Get pointer to impulse data, handling o wrapping and z offset. */
  107. const float* getImpulse(int o, int z) const {
  108. z += o / O;
  109. o = o % O;
  110. if (o < 0) {
  111. z -= 1;
  112. o += O;
  113. }
  114. // assert(0 <= o && o < O);
  115. // assert(-1 <= z && z < 2 * Z + 4);
  116. return &impulseReordered[o][z + 1];
  117. }
  118. /** Places a discontinuity at 0 < p <= 1 relative to the current frame.
  119. `out` must have enough space to write 2*Z floats, spaced by `stride` floats.
  120. `p` is the subsample position to insert a discontinuity of `magnitude`.
  121. For example if a square wave will jump from 1 to -1 in 0.1 frames, use insertDiscontinuity(out, 1, 0.1f, -2.f).
  122. Note: In the deprecated MinBlepGenerator, `p` was in the range (-1, 0], so add 1 to p if updating to MinBlep.
  123. */
  124. void insertDiscontinuity(float* out, int stride, float p, float magnitude) const {
  125. if (!(0 < p && p <= 1))
  126. return;
  127. // Calculate impulse array index and fractional part
  128. float subsample = (1 - p) * O;
  129. int o = (int) subsample;
  130. float t = subsample - o;
  131. // For each zero crossing, interpolate impulse response from oversample points
  132. using simd::float_4;
  133. for (int z = 0; z < 2 * Z; z += 4) {
  134. float_4 y0 = float_4::load(getImpulse(o - 1, z));
  135. float_4 y1 = float_4::load(getImpulse(o, z));
  136. float_4 y2 = float_4::load(getImpulse(o + 1, z));
  137. float_4 y3 = float_4::load(getImpulse(o + 2, z));
  138. float_4 y = catmullRomInterpolate(y0, y1, y2, y3, float_4(t));
  139. y *= magnitude;
  140. // Write all 4 samples to buffer
  141. for (int zi = 0; zi < 4; zi++) {
  142. out[(z + zi) * stride] += y[zi];
  143. }
  144. }
  145. }
  146. };
  147. /** Buffer that allows reading/writing up to N future elements contiguously.
  148. */
  149. template <int N, typename T>
  150. struct MinBlepBuffer {
  151. T buffer[2 * N] = {};
  152. int32_t bufferIndex = 0;
  153. T* startData() {
  154. return &buffer[bufferIndex];
  155. }
  156. /** Returns the current element and advances the buffer.
  157. */
  158. T shift() {
  159. T v = buffer[bufferIndex];
  160. bufferIndex++;
  161. if (bufferIndex >= N) {
  162. // Move second half of buffer to beginning
  163. std::memcpy(buffer, buffer + N, N * sizeof(T));
  164. std::memset(buffer + N, 0, N * sizeof(T));
  165. bufferIndex = 0;
  166. }
  167. return v;
  168. }
  169. /** Copies `n` elements to `out` and advances the buffer.
  170. */
  171. void shiftBuffer(T* out, size_t n) {
  172. std::memcpy(out, buffer + bufferIndex, n * sizeof(T));
  173. bufferIndex += n;
  174. if (bufferIndex >= N) {
  175. std::memcpy(buffer, buffer + N, N * sizeof(T));
  176. std::memset(buffer + N, 0, N * sizeof(T));
  177. bufferIndex = 0;
  178. }
  179. }
  180. };
  181. static MinBlep<16, 16> minBlep;
  182. template <typename T>
  183. struct VCOProcessor {
  184. T phase = 0.f;
  185. /** 1 for forward, -1 for backward. */
  186. T syncDirection = 1.f;
  187. T lastSync = 0.f;
  188. /** 1 or -1 */
  189. T lastSqrState = 1.f;
  190. /** Leaky integrator result. */
  191. T triFilterState = 0.f;
  192. OnePoleHighpass dcFilter;
  193. OnePoleHighpass::State<T> dcFilterStateSqr;
  194. OnePoleHighpass::State<T> dcFilterStateSaw;
  195. OnePoleHighpass::State<T> dcFilterStateTri;
  196. OnePoleHighpass::State<T> dcFilterStateSin;
  197. MinBlepBuffer<16*2, T> sqrMinBlep;
  198. MinBlepBuffer<16*2, T> sawMinBlep;
  199. MinBlepBuffer<16*2, T> sinMinBlep;
  200. void setSampleTime(float sampleTime) {
  201. dcFilter.setCutoff(std::min(0.4f, 20.f * sampleTime));
  202. }
  203. struct Frame {
  204. /** Number of channels valid in SIMD type
  205. For optimizing serial operations.
  206. */
  207. uint8_t channels = 0;
  208. bool soft = false;
  209. bool syncEnabled = false;
  210. bool sqrEnabled = false;
  211. bool sawEnabled = false;
  212. bool triEnabled = false;
  213. bool sinEnabled = false;
  214. T pulseWidth = 0.5f;
  215. T sync = 0.f;
  216. T freq = 0.f;
  217. // Outputs
  218. T sqr = 0.f;
  219. T saw = 0.f;
  220. T tri = 0.f;
  221. T sin = 0.f;
  222. };
  223. void process(Frame& frame, float sampleTime) {
  224. // Compute deltaPhase for full frame
  225. T deltaPhase = simd::clamp(frame.freq * sampleTime, 0.f, 0.49f);
  226. if (frame.soft) {
  227. deltaPhase *= syncDirection;
  228. }
  229. else {
  230. syncDirection = 1.f;
  231. }
  232. T prevPhase = phase;
  233. const float pwMin = 0.01f;
  234. T pulseWidth = simd::clamp(frame.pulseWidth, pwMin, 1.f - pwMin);
  235. // Inserts minBLEP for each channel where mask is true.
  236. // Serial but does nothing if mask is all zero.
  237. auto insertMinBlep = [&](T mask, T p, T magnitude, MinBlepBuffer<16*2, T>& minBlepBuffer) {
  238. int m = simd::movemask(mask);
  239. if (!m)
  240. return;
  241. float* buffer = (float*) minBlepBuffer.startData();
  242. for (int i = 0; i < frame.channels; i++) {
  243. if (m & (1 << i)) {
  244. minBlep.insertDiscontinuity(&buffer[i], 4, p[i], magnitude[i]);
  245. }
  246. }
  247. };
  248. // Computes subsample time where phase crosses threshold.
  249. auto getCrossing = [](T threshold, T startPhase, T endPhase, T startSubsample, T endSubsample) -> T {
  250. // Wrap threshold mod 1 to be in (startPhase, startPhase + 1]
  251. threshold -= simd::floor(threshold - startPhase);
  252. // Map to (0, 1]
  253. T p = (threshold - startPhase) / (endPhase - startPhase);
  254. // Map to (startSubsample, endSubsample]
  255. return startSubsample + p * (endSubsample - startSubsample);
  256. };
  257. // Processes wrap/pulse/saw crossings between startPhase and endPhase.
  258. // startSubsample and endSubsample define the time range within the frame.
  259. // channelMask limits processing to specific channels.
  260. auto processCrossings = [&](T startPhase, T endPhase, T startSubsample, T endSubsample, T channelMask) {
  261. if (frame.sqrEnabled || frame.triEnabled) {
  262. // Wrap crossing (phase crosses 0 mod 1)
  263. T wrapSubsample = getCrossing(1.f, startPhase, endPhase, startSubsample, endSubsample);
  264. T wrapMask = channelMask & (startSubsample < wrapSubsample) & (wrapSubsample <= endSubsample);
  265. insertMinBlep(wrapMask, wrapSubsample, 2.f * syncDirection, sqrMinBlep);
  266. // Pulse width crossing
  267. T pulseSubsample = getCrossing(pulseWidth, startPhase, endPhase, startSubsample, endSubsample);
  268. T pulseMask = channelMask & (startSubsample < pulseSubsample) & (pulseSubsample <= endSubsample);
  269. insertMinBlep(pulseMask, pulseSubsample, -2.f * syncDirection, sqrMinBlep);
  270. }
  271. if (frame.sawEnabled) {
  272. // Saw crossing at 0.5
  273. T sawSubsample = getCrossing(0.5f, startPhase, endPhase, startSubsample, endSubsample);
  274. T sawMask = channelMask & (startSubsample < sawSubsample) & (sawSubsample <= endSubsample);
  275. insertMinBlep(sawMask, sawSubsample, -2.f * syncDirection, sawMinBlep);
  276. }
  277. };
  278. // Check if square value changed due to pulseWidth changing since last frame
  279. if (frame.sqrEnabled || frame.triEnabled) {
  280. T sqrState = simd::ifelse(prevPhase < pulseWidth, 1.f, -1.f);
  281. T changed = (sqrState != lastSqrState);
  282. T magnitude = (sqrState - lastSqrState) * syncDirection;
  283. insertMinBlep(changed, 1e-6f, magnitude, sqrMinBlep);
  284. }
  285. if (!frame.syncEnabled) {
  286. // No sync. Process full frame
  287. T endPhase = prevPhase + deltaPhase;
  288. processCrossings(prevPhase, endPhase, 0.f, 1.f, T::mask());
  289. phase = endPhase;
  290. }
  291. else {
  292. // Compute sync subsample position
  293. T deltaSync = frame.sync - lastSync;
  294. T syncSubsample = -lastSync / deltaSync;
  295. lastSync = frame.sync;
  296. T syncOccurred = (0.f < syncSubsample) & (syncSubsample <= 1.f) & (frame.sync >= 0.f);
  297. T noSync = ~syncOccurred;
  298. if (simd::movemask(noSync)) {
  299. // No sync for these channels. Process full frame
  300. T endPhase = prevPhase + deltaPhase;
  301. processCrossings(prevPhase, endPhase, 0.f, 1.f, noSync);
  302. phase = simd::ifelse(noSync, endPhase, phase);
  303. }
  304. if (simd::movemask(syncOccurred)) {
  305. // Process crossings before sync
  306. T syncPhase = prevPhase + deltaPhase * syncSubsample;
  307. processCrossings(prevPhase, syncPhase, 0.f, syncSubsample, syncOccurred);
  308. if (frame.soft) {
  309. // Soft sync: Reverse direction, continue from syncPhase
  310. syncDirection = simd::ifelse(syncOccurred, -syncDirection, syncDirection);
  311. T endPhase = syncPhase + (-deltaPhase) * (1.f - syncSubsample);
  312. processCrossings(syncPhase, endPhase, syncSubsample, 1.f, syncOccurred);
  313. phase = simd::ifelse(syncOccurred, endPhase, phase);
  314. }
  315. else {
  316. // Hard sync: reset to 0, insert discontinuities
  317. if (frame.sqrEnabled || frame.triEnabled) {
  318. // Sqr jumps from current state to +1
  319. T sqrJump = (syncPhase - simd::floor(syncPhase) < pulseWidth);
  320. insertMinBlep(syncOccurred & sqrJump, syncSubsample, 2.f, sqrMinBlep);
  321. }
  322. if (frame.triEnabled) {
  323. // TODO Insert minBLEP to Tri
  324. }
  325. if (frame.sawEnabled) {
  326. // Saw jumps from saw(syncPhase) to saw(0) = 0
  327. insertMinBlep(syncOccurred, syncSubsample, -saw(syncPhase), sawMinBlep);
  328. }
  329. if (frame.sinEnabled) {
  330. // sin jumps from sin(syncPhase) to sin(0) = 0
  331. insertMinBlep(syncOccurred, syncSubsample, -sin(syncPhase), sinMinBlep);
  332. }
  333. // Process crossings after sync (starting from phase 0)
  334. T endPhase = deltaPhase * (1.f - syncSubsample);
  335. processCrossings(0.f, endPhase, syncSubsample, 1.f, syncOccurred);
  336. phase = simd::ifelse(syncOccurred, endPhase, phase);
  337. }
  338. }
  339. }
  340. // Wrap phase to [0, 1)
  341. phase -= simd::floor(phase);
  342. // Generate outputs
  343. if (frame.sawEnabled) {
  344. frame.saw = saw(phase);
  345. frame.saw += sawMinBlep.shift();
  346. frame.saw = dcFilter.process(dcFilterStateSaw, frame.saw);
  347. }
  348. if (frame.sqrEnabled || frame.triEnabled) {
  349. frame.sqr = simd::ifelse(phase < pulseWidth, 1.f, -1.f);
  350. lastSqrState = frame.sqr;
  351. frame.sqr += sqrMinBlep.shift();
  352. T triSqr = frame.sqr;
  353. frame.sqr = dcFilter.process(dcFilterStateSqr, frame.sqr);
  354. if (frame.triEnabled) {
  355. // Integrate square wave
  356. const float triShape = 0.2f;
  357. T triFreq = sampleTime * triShape * frame.freq;
  358. // Use bilinear transform to derive alpha
  359. T alpha = 1 / (1 + 1 / (M_PI * triFreq));
  360. // T alpha = 1.f - simd::exp(-2.f * M_PI * triFreq);
  361. triFilterState += alpha * (triSqr - triFilterState);
  362. // Apply gain to roughly have unit amplitude at 0.5 pulseWidth. Depends on triShape.
  363. frame.tri = triFilterState * 6.6f;
  364. frame.tri = dcFilter.process(dcFilterStateTri, frame.tri);
  365. }
  366. }
  367. if (frame.sinEnabled) {
  368. frame.sin = sin(phase);
  369. frame.sin += sinMinBlep.shift();
  370. frame.sin = dcFilter.process(dcFilterStateSin, frame.sin);
  371. }
  372. }
  373. T light() const {
  374. return sin(phase);
  375. }
  376. static T saw(T phase) {
  377. T x = phase + 0.5f;
  378. x -= simd::trunc(x);
  379. return 2 * x - 1;
  380. }
  381. static T tri(T phase) {
  382. return 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
  383. }
  384. static T sin(T phase) {
  385. return sin_pi_9(2 * phase - 1);
  386. }
  387. };
  388. using simd::float_4;
  389. struct VCO : Module {
  390. enum ParamIds {
  391. MODE_PARAM, // removed
  392. SYNC_PARAM,
  393. FREQ_PARAM,
  394. FINE_PARAM, // removed
  395. FM_PARAM,
  396. PW_PARAM,
  397. PW_CV_PARAM,
  398. // new in 2.0
  399. LINEAR_PARAM,
  400. NUM_PARAMS
  401. };
  402. enum InputIds {
  403. PITCH_INPUT,
  404. FM_INPUT,
  405. SYNC_INPUT,
  406. PW_INPUT,
  407. NUM_INPUTS
  408. };
  409. enum OutputIds {
  410. SIN_OUTPUT,
  411. TRI_OUTPUT,
  412. SAW_OUTPUT,
  413. SQR_OUTPUT,
  414. NUM_OUTPUTS
  415. };
  416. enum LightIds {
  417. ENUMS(PHASE_LIGHT, 3),
  418. LINEAR_LIGHT,
  419. SOFT_LIGHT,
  420. NUM_LIGHTS
  421. };
  422. VCOProcessor<float_4> processors[4];
  423. dsp::ClockDivider lightDivider;
  424. VCO() {
  425. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  426. configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "FM mode", {"1V/octave", "Linear"});
  427. configSwitch(SYNC_PARAM, 0.f, 1.f, 1.f, "Sync mode", {"Soft", "Hard"});
  428. configParam(FREQ_PARAM, -75.f, 75.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
  429. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  430. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  431. configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f);
  432. configParam(PW_CV_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
  433. getParamQuantity(PW_CV_PARAM)->randomizeEnabled = false;
  434. configInput(PITCH_INPUT, "1V/octave pitch");
  435. configInput(FM_INPUT, "Frequency modulation");
  436. configInput(SYNC_INPUT, "Sync");
  437. configInput(PW_INPUT, "Pulse width modulation");
  438. configOutput(SIN_OUTPUT, "Sine");
  439. configOutput(TRI_OUTPUT, "Triangle");
  440. configOutput(SAW_OUTPUT, "Sawtooth");
  441. configOutput(SQR_OUTPUT, "Square");
  442. lightDivider.setDivision(16);
  443. }
  444. void onSampleRateChange(const SampleRateChangeEvent& e) override {
  445. for (int c = 0; c < 16; c += 4) {
  446. processors[c / 4].setSampleTime(e.sampleTime);
  447. }
  448. }
  449. void process(const ProcessArgs& args) override {
  450. VCOProcessor<float_4>::Frame frame;
  451. float freqParam = params[FREQ_PARAM].getValue() / 12.f;
  452. float fmParam = params[FM_PARAM].getValue();
  453. float pwParam = params[PW_PARAM].getValue();
  454. float pwCvParam = params[PW_CV_PARAM].getValue();
  455. bool linear = params[LINEAR_PARAM].getValue() > 0.f;
  456. frame.soft = params[SYNC_PARAM].getValue() <= 0.f;
  457. frame.syncEnabled = inputs[SYNC_INPUT].isConnected();
  458. frame.sqrEnabled = outputs[SQR_OUTPUT].isConnected();
  459. frame.sawEnabled = outputs[SAW_OUTPUT].isConnected();
  460. frame.triEnabled = outputs[TRI_OUTPUT].isConnected();
  461. frame.sinEnabled = outputs[SIN_OUTPUT].isConnected();
  462. int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
  463. for (int c = 0; c < channels; c += 4) {
  464. frame.channels = std::min(channels - c, 4);
  465. // Get frequency
  466. float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
  467. float_4 freq;
  468. if (!linear) {
  469. pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  470. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  471. }
  472. else {
  473. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  474. freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  475. }
  476. frame.freq = clamp(freq, 0.f, args.sampleRate / 2.f);
  477. // Get pulse width
  478. frame.pulseWidth = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwCvParam;
  479. frame.sync = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
  480. processors[c / 4].process(frame, args.sampleTime);
  481. // Set output
  482. outputs[SQR_OUTPUT].setVoltageSimd(5.f * frame.sqr, c);
  483. outputs[SAW_OUTPUT].setVoltageSimd(5.f * frame.saw, c);
  484. outputs[TRI_OUTPUT].setVoltageSimd(5.f * frame.tri, c);
  485. outputs[SIN_OUTPUT].setVoltageSimd(5.f * frame.sin, c);
  486. }
  487. outputs[SIN_OUTPUT].setChannels(channels);
  488. outputs[TRI_OUTPUT].setChannels(channels);
  489. outputs[SAW_OUTPUT].setChannels(channels);
  490. outputs[SQR_OUTPUT].setChannels(channels);
  491. // Light
  492. if (lightDivider.process()) {
  493. if (channels == 1) {
  494. float lightValue = processors[0].light()[0];
  495. lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
  496. lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
  497. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  498. }
  499. else {
  500. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  501. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  502. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  503. }
  504. lights[LINEAR_LIGHT].setBrightness(linear);
  505. lights[SOFT_LIGHT].setBrightness(frame.soft);
  506. }
  507. }
  508. };
  509. struct VCOWidget : ModuleWidget {
  510. VCOWidget(VCO* module) {
  511. setModule(module);
  512. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO.svg"), asset::plugin(pluginInstance, "res/VCO-dark.svg")));
  513. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  514. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  515. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  516. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  517. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.905, 29.808)), module, VCO::FREQ_PARAM));
  518. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.862, 56.388)), module, VCO::PW_PARAM));
  519. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.607, 80.603)), module, VCO::FM_PARAM));
  520. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.444, 80.603)), module, VCO::LINEAR_PARAM, VCO::LINEAR_LIGHT));
  521. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(28.282, 80.603)), module, VCO::SYNC_PARAM, VCO::SOFT_LIGHT));
  522. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.118, 80.603)), module, VCO::PW_CV_PARAM));
  523. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 96.859)), module, VCO::FM_INPUT));
  524. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 96.859)), module, VCO::PITCH_INPUT));
  525. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 96.859)), module, VCO::SYNC_INPUT));
  526. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.15, 96.859)), module, VCO::PW_INPUT));
  527. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 113.115)), module, VCO::SIN_OUTPUT));
  528. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 113.115)), module, VCO::TRI_OUTPUT));
  529. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 113.115)), module, VCO::SAW_OUTPUT));
  530. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 113.115)), module, VCO::SQR_OUTPUT));
  531. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(31.089, 16.428)), module, VCO::PHASE_LIGHT));
  532. }
  533. };
  534. Model* modelVCO = createModel<VCO, VCOWidget>("VCO");