| @@ -1,6 +1,23 @@ | |||||
| #include "plugin.hpp" | #include "plugin.hpp" | ||||
| struct SlewFilter { | |||||
| float value = 0.f; | |||||
| float process(float in, float slew) { | |||||
| value += math::clamp(in - value, -slew, slew); | |||||
| return value; | |||||
| } | |||||
| float jump(float in) { | |||||
| value = in; | |||||
| return value; | |||||
| } | |||||
| float getValue() { | |||||
| return value; | |||||
| } | |||||
| }; | |||||
| struct Process : Module { | struct Process : Module { | ||||
| enum ParamId { | enum ParamId { | ||||
| SLEW_PARAM, | SLEW_PARAM, | ||||
| @@ -27,16 +44,29 @@ struct Process : Module { | |||||
| LIGHTS_LEN | LIGHTS_LEN | ||||
| }; | }; | ||||
| bool state[16] = {}; | |||||
| float sample1[16] = {}; | |||||
| float sample2[16] = {}; | |||||
| float holdValue[16] = {}; | |||||
| float slewValue[16] = {}; | |||||
| float glideValue[16] = {}; | |||||
| struct Engine { | |||||
| bool state = false; | |||||
| // For glide to turn on after 1ms | |||||
| float onTime = 0.f; | |||||
| float sample1 = 0.f; | |||||
| float sample2 = 0.f; | |||||
| SlewFilter sample1Filter; | |||||
| SlewFilter sample2Filter; | |||||
| float holdValue = 0.f; | |||||
| SlewFilter slewFilter; | |||||
| SlewFilter glideFilter; | |||||
| }; | |||||
| Engine engines[16]; | |||||
| Process() { | Process() { | ||||
| config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | ||||
| configParam(SLEW_PARAM, std::log2(1e-3f), std::log2(10.f), std::log2(1e-3f), "Slew", " ms/V", 2, 1000); | |||||
| struct SlewQuantity : ParamQuantity { | |||||
| float getDisplayValue() override { | |||||
| return (getSmoothValue() <= getMinValue()) ? 0.f : ParamQuantity::getDisplayValue(); | |||||
| } | |||||
| }; | |||||
| configParam<SlewQuantity>(SLEW_PARAM, std::log2(1e-3f), std::log2(10.f), std::log2(1e-3f), "Slew", " ms/V", 2, 1000); | |||||
| configButton(GATE_PARAM, "Gate"); | configButton(GATE_PARAM, "Gate"); | ||||
| configInput(SLEW_INPUT, "Slew"); | configInput(SLEW_INPUT, "Slew"); | ||||
| configInput(IN_INPUT, "Voltage"); | configInput(IN_INPUT, "Voltage"); | ||||
| @@ -52,55 +82,75 @@ struct Process : Module { | |||||
| void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
| int channels = inputs[IN_INPUT].getChannels(); | int channels = inputs[IN_INPUT].getChannels(); | ||||
| bool gateButton = params[GATE_PARAM].getValue() > 0.f; | bool gateButton = params[GATE_PARAM].getValue() > 0.f; | ||||
| float slewParam = params[SLEW_PARAM].getValue(); | |||||
| // Hard-left param means infinite slew | |||||
| if (slewParam <= std::log2(1e-3f)) | |||||
| slewParam = -INFINITY; | |||||
| for (int c = 0; c < channels; c++) { | for (int c = 0; c < channels; c++) { | ||||
| float in = inputs[IN_INPUT].getVoltage(c); | |||||
| float slewPitch = -params[SLEW_PARAM].getValue() - inputs[SLEW_INPUT].getPolyVoltage(c); | |||||
| // V/s | |||||
| float slew = dsp::approxExp2_taylor5(slewPitch + 30.f) / 1073741824; | |||||
| Engine& e = engines[c]; | |||||
| float in = inputs[IN_INPUT].getVoltage(c); | |||||
| float gateValue = inputs[GATE_INPUT].getPolyVoltage(c); | float gateValue = inputs[GATE_INPUT].getPolyVoltage(c); | ||||
| if (!state[c]) { | |||||
| // Slew rate in V/s | |||||
| float slew = INFINITY; | |||||
| if (std::isfinite(slewParam)) { | |||||
| float slewPitch = slewParam + inputs[SLEW_INPUT].getPolyVoltage(c); | |||||
| slew = dsp::approxExp2_taylor5(-slewPitch + 30.f) / std::exp2(30.f); | |||||
| } | |||||
| float slewDelta = slew * args.sampleTime; | |||||
| // Gate trigger/untrigger | |||||
| if (!e.state) { | |||||
| if (gateValue >= 2.f || gateButton) { | if (gateValue >= 2.f || gateButton) { | ||||
| // Triggered | // Triggered | ||||
| state[c] = true; | |||||
| e.state = true; | |||||
| e.onTime = 0.f; | |||||
| // Hold and track | // Hold and track | ||||
| holdValue[c] = in; | |||||
| e.holdValue = in; | |||||
| // Sample and hold | // Sample and hold | ||||
| sample2[c] = sample1[c]; | |||||
| sample1[c] = in; | |||||
| // Glide | |||||
| // TODO delay timer | |||||
| glideValue[c] = in; | |||||
| e.sample2 = e.sample1; | |||||
| e.sample1 = in; | |||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| if (gateValue <= 0.1f && !gateButton) { | if (gateValue <= 0.1f && !gateButton) { | ||||
| // Untriggered | // Untriggered | ||||
| state[c] = false; | |||||
| e.state = false; | |||||
| // Track and hold | // Track and hold | ||||
| holdValue[c] = in; | |||||
| e.holdValue = in; | |||||
| } | } | ||||
| } | } | ||||
| // Slew each value | |||||
| float slewDelta = slew * args.sampleTime; | |||||
| if (state[c]) { | |||||
| slewValue[c] = in; | |||||
| // Track & hold | |||||
| float tr = e.state ? e.holdValue : in; | |||||
| float ht = e.state ? in : e.holdValue; | |||||
| // Slew | |||||
| if (e.state) { | |||||
| e.slewFilter.jump(in); | |||||
| e.onTime += args.sampleTime; | |||||
| } | } | ||||
| else { | else { | ||||
| slewValue[c] += clamp(in - slewValue[c], -slewDelta, slewDelta); | |||||
| e.slewFilter.process(in, slewDelta); | |||||
| } | } | ||||
| glideValue[c] += clamp(in - glideValue[c], -slewDelta, slewDelta); | |||||
| outputs[SH1_OUTPUT].setVoltage(sample1[c], c); | |||||
| outputs[SH2_OUTPUT].setVoltage(sample2[c], c); | |||||
| outputs[TH_OUTPUT].setVoltage(state[c] ? holdValue[c] : in, c); | |||||
| outputs[HT_OUTPUT].setVoltage(state[c] ? in : holdValue[c], c); | |||||
| outputs[SLEW_OUTPUT].setVoltage(slewValue[c], c); | |||||
| outputs[GLIDE_OUTPUT].setVoltage(glideValue[c], c); | |||||
| // Glide | |||||
| // Wait 1ms before considering gate as legato | |||||
| if (e.state && e.onTime > 1e-3f) { | |||||
| e.glideFilter.process(in, slewDelta); | |||||
| } | |||||
| else { | |||||
| e.glideFilter.jump(in); | |||||
| } | |||||
| outputs[SH1_OUTPUT].setVoltage(e.sample1Filter.process(e.sample1, slewDelta), c); | |||||
| outputs[SH2_OUTPUT].setVoltage(e.sample2Filter.process(e.sample2, slewDelta), c); | |||||
| outputs[TH_OUTPUT].setVoltage(tr, c); | |||||
| outputs[HT_OUTPUT].setVoltage(ht, c); | |||||
| outputs[SLEW_OUTPUT].setVoltage(e.slewFilter.getValue(), c); | |||||
| outputs[GLIDE_OUTPUT].setVoltage(e.glideFilter.getValue(), c); | |||||
| } | } | ||||
| outputs[SH1_OUTPUT].setChannels(channels); | outputs[SH1_OUTPUT].setChannels(channels); | ||||