|  | @@ -1,6 +1,7 @@ | 
														
													
														
															
																|  |  | #include "plugin.hpp" |  |  | #include "plugin.hpp" | 
														
													
														
															
																|  |  | #include "ChowDSP.hpp" |  |  | #include "ChowDSP.hpp" | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | using simd::float_4; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // references: |  |  | // references: | 
														
													
														
															
																|  |  | // * "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) |  |  | // * "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) | 
														
													
												
													
														
															
																|  | @@ -8,46 +9,27 @@ | 
														
													
														
															
																|  |  | // * https://ccrma.stanford.edu/~jatin/Notebooks/adaa.html |  |  | // * https://ccrma.stanford.edu/~jatin/Notebooks/adaa.html | 
														
													
														
															
																|  |  | // * Pony waveshape  https://www.desmos.com/calculator/1kvahyl4ti |  |  | // * Pony waveshape  https://www.desmos.com/calculator/1kvahyl4ti | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template<typename T> | 
														
													
														
															
																|  |  | class FoldStage1 { |  |  | class FoldStage1 { | 
														
													
														
															
																|  |  | public: |  |  | public: | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | float process(float x, float xt) { |  |  |  | 
														
													
														
															
																|  |  | float y; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | T process(T x, T xt) { | 
														
													
														
															
																|  |  |  |  |  | T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5, | 
														
													
														
															
																|  |  |  |  |  | f(0.5 * (xPrev + x), xt), | 
														
													
														
															
																|  |  |  |  |  | (F(x, xt) - F(xPrev, xt)) / (x - xPrev)); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | if (fabs(x - xPrev) < 1e-5) { |  |  |  | 
														
													
														
															
																|  |  | y = f(0.5 * (xPrev + x), xt); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | y = (F(x, xt) - F(xPrev, xt)) / (x - xPrev); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | xPrev = x; |  |  | xPrev = x; | 
														
													
														
															
																|  |  | return y; |  |  | return y; | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // xt - threshold x |  |  | // xt - threshold x | 
														
													
														
															
																|  |  | static float f(float x, float xt) { |  |  |  | 
														
													
														
															
																|  |  | if (x > xt) { |  |  |  | 
														
													
														
															
																|  |  | return +5 * xt - 4 * x; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < -xt) { |  |  |  | 
														
													
														
															
																|  |  | return -5 * xt - 4 * x; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | return x; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | static T f(T x, T xt) { | 
														
													
														
															
																|  |  |  |  |  | return simd::ifelse(x > xt, +5 * xt - 4 * x, simd::ifelse(x < -xt, -5 * xt - 4 * x, x)); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | static float F(float x, float xt) { |  |  |  | 
														
													
														
															
																|  |  | if (x > xt) { |  |  |  | 
														
													
														
															
																|  |  | return 5 * xt * x - 2 * x * x - 2.5 * xt * xt; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < -xt) { |  |  |  | 
														
													
														
															
																|  |  | return -5 * xt * x - 2 * x * x - 2.5 * xt * xt; |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | return x * x / 2.f; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | static T F(T x, T xt) { | 
														
													
														
															
																|  |  |  |  |  | return simd::ifelse(x > xt,  5 * xt * x - 2 * x * x - 2.5 * xt * xt, | 
														
													
														
															
																|  |  |  |  |  | simd::ifelse(x < -xt, -5 * xt * x - 2 * x * x - 2.5 * xt * xt, x * x / 2.f)); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | void reset() { |  |  | void reset() { | 
														
													
												
													
														
															
																|  | @@ -55,55 +37,29 @@ public: | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | private: |  |  | private: | 
														
													
														
															
																|  |  | float xPrev = 0.f; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | T xPrev = 0.f; | 
														
													
														
															
																|  |  | }; |  |  | }; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | template<typename T> | 
														
													
														
															
																|  |  | class FoldStage2 { |  |  | class FoldStage2 { | 
														
													
														
															
																|  |  | public: |  |  | public: | 
														
													
														
															
																|  |  | float process(float x) { |  |  |  | 
														
													
														
															
																|  |  | float y; |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  |  | 
														
													
														
															
																|  |  | if (fabs(x - xPrev) < 1e-5) { |  |  |  | 
														
													
														
															
																|  |  | y = f(0.5 * (xPrev + x)); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | y = (F(x) - F(xPrev)) / (x - xPrev); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | T process(T x) { | 
														
													
														
															
																|  |  |  |  |  | const T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5, f(0.5 * (xPrev + x)), (F(x) - F(xPrev)) / (x - xPrev)); | 
														
													
														
															
																|  |  | xPrev = x; |  |  | xPrev = x; | 
														
													
														
															
																|  |  | return y; |  |  | return y; | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | static float f(float x) { |  |  |  | 
														
													
														
															
																|  |  | if (-(x + 2) > c) { |  |  |  | 
														
													
														
															
																|  |  | return c; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < -1) { |  |  |  | 
														
													
														
															
																|  |  | return -(x + 2); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < 1) { |  |  |  | 
														
													
														
															
																|  |  | return x; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (-x + 2 > -c) { |  |  |  | 
														
													
														
															
																|  |  | return -x + 2; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | return -c; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | static T f(T x) { | 
														
													
														
															
																|  |  |  |  |  | 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)))); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | static float F(float x) { |  |  |  | 
														
													
														
															
																|  |  | if (x < 0) { |  |  |  | 
														
													
														
															
																|  |  | return F(-x); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < 1) { |  |  |  | 
														
													
														
															
																|  |  | return x * x * 0.5; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else if (x < 2 + c) { |  |  |  | 
														
													
														
															
																|  |  | return 2 * x * (1.f - x * 0.25f) - 1.f; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | else { |  |  |  | 
														
													
														
															
																|  |  | return 2 * (2 + c) * (1 - (2 + c) * 0.25f) - 1.f - c * (x - 2 - c); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | static T F(T x) { | 
														
													
														
															
																|  |  |  |  |  | return simd::ifelse(x > 0, F_signed(x), F_signed(-x)); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | static T F_signed(T x) { | 
														
													
														
															
																|  |  |  |  |  | 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, | 
														
													
														
															
																|  |  |  |  |  | 2.f * (2.f + c) * (1.f - (2.f + c) * 0.25f) - 1.f - c * (x - 2.f - c))); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | void reset() { |  |  | void reset() { | 
														
													
												
													
														
															
																|  | @@ -111,8 +67,8 @@ public: | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | private: |  |  | private: | 
														
													
														
															
																|  |  | float xPrev = 0.f; |  |  |  | 
														
													
														
															
																|  |  | static constexpr float c = 0.1; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | T xPrev = 0.f; | 
														
													
														
															
																|  |  |  |  |  | static constexpr float c = 0.1f; | 
														
													
														
															
																|  |  | }; |  |  | }; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
												
													
														
															
																|  | @@ -148,10 +104,10 @@ struct PonyVCO : Module { | 
														
													
														
															
																|  |  | }; |  |  | }; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | float range[4] = {8.f, 1.f, 1.f / 12.f, 10.f}; |  |  | float range[4] = {8.f, 1.f, 1.f / 12.f, 10.f}; | 
														
													
														
															
																|  |  | chowdsp::VariableOversampling<6> oversampler; 	// uses a 2*6=12th order Butterworth filter |  |  |  | 
														
													
														
															
																|  |  |  |  |  | chowdsp::VariableOversampling<6, float_4> oversampler[4]; 	// uses a 2*6=12th order Butterworth filter | 
														
													
														
															
																|  |  | int oversamplingIndex = 1; 	// default is 2^oversamplingIndex == x2 oversampling |  |  | int oversamplingIndex = 1; 	// default is 2^oversamplingIndex == x2 oversampling | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | dsp::RCFilter blockTZFMDCFilter; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | dsp::TRCFilter<float_4> blockTZFMDCFilter[4]; | 
														
													
														
															
																|  |  | bool blockTZFMDC = true; |  |  | bool blockTZFMDC = true; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // hardware doesn't limit PW but some user might want to (to 5%->95%) |  |  | // hardware doesn't limit PW but some user might want to (to 5%->95%) | 
														
													
												
													
														
															
																|  | @@ -160,10 +116,10 @@ struct PonyVCO : Module { | 
														
													
														
															
																|  |  | // hardware has DC for non-50% duty cycle, optionally add/remove it |  |  | // hardware has DC for non-50% duty cycle, optionally add/remove it | 
														
													
														
															
																|  |  | bool removePulseDC = true; |  |  | bool removePulseDC = true; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | dsp::SchmittTrigger syncTrigger; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | dsp::TSchmittTrigger<float_4> syncTrigger[4]; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | FoldStage1 stage1; |  |  |  | 
														
													
														
															
																|  |  | FoldStage2 stage2; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | FoldStage1<float_4> stage1[4]; | 
														
													
														
															
																|  |  |  |  |  | FoldStage2<float_4> stage2[4]; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | PonyVCO() { |  |  | PonyVCO() { | 
														
													
														
															
																|  |  | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); |  |  | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | 
														
													
												
													
														
															
																|  | @@ -191,22 +147,21 @@ struct PonyVCO : Module { | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | void onSampleRateChange() override { |  |  | void onSampleRateChange() override { | 
														
													
														
															
																|  |  | float sampleRate = APP->engine->getSampleRate(); |  |  | float sampleRate = APP->engine->getSampleRate(); | 
														
													
														
															
																|  |  | blockTZFMDCFilter.setCutoffFreq(5.0 / sampleRate); |  |  |  | 
														
													
														
															
																|  |  | oversampler.setOversamplingIndex(oversamplingIndex); |  |  |  | 
														
													
														
															
																|  |  | oversampler.reset(sampleRate); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < 4; c++) { | 
														
													
														
															
																|  |  |  |  |  | blockTZFMDCFilter[c].setCutoffFreq(5.0 / sampleRate); | 
														
													
														
															
																|  |  |  |  |  | oversampler[c].setOversamplingIndex(oversamplingIndex); | 
														
													
														
															
																|  |  |  |  |  | oversampler[c].reset(sampleRate); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | stage1.reset(); |  |  |  | 
														
													
														
															
																|  |  | stage2.reset(); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | stage1[c].reset(); | 
														
													
														
															
																|  |  |  |  |  | stage2[c].reset(); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // implementation taken from "Alias-Suppressed Oscillators Based on Differentiated Polynomial Waveforms", |  |  | // implementation taken from "Alias-Suppressed Oscillators Based on Differentiated Polynomial Waveforms", | 
														
													
														
															
																|  |  | // also the notes from Surge Synthesier repo: |  |  | // also the notes from Surge Synthesier repo: | 
														
													
														
															
																|  |  | // https://github.com/surge-synthesizer/surge/blob/09f1ec8e103265bef6fc0d8a0fc188238197bf8c/src/common/dsp/oscillators/ModernOscillator.cpp#L19 |  |  | // https://github.com/surge-synthesizer/surge/blob/09f1ec8e103265bef6fc0d8a0fc188238197bf8c/src/common/dsp/oscillators/ModernOscillator.cpp#L19 | 
														
													
														
															
																|  |  | // Calculation is performed at double precision, as the differencing equations appeared to work poorly with only float. |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | double phase = 0.0; 	// phase at current (sub)sample |  |  |  | 
														
													
														
															
																|  |  | double phases[3] = {}; 	// phase as extrapolated to the current and two previous samples |  |  |  | 
														
													
														
															
																|  |  | double sawBuffer[3] = {}, sawOffsetBuff[3] = {}, triBuffer[3] = {}; 	// buffers for storing the terms in the difference equation |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 phase[4] = {}; 	// phase at current (sub)sample | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | void process(const ProcessArgs& args) override { |  |  | void process(const ProcessArgs& args) override { | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
												
													
														
															
																|  | @@ -216,130 +171,161 @@ struct PonyVCO : Module { | 
														
													
														
															
																|  |  | const Waveform waveform = (Waveform) params[WAVE_PARAM].getValue(); |  |  | const Waveform waveform = (Waveform) params[WAVE_PARAM].getValue(); | 
														
													
														
															
																|  |  | const float mult = lfoMode ? 1.0 : dsp::FREQ_C4; |  |  | const float mult = lfoMode ? 1.0 : dsp::FREQ_C4; | 
														
													
														
															
																|  |  | const float baseFreq = std::pow(2, (int)(params[OCT_PARAM].getValue() - 3)) * mult; |  |  | const float baseFreq = std::pow(2, (int)(params[OCT_PARAM].getValue() - 3)) * mult; | 
														
													
														
															
																|  |  | const int oversamplingRatio = lfoMode ? 1 : oversampler.getOversamplingRatio(); |  |  |  | 
														
													
														
															
																|  |  | const float timbre = clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getVoltage() / 10.f, 0.f, 1.f); |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  |  | 
														
													
														
															
																|  |  | float tzfmVoltage = inputs[TZFM_INPUT].getVoltage(); |  |  |  | 
														
													
														
															
																|  |  | if (blockTZFMDC) { |  |  |  | 
														
													
														
															
																|  |  | blockTZFMDCFilter.process(tzfmVoltage); |  |  |  | 
														
													
														
															
																|  |  | tzfmVoltage = blockTZFMDCFilter.highpass(); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | const int oversamplingRatio = lfoMode ? 1 : oversampler[0].getOversamplingRatio(); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | const double pitch = inputs[VOCT_INPUT].getVoltage() + params[FREQ_PARAM].getValue() * range[rangeIndex]; |  |  |  | 
														
													
														
															
																|  |  | const double freq = baseFreq * simd::pow(2.f, pitch); |  |  |  | 
														
													
														
															
																|  |  | const double deltaBasePhase = clamp(freq * args.sampleTime / oversamplingRatio, -0.5f, 0.5f); |  |  |  | 
														
													
														
															
																|  |  | // denominator for the second-order FD |  |  |  | 
														
													
														
															
																|  |  | const double denominator = 0.25 / (deltaBasePhase * deltaBasePhase); |  |  |  | 
														
													
														
															
																|  |  | // not clamped, but _total_ phase treated later with floor/ceil |  |  |  | 
														
													
														
															
																|  |  | const double deltaFMPhase = freq * tzfmVoltage * args.sampleTime / oversamplingRatio; |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  |  | 
														
													
														
															
																|  |  | float pw = timbre; |  |  |  | 
														
													
														
															
																|  |  | if (limitPW) { |  |  |  | 
														
													
														
															
																|  |  | pw = clamp(pw, 0.05, 0.95); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option |  |  |  | 
														
													
														
															
																|  |  | // for it to be added back in for hardware compatibility reasons |  |  |  | 
														
													
														
															
																|  |  | const float pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw); |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  |  | 
														
													
														
															
																|  |  | // hard sync |  |  |  | 
														
													
														
															
																|  |  | if (syncTrigger.process(inputs[SYNC_INPUT].getVoltage())) { |  |  |  | 
														
													
														
															
																|  |  | // hardware waveform is actually cos, so pi/2 phase offset is required |  |  |  | 
														
													
														
															
																|  |  | // - variable phase is defined on [0, 1] rather than [0, 2pi] so pi/2 -> 0.25 |  |  |  | 
														
													
														
															
																|  |  | phase = (waveform == WAVE_SIN) ? 0.25f : 0.f; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // number of active polyphony engines (must be at least 1) | 
														
													
														
															
																|  |  |  |  |  | const int channels = std::max({inputs[TZFM_INPUT].getChannels(), inputs[VOCT_INPUT].getChannels(), inputs[TIMBRE_INPUT].getChannels(), 1}); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | float* osBuffer = oversampler.getOSBuffer(); |  |  |  | 
														
													
														
															
																|  |  | for (int i = 0; i < oversamplingRatio; ++i) { |  |  |  | 
														
													
														
															
																|  |  |  |  |  | for (int c = 0; c < channels; c += 4) { | 
														
													
														
															
																|  |  |  |  |  | const float_4 timbre = simd::clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f, 0.f, 1.f); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | phase += deltaBasePhase + deltaFMPhase; |  |  |  | 
														
													
														
															
																|  |  | if (phase > 1.f) { |  |  |  | 
														
													
														
															
																|  |  | phase -= floor(phase); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 tzfmVoltage = inputs[TZFM_INPUT].getPolyVoltageSimd<float_4>(c); | 
														
													
														
															
																|  |  |  |  |  | if (blockTZFMDC) { | 
														
													
														
															
																|  |  |  |  |  | blockTZFMDCFilter[c / 4].process(tzfmVoltage); | 
														
													
														
															
																|  |  |  |  |  | tzfmVoltage = blockTZFMDCFilter[c / 4].highpass(); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | else if (phase < 0.f) { |  |  |  | 
														
													
														
															
																|  |  | phase += -ceil(phase) + 1; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | const float_4 pitch = inputs[VOCT_INPUT].getPolyVoltageSimd<float_4>(c) + params[FREQ_PARAM].getValue() * range[rangeIndex]; | 
														
													
														
															
																|  |  |  |  |  | const float_4 freq = baseFreq * simd::pow(2.f, pitch); | 
														
													
														
															
																|  |  |  |  |  | const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, -0.5f, 0.5f); | 
														
													
														
															
																|  |  |  |  |  | // floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator | 
														
													
														
															
																|  |  |  |  |  | // becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't | 
														
													
														
															
																|  |  |  |  |  | // a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz. | 
														
													
														
															
																|  |  |  |  |  | const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | // 1 / denominator for the second-order FD | 
														
													
														
															
																|  |  |  |  |  | const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase); | 
														
													
														
															
																|  |  |  |  |  | // not clamped, but _total_ phase treated later with floor/ceil | 
														
													
														
															
																|  |  |  |  |  | const float_4 deltaFMPhase = freq * tzfmVoltage * args.sampleTime / oversamplingRatio; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float_4 pw = timbre; | 
														
													
														
															
																|  |  |  |  |  | if (limitPW) { | 
														
													
														
															
																|  |  |  |  |  | pw = clamp(pw, 0.05, 0.95); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  |  |  |  | // pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option | 
														
													
														
															
																|  |  |  |  |  | // for it to be added back in for hardware compatibility reasons | 
														
													
														
															
																|  |  |  |  |  | const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // sin is simple |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // hard sync | 
														
													
														
															
																|  |  |  |  |  | const float_4 syncMask = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c)); | 
														
													
														
															
																|  |  | if (waveform == WAVE_SIN) { |  |  | if (waveform == WAVE_SIN) { | 
														
													
														
															
																|  |  | osBuffer[i] = sin2pi_pade_05_5_4(phase); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // hardware waveform is actually cos, so pi/2 phase offset is required | 
														
													
														
															
																|  |  |  |  |  | // - variable phase is defined on [0, 1] rather than [0, 2pi] so pi/2 -> 0.25 | 
														
													
														
															
																|  |  |  |  |  | phase[c / 4] = simd::ifelse(syncMask, 0.25f, phase[c / 4]); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | else { |  |  | else { | 
														
													
														
															
																|  |  |  |  |  | phase[c / 4] = simd::ifelse(syncMask, 0.f, phase[c / 4]); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | phases[0] = phase - 2 * deltaBasePhase + (phase < 2 * deltaBasePhase); |  |  |  | 
														
													
														
															
																|  |  | phases[1] = phase - deltaBasePhase + (phase < deltaBasePhase); |  |  |  | 
														
													
														
															
																|  |  | phases[2] = phase; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4* osBuffer = oversampler[c / 4].getOSBuffer(); | 
														
													
														
															
																|  |  |  |  |  | for (int i = 0; i < oversamplingRatio; ++i) { | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | switch (waveform) { |  |  |  | 
														
													
														
															
																|  |  | case WAVE_TRI: { |  |  |  | 
														
													
														
															
																|  |  | osBuffer[i] = aliasSuppressedTri() * denominator; |  |  |  | 
														
													
														
															
																|  |  | break; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | case WAVE_SAW: { |  |  |  | 
														
													
														
															
																|  |  | osBuffer[i] = aliasSuppressedSaw() * denominator; |  |  |  | 
														
													
														
															
																|  |  | break; |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | case WAVE_PULSE: { |  |  |  | 
														
													
														
															
																|  |  | double saw = aliasSuppressedSaw(); |  |  |  | 
														
													
														
															
																|  |  | double sawOffset = aliasSuppressedOffsetSaw(pw); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | phase[c / 4] += deltaBasePhase + deltaFMPhase; | 
														
													
														
															
																|  |  |  |  |  | // ensure within [0, 1] | 
														
													
														
															
																|  |  |  |  |  | phase[c / 4] -= simd::floor(phase[c / 4]); | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | osBuffer[i] = (sawOffset - saw) * denominator; |  |  |  | 
														
													
														
															
																|  |  | osBuffer[i] += pulseDCOffset; |  |  |  | 
														
													
														
															
																|  |  | break; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // sin is simple | 
														
													
														
															
																|  |  |  |  |  | if (waveform == WAVE_SIN) { | 
														
													
														
															
																|  |  |  |  |  | osBuffer[i] = sin2pi_pade_05_5_4(phase[c / 4]); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | else { | 
														
													
														
															
																|  |  |  |  |  | float_4 phases[3]; // phase as extrapolated to the current and two previous samples | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | phases[0] = phase[c / 4] - 2 * deltaBasePhase + simd::ifelse(phase[c / 4] < 2 * deltaBasePhase, 1.f, 0.f); | 
														
													
														
															
																|  |  |  |  |  | phases[1] = phase[c / 4] - deltaBasePhase + simd::ifelse(phase[c / 4] < deltaBasePhase, 1.f, 0.f); | 
														
													
														
															
																|  |  |  |  |  | phases[2] = phase[c / 4]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | switch (waveform) { | 
														
													
														
															
																|  |  |  |  |  | case WAVE_TRI: { | 
														
													
														
															
																|  |  |  |  |  | const float_4 dpwOrder1 = 1.0 - 2.0 * simd::abs(2 * phase[c / 4] - 1.0); | 
														
													
														
															
																|  |  |  |  |  | const float_4 dpwOrder3 = aliasSuppressedTri(phases) * denominatorInv; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | case WAVE_SAW: { | 
														
													
														
															
																|  |  |  |  |  | const float_4 dpwOrder1 = 2 * phase[c / 4] - 1.0; | 
														
													
														
															
																|  |  |  |  |  | const float_4 dpwOrder3 = aliasSuppressedSaw(phases) * denominatorInv; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | case WAVE_PULSE: { | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, -1.0, +1.0); | 
														
													
														
															
																|  |  |  |  |  | dpwOrder1 -= simd::ifelse(removePulseDC, 2.f * (0.5f - pw), 0.f); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | float_4 saw = aliasSuppressedSaw(phases); | 
														
													
														
															
																|  |  |  |  |  | float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw); | 
														
													
														
															
																|  |  |  |  |  | float_4 dpwOrder3 = (sawOffset - saw) * denominatorInv + pulseDCOffset; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | osBuffer[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
														
													
														
															
																|  |  |  |  |  | break; | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | default: break; | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | default: break; |  |  |  | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | if (waveform != WAVE_PULSE) { |  |  |  | 
														
													
														
															
																|  |  | osBuffer[i] = wavefolder(osBuffer[i], (1 - 0.85 * timbre)); |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  | } |  |  |  | 
														
													
														
															
																|  |  |  |  |  | if (waveform != WAVE_PULSE) { | 
														
													
														
															
																|  |  |  |  |  | osBuffer[i] = wavefolder(osBuffer[i], (1 - 0.85 * timbre), c); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | } 	// end of oversampling loop | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // downsample (if required) |  |  |  | 
														
													
														
															
																|  |  | const float out = (oversamplingRatio > 1) ? oversampler.downsample() : osBuffer[0]; |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // downsample (if required) | 
														
													
														
															
																|  |  |  |  |  | const float_4 out = (oversamplingRatio > 1) ? oversampler[c / 4].downsample() : osBuffer[0]; | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | // end of chain VCA |  |  |  | 
														
													
														
															
																|  |  | const float gain = std::max(0.f, inputs[VCA_INPUT].getNormalVoltage(10.f) / 10.f); |  |  |  | 
														
													
														
															
																|  |  | outputs[OUT_OUTPUT].setVoltage(5.f * out * gain); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | // end of chain VCA | 
														
													
														
															
																|  |  |  |  |  | const float_4 gain = simd::clamp(inputs[VCA_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f, 0.f, 1.f); | 
														
													
														
															
																|  |  |  |  |  | outputs[OUT_OUTPUT].setVoltageSimd(5.f * out * gain, c); | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | } 	// end of channels loop | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | outputs[OUT_OUTPUT].setChannels(channels); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | double aliasSuppressedTri() { |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 aliasSuppressedTri(float_4* phases) { | 
														
													
														
															
																|  |  |  |  |  | float_4 triBuffer[3]; | 
														
													
														
															
																|  |  | for (int i = 0; i < 3; ++i) { |  |  | for (int i = 0; i < 3; ++i) { | 
														
													
														
															
																|  |  | double p = 2 * phases[i] - 1.0; 				// range -1.0 to +1.0 |  |  |  | 
														
													
														
															
																|  |  | double s = 0.5 - std::abs(p); 					// eq 30 |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 p = 2 * phases[i] - 1.0; 				// range -1.0 to +1.0 | 
														
													
														
															
																|  |  |  |  |  | float_4 s = 0.5 - simd::abs(p); 				// eq 30 | 
														
													
														
															
																|  |  | triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; 	// eq 29 |  |  | triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; 	// eq 29 | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]); |  |  | return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | double aliasSuppressedSaw() { |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 aliasSuppressedSaw(float_4* phases) { | 
														
													
														
															
																|  |  |  |  |  | float_4 sawBuffer[3]; | 
														
													
														
															
																|  |  | for (int i = 0; i < 3; ++i) { |  |  | for (int i = 0; i < 3; ++i) { | 
														
													
														
															
																|  |  | double p = 2 * phases[i] - 1.0; 		// range -1 to +1 |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 p = 2 * phases[i] - 1.0; 		// range -1 to +1 | 
														
													
														
															
																|  |  | sawBuffer[i] = (p * p * p - p) / 6.0;	// eq 11 |  |  | sawBuffer[i] = (p * p * p - p) / 6.0;	// eq 11 | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]); |  |  | return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | double aliasSuppressedOffsetSaw(double pw) { |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 aliasSuppressedOffsetSaw(float_4* phases, float_4 pw) { | 
														
													
														
															
																|  |  |  |  |  | float_4 sawOffsetBuff[3]; | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  | for (int i = 0; i < 3; ++i) { |  |  | for (int i = 0; i < 3; ++i) { | 
														
													
														
															
																|  |  | double p = 2 * phases[i] - 1.0; 	// range -1 to +1 |  |  |  | 
														
													
														
															
																|  |  | double pwp = p + 2 * pw;			// phase after pw (pw in [0, 1]) |  |  |  | 
														
													
														
															
																|  |  | pwp += (pwp > 1) * -2;     			// modulo on [-1, +1] |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 p = 2 * phases[i] - 1.0; 		// range -1 to +1 | 
														
													
														
															
																|  |  |  |  |  | float_4 pwp = p + 2 * pw;				// phase after pw (pw in [0, 1]) | 
														
													
														
															
																|  |  |  |  |  | pwp += simd::ifelse(pwp > 1, -2, 0);     			// modulo on [-1, +1] | 
														
													
														
															
																|  |  | sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0;	// eq 11 |  |  | sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0;	// eq 11 | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); |  |  | return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | float wavefolder(float x, float xt) { |  |  |  | 
														
													
														
															
																|  |  | return stage2.process(stage1.process(x, xt)); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | float_4 wavefolder(float_4 x, float_4 xt, int c) { | 
														
													
														
															
																|  |  |  |  |  | return stage2[c / 4].process(stage1[c / 4].process(x, xt)); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  | json_t* dataToJson() override { |  |  | json_t* dataToJson() override { | 
														
													
														
															
																|  |  | json_t* rootJ = json_object(); |  |  | json_t* rootJ = json_object(); | 
														
													
														
															
																|  |  | json_object_set_new(rootJ, "blockTZFMDC", json_boolean(blockTZFMDC)); |  |  | json_object_set_new(rootJ, "blockTZFMDC", json_boolean(blockTZFMDC)); | 
														
													
														
															
																|  |  | json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC)); |  |  | json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC)); | 
														
													
														
															
																|  |  | json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler.getOversamplingIndex())); |  |  |  | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "limitPW", json_boolean(limitPW)); | 
														
													
														
															
																|  |  |  |  |  | json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex())); | 
														
													
														
															
																|  |  | return rootJ; |  |  | return rootJ; | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
												
													
														
															
																|  | @@ -355,6 +341,11 @@ struct PonyVCO : Module { | 
														
													
														
															
																|  |  | removePulseDC = json_boolean_value(removePulseDCJ); |  |  | removePulseDC = json_boolean_value(removePulseDCJ); | 
														
													
														
															
																|  |  | } |  |  | } | 
														
													
														
															
																|  |  | 
 |  |  | 
 | 
														
													
														
															
																|  |  |  |  |  | json_t* limitPWJ = json_object_get(rootJ, "limitPW"); | 
														
													
														
															
																|  |  |  |  |  | if (limitPWJ) { | 
														
													
														
															
																|  |  |  |  |  | limitPW = json_boolean_value(limitPWJ); | 
														
													
														
															
																|  |  |  |  |  | } | 
														
													
														
															
																|  |  |  |  |  | 
 | 
														
													
														
															
																|  |  | json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex"); |  |  | json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex"); | 
														
													
														
															
																|  |  | if (oversamplingIndexJ) { |  |  | if (oversamplingIndexJ) { | 
														
													
														
															
																|  |  | oversamplingIndex = json_integer_value(oversamplingIndexJ); |  |  | oversamplingIndex = json_integer_value(oversamplingIndexJ); | 
														
													
												
													
														
															
																|  | 
 |