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.

413 lines
12KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. // Accurate only on [0, 1]
  4. template <typename T>
  5. T sin2pi_pade_05_7_6(T x) {
  6. x -= 0.5f;
  7. return (T(-6.28319) * x + T(35.353) * simd::pow(x, 3) - T(44.9043) * simd::pow(x, 5) + T(16.0951) * simd::pow(x, 7))
  8. / (1 + T(0.953136) * simd::pow(x, 2) + T(0.430238) * simd::pow(x, 4) + T(0.0981408) * simd::pow(x, 6));
  9. }
  10. template <typename T>
  11. T sin2pi_pade_05_5_4(T x) {
  12. x -= 0.5f;
  13. return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5))
  14. / (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4));
  15. }
  16. template <typename T>
  17. T expCurve(T x) {
  18. return (3 + x * (-13 + 5 * x)) / (3 + 2 * x);
  19. }
  20. template <int OVERSAMPLE, int QUALITY, typename T>
  21. struct VoltageControlledOscillator {
  22. bool analog = false;
  23. bool soft = false;
  24. bool syncEnabled = false;
  25. // For optimizing in serial code
  26. int channels = 0;
  27. T lastSyncValue = 0.f;
  28. T phase = 0.f;
  29. T freq = 0.f;
  30. T pulseWidth = 0.5f;
  31. T syncDirection = 1.f;
  32. dsp::TRCFilter<T> sqrFilter;
  33. dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sqrMinBlep;
  34. dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sawMinBlep;
  35. dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> triMinBlep;
  36. dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sinMinBlep;
  37. T sqrValue = 0.f;
  38. T sawValue = 0.f;
  39. T triValue = 0.f;
  40. T sinValue = 0.f;
  41. void setPulseWidth(T pulseWidth) {
  42. const float pwMin = 0.01f;
  43. this->pulseWidth = simd::clamp(pulseWidth, pwMin, 1.f - pwMin);
  44. }
  45. void process(float deltaTime, T syncValue) {
  46. // Advance phase
  47. T deltaPhase = simd::clamp(freq * deltaTime, 0.f, 0.35f);
  48. if (soft) {
  49. // Reverse direction
  50. deltaPhase *= syncDirection;
  51. }
  52. else {
  53. // Reset back to forward
  54. syncDirection = 1.f;
  55. }
  56. phase += deltaPhase;
  57. // Wrap phase
  58. phase -= simd::floor(phase);
  59. // Jump sqr when crossing 0, or 1 if backwards
  60. T wrapPhase = (syncDirection == -1.f) & 1.f;
  61. T wrapCrossing = (wrapPhase - (phase - deltaPhase)) / deltaPhase;
  62. int wrapMask = simd::movemask((0 < wrapCrossing) & (wrapCrossing <= 1.f));
  63. if (wrapMask) {
  64. for (int i = 0; i < channels; i++) {
  65. if (wrapMask & (1 << i)) {
  66. T mask = simd::movemaskInverse<T>(1 << i);
  67. float p = wrapCrossing[i] - 1.f;
  68. T x = mask & (2.f * syncDirection);
  69. sqrMinBlep.insertDiscontinuity(p, x);
  70. }
  71. }
  72. }
  73. // Jump sqr when crossing `pulseWidth`
  74. T pulseCrossing = (pulseWidth - (phase - deltaPhase)) / deltaPhase;
  75. int pulseMask = simd::movemask((0 < pulseCrossing) & (pulseCrossing <= 1.f));
  76. if (pulseMask) {
  77. for (int i = 0; i < channels; i++) {
  78. if (pulseMask & (1 << i)) {
  79. T mask = simd::movemaskInverse<T>(1 << i);
  80. float p = pulseCrossing[i] - 1.f;
  81. T x = mask & (-2.f * syncDirection);
  82. sqrMinBlep.insertDiscontinuity(p, x);
  83. }
  84. }
  85. }
  86. // Jump saw when crossing 0.5
  87. T halfCrossing = (0.5f - (phase - deltaPhase)) / deltaPhase;
  88. int halfMask = simd::movemask((0 < halfCrossing) & (halfCrossing <= 1.f));
  89. if (halfMask) {
  90. for (int i = 0; i < channels; i++) {
  91. if (halfMask & (1 << i)) {
  92. T mask = simd::movemaskInverse<T>(1 << i);
  93. float p = halfCrossing[i] - 1.f;
  94. T x = mask & (-2.f * syncDirection);
  95. sawMinBlep.insertDiscontinuity(p, x);
  96. }
  97. }
  98. }
  99. // Detect sync
  100. // Might be NAN or outside of [0, 1) range
  101. if (syncEnabled) {
  102. T deltaSync = syncValue - lastSyncValue;
  103. T syncCrossing = -lastSyncValue / deltaSync;
  104. lastSyncValue = syncValue;
  105. T sync = (0.f < syncCrossing) & (syncCrossing <= 1.f) & (syncValue >= 0.f);
  106. int syncMask = simd::movemask(sync);
  107. if (syncMask) {
  108. if (soft) {
  109. syncDirection = simd::ifelse(sync, -syncDirection, syncDirection);
  110. }
  111. else {
  112. T newPhase = simd::ifelse(sync, (1.f - syncCrossing) * deltaPhase, phase);
  113. // Insert minBLEP for sync
  114. for (int i = 0; i < channels; i++) {
  115. if (syncMask & (1 << i)) {
  116. T mask = simd::movemaskInverse<T>(1 << i);
  117. float p = syncCrossing[i] - 1.f;
  118. T x;
  119. x = mask & (sqr(newPhase) - sqr(phase));
  120. sqrMinBlep.insertDiscontinuity(p, x);
  121. x = mask & (saw(newPhase) - saw(phase));
  122. sawMinBlep.insertDiscontinuity(p, x);
  123. x = mask & (tri(newPhase) - tri(phase));
  124. triMinBlep.insertDiscontinuity(p, x);
  125. x = mask & (sin(newPhase) - sin(phase));
  126. sinMinBlep.insertDiscontinuity(p, x);
  127. }
  128. }
  129. phase = newPhase;
  130. }
  131. }
  132. }
  133. // Square
  134. sqrValue = sqr(phase);
  135. sqrValue += sqrMinBlep.process();
  136. if (analog) {
  137. sqrFilter.setCutoffFreq(20.f * deltaTime);
  138. sqrFilter.process(sqrValue);
  139. sqrValue = sqrFilter.highpass() * 0.95f;
  140. }
  141. // Saw
  142. sawValue = saw(phase);
  143. sawValue += sawMinBlep.process();
  144. // Tri
  145. triValue = tri(phase);
  146. triValue += triMinBlep.process();
  147. // Sin
  148. sinValue = sin(phase);
  149. sinValue += sinMinBlep.process();
  150. }
  151. T sin(T phase) {
  152. T v;
  153. if (analog) {
  154. // Quadratic approximation of sine, slightly richer harmonics
  155. T halfPhase = (phase < 0.5f);
  156. T x = phase - simd::ifelse(halfPhase, 0.25f, 0.75f);
  157. v = 1.f - 16.f * simd::pow(x, 2);
  158. v *= simd::ifelse(halfPhase, 1.f, -1.f);
  159. }
  160. else {
  161. v = sin2pi_pade_05_5_4(phase);
  162. // v = sin2pi_pade_05_7_6(phase);
  163. // v = simd::sin(2 * T(M_PI) * phase);
  164. }
  165. return v;
  166. }
  167. T sin() {
  168. return sinValue;
  169. }
  170. T tri(T phase) {
  171. T v;
  172. if (analog) {
  173. T x = phase + 0.25f;
  174. x -= simd::trunc(x);
  175. T halfX = (x >= 0.5f);
  176. x *= 2;
  177. x -= simd::trunc(x);
  178. v = expCurve(x) * simd::ifelse(halfX, 1.f, -1.f);
  179. }
  180. else {
  181. v = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
  182. }
  183. return v;
  184. }
  185. T tri() {
  186. return triValue;
  187. }
  188. T saw(T phase) {
  189. T v;
  190. T x = phase + 0.5f;
  191. x -= simd::trunc(x);
  192. if (analog) {
  193. v = -expCurve(x);
  194. }
  195. else {
  196. v = 2 * x - 1;
  197. }
  198. return v;
  199. }
  200. T saw() {
  201. return sawValue;
  202. }
  203. T sqr(T phase) {
  204. T v = simd::ifelse(phase < pulseWidth, 1.f, -1.f);
  205. return v;
  206. }
  207. T sqr() {
  208. return sqrValue;
  209. }
  210. T light() {
  211. return simd::sin(2 * T(M_PI) * phase);
  212. }
  213. };
  214. struct VCO : Module {
  215. enum ParamIds {
  216. MODE_PARAM, // removed
  217. SYNC_PARAM,
  218. FREQ_PARAM,
  219. FINE_PARAM, // removed
  220. FM_PARAM,
  221. PW_PARAM,
  222. PW_CV_PARAM,
  223. // new in 2.0
  224. LINEAR_PARAM,
  225. NUM_PARAMS
  226. };
  227. enum InputIds {
  228. PITCH_INPUT,
  229. FM_INPUT,
  230. SYNC_INPUT,
  231. PW_INPUT,
  232. NUM_INPUTS
  233. };
  234. enum OutputIds {
  235. SIN_OUTPUT,
  236. TRI_OUTPUT,
  237. SAW_OUTPUT,
  238. SQR_OUTPUT,
  239. NUM_OUTPUTS
  240. };
  241. enum LightIds {
  242. ENUMS(PHASE_LIGHT, 3),
  243. LINEAR_LIGHT,
  244. SOFT_LIGHT,
  245. NUM_LIGHTS
  246. };
  247. VoltageControlledOscillator<16, 16, float_4> oscillators[4];
  248. dsp::ClockDivider lightDivider;
  249. VCO() {
  250. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  251. configSwitch(LINEAR_PARAM, 0.f, 1.f, 0.f, "FM mode", {"1V/octave", "Linear"});
  252. configSwitch(SYNC_PARAM, 0.f, 1.f, 1.f, "Sync mode", {"Soft", "Hard"});
  253. configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
  254. configParam(FM_PARAM, -1.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
  255. getParamQuantity(FM_PARAM)->randomizeEnabled = false;
  256. configParam(PW_PARAM, 0.01f, 0.99f, 0.5f, "Pulse width", "%", 0.f, 100.f);
  257. configParam(PW_CV_PARAM, -1.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
  258. getParamQuantity(PW_CV_PARAM)->randomizeEnabled = false;
  259. configInput(PITCH_INPUT, "1V/octave pitch");
  260. configInput(FM_INPUT, "Frequency modulation");
  261. configInput(SYNC_INPUT, "Sync");
  262. configInput(PW_INPUT, "Pulse width modulation");
  263. configOutput(SIN_OUTPUT, "Sine");
  264. configOutput(TRI_OUTPUT, "Triangle");
  265. configOutput(SAW_OUTPUT, "Sawtooth");
  266. configOutput(SQR_OUTPUT, "Square");
  267. lightDivider.setDivision(16);
  268. }
  269. void process(const ProcessArgs& args) override {
  270. float freqParam = params[FREQ_PARAM].getValue() / 12.f;
  271. float fmParam = params[FM_PARAM].getValue();
  272. float pwParam = params[PW_PARAM].getValue();
  273. float pwCvParam = params[PW_CV_PARAM].getValue();
  274. bool linear = params[LINEAR_PARAM].getValue() > 0.f;
  275. bool soft = params[SYNC_PARAM].getValue() <= 0.f;
  276. int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
  277. for (int c = 0; c < channels; c += 4) {
  278. auto& oscillator = oscillators[c / 4];
  279. oscillator.channels = std::min(channels - c, 4);
  280. // removed
  281. oscillator.analog = true;
  282. oscillator.soft = soft;
  283. // Get frequency
  284. float_4 pitch = freqParam + inputs[PITCH_INPUT].getPolyVoltageSimd<float_4>(c);
  285. float_4 freq;
  286. if (!linear) {
  287. pitch += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  288. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  289. }
  290. else {
  291. freq = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  292. freq += dsp::FREQ_C4 * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * fmParam;
  293. }
  294. freq = clamp(freq, 0.f, args.sampleRate / 2.f);
  295. oscillator.freq = freq;
  296. // Get pulse width
  297. float_4 pw = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwCvParam;
  298. oscillator.setPulseWidth(pw);
  299. oscillator.syncEnabled = inputs[SYNC_INPUT].isConnected();
  300. float_4 sync = inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c);
  301. oscillator.process(args.sampleTime, sync);
  302. // Set output
  303. if (outputs[SIN_OUTPUT].isConnected())
  304. outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator.sin(), c);
  305. if (outputs[TRI_OUTPUT].isConnected())
  306. outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator.tri(), c);
  307. if (outputs[SAW_OUTPUT].isConnected())
  308. outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator.saw(), c);
  309. if (outputs[SQR_OUTPUT].isConnected())
  310. outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator.sqr(), c);
  311. }
  312. outputs[SIN_OUTPUT].setChannels(channels);
  313. outputs[TRI_OUTPUT].setChannels(channels);
  314. outputs[SAW_OUTPUT].setChannels(channels);
  315. outputs[SQR_OUTPUT].setChannels(channels);
  316. // Light
  317. if (lightDivider.process()) {
  318. if (channels == 1) {
  319. float lightValue = oscillators[0].light()[0];
  320. lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
  321. lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
  322. lights[PHASE_LIGHT + 2].setBrightness(0.f);
  323. }
  324. else {
  325. lights[PHASE_LIGHT + 0].setBrightness(0.f);
  326. lights[PHASE_LIGHT + 1].setBrightness(0.f);
  327. lights[PHASE_LIGHT + 2].setBrightness(1.f);
  328. }
  329. lights[LINEAR_LIGHT].setBrightness(linear);
  330. lights[SOFT_LIGHT].setBrightness(soft);
  331. }
  332. }
  333. };
  334. struct VCOWidget : ModuleWidget {
  335. VCOWidget(VCO* module) {
  336. setModule(module);
  337. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCO.svg"), asset::plugin(pluginInstance, "res/VCO-dark.svg")));
  338. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  339. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  340. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  341. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  342. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(22.905, 29.808)), module, VCO::FREQ_PARAM));
  343. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(22.862, 56.388)), module, VCO::PW_PARAM));
  344. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.607, 80.603)), module, VCO::FM_PARAM));
  345. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(17.444, 80.603)), module, VCO::LINEAR_PARAM, VCO::LINEAR_LIGHT));
  346. addParam(createLightParamCentered<VCVLightLatch<MediumSimpleLight<WhiteLight>>>(mm2px(Vec(28.282, 80.603)), module, VCO::SYNC_PARAM, VCO::SOFT_LIGHT));
  347. addParam(createParamCentered<Trimpot>(mm2px(Vec(39.118, 80.603)), module, VCO::PW_CV_PARAM));
  348. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 96.859)), module, VCO::FM_INPUT));
  349. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 96.859)), module, VCO::PITCH_INPUT));
  350. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 96.859)), module, VCO::SYNC_INPUT));
  351. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(39.15, 96.859)), module, VCO::PW_INPUT));
  352. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(6.607, 113.115)), module, VCO::SIN_OUTPUT));
  353. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.444, 113.115)), module, VCO::TRI_OUTPUT));
  354. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.282, 113.115)), module, VCO::SAW_OUTPUT));
  355. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(39.119, 113.115)), module, VCO::SQR_OUTPUT));
  356. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(31.089, 16.428)), module, VCO::PHASE_LIGHT));
  357. }
  358. };
  359. Model* modelVCO = createModel<VCO, VCOWidget>("VCO");