#include "plugin.hpp" #define MAX(a,b) (a>b)?a:b 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 phase = 0.0; /** The value of the last sync input */ float sync = 0.0; /** The outputs */ float tri = 0.0; /** Whether we are past the pulse width already */ bool halfPhase = false; dsp::MinBlepGenerator<16, 32> triSquareMinBlep; dsp::MinBlepGenerator<16, 32> triMinBlep; dsp::MinBlepGenerator<16, 32> sineMinBlep; dsp::MinBlepGenerator<16, 32> doubleSawMinBlep; dsp::MinBlepGenerator<16, 32> sawMinBlep; dsp::MinBlepGenerator<16, 32> squareMinBlep; 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"); } void process(const ProcessArgs &args) override { // Compute frequency, pitch is 1V/oct int channels_pitch1 = inputs[PITCH1_INPUT].getChannels(); int channels_pitch2 = inputs[PITCH2_INPUT].getChannels(); int channels_fm = inputs[FM_INPUT].getChannels(); int channels = 1; channels = MAX(channels, channels_pitch1); channels = MAX(channels, channels_pitch2); // channels = MAX(channels, channels_fm); float pitch_0 = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; pitch += inputs[PITCH1_INPUT].getVoltage() + inputs[PITCH2_INPUT].getVoltage(); pitch += inputs[FM_INPUT].getVoltage() / 4.f; float freq = dsp::FREQ_C4 * std::pow(2.f, pitch); freq = clamp(freq, 0.f, 20000.f); // Pulse width float pw = params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getVoltage() / 5.f; const float minPw = 0.05; pw = rescale(clamp(pw, -1.f, 1.f), -1.f, 1.f, minPw, 1.f - minPw); // Advance phase float deltaPhase = clamp(freq * args.sampleTime, 1e-6f, 0.5f); float oldPhase = phase; phase += deltaPhase; if (oldPhase < 0.5 && phase >= 0.5) { float crossing = -(phase - 0.5) / deltaPhase; triSquareMinBlep.insertDiscontinuity(crossing, 2.f); doubleSawMinBlep.insertDiscontinuity(crossing, -2.f); } if (!halfPhase && phase >= pw) { float crossing = -(phase - pw) / deltaPhase; squareMinBlep.insertDiscontinuity(crossing, 2.f); halfPhase = true; } // Reset phase if at end of cycle if (phase >= 1.f) { phase -= 1.f; float crossing = -phase / deltaPhase; triSquareMinBlep.insertDiscontinuity(crossing, -2.f); doubleSawMinBlep.insertDiscontinuity(crossing, -2.f); squareMinBlep.insertDiscontinuity(crossing, -2.f); sawMinBlep.insertDiscontinuity(crossing, -2.f); halfPhase = false; } // Outputs float triSquare = (phase < 0.5) ? -1.f : 1.f; triSquare += triSquareMinBlep.process(); // Integrate square for triangle tri += 4.f * triSquare * freq * args.sampleTime; tri *= (1.f - 40.f * args.sampleTime); float sine = -std::cos(2*M_PI * phase); float doubleSaw = (phase < 0.5) ? (-1.f + 4.f*phase) : (-1.f + 4.f*(phase - 0.5)); doubleSaw += doubleSawMinBlep.process(); float even = 0.55 * (doubleSaw + 1.27 * sine); float saw = -1.f + 2.f*phase; saw += sawMinBlep.process(); float square = (phase < pw) ? -1.f : 1.f; square += squareMinBlep.process(); // Set outputs outputs[TRI_OUTPUT].setVoltage(5.f*tri); outputs[SINE_OUTPUT].setVoltage(5.f*sine); outputs[EVEN_OUTPUT].setVoltage(5.f*even); outputs[SAW_OUTPUT].setVoltage(5.f*saw); outputs[SQUARE_OUTPUT].setVoltage(5.f*square); } }; 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");