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.

362 lines
13KB

  1. #include "plugin.hpp"
  2. #include "ChowDSP.hpp"
  3. using simd::float_4;
  4. struct EvenVCO : Module {
  5. enum ParamIds {
  6. OCTAVE_PARAM,
  7. TUNE_PARAM,
  8. PWM_PARAM,
  9. NUM_PARAMS
  10. };
  11. enum InputIds {
  12. PITCH1_INPUT,
  13. PITCH2_INPUT,
  14. FM_INPUT,
  15. SYNC_INPUT,
  16. PWM_INPUT,
  17. NUM_INPUTS
  18. };
  19. enum OutputIds {
  20. TRI_OUTPUT,
  21. SINE_OUTPUT,
  22. EVEN_OUTPUT,
  23. SAW_OUTPUT,
  24. SQUARE_OUTPUT,
  25. NUM_OUTPUTS
  26. };
  27. float_4 phase[4] = {};
  28. dsp::TSchmittTrigger<float_4> syncTrigger[4];
  29. bool removePulseDC = true;
  30. bool limitPW = true;
  31. EvenVCO() {
  32. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
  33. configParam(OCTAVE_PARAM, -5.0, 4.0, 0.0, "Octave", "'", 0.5);
  34. getParamQuantity(OCTAVE_PARAM)->snapEnabled = true;
  35. configParam(TUNE_PARAM, -7.0, 7.0, 0.0, "Tune", " semitones");
  36. configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width");
  37. configInput(PITCH1_INPUT, "Pitch 1");
  38. configInput(PITCH2_INPUT, "Pitch 2");
  39. configInput(FM_INPUT, "FM");
  40. configInput(SYNC_INPUT, "Hard Sync");
  41. configInput(PWM_INPUT, "Pulse Width Modulation");
  42. configOutput(TRI_OUTPUT, "Triangle");
  43. configOutput(SINE_OUTPUT, "Sine");
  44. configOutput(EVEN_OUTPUT, "Even");
  45. configOutput(SAW_OUTPUT, "Sawtooth");
  46. configOutput(SQUARE_OUTPUT, "Square");
  47. }
  48. void onSampleRateChange() override {
  49. float sampleRate = APP->engine->getSampleRate();
  50. for (int i = 0; i < NUM_OUTPUTS; ++i) {
  51. for (int c = 0; c < 4; c++) {
  52. oversampler[i][c].setOversamplingIndex(oversamplingIndex);
  53. oversampler[i][c].reset(sampleRate);
  54. }
  55. }
  56. for (int c = 0; c < 4; c++) {
  57. for (int i = 0; i < NUM_UPSAMPLED_INPUTS; i++) {
  58. oversamplerInputs[i][c].setOversamplingIndex(oversamplingIndex);
  59. oversamplerInputs[i][c].reset(sampleRate);
  60. }
  61. }
  62. const float lowFreqRegime = oversampler[0][0].getOversamplingRatio() * 1e-3 * sampleRate;
  63. DEBUG("Low freq regime: %g", lowFreqRegime);
  64. }
  65. float_4 aliasSuppressedTri(float_4* phases) {
  66. float_4 triBuffer[3];
  67. for (int i = 0; i < 3; ++i) {
  68. float_4 p = 2 * phases[i] - 1.0; // range -1.0 to +1.0
  69. float_4 s = 0.5 - simd::abs(p); // eq 30
  70. triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; // eq 29
  71. }
  72. return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]);
  73. }
  74. float_4 aliasSuppressedSaw(float_4* phases) {
  75. float_4 sawBuffer[3];
  76. for (int i = 0; i < 3; ++i) {
  77. float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
  78. sawBuffer[i] = (p * p * p - p) / 6.0; // eq 11
  79. }
  80. return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
  81. }
  82. float_4 aliasSuppressedDoubleSaw(float_4* phases) {
  83. float_4 sawBuffer[3];
  84. for (int i = 0; i < 3; ++i) {
  85. float_4 p = 4.0 * simd::ifelse(phases[i] < 0.5, phases[i], phases[i] - 0.5) - 1.0;
  86. sawBuffer[i] = (p * p * p - p) / 24.0; // eq 11 (modified for doubled freq)
  87. }
  88. return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
  89. }
  90. float_4 aliasSuppressedOffsetSaw(float_4* phases, float_4 pw) {
  91. float_4 sawOffsetBuff[3];
  92. for (int i = 0; i < 3; ++i) {
  93. float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
  94. float_4 pwp = p + 2 * pw; // phase after pw (pw in [0, 1])
  95. pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1]
  96. sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
  97. }
  98. return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]);
  99. }
  100. enum UpsampledInputs {
  101. FM_INPUT_UP,
  102. SYNC_INPUT_UP,
  103. NUM_UPSAMPLED_INPUTS
  104. };
  105. chowdsp::VariableOversampling<6, float_4> oversamplerInputs[NUM_UPSAMPLED_INPUTS][4]; // uses a 2*6=12th order Butterworth filter
  106. chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; // uses a 2*6=12th order Butterworth filter
  107. int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling
  108. void process(const ProcessArgs& args) override {
  109. // pitch inputs determine number of polyphony engines
  110. const int channels = std::max({1, inputs[PITCH1_INPUT].getChannels(), inputs[PITCH2_INPUT].getChannels()});
  111. const float pitchKnobs = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f;
  112. const int oversamplingRatio = oversampler[0][0].getOversamplingRatio();
  113. for (int c = 0; c < channels; c += 4) {
  114. float_4 pw = simd::clamp(params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getPolyVoltageSimd<float_4>(c) / 5.f, -1.f, 1.f);
  115. if (limitPW) {
  116. pw = simd::rescale(pw, -1, +1, 0.05f, 0.95f);
  117. }
  118. else {
  119. pw = simd::rescale(pw, -1.f, +1.f, 0.f, 1.f);
  120. }
  121. const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(c);
  122. // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option
  123. // for it to be added back in for hardware compatibility reasons
  124. const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw);
  125. // input oversampling buffers
  126. float_4* osBufferSync = oversamplerInputs[SYNC_INPUT_UP][c / 4].getOSBuffer();
  127. float_4* osBufferFM = oversamplerInputs[FM_INPUT_UP][c / 4].getOSBuffer();
  128. // upsample hard sync input (if connected)
  129. if (inputs[SYNC_INPUT].isConnected()) {
  130. oversamplerInputs[SYNC_INPUT_UP][c].upsample(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c));
  131. }
  132. else {
  133. std::fill(osBufferSync, &osBufferSync[oversamplingRatio], float_4::zero());
  134. }
  135. // upsample FM input (if connected)
  136. if (inputs[FM_INPUT].isConnected()) {
  137. oversamplerInputs[FM_INPUT_UP][c].upsample(inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c));
  138. }
  139. else {
  140. std::fill(osBufferFM, &osBufferFM[oversamplingRatio], float_4::zero());
  141. }
  142. float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer();
  143. float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer();
  144. float_4* osBufferSin = oversampler[SINE_OUTPUT][c / 4].getOSBuffer();
  145. float_4* osBufferSquare = oversampler[SQUARE_OUTPUT][c / 4].getOSBuffer();
  146. float_4* osBufferEven = oversampler[EVEN_OUTPUT][c / 4].getOSBuffer();
  147. for (int i = 0; i < oversamplingRatio; ++i) {
  148. // use upsampled FM input
  149. const float_4 fmVoltage = osBufferFM[i] * 0.25f;
  150. const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage);
  151. const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 0.5f);
  152. // floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator
  153. // becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't
  154. // a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz.
  155. const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3;
  156. // 1 / denominator for the second-order FD
  157. const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase);
  158. phase[c / 4] += deltaBasePhase;
  159. // ensure within [0, 1]
  160. phase[c / 4] -= simd::floor(phase[c / 4]);
  161. const float_4 syncMask = syncTrigger[c / 4].process(osBufferSync[i]);
  162. phase[c / 4] = simd::ifelse(syncMask, 0.5f, phase[c / 4]);
  163. float_4 phases[3]; // phase as extrapolated to the current and two previous samples
  164. phases[0] = phase[c / 4] - 2 * deltaBasePhase + simd::ifelse(phase[c / 4] < 2 * deltaBasePhase, 1.f, 0.f);
  165. phases[1] = phase[c / 4] - deltaBasePhase + simd::ifelse(phase[c / 4] < deltaBasePhase, 1.f, 0.f);
  166. phases[2] = phase[c / 4];
  167. if (outputs[SINE_OUTPUT].isConnected() || outputs[EVEN_OUTPUT].isConnected()) {
  168. // sin doesn't need PDW
  169. osBufferSin[i] = -simd::cos(M_PI + 2.0 * M_PI * phase[c / 4]);
  170. }
  171. if (outputs[TRI_OUTPUT].isConnected()) {
  172. const float_4 dpwOrder1 = 1.0 - 2.0 * simd::abs(2 * phase[c / 4] - 1.0);
  173. const float_4 dpwOrder3 = aliasSuppressedTri(phases) * denominatorInv;
  174. osBufferTri[i] = -simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  175. }
  176. if (outputs[SAW_OUTPUT].isConnected()) {
  177. const float_4 dpwOrder1 = 2 * phase[c / 4] - 1.0;
  178. const float_4 dpwOrder3 = aliasSuppressedSaw(phases) * denominatorInv;
  179. osBufferSaw[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  180. }
  181. if (outputs[SQUARE_OUTPUT].isConnected()) {
  182. float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, -1.0, +1.0);
  183. dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pw) : 0.f;
  184. float_4 saw = aliasSuppressedSaw(phases);
  185. float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw);
  186. float_4 dpwOrder3 = (saw - sawOffset) * denominatorInv + pulseDCOffset;
  187. osBufferSquare[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  188. }
  189. if (outputs[EVEN_OUTPUT].isConnected()) {
  190. float_4 dpwOrder1 = 4.0 * simd::ifelse(phase[c / 4] < 0.5, phase[c / 4], phase[c / 4] - 0.5) - 1.0;
  191. float_4 dpwOrder3 = aliasSuppressedDoubleSaw(phases) * denominatorInv;
  192. float_4 doubleSaw = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  193. osBufferEven[i] = 0.55 * (doubleSaw + 1.27 * osBufferSin[i]);
  194. }
  195. } // end of oversampling loop
  196. // downsample (if required)
  197. if (outputs[SINE_OUTPUT].isConnected()) {
  198. const float_4 outSin = (oversamplingRatio > 1) ? oversampler[SINE_OUTPUT][c / 4].downsample() : osBufferSin[0];
  199. outputs[SINE_OUTPUT].setVoltageSimd(5.f * outSin, c);
  200. }
  201. if (outputs[TRI_OUTPUT].isConnected()) {
  202. const float_4 outTri = (oversamplingRatio > 1) ? oversampler[TRI_OUTPUT][c / 4].downsample() : osBufferTri[0];
  203. outputs[TRI_OUTPUT].setVoltageSimd(5.f * outTri, c);
  204. }
  205. if (outputs[SAW_OUTPUT].isConnected()) {
  206. const float_4 outSaw = (oversamplingRatio > 1) ? oversampler[SAW_OUTPUT][c / 4].downsample() : osBufferSaw[0];
  207. outputs[SAW_OUTPUT].setVoltageSimd(5.f * outSaw, c);
  208. }
  209. if (outputs[SQUARE_OUTPUT].isConnected()) {
  210. const float_4 outSquare = (oversamplingRatio > 1) ? oversampler[SQUARE_OUTPUT][c / 4].downsample() : osBufferSquare[0];
  211. outputs[SQUARE_OUTPUT].setVoltageSimd(5.f * outSquare, c);
  212. }
  213. if (outputs[EVEN_OUTPUT].isConnected()) {
  214. const float_4 outEven = (oversamplingRatio > 1) ? oversampler[EVEN_OUTPUT][c / 4].downsample() : osBufferEven[0];
  215. outputs[EVEN_OUTPUT].setVoltageSimd(5.f * outEven, c);
  216. }
  217. } // end of channels loop
  218. // Outputs
  219. outputs[TRI_OUTPUT].setChannels(channels);
  220. outputs[SINE_OUTPUT].setChannels(channels);
  221. outputs[EVEN_OUTPUT].setChannels(channels);
  222. outputs[SAW_OUTPUT].setChannels(channels);
  223. outputs[SQUARE_OUTPUT].setChannels(channels);
  224. }
  225. json_t* dataToJson() override {
  226. json_t* rootJ = json_object();
  227. json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
  228. json_object_set_new(rootJ, "limitPW", json_boolean(limitPW));
  229. json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0][0].getOversamplingIndex()));
  230. return rootJ;
  231. }
  232. void dataFromJson(json_t* rootJ) override {
  233. json_t* pulseDCJ = json_object_get(rootJ, "removePulseDC");
  234. if (pulseDCJ) {
  235. removePulseDC = json_boolean_value(pulseDCJ);
  236. }
  237. json_t* limitPWJ = json_object_get(rootJ, "limitPW");
  238. if (limitPWJ) {
  239. limitPW = json_boolean_value(limitPWJ);
  240. }
  241. json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
  242. if (oversamplingIndexJ) {
  243. oversamplingIndex = json_integer_value(oversamplingIndexJ);
  244. onSampleRateChange();
  245. }
  246. }
  247. };
  248. struct EvenVCOWidget : ModuleWidget {
  249. EvenVCOWidget(EvenVCO* module) {
  250. setModule(module);
  251. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/EvenVCO.svg")));
  252. addChild(createWidget<Knurlie>(Vec(15, 0)));
  253. addChild(createWidget<Knurlie>(Vec(15, 365)));
  254. addChild(createWidget<Knurlie>(Vec(15 * 6, 0)));
  255. addChild(createWidget<Knurlie>(Vec(15 * 6, 365)));
  256. addParam(createParam<BefacoBigKnob>(Vec(22, 32), module, EvenVCO::OCTAVE_PARAM));
  257. addParam(createParam<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO::TUNE_PARAM));
  258. addParam(createParam<Davies1900hRedKnob>(Vec(16, 230), module, EvenVCO::PWM_PARAM));
  259. addInput(createInput<BefacoInputPort>(Vec(8, 120), module, EvenVCO::PITCH1_INPUT));
  260. addInput(createInput<BefacoInputPort>(Vec(19, 157), module, EvenVCO::PITCH2_INPUT));
  261. addInput(createInput<BefacoInputPort>(Vec(48, 183), module, EvenVCO::FM_INPUT));
  262. addInput(createInput<BefacoInputPort>(Vec(86, 189), module, EvenVCO::SYNC_INPUT));
  263. addInput(createInput<BefacoInputPort>(Vec(72, 236), module, EvenVCO::PWM_INPUT));
  264. addOutput(createOutput<BefacoOutputPort>(Vec(10, 283), module, EvenVCO::TRI_OUTPUT));
  265. addOutput(createOutput<BefacoOutputPort>(Vec(87, 283), module, EvenVCO::SINE_OUTPUT));
  266. addOutput(createOutput<BefacoOutputPort>(Vec(48, 306), module, EvenVCO::EVEN_OUTPUT));
  267. addOutput(createOutput<BefacoOutputPort>(Vec(10, 327), module, EvenVCO::SAW_OUTPUT));
  268. addOutput(createOutput<BefacoOutputPort>(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT));
  269. }
  270. void appendContextMenu(Menu* menu) override {
  271. EvenVCO* module = dynamic_cast<EvenVCO*>(this->module);
  272. assert(module);
  273. menu->addChild(new MenuSeparator());
  274. menu->addChild(createSubmenuItem("Hardware compatibility", "",
  275. [ = ](Menu * menu) {
  276. menu->addChild(createBoolPtrMenuItem("Remove DC from pulse", "", &module->removePulseDC));
  277. menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW));
  278. }
  279. ));
  280. menu->addChild(createIndexSubmenuItem("Oversampling",
  281. {"Off", "x2", "x4", "x8"},
  282. [ = ]() {
  283. return module->oversamplingIndex;
  284. },
  285. [ = ](int mode) {
  286. module->oversamplingIndex = mode;
  287. module->onSampleRateChange();
  288. }
  289. ));
  290. }
  291. };
  292. Model* modelEvenVCO = createModel<EvenVCO, EvenVCOWidget>("EvenVCO");