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.

407 lines
14KB

  1. #include "plugin.hpp"
  2. #include "ChowDSP.hpp"
  3. using simd::float_4;
  4. // references:
  5. // * "REDUCING THE ALIASING OF NONLINEAR WAVESHAPING USING CONTINUOUS-TIME CONVOLUTION" (https://www.dafx.de/paper-archive/2016/dafxpapers/20-DAFx-16_paper_41-PN.pdf)
  6. // * "Antiderivative Antialiasing for Memoryless Nonlinearities" https://acris.aalto.fi/ws/portalfiles/portal/27135145/ELEC_bilbao_et_al_antiderivative_antialiasing_IEEESPL.pdf
  7. // * https://ccrma.stanford.edu/~jatin/Notebooks/adaa.html
  8. // * Pony waveshape https://www.desmos.com/calculator/1kvahyl4ti
  9. template<typename T>
  10. class FoldStage1 {
  11. public:
  12. T process(T x, T xt) {
  13. T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5,
  14. f(0.5 * (xPrev + x), xt),
  15. (F(x, xt) - F(xPrev, xt)) / (x - xPrev));
  16. xPrev = x;
  17. return y;
  18. }
  19. // xt - threshold x
  20. static T f(T x, T xt) {
  21. return simd::ifelse(x > xt, +5 * xt - 4 * x, simd::ifelse(x < -xt, -5 * xt - 4 * x, x));
  22. }
  23. static T F(T x, T xt) {
  24. return simd::ifelse(x > xt, 5 * xt * x - 2 * x * x - 2.5 * xt * xt,
  25. simd::ifelse(x < -xt, -5 * xt * x - 2 * x * x - 2.5 * xt * xt, x * x / 2.f));
  26. }
  27. void reset() {
  28. xPrev = 0.f;
  29. }
  30. private:
  31. T xPrev = 0.f;
  32. };
  33. template<typename T>
  34. class FoldStage2 {
  35. public:
  36. T process(T x) {
  37. const T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5, f(0.5 * (xPrev + x)), (F(x) - F(xPrev)) / (x - xPrev));
  38. xPrev = x;
  39. return y;
  40. }
  41. static T f(T x) {
  42. return simd::ifelse(-(x + 2) > c, c, simd::ifelse(x < -1, -(x + 2), simd::ifelse(x < 1, x, simd::ifelse(-x + 2 > -c, -x + 2, -c))));
  43. }
  44. static T F(T x) {
  45. return simd::ifelse(x > 0, F_signed(x), F_signed(-x));
  46. }
  47. static T F_signed(T x) {
  48. return simd::ifelse(x < 1, x * x * 0.5, simd::ifelse(x < 2.f + c, 2.f * x * (1.f - x * 0.25f) - 1.f,
  49. 2.f * (2.f + c) * (1.f - (2.f + c) * 0.25f) - 1.f - c * (x - 2.f - c)));
  50. }
  51. void reset() {
  52. xPrev = 0.f;
  53. }
  54. private:
  55. T xPrev = 0.f;
  56. static constexpr float c = 0.1f;
  57. };
  58. struct PonyVCO : Module {
  59. enum ParamId {
  60. FREQ_PARAM,
  61. RANGE_PARAM,
  62. TIMBRE_PARAM,
  63. OCT_PARAM,
  64. WAVE_PARAM,
  65. PARAMS_LEN
  66. };
  67. enum InputId {
  68. TZFM_INPUT,
  69. TIMBRE_INPUT,
  70. VOCT_INPUT,
  71. SYNC_INPUT,
  72. VCA_INPUT,
  73. INPUTS_LEN
  74. };
  75. enum OutputId {
  76. OUT_OUTPUT,
  77. OUTPUTS_LEN
  78. };
  79. enum LightId {
  80. LIGHTS_LEN
  81. };
  82. enum Waveform {
  83. WAVE_SIN,
  84. WAVE_TRI,
  85. WAVE_SAW,
  86. WAVE_PULSE
  87. };
  88. float range[4] = {8.f, 1.f, 1.f / 12.f, 10.f};
  89. chowdsp::VariableOversampling<6, float_4> oversampler[4]; // uses a 2*6=12th order Butterworth filter
  90. int oversamplingIndex = 1; // default is 2^oversamplingIndex == x2 oversampling
  91. dsp::TRCFilter<float_4> blockTZFMDCFilter[4];
  92. bool blockTZFMDC = true;
  93. // hardware doesn't limit PW but some user might want to (to 5%->95%)
  94. bool limitPW = true;
  95. // hardware has DC for non-50% duty cycle, optionally add/remove it
  96. bool removePulseDC = true;
  97. dsp::TSchmittTrigger<float_4> syncTrigger[4];
  98. FoldStage1<float_4> stage1[4];
  99. FoldStage2<float_4> stage2[4];
  100. PonyVCO() {
  101. config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
  102. configParam(FREQ_PARAM, -0.5f, 0.5f, 0.0f, "Frequency");
  103. auto rangeParam = configSwitch(RANGE_PARAM, 0.f, 3.f, 0.f, "Range", {"VCO: Full", "VCO: Octave", "VCO: Semitone", "LFO"});
  104. rangeParam->snapEnabled = true;
  105. configParam(TIMBRE_PARAM, 0.f, 1.f, 0.f, "Timbre");
  106. auto octParam = configSwitch(OCT_PARAM, 0.f, 6.f, 4.f, "Octave", {"C1", "C2", "C3", "C4", "C5", "C6", "C7"});
  107. octParam->snapEnabled = true;
  108. auto waveParam = configSwitch(WAVE_PARAM, 0.f, 3.f, 0.f, "Wave", {"Sin", "Triangle", "Sawtooth", "Pulse"});
  109. waveParam->snapEnabled = true;
  110. configInput(TZFM_INPUT, "Through-zero FM");
  111. configInput(TIMBRE_INPUT, "Timber (wavefolder/PWM)");
  112. configInput(VOCT_INPUT, "Volt per octave");
  113. configInput(SYNC_INPUT, "Hard sync");
  114. configInput(VCA_INPUT, "VCA");
  115. configOutput(OUT_OUTPUT, "Waveform");
  116. // calculate up/downsampling rates
  117. onSampleRateChange();
  118. }
  119. void onSampleRateChange() override {
  120. float sampleRate = APP->engine->getSampleRate();
  121. for (int c = 0; c < 4; c++) {
  122. blockTZFMDCFilter[c].setCutoffFreq(5.0 / sampleRate);
  123. oversampler[c].setOversamplingIndex(oversamplingIndex);
  124. oversampler[c].reset(sampleRate);
  125. stage1[c].reset();
  126. stage2[c].reset();
  127. }
  128. }
  129. // implementation taken from "Alias-Suppressed Oscillators Based on Differentiated Polynomial Waveforms",
  130. // also the notes from Surge Synthesier repo:
  131. // https://github.com/surge-synthesizer/surge/blob/09f1ec8e103265bef6fc0d8a0fc188238197bf8c/src/common/dsp/oscillators/ModernOscillator.cpp#L19
  132. float_4 phase[4] = {}; // phase at current (sub)sample
  133. void process(const ProcessArgs& args) override {
  134. const int rangeIndex = params[RANGE_PARAM].getValue();
  135. const bool lfoMode = rangeIndex == 3;
  136. const Waveform waveform = (Waveform) params[WAVE_PARAM].getValue();
  137. const float mult = lfoMode ? 1.0 : dsp::FREQ_C4;
  138. const float baseFreq = std::pow(2, (int)(params[OCT_PARAM].getValue() - 3)) * mult;
  139. const int oversamplingRatio = lfoMode ? 1 : oversampler[0].getOversamplingRatio();
  140. // number of active polyphony engines (must be at least 1)
  141. const int channels = std::max({inputs[TZFM_INPUT].getChannels(), inputs[VOCT_INPUT].getChannels(), inputs[TIMBRE_INPUT].getChannels(), 1});
  142. for (int c = 0; c < channels; c += 4) {
  143. const float_4 timbre = simd::clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f, 0.f, 1.f);
  144. float_4 tzfmVoltage = inputs[TZFM_INPUT].getPolyVoltageSimd<float_4>(c);
  145. if (blockTZFMDC) {
  146. blockTZFMDCFilter[c / 4].process(tzfmVoltage);
  147. tzfmVoltage = blockTZFMDCFilter[c / 4].highpass();
  148. }
  149. const float_4 pitch = inputs[VOCT_INPUT].getPolyVoltageSimd<float_4>(c) + params[FREQ_PARAM].getValue() * range[rangeIndex];
  150. const float_4 freq = baseFreq * simd::pow(2.f, pitch);
  151. const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, -0.5f, 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. // not clamped, but _total_ phase treated later with floor/ceil
  159. const float_4 deltaFMPhase = freq * tzfmVoltage * args.sampleTime / oversamplingRatio;
  160. float_4 pw = timbre;
  161. if (limitPW) {
  162. pw = clamp(pw, 0.05, 0.95);
  163. }
  164. // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option
  165. // for it to be added back in for hardware compatibility reasons
  166. const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw);
  167. // hard sync
  168. const float_4 syncMask = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c));
  169. if (waveform == WAVE_SIN) {
  170. // hardware waveform is actually cos, so pi/2 phase offset is required
  171. // - variable phase is defined on [0, 1] rather than [0, 2pi] so pi/2 -> 0.25
  172. phase[c / 4] = simd::ifelse(syncMask, 0.25f, phase[c / 4]);
  173. }
  174. else {
  175. phase[c / 4] = simd::ifelse(syncMask, 0.f, phase[c / 4]);
  176. }
  177. float_4* osBuffer = oversampler[c / 4].getOSBuffer();
  178. for (int i = 0; i < oversamplingRatio; ++i) {
  179. phase[c / 4] += deltaBasePhase + deltaFMPhase;
  180. // ensure within [0, 1]
  181. phase[c / 4] -= simd::floor(phase[c / 4]);
  182. // sin is simple
  183. if (waveform == WAVE_SIN) {
  184. osBuffer[i] = sin2pi_pade_05_5_4(phase[c / 4]);
  185. }
  186. else {
  187. float_4 phases[3]; // phase as extrapolated to the current and two previous samples
  188. phases[0] = phase[c / 4] - 2 * deltaBasePhase + simd::ifelse(phase[c / 4] < 2 * deltaBasePhase, 1.f, 0.f);
  189. phases[1] = phase[c / 4] - deltaBasePhase + simd::ifelse(phase[c / 4] < deltaBasePhase, 1.f, 0.f);
  190. phases[2] = phase[c / 4];
  191. switch (waveform) {
  192. case WAVE_TRI: {
  193. const float_4 dpwOrder1 = 1.0 - 2.0 * simd::abs(2 * phase[c / 4] - 1.0);
  194. const float_4 dpwOrder3 = aliasSuppressedTri(phases) * denominatorInv;
  195. osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  196. break;
  197. }
  198. case WAVE_SAW: {
  199. const float_4 dpwOrder1 = 2 * phase[c / 4] - 1.0;
  200. const float_4 dpwOrder3 = aliasSuppressedSaw(phases) * denominatorInv;
  201. osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  202. break;
  203. }
  204. case WAVE_PULSE: {
  205. float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < 1. - pw, +1.0, -1.0);
  206. dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pw) : 0.f;
  207. float_4 saw = aliasSuppressedSaw(phases);
  208. float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw);
  209. float_4 dpwOrder3 = (sawOffset - saw) * denominatorInv + pulseDCOffset;
  210. osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3);
  211. break;
  212. }
  213. default: break;
  214. }
  215. }
  216. if (waveform != WAVE_PULSE) {
  217. osBuffer[i] = wavefolder(osBuffer[i], (1 - 0.85 * timbre), c);
  218. }
  219. } // end of oversampling loop
  220. // downsample (if required)
  221. const float_4 out = (oversamplingRatio > 1) ? oversampler[c / 4].downsample() : osBuffer[0];
  222. // end of chain VCA
  223. const float_4 gain = simd::clamp(inputs[VCA_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f, 0.f, 1.f);
  224. outputs[OUT_OUTPUT].setVoltageSimd(5.f * out * gain, c);
  225. } // end of channels loop
  226. outputs[OUT_OUTPUT].setChannels(channels);
  227. }
  228. float_4 aliasSuppressedTri(float_4* phases) {
  229. float_4 triBuffer[3];
  230. for (int i = 0; i < 3; ++i) {
  231. float_4 p = 2 * phases[i] - 1.0; // range -1.0 to +1.0
  232. float_4 s = 0.5 - simd::abs(p); // eq 30
  233. triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; // eq 29
  234. }
  235. return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]);
  236. }
  237. float_4 aliasSuppressedSaw(float_4* phases) {
  238. float_4 sawBuffer[3];
  239. for (int i = 0; i < 3; ++i) {
  240. float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
  241. sawBuffer[i] = (p * p * p - p) / 6.0; // eq 11
  242. }
  243. return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
  244. }
  245. float_4 aliasSuppressedOffsetSaw(float_4* phases, float_4 pw) {
  246. float_4 sawOffsetBuff[3];
  247. for (int i = 0; i < 3; ++i) {
  248. float_4 p = 2 * phases[i] - 1.0; // range -1 to +1
  249. float_4 pwp = p + 2 * pw; // phase after pw (pw in [0, 1])
  250. pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1]
  251. sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
  252. }
  253. return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]);
  254. }
  255. float_4 wavefolder(float_4 x, float_4 xt, int c) {
  256. return stage2[c / 4].process(stage1[c / 4].process(x, xt));
  257. }
  258. json_t* dataToJson() override {
  259. json_t* rootJ = json_object();
  260. json_object_set_new(rootJ, "blockTZFMDC", json_boolean(blockTZFMDC));
  261. json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
  262. json_object_set_new(rootJ, "limitPW", json_boolean(limitPW));
  263. json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex()));
  264. return rootJ;
  265. }
  266. void dataFromJson(json_t* rootJ) override {
  267. json_t* blockTZFMDCJ = json_object_get(rootJ, "blockTZFMDC");
  268. if (blockTZFMDCJ) {
  269. blockTZFMDC = json_boolean_value(blockTZFMDCJ);
  270. }
  271. json_t* removePulseDCJ = json_object_get(rootJ, "removePulseDC");
  272. if (removePulseDCJ) {
  273. removePulseDC = json_boolean_value(removePulseDCJ);
  274. }
  275. json_t* limitPWJ = json_object_get(rootJ, "limitPW");
  276. if (limitPWJ) {
  277. limitPW = json_boolean_value(limitPWJ);
  278. }
  279. json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
  280. if (oversamplingIndexJ) {
  281. oversamplingIndex = json_integer_value(oversamplingIndexJ);
  282. onSampleRateChange();
  283. }
  284. }
  285. };
  286. struct PonyVCOWidget : ModuleWidget {
  287. PonyVCOWidget(PonyVCO* module) {
  288. setModule(module);
  289. setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/PonyVCO.svg")));
  290. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  291. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  292. addParam(createParamCentered<Davies1900hDarkGreyKnob>(mm2px(Vec(10.0, 14.999)), module, PonyVCO::FREQ_PARAM));
  293. addParam(createParam<CKSSHoriz4>(mm2px(Vec(5.498, 27.414)), module, PonyVCO::RANGE_PARAM));
  294. addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(12.65, 37.0)), module, PonyVCO::TIMBRE_PARAM));
  295. addParam(createParam<CKSSVert7>(mm2px(Vec(3.8, 40.54)), module, PonyVCO::OCT_PARAM));
  296. addParam(createParam<CKSSHoriz4>(mm2px(Vec(5.681, 74.436)), module, PonyVCO::WAVE_PARAM));
  297. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 87.455)), module, PonyVCO::TZFM_INPUT));
  298. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.978, 87.455)), module, PonyVCO::TIMBRE_INPUT));
  299. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 100.413)), module, PonyVCO::VOCT_INPUT));
  300. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.978, 100.413)), module, PonyVCO::SYNC_INPUT));
  301. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 113.409)), module, PonyVCO::VCA_INPUT));
  302. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.0, 113.363)), module, PonyVCO::OUT_OUTPUT));
  303. }
  304. void appendContextMenu(Menu* menu) override {
  305. PonyVCO* module = dynamic_cast<PonyVCO*>(this->module);
  306. assert(module);
  307. menu->addChild(new MenuSeparator());
  308. menu->addChild(createSubmenuItem("Hardware compatibility", "",
  309. [ = ](Menu * menu) {
  310. menu->addChild(createBoolPtrMenuItem("Filter TZFM DC", "", &module->blockTZFMDC));
  311. menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW));
  312. menu->addChild(createBoolPtrMenuItem("Remove pulse DC", "", &module->removePulseDC));
  313. }
  314. ));
  315. menu->addChild(createIndexSubmenuItem("Oversampling",
  316. {"Off", "x2", "x4", "x8"},
  317. [ = ]() {
  318. return module->oversamplingIndex;
  319. },
  320. [ = ](int mode) {
  321. module->oversamplingIndex = mode;
  322. module->onSampleRateChange();
  323. }
  324. ));
  325. }
  326. };
  327. Model* modelPonyVCO = createModel<PonyVCO, PonyVCOWidget>("PonyVCO");