#include "plugin.hpp" using simd::float_4; struct EvenVCO : 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]; float_4 tri[4]; /** The value of the last sync input */ float sync = 0.0; /** The outputs */ /** Whether we are past the pulse width already */ bool halfPhase[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> triSquareMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> triMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> sineMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> doubleSawMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> sawMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> squareMinBlep[PORT_MAX_CHANNELS]; dsp::RCFilter triFilter; EvenVCO() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); configParam(OCTAVE_PARAM, -5.0, 4.0, 0.0, "Octave", "'", 0.5); configParam(TUNE_PARAM, -7.0, 7.0, 0.0, "Tune", " semitones"); configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width"); for (int i = 0; i < 4; i++) { phase[i] = 0.f; tri[i] = 0.f; } for (int c = 0; c < PORT_MAX_CHANNELS; c++) halfPhase[c] = false; } void process(const ProcessArgs& args) override { int channels_pitch1 = inputs[PITCH1_INPUT].getChannels(); int channels_pitch2 = inputs[PITCH2_INPUT].getChannels(); int channels = 1; channels = std::max(channels, channels_pitch1); channels = std::max(channels, channels_pitch2); float pitch_0 = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; // Compute frequency, pitch is 1V/oct float_4 pitch[4]; for (int c = 0; c < channels; c += 4) pitch[c / 4] = float_4(pitch_0); if (inputs[PITCH1_INPUT].isConnected()) { for (int c = 0; c < channels; c += 4) pitch[c / 4] += inputs[PITCH1_INPUT].getPolyVoltageSimd(c); } if (inputs[PITCH2_INPUT].isConnected()) { for (int c = 0; c < channels; c += 4) pitch[c / 4] += inputs[PITCH2_INPUT].getPolyVoltageSimd(c); } if (inputs[FM_INPUT].isConnected()) { for (int c = 0; c < channels; c += 4) pitch[c / 4] += inputs[FM_INPUT].getPolyVoltageSimd(c) / 4.f; } float_4 freq[4]; for (int c = 0; c < channels; c += 4) { freq[c / 4] = dsp::FREQ_C4 * simd::pow(2.f, pitch[c / 4]); freq[c / 4] = clamp(freq[c / 4], 0.f, 20000.f); } // Pulse width float pw_0 = params[PWM_PARAM].getValue(); float_4 pw[4]; for (int c = 0; c < channels; c += 4) pw[c / 4] = float_4(pw_0); if (inputs[PWM_INPUT].isConnected()) { for (int c = 0; c < channels; c += 4) pw[c / 4] += inputs[PWM_INPUT].getPolyVoltageSimd(c) / 5.f; } const float_4 minPw_4 = float_4(0.05f); const float_4 m_one_4 = float_4(-1.0f); const float_4 one_4 = float_4(1.0f); float_4 deltaPhase[4]; float_4 oldPhase[4]; for (int c = 0; c < channels; c += 4) { pw[c / 4] = rescale(clamp(pw[c / 4], m_one_4, one_4), m_one_4, one_4, minPw_4, one_4 - minPw_4); // Advance phase deltaPhase[c / 4] = clamp(freq[c / 4] * args.sampleTime, float_4(1e-6f), float_4(0.5f)); oldPhase[c / 4] = phase[c / 4]; phase[c / 4] += deltaPhase[c / 4]; } // the next block can't be done with SIMD instructions: for (int c = 0; c < channels; c++) { if (oldPhase[c / 4].s[c % 4] < 0.5 && phase[c / 4].s[c % 4] >= 0.5) { float crossing = -(phase[c / 4].s[c % 4] - 0.5) / deltaPhase[c / 4].s[c % 4]; triSquareMinBlep[c].insertDiscontinuity(crossing, 2.f); doubleSawMinBlep[c].insertDiscontinuity(crossing, -2.f); } if (!halfPhase[c] && phase[c / 4].s[c % 4] >= pw[c / 4].s[c % 4]) { float crossing = -(phase[c / 4].s[c % 4] - pw[c / 4].s[c % 4]) / deltaPhase[c / 4].s[c % 4]; squareMinBlep[c].insertDiscontinuity(crossing, 2.f); halfPhase[c] = true; } // Reset phase if at end of cycle if (phase[c / 4].s[c % 4] >= 1.f) { phase[c / 4].s[c % 4] -= 1.f; float crossing = -phase[c / 4].s[c % 4] / deltaPhase[c / 4].s[c % 4]; triSquareMinBlep[c].insertDiscontinuity(crossing, -2.f); doubleSawMinBlep[c].insertDiscontinuity(crossing, -2.f); squareMinBlep[c].insertDiscontinuity(crossing, -2.f); sawMinBlep[c].insertDiscontinuity(crossing, -2.f); halfPhase[c] = false; } } float_4 triSquareMinBlepOut[4]; float_4 doubleSawMinBlepOut[4]; float_4 sawMinBlepOut[4]; float_4 squareMinBlepOut[4]; float_4 triSquare[4]; float_4 sine[4]; float_4 doubleSaw[4]; float_4 even[4]; float_4 saw[4]; float_4 square[4]; float_4 triOut[4]; for (int c = 0; c < channels; c++) { triSquareMinBlepOut[c / 4].s[c % 4] = triSquareMinBlep[c].process(); doubleSawMinBlepOut[c / 4].s[c % 4] = doubleSawMinBlep[c].process(); sawMinBlepOut[c / 4].s[c % 4] = sawMinBlep[c].process(); squareMinBlepOut[c / 4].s[c % 4] = squareMinBlep[c].process(); } // 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); for (int c = 0; c < channels; c += 4) { triSquare[c / 4] = simd::ifelse((phase[c / 4] < 0.5f * one_4), m_one_4, one_4); triSquare[c / 4] += triSquareMinBlepOut[c / 4]; // Integrate square for triangle tri[c / 4] += (4.f * triSquare[c / 4]) * (freq[c / 4] * args.sampleTime); tri[c / 4] *= (1.f - 40.f * args.sampleTime); triOut[c / 4] = 5.f * tri[c / 4]; sine[c / 4] = 5.f * simd::cos(2 * M_PI * phase[c / 4]); doubleSaw[c / 4] = simd::ifelse((phase[c / 4] < 0.5), (-1.f + 4.f * phase[c / 4]), (-1.f + 4.f * (phase[c / 4] - 0.5f))); doubleSaw[c / 4] += doubleSawMinBlepOut[c / 4]; doubleSaw[c / 4] *= 5.f; even[c / 4] = 0.55 * (doubleSaw[c / 4] + 1.27 * sine[c / 4]); saw[c / 4] = -1.f + 2.f * phase[c / 4]; saw[c / 4] += sawMinBlepOut[c / 4]; saw[c / 4] *= 5.f; square[c / 4] = simd::ifelse((phase[c / 4] < pw[c / 4]), m_one_4, one_4) ; square[c / 4] += squareMinBlepOut[c / 4]; square[c / 4] *= 5.f; // Set outputs outputs[TRI_OUTPUT].setVoltageSimd(triOut[c / 4], c); outputs[SINE_OUTPUT].setVoltageSimd(sine[c / 4], c); outputs[EVEN_OUTPUT].setVoltageSimd(even[c / 4], c); outputs[SAW_OUTPUT].setVoltageSimd(saw[c / 4], c); outputs[SQUARE_OUTPUT].setVoltageSimd(square[c / 4], c); } } }; struct EvenVCOWidget : ModuleWidget { EvenVCOWidget(EvenVCO* module) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/EvenVCO.svg"))); addChild(createWidget(Vec(15, 0))); addChild(createWidget(Vec(15, 365))); addChild(createWidget(Vec(15 * 6, 0))); addChild(createWidget(Vec(15 * 6, 365))); addParam(createParam(Vec(22, 32), module, EvenVCO::OCTAVE_PARAM)); addParam(createParam(Vec(73, 131), module, EvenVCO::TUNE_PARAM)); addParam(createParam(Vec(16, 230), module, EvenVCO::PWM_PARAM)); addInput(createInput(Vec(8, 120), module, EvenVCO::PITCH1_INPUT)); addInput(createInput(Vec(19, 157), module, EvenVCO::PITCH2_INPUT)); addInput(createInput(Vec(48, 183), module, EvenVCO::FM_INPUT)); addInput(createInput(Vec(86, 189), module, EvenVCO::SYNC_INPUT)); addInput(createInput(Vec(72, 236), module, EvenVCO::PWM_INPUT)); addOutput(createOutput(Vec(10, 283), module, EvenVCO::TRI_OUTPUT)); addOutput(createOutput(Vec(87, 283), module, EvenVCO::SINE_OUTPUT)); addOutput(createOutput(Vec(48, 306), module, EvenVCO::EVEN_OUTPUT)); addOutput(createOutput(Vec(10, 327), module, EvenVCO::SAW_OUTPUT)); addOutput(createOutput(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT)); } }; Model* modelEvenVCO = createModel("EvenVCO");