| 
				
				
				
				 | 
			
			 | 
			@@ -0,0 +1,331 @@ | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			#include "plugin.hpp" | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			#include "ChowDSP.hpp" | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			using simd::float_4; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			struct EvenVCO2 : Module { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				enum ParamIds { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					OCTAVE_PARAM, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					TUNE_PARAM, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					PWM_PARAM, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					NUM_PARAMS | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				enum InputIds { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					PITCH1_INPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					PITCH2_INPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					FM_INPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					SYNC_INPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					PWM_INPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					NUM_INPUTS | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				enum OutputIds { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					TRI_OUTPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					SINE_OUTPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					EVEN_OUTPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					SAW_OUTPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					SQUARE_OUTPUT, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					NUM_OUTPUTS | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				float_4 phase[4] = {}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				dsp::TSchmittTrigger<float_4> syncTrigger[4]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				bool removePulseDC = true; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				bool limitPW = true; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				EvenVCO2() { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configParam(OCTAVE_PARAM, -5.0, 4.0, 0.0, "Octave", "'", 0.5); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					getParamQuantity(OCTAVE_PARAM)->snapEnabled = true; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configParam(TUNE_PARAM, -7.0, 7.0, 0.0, "Tune", " semitones"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configInput(PITCH1_INPUT, "Pitch 1"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configInput(PITCH2_INPUT, "Pitch 2"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configInput(FM_INPUT, "FM"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configInput(SYNC_INPUT, "Sync"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configInput(PWM_INPUT, "Pulse Width Modulation"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configOutput(TRI_OUTPUT, "Triangle"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configOutput(SINE_OUTPUT, "Sine"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configOutput(EVEN_OUTPUT, "Even"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configOutput(SAW_OUTPUT, "Sawtooth"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					configOutput(SQUARE_OUTPUT, "Square"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					// calculate up/downsampling rates | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					onSampleRateChange(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				void onSampleRateChange() override { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					float sampleRate = APP->engine->getSampleRate(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int i = 0; i < NUM_OUTPUTS; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for (int c = 0; c < 4; c++) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							oversampler[i][c].setOversamplingIndex(oversamplingIndex); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							oversampler[i][c].reset(sampleRate); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					const float lowFreqRegime = oversampler[0][0].getOversamplingRatio() * 1e-3 * sampleRate; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					DEBUG("Low freq regime: %g", lowFreqRegime); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				float_4 aliasSuppressedTri(float_4* phases) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					float_4 triBuffer[3]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int i = 0; i < 3; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						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 | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				float_4 aliasSuppressedSaw(float_4* phases) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					float_4 sawBuffer[3]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int i = 0; i < 3; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4 p = 2 * phases[i] - 1.0; 		// range -1 to +1 | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						sawBuffer[i] = (p * p * p - p) / 6.0;	// eq 11 | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				float_4 aliasSuppressedDoubleSaw(float_4* phases) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					float_4 sawBuffer[3]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int i = 0; i < 3; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4 p = 4.0 * simd::ifelse(phases[i] < 0.5,  phases[i], phases[i] - 0.5) - 1.0; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						sawBuffer[i] = (p * p * p - p) / 24.0;	// eq 11 (modified for doubled freq) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				float_4 aliasSuppressedOffsetSaw(float_4* phases, float_4 pw) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					float_4 sawOffsetBuff[3]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int i = 0; i < 3; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						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 | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; 	// uses a 2*6=12th order Butterworth filter | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				int oversamplingIndex = 1; 	// default is 2^oversamplingIndex == x2 oversampling | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				void process(const ProcessArgs& args) override { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					// pitch inputs determine number of polyphony engines | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					const int channels = std::max({1, inputs[PITCH1_INPUT].getChannels(), inputs[PITCH2_INPUT].getChannels()}); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					const float pitchKnobs = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					const int oversamplingRatio = oversampler[0][0].getOversamplingRatio(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for (int c = 0; c < channels; c += 4) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4 pw = simd::clamp(params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getPolyVoltageSimd<float_4>(c) / 5.f, -1.f, 1.f); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (limitPW) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							pw = simd::rescale(pw, -1, +1, 0.05f, 0.95f); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						else { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							pw = simd::rescale(pw, -1.f, +1.f, 0.f, 1.f); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						const float_4 fmVoltage = inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * 0.25f; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 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); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						// 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); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						// hard sync | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						const float_4 syncMask = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						phase[c / 4] = simd::ifelse(syncMask, 0.5f, phase[c / 4]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4* osBufferSin = oversampler[SINE_OUTPUT][c / 4].getOSBuffer(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4* osBufferSquare = oversampler[SQUARE_OUTPUT][c / 4].getOSBuffer(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						float_4* osBufferEven = oversampler[EVEN_OUTPUT][c / 4].getOSBuffer(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for (int i = 0; i < oversamplingRatio; ++i) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							phase[c / 4] += deltaBasePhase; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							// ensure within [0, 1] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							phase[c / 4] -= simd::floor(phase[c / 4]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							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]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if (outputs[SINE_OUTPUT].isConnected() || outputs[EVEN_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								// sin doesn't need PDW | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								osBufferSin[i] = -simd::cos(2.0 * M_PI * phase[c / 4]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if (outputs[TRI_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								const float_4 dpwOrder1 = 1.0 - 2.0 * simd::abs(2 * phase[c / 4] - 1.0); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								const float_4 dpwOrder3 = aliasSuppressedTri(phases) * denominatorInv; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								osBufferTri[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if (outputs[SAW_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								const float_4 dpwOrder1 = 2 * phase[c / 4] - 1.0; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								const float_4 dpwOrder3 = aliasSuppressedSaw(phases) * denominatorInv; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								osBufferSaw[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if (outputs[SQUARE_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, -1.0, +1.0); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pw) : 0.f; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 saw = aliasSuppressedSaw(phases); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 dpwOrder3 = (saw - sawOffset) * denominatorInv + pulseDCOffset; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								osBufferSquare[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if (outputs[EVEN_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 dpwOrder1 = 4.0 * simd::ifelse(phase[c / 4] < 0.5, phase[c / 4], phase[c / 4] - 0.5) - 1.0; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 dpwOrder3 = aliasSuppressedDoubleSaw(phases) * denominatorInv; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								float_4 doubleSaw = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								osBufferEven[i] = 0.55 * (doubleSaw + 1.27 * osBufferSin[i]); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} 	// end of oversampling loop | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						// downsample (if required) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (outputs[SINE_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							const float_4 outSin = (oversamplingRatio > 1) ? oversampler[SINE_OUTPUT][c / 4].downsample() : osBufferSin[0]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							outputs[SINE_OUTPUT].setVoltageSimd(5.f * outSin, c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (outputs[TRI_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							const float_4 outTri = (oversamplingRatio > 1) ? oversampler[TRI_OUTPUT][c / 4].downsample() : osBufferTri[0]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							outputs[TRI_OUTPUT].setVoltageSimd(5.f * outTri, c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (outputs[SAW_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							const float_4 outSaw = (oversamplingRatio > 1) ? oversampler[SAW_OUTPUT][c / 4].downsample() : osBufferSaw[0]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							outputs[SAW_OUTPUT].setVoltageSimd(5.f * outSaw, c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (outputs[SQUARE_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							const float_4 outSquare = (oversamplingRatio > 1) ? oversampler[SQUARE_OUTPUT][c / 4].downsample() : osBufferSquare[0]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							outputs[SQUARE_OUTPUT].setVoltageSimd(5.f * outSquare, c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if (outputs[EVEN_OUTPUT].isConnected()) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							const float_4 outEven = (oversamplingRatio > 1) ? oversampler[EVEN_OUTPUT][c / 4].downsample() : osBufferEven[0]; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							outputs[EVEN_OUTPUT].setVoltageSimd(5.f * outEven, c); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} 	// end of channels loop | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					// Outputs | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					outputs[TRI_OUTPUT].setChannels(channels); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					outputs[SINE_OUTPUT].setChannels(channels); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					outputs[EVEN_OUTPUT].setChannels(channels); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					outputs[SAW_OUTPUT].setChannels(channels); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					outputs[SQUARE_OUTPUT].setChannels(channels); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				json_t* dataToJson() override { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_t* rootJ = json_object(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_object_set_new(rootJ, "limitPW", json_boolean(limitPW)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0][0].getOversamplingIndex())); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return rootJ; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				void dataFromJson(json_t* rootJ) override { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_t* pulseDCJ = json_object_get(rootJ, "removePulseDC"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if (pulseDCJ) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						removePulseDC = json_boolean_value(pulseDCJ); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_t* limitPWJ = json_object_get(rootJ, "limitPW"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if (limitPWJ) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						limitPW = json_boolean_value(limitPWJ); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex"); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if (oversamplingIndexJ) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						oversamplingIndex = json_integer_value(oversamplingIndexJ); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						onSampleRateChange(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			struct EvenVCO2Widget : ModuleWidget { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				EvenVCO2Widget(EvenVCO2* module) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					setModule(module); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/EvenVCObeta.svg"))); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addChild(createWidget<Knurlie>(Vec(15, 0))); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addChild(createWidget<Knurlie>(Vec(15, 365))); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addChild(createWidget<Knurlie>(Vec(15 * 6, 0))); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addChild(createWidget<Knurlie>(Vec(15 * 6, 365))); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addParam(createParam<BefacoBigKnob>(Vec(22, 32), module, EvenVCO2::OCTAVE_PARAM)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addParam(createParam<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO2::TUNE_PARAM)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addParam(createParam<Davies1900hRedKnob>(Vec(16, 230), module, EvenVCO2::PWM_PARAM)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addInput(createInput<BefacoInputPort>(Vec(8, 120), module, EvenVCO2::PITCH1_INPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addInput(createInput<BefacoInputPort>(Vec(19, 157), module, EvenVCO2::PITCH2_INPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addInput(createInput<BefacoInputPort>(Vec(48, 183), module, EvenVCO2::FM_INPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addInput(createInput<BefacoInputPort>(Vec(86, 189), module, EvenVCO2::SYNC_INPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addInput(createInput<BefacoInputPort>(Vec(72, 236), module, EvenVCO2::PWM_INPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addOutput(createOutput<BefacoOutputPort>(Vec(10, 283), module, EvenVCO2::TRI_OUTPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addOutput(createOutput<BefacoOutputPort>(Vec(87, 283), module, EvenVCO2::SINE_OUTPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addOutput(createOutput<BefacoOutputPort>(Vec(48, 306), module, EvenVCO2::EVEN_OUTPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addOutput(createOutput<BefacoOutputPort>(Vec(10, 327), module, EvenVCO2::SAW_OUTPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					addOutput(createOutput<BefacoOutputPort>(Vec(87, 327), module, EvenVCO2::SQUARE_OUTPUT)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				void appendContextMenu(Menu* menu) override { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					EvenVCO2* module = dynamic_cast<EvenVCO2*>(this->module); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					assert(module); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					menu->addChild(new MenuSeparator()); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					menu->addChild(createSubmenuItem("Hardware compatibility", "", | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					[ = ](Menu * menu) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						menu->addChild(createBoolPtrMenuItem("Remove DC from pulse", "", &module->removePulseDC)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW)); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					                                )); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					menu->addChild(createIndexSubmenuItem("Oversampling", | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					{"Off", "x2", "x4", "x8"}, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					[ = ]() { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						return module->oversamplingIndex; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					}, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					[ = ](int mode) { | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						module->oversamplingIndex = mode; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						module->onSampleRateChange(); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					                                     )); | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			}; | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			Model* modelEvenVCO2 = createModel<EvenVCO2, EvenVCO2Widget>("EvenVCO2"); |