diff --git a/plugin.json b/plugin.json index b982c0d..5620116 100644 --- a/plugin.json +++ b/plugin.json @@ -60,7 +60,8 @@ "name": "LFO-1", "description": "Low-frequency oscillator", "tags": [ - "LFO" + "LFO", + "Polyphonic" ] }, { @@ -68,7 +69,8 @@ "name": "LFO-2", "description": "Low-frequency oscillator", "tags": [ - "LFO" + "LFO", + "Polyphonic" ] }, { @@ -130,7 +132,8 @@ "name": "Scope", "description": "Inspect waveforms with an oscilloscope", "tags": [ - "Visual" + "Visual", + "Polyphonic" ] }, { diff --git a/src/LFO.cpp b/src/LFO.cpp index 19787dd..0ed3914 100644 --- a/src/LFO.cpp +++ b/src/LFO.cpp @@ -1,64 +1,69 @@ #include "plugin.hpp" +template struct LowFrequencyOscillator { - float phase = 0.f; - float pw = 0.5f; - float freq = 1.f; - bool offset = false; + T phase = 0.f; + T pw = 0.5f; + T freq = 1.f; bool invert = false; - dsp::SchmittTrigger resetTrigger; + bool bipolar = false; + T resetState = T::mask(); - LowFrequencyOscillator() {} - void setPitch(float pitch) { - pitch = std::fmin(pitch, 10.f); - freq = std::pow(2.f, pitch); + void setPitch(T pitch) { + pitch = simd::fmin(pitch, 10.f); + freq = simd::pow(2.f, pitch); } - void setPulseWidth(float pw_) { - const float pwMin = 0.01f; - pw = clamp(pw_, pwMin, 1.f - pwMin); + void setPulseWidth(T pw) { + const T pwMin = 0.01f; + this->pw = clamp(pw, pwMin, 1.f - pwMin); } - void setReset(float reset) { - if (resetTrigger.process(reset / 0.01f)) { - phase = 0.f; - } + void setReset(T reset) { + reset = simd::rescale(reset, 0.1f, 2.f, 0.f, 1.f); + T on = (reset >= 1.f); + T off = (reset <= 0.f); + T triggered = ~resetState & on; + resetState = simd::ifelse(off, 0.f, resetState); + resetState = simd::ifelse(on, T::mask(), resetState); + phase = simd::ifelse(triggered, 0.f, phase); } void step(float dt) { - float deltaPhase = std::fmin(freq * dt, 0.5f); + T deltaPhase = simd::fmin(freq * dt, 0.5f); phase += deltaPhase; - if (phase >= 1.f) - phase -= 1.f; - } - float sin() { - if (offset) - return 1.f - std::cos(2*M_PI * phase) * (invert ? -1.f : 1.f); - else - return std::sin(2*M_PI * phase) * (invert ? -1.f : 1.f); - } - float tri(float x) { - return 4.f * std::fabs(x - std::round(x)); + phase -= (phase >= 1.f) & 1.f; } - float tri() { - if (offset) - return tri(invert ? phase - 0.5f : phase); - else - return -1.f + tri(invert ? phase - 0.25f : phase - 0.75f); + T sin() { + T p = phase; + if (!bipolar) p -= 0.25f; + T v = simd::sin(2*M_PI * p); + if (invert) v *= -1.f; + if (!bipolar) v += 1.f; + return v; } - float saw(float x) { - return 2.f * (x - std::round(x)); + T tri() { + T p = phase; + if (bipolar) p += 0.25f; + T v = 4.f * simd::fabs(p - simd::round(p)) - 1.f; + if (invert) v *= -1.f; + if (!bipolar) v += 1.f; + return v; } - float saw() { - if (offset) - return invert ? 2.f * (1.f - phase) : 2.f * phase; - else - return saw(phase) * (invert ? -1.f : 1.f); + T saw() { + T p = phase; + if (!bipolar) p -= 0.5f; + T v = 2.f * (p - simd::round(p)); + if (invert) v *= -1.f; + if (!bipolar) v += 1.f; + return v; } - float sqr() { - float sqr = (phase < pw) ^ invert ? 1.f : -1.f; - return offset ? sqr + 1.f : sqr; + T sqr() { + T v = simd::ifelse(phase < pw, 1.f, -1.f); + if (invert) v *= -1.f; + if (!bipolar) v += 1.f; + return v; } - float light() { - return std::sin(2*M_PI * phase); + T light() { + return 1.f - 2.f * phase; } }; @@ -89,12 +94,12 @@ struct LFO : Module { NUM_OUTPUTS }; enum LightIds { - PHASE_POS_LIGHT, - PHASE_NEG_LIGHT, + ENUMS(PHASE_LIGHT, 3), NUM_LIGHTS }; - LowFrequencyOscillator oscillator; + LowFrequencyOscillator oscillators[4]; + dsp::ClockDivider lightDivider; LFO() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -105,25 +110,84 @@ struct LFO : Module { configParam(PW_PARAM, 0.f, 1.f, 0.5f); configParam(FM2_PARAM, 0.f, 1.f, 0.f); configParam(PWM_PARAM, 0.f, 1.f, 0.f); + lightDivider.setDivision(16); } void process(const ProcessArgs &args) override { - oscillator.setPitch(params[FREQ_PARAM].getValue() + params[FM1_PARAM].getValue() * inputs[FM1_INPUT].getVoltage() + params[FM2_PARAM].getValue() * inputs[FM2_INPUT].getVoltage()); - oscillator.setPulseWidth(params[PW_PARAM].getValue() + params[PWM_PARAM].getValue() * inputs[PW_INPUT].getVoltage() / 10.f); - oscillator.offset = (params[OFFSET_PARAM].getValue() > 0.f); - oscillator.invert = (params[INVERT_PARAM].getValue() <= 0.f); - oscillator.step(args.sampleTime); - oscillator.setReset(inputs[RESET_INPUT].getVoltage()); - - outputs[SIN_OUTPUT].setVoltage(5.f * oscillator.sin()); - outputs[TRI_OUTPUT].setVoltage(5.f * oscillator.tri()); - outputs[SAW_OUTPUT].setVoltage(5.f * oscillator.saw()); - outputs[SQR_OUTPUT].setVoltage(5.f * oscillator.sqr()); - - lights[PHASE_POS_LIGHT].setSmoothBrightness(oscillator.light(), args.sampleTime); - lights[PHASE_NEG_LIGHT].setSmoothBrightness(-oscillator.light(), args.sampleTime); - } + using simd::float_4; + + float freqParam = params[FREQ_PARAM].getValue(); + float fm1Param = params[FM1_PARAM].getValue(); + float fm2Param = params[FM2_PARAM].getValue(); + float pwParam = params[PW_PARAM].getValue(); + float pwmParam = params[PWM_PARAM].getValue(); + + int channels = std::max(inputs[FM1_INPUT].getChannels(), inputs[FM2_INPUT].getChannels()); + + for (int c = 0; c < channels; c += 4) { + auto *oscillator = &oscillators[c / 4]; + float_4 pitch = freqParam; + // FM1, polyphonic + pitch += float_4::load(inputs[FM1_INPUT].getVoltages(c)) * fm1Param; + // FM2, polyphonic or monophonic + if (inputs[FM2_INPUT].getChannels() == 1) + pitch += inputs[FM2_INPUT].getVoltage() * fm2Param; + else + pitch += float_4::load(inputs[FM2_INPUT].getVoltages(c)) * fm2Param; + oscillator->setPitch(pitch); + + // Pulse width + float_4 pw = pwParam; + if (inputs[PW_INPUT].getChannels() == 1) + pw += inputs[PW_INPUT].getVoltage() / 10.f * pwmParam; + else + pw += float_4::load(inputs[PW_INPUT].getVoltages(c)) / 10.f * pwmParam; + oscillator->setPulseWidth(pw); + + // Settings + oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f); + oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f); + oscillator->step(args.sampleTime); + oscillator->setReset(inputs[RESET_INPUT].getVoltage()); + + // Outputs + if (outputs[SIN_OUTPUT].isConnected()) { + outputs[SIN_OUTPUT].setChannels(channels); + float_4 v = 5.f * oscillator->sin(); + v.store(outputs[SIN_OUTPUT].getVoltages(c)); + } + if (outputs[TRI_OUTPUT].isConnected()) { + outputs[TRI_OUTPUT].setChannels(channels); + float_4 v = 5.f * oscillator->tri(); + v.store(outputs[TRI_OUTPUT].getVoltages(c)); + } + if (outputs[SAW_OUTPUT].isConnected()) { + outputs[SAW_OUTPUT].setChannels(channels); + float_4 v = 5.f * oscillator->saw(); + v.store(outputs[SAW_OUTPUT].getVoltages(c)); + } + if (outputs[SQR_OUTPUT].isConnected()) { + outputs[SQR_OUTPUT].setChannels(channels); + float_4 v = 5.f * oscillator->sqr(); + v.store(outputs[SQR_OUTPUT].getVoltages(c)); + } + } + // Light + if (lightDivider.process()) { + if (channels == 1) { + float lightValue = oscillators[0].light().f[0]; + lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); + lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); + lights[PHASE_LIGHT + 2].setBrightness(0.f); + } + else { + lights[PHASE_LIGHT + 0].setBrightness(0.f); + lights[PHASE_LIGHT + 1].setBrightness(0.f); + lights[PHASE_LIGHT + 2].setBrightness(1.f); + } + } + } }; @@ -157,7 +221,7 @@ struct LFOWidget : ModuleWidget { addOutput(createOutput(Vec(80, 320), module, LFO::SAW_OUTPUT)); addOutput(createOutput(Vec(114, 320), module, LFO::SQR_OUTPUT)); - addChild(createLight>(Vec(99, 42.5f), module, LFO::PHASE_POS_LIGHT)); + addChild(createLight>(Vec(99, 42.5f), module, LFO::PHASE_LIGHT)); } }; @@ -185,12 +249,12 @@ struct LFO2 : Module { NUM_OUTPUTS }; enum LightIds { - PHASE_POS_LIGHT, - PHASE_NEG_LIGHT, + ENUMS(PHASE_LIGHT, 3), NUM_LIGHTS }; - LowFrequencyOscillator oscillator; + LowFrequencyOscillator oscillators[4]; + dsp::ClockDivider lightDivider; LFO2() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -199,29 +263,67 @@ struct LFO2 : Module { configParam(FREQ_PARAM, -8.f, 10.f, 1.f); configParam(WAVE_PARAM, 0.f, 3.f, 1.5f); configParam(FM_PARAM, 0.f, 1.f, 0.5f); + lightDivider.setDivision(16); } void process(const ProcessArgs &args) override { - float deltaTime = args.sampleTime; - oscillator.setPitch(params[FREQ_PARAM].getValue() + params[FM_PARAM].getValue() * inputs[FM_INPUT].getVoltage()); - oscillator.offset = (params[OFFSET_PARAM].getValue() > 0.f); - oscillator.invert = (params[INVERT_PARAM].getValue() <= 0.f); - oscillator.step(deltaTime); - oscillator.setReset(inputs[RESET_INPUT].getVoltage()); - - float wave = params[WAVE_PARAM].getValue() + inputs[WAVE_INPUT].getVoltage(); - wave = clamp(wave, 0.f, 3.f); - float interp; - if (wave < 1.f) - interp = crossfade(oscillator.sin(), oscillator.tri(), wave); - else if (wave < 2.f) - interp = crossfade(oscillator.tri(), oscillator.saw(), wave - 1.f); - else - interp = crossfade(oscillator.saw(), oscillator.sqr(), wave - 2.f); - outputs[INTERP_OUTPUT].setVoltage(5.f * interp); - - lights[PHASE_POS_LIGHT].setSmoothBrightness(oscillator.light(), deltaTime); - lights[PHASE_NEG_LIGHT].setSmoothBrightness(-oscillator.light(), deltaTime); + using simd::float_4; + + float freqParam = params[FREQ_PARAM].getValue(); + float waveParam = params[WAVE_PARAM].getValue(); + float fmParam = params[FM_PARAM].getValue(); + + int channels = inputs[FM_INPUT].getChannels(); + + for (int c = 0; c < channels; c += 4) { + auto *oscillator = &oscillators[c / 4]; + float_4 pitch = freqParam; + // FM, polyphonic + pitch += float_4::load(inputs[FM_INPUT].getVoltages(c)) * fmParam; + oscillator->setPitch(pitch); + + // Wave + float_4 wave = waveParam; + inputs[WAVE_INPUT].getVoltage(); + if (inputs[WAVE_INPUT].getChannels() == 1) + wave += inputs[WAVE_INPUT].getVoltage() / 10.f * 3.f; + else + wave += float_4::load(inputs[WAVE_INPUT].getVoltages(c)) / 10.f * 3.f; + wave = clamp(wave, 0.f, 3.f); + + // Settings + oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f); + oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f); + oscillator->step(args.sampleTime); + oscillator->setReset(inputs[RESET_INPUT].getVoltage()); + + // Outputs + if (outputs[INTERP_OUTPUT].isConnected()) { + outputs[INTERP_OUTPUT].setChannels(channels); + float_4 v = 0.f; + v += oscillator->sin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 0.f)); + v += oscillator->tri() * simd::fmax(0.f, 1.f - simd::fabs(wave - 1.f)); + v += oscillator->saw() * simd::fmax(0.f, 1.f - simd::fabs(wave - 2.f)); + v += oscillator->sqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f)); + v *= 5.f; + v.store(outputs[INTERP_OUTPUT].getVoltages(c)); + } + } + + // Light + if (lightDivider.process()) { + if (channels == 1) { + float lightValue = oscillators[0].light().f[0]; + lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); + lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); + lights[PHASE_LIGHT + 2].setBrightness(0.f); + } + else { + lights[PHASE_LIGHT + 0].setBrightness(0.f); + lights[PHASE_LIGHT + 1].setBrightness(0.f); + lights[PHASE_LIGHT + 2].setBrightness(1.f); + } + } } }; @@ -249,7 +351,7 @@ struct LFO2Widget : ModuleWidget { addOutput(createOutput(Vec(54, 319), module, LFO2::INTERP_OUTPUT)); - addChild(createLight>(Vec(68, 42.5f), module, LFO2::PHASE_POS_LIGHT)); + addChild(createLight>(Vec(68, 42.5f), module, LFO2::PHASE_LIGHT)); } };