From 9c050c8c9f75c8ab730f51793d957d5f30fd146f Mon Sep 17 00:00:00 2001 From: hemmer <915048+hemmer@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:06:44 +0100 Subject: [PATCH] Bandit initial commit Fix bypass polyphony --- plugin.json | 14 + res/components/VCVBezelBig.svg | 150 +++ res/panels/Bandit.svg | 1739 ++++++++++++++++++++++++++++++++ src/Bandit.cpp | 208 ++++ src/Bypass.cpp | 117 ++- src/plugin.cpp | 1 + src/plugin.hpp | 1 + 7 files changed, 2205 insertions(+), 25 deletions(-) create mode 100644 res/components/VCVBezelBig.svg create mode 100644 res/panels/Bandit.svg create mode 100644 src/Bandit.cpp diff --git a/plugin.json b/plugin.json index e123313..7fafb0b 100644 --- a/plugin.json +++ b/plugin.json @@ -341,6 +341,20 @@ "tags": [ "Hardware clone", "Mixer", + "Polyphonic", + "Utility" + ] + }, + { + "slug": "Bandit", + "name": "Bandit", + "description": "A spectral processing playground.", + "tags": [ + "Equalizer", + "Filter", + "Hardware clone", + "Mixer", + "Polyphonic", "Utility" ] } diff --git a/res/components/VCVBezelBig.svg b/res/components/VCVBezelBig.svg new file mode 100644 index 0000000..e74ade8 --- /dev/null +++ b/res/components/VCVBezelBig.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/panels/Bandit.svg b/res/panels/Bandit.svg new file mode 100644 index 0000000..c93ed8c --- /dev/null +++ b/res/panels/Bandit.svg @@ -0,0 +1,1739 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Bandit.cpp b/src/Bandit.cpp new file mode 100644 index 0000000..4b4f5b9 --- /dev/null +++ b/src/Bandit.cpp @@ -0,0 +1,208 @@ +#include "plugin.hpp" + +using namespace simd; + +struct Bandit : Module { + enum ParamId { + LOW_GAIN_PARAM, + LOW_MID_GAIN_PARAM, + HIGH_MID_GAIN_PARAM, + HIGH_GAIN_PARAM, + PARAMS_LEN + }; + enum InputId { + LOW_INPUT, + LOW_MID_INPUT, + HIGH_MID_INPUT, + HIGH_INPUT, + LOW_RETURN_INPUT, + LOW_MID_RETURN_INPUT, + HIGH_MID_RETURN_INPUT, + HIGH_RETURN_INPUT, + LOW_CV_INPUT, + LOW_MID_CV_INPUT, + HIGH_MID_CV_INPUT, + HIGH_CV_INPUT, + ALL_INPUT, + ALL_CV_INPUT, + INPUTS_LEN + }; + enum OutputId { + LOW_OUTPUT, + LOW_MID_OUTPUT, + HIGH_MID_OUTPUT, + HIGH_OUTPUT, + MIX_OUTPUT, + OUTPUTS_LEN + }; + enum LightId { + ENUMS(MIX_CLIP_LIGHT, 3), + ENUMS(MIX_LIGHT, 3), + LIGHTS_LEN + }; + + dsp::TBiquadFilter filterLow[4], filterLowMid[4], filterHighMid[4], filterHigh[4]; + + Bandit() { + config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); + configParam(LOW_GAIN_PARAM, -1.f, 1.f, 0.f, "Low gain"); + configParam(LOW_MID_GAIN_PARAM, -1.f, 1.f, 0.f, "Low mid gain"); + configParam(HIGH_MID_GAIN_PARAM, -1.f, 1.f, 0.f, "High mid gain"); + configParam(HIGH_GAIN_PARAM, -1.f, 1.f, 0.f, "High gain"); + + configInput(LOW_INPUT, "Low"); + configInput(LOW_MID_INPUT, "Low mid"); + configInput(HIGH_MID_INPUT, "High mid"); + configInput(HIGH_INPUT, "High"); + configInput(LOW_RETURN_INPUT, "Low return"); + configInput(LOW_MID_RETURN_INPUT, "Low mid return"); + configInput(HIGH_MID_RETURN_INPUT, "High mid return"); + configInput(HIGH_RETURN_INPUT, "High return"); + configInput(LOW_CV_INPUT, "Low CV"); + configInput(LOW_MID_CV_INPUT, "Low mid CV"); + configInput(HIGH_MID_CV_INPUT, "High mid CV"); + configInput(HIGH_CV_INPUT, "High CV"); + configInput(ALL_INPUT, "All"); + configInput(ALL_CV_INPUT, "All CV"); + + configOutput(LOW_OUTPUT, "Low"); + configOutput(LOW_MID_OUTPUT, "Low mid"); + configOutput(HIGH_MID_OUTPUT, "High mid"); + configOutput(HIGH_OUTPUT, "High"); + configOutput(MIX_OUTPUT, "Mix"); + + onSampleRateChange(); + } + + void onSampleRateChange() override { + const float sr = APP->engine->getSampleRate(); + const float lowFc = 300.f / sr; + const float lowMidFc = 750.f / sr; + const float highMidFc = 1500.f / sr; + const float highFc = 5000.f / sr; + const float Q = 1.f, V = 1.f; + + for (int i = 0; i < 4; ++i) { + filterLow[i].setParameters(dsp::TBiquadFilter::Type::LOWPASS, lowFc, Q, V); + filterLowMid[i].setParameters(dsp::TBiquadFilter::Type::BANDPASS, lowMidFc, Q, V); + filterHighMid[i].setParameters(dsp::TBiquadFilter::Type::BANDPASS, highMidFc, Q, V); + filterHigh[i].setParameters(dsp::TBiquadFilter::Type::HIGHPASS, highFc, Q, V); + } + } + + void processBypass(const ProcessArgs& args) override { + const int maxPolyphony = std::max({1, inputs[ALL_INPUT].getChannels(), inputs[LOW_INPUT].getChannels(), + inputs[LOW_MID_INPUT].getChannels(), inputs[HIGH_MID_INPUT].getChannels(), + inputs[HIGH_INPUT].getChannels()}); + + + for (int c = 0; c < maxPolyphony; c += 4) { + const float_4 inLow = inputs[LOW_INPUT].getPolyVoltageSimd(c); + const float_4 inLowMid = inputs[LOW_MID_INPUT].getPolyVoltageSimd(c); + const float_4 inHighMid = inputs[HIGH_MID_INPUT].getPolyVoltageSimd(c); + const float_4 inHigh = inputs[HIGH_INPUT].getPolyVoltageSimd(c); + const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd(c); + + // bypass sums all inputs to the output + outputs[MIX_OUTPUT].setVoltageSimd(inLow + inLowMid + inHighMid + inHigh + inAll, c); + } + + outputs[MIX_OUTPUT].setChannels(maxPolyphony); + } + + + void process(const ProcessArgs& args) override { + + const int maxPolyphony = std::max({1, inputs[ALL_INPUT].getChannels(), inputs[LOW_INPUT].getChannels(), + inputs[LOW_MID_INPUT].getChannels(), inputs[HIGH_MID_INPUT].getChannels(), + inputs[HIGH_INPUT].getChannels()}); + + + for (int c = 0; c < maxPolyphony; c += 4) { + + const float_4 inLow = inputs[LOW_INPUT].getPolyVoltageSimd(c); + const float_4 inLowMid = inputs[LOW_MID_INPUT].getPolyVoltageSimd(c); + const float_4 inHighMid = inputs[HIGH_MID_INPUT].getPolyVoltageSimd(c); + const float_4 inHigh = inputs[HIGH_INPUT].getPolyVoltageSimd(c); + const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd(c); + + const float_4 lowGain = params[LOW_GAIN_PARAM].getValue() * inputs[LOW_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; + const float_4 outLow = filterLow[c / 4].process((inLow + inAll) * lowGain); + outputs[LOW_OUTPUT].setVoltageSimd(outLow, c); + + const float_4 lowMidGain = params[LOW_MID_GAIN_PARAM].getValue() * inputs[LOW_MID_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; + const float_4 outLowMid = filterLowMid[c / 4].process((inLowMid + inAll) * lowMidGain); + outputs[LOW_MID_OUTPUT].setVoltageSimd(outLowMid, c); + + const float_4 highMidGain = params[HIGH_MID_GAIN_PARAM].getValue() * inputs[HIGH_MID_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; + const float_4 outHighMid = filterHighMid[c / 4].process((inHighMid + inAll) * highMidGain); + outputs[HIGH_MID_OUTPUT].setVoltageSimd(outHighMid, c); + + const float_4 highGain = params[HIGH_GAIN_PARAM].getValue() * inputs[HIGH_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; + const float_4 outHigh = filterHigh[c / 4].process((inHigh + inAll) * highGain); + outputs[HIGH_OUTPUT].setVoltageSimd(outHigh, c); + + const float_4 fxReturnSum = inputs[LOW_RETURN_INPUT].getPolyVoltageSimd(c) + + inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd(c) + + inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd(c) + + inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd(c); + + outputs[MIX_OUTPUT].setVoltageSimd(fxReturnSum, c); + } + + outputs[LOW_OUTPUT].setChannels(maxPolyphony); + + if (maxPolyphony == 1) { + lights[MIX_LIGHT + 0].setBrightness(0.f); + lights[MIX_LIGHT + 1].setBrightnessSmooth(outputs[MIX_OUTPUT].getVoltageRMS(), args.sampleTime); + lights[MIX_LIGHT + 2].setBrightness(0.f); + } + else { + lights[MIX_LIGHT + 0].setBrightness(0.f); + lights[MIX_LIGHT + 1].setBrightness(0.f); + lights[MIX_LIGHT + 2].setBrightnessSmooth(outputs[MIX_OUTPUT].getVoltageRMS(), args.sampleTime); + } + } +}; + + +struct BanditWidget : ModuleWidget { + BanditWidget(Bandit* module) { + setModule(module); + setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/Bandit.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(createParam(mm2px(Vec(3.062, 51.365)), module, Bandit::LOW_GAIN_PARAM)); + addParam(createParam(mm2px(Vec(13.23, 51.365)), module, Bandit::LOW_MID_GAIN_PARAM)); + addParam(createParam(mm2px(Vec(23.398, 51.365)), module, Bandit::HIGH_MID_GAIN_PARAM)); + addParam(createParam(mm2px(Vec(33.566, 51.365)), module, Bandit::HIGH_GAIN_PARAM)); + + addInput(createInputCentered(mm2px(Vec(5.038, 14.5)), module, Bandit::LOW_INPUT)); + addInput(createInputCentered(mm2px(Vec(15.178, 14.5)), module, Bandit::LOW_MID_INPUT)); + addInput(createInputCentered(mm2px(Vec(25.253, 14.5)), module, Bandit::HIGH_MID_INPUT)); + addInput(createInputCentered(mm2px(Vec(35.328, 14.5)), module, Bandit::HIGH_INPUT)); + addInput(createInputCentered(mm2px(Vec(5.045, 40.34)), module, Bandit::LOW_RETURN_INPUT)); + addInput(createInputCentered(mm2px(Vec(15.118, 40.34)), module, Bandit::LOW_MID_RETURN_INPUT)); + addInput(createInputCentered(mm2px(Vec(25.19, 40.338)), module, Bandit::HIGH_MID_RETURN_INPUT)); + addInput(createInputCentered(mm2px(Vec(35.263, 40.34)), module, Bandit::HIGH_RETURN_INPUT)); + addInput(createInputCentered(mm2px(Vec(5.038, 101.229)), module, Bandit::LOW_CV_INPUT)); + addInput(createInputCentered(mm2px(Vec(15.113, 101.229)), module, Bandit::LOW_MID_CV_INPUT)); + addInput(createInputCentered(mm2px(Vec(25.187, 101.231)), module, Bandit::HIGH_MID_CV_INPUT)); + addInput(createInputCentered(mm2px(Vec(35.263, 101.229)), module, Bandit::HIGH_CV_INPUT)); + addInput(createInputCentered(mm2px(Vec(10.075, 113.502)), module, Bandit::ALL_INPUT)); + addInput(createInputCentered(mm2px(Vec(20.15, 113.5)), module, Bandit::ALL_CV_INPUT)); + + addOutput(createOutputCentered(mm2px(Vec(5.045, 27.248)), module, Bandit::LOW_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(15.118, 27.256)), module, Bandit::LOW_MID_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(25.19, 27.256)), module, Bandit::HIGH_MID_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(35.263, 27.256)), module, Bandit::HIGH_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(30.225, 113.5)), module, Bandit::MIX_OUTPUT)); + + addChild(createLightCentered>(mm2px(Vec(37.781, 111.125)), module, Bandit::MIX_CLIP_LIGHT)); + addChild(createLightCentered>(mm2px(Vec(37.781, 115.875)), module, Bandit::MIX_LIGHT)); + } +}; + +Model* modelBandit = createModel("Bandit"); \ No newline at end of file diff --git a/src/Bypass.cpp b/src/Bypass.cpp index 542cb50..10a4b2d 100644 --- a/src/Bypass.cpp +++ b/src/Bypass.cpp @@ -8,6 +8,7 @@ struct Bypass : Module { FX_GAIN_PARAM, LAUNCH_MODE_PARAM, LAUNCH_BUTTON_PARAM, + SLEW_TIME_PARAM, PARAMS_LEN }; enum InputId { @@ -37,13 +38,13 @@ struct Bypass : Module { HARD_MODE, SOFT_MODE }; - LatchMode latchMode = LatchMode::MOMENTARY_MODE; ReturnMode returnMode = ReturnMode::HARD_MODE; - ParamQuantity* launchParam; + ParamQuantity* launchParam, * slewTimeParam; dsp::SchmittTrigger launchCvTrigger; dsp::BooleanTrigger launchButtonTrigger; dsp::BooleanTrigger latchTrigger; dsp::SlewLimiter clickFilter; + bool launchButtonHeld = false; Bypass() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); @@ -52,6 +53,9 @@ struct Bypass : Module { configSwitch(LAUNCH_MODE_PARAM, 0.f, 1.f, 0.f, "Launch Mode", {"Latch (Toggle)", "Gate (Momentary)"}); launchParam = configButton(LAUNCH_BUTTON_PARAM, "Launch"); + slewTimeParam = configParam(SLEW_TIME_PARAM, .005f, 0.05f, 0.01f, "Slew time", "s"); + + configInput(IN_L_INPUT, "Left"); configInput(IN_R_INPUT, "Right"); configInput(FROM_FX_L_INPUT, "From FX L"); @@ -64,30 +68,32 @@ struct Bypass : Module { configOutput(OUT_R_OUTPUT, "Right"); configBypass(IN_L_INPUT, OUT_L_OUTPUT); - configBypass(IN_R_INPUT, OUT_R_OUTPUT); + configBypass(IN_R_INPUT, OUT_R_OUTPUT); + - clickFilter.rise = 1 / 0.01f; // 0.01 ms - clickFilter.fall = 1 / 0.01f; // 0.01 ms } bool active = false; void process(const ProcessArgs& args) override { - const int maxChannels = std::max(inputs[IN_L_INPUT].getChannels(), inputs[IN_R_INPUT].getChannels()); + // slew time in secs (so take inverse for lambda) + clickFilter.rise = clickFilter.fall = 1.0 / params[SLEW_TIME_PARAM].getValue(); + + const int maxInputChannels = std::max({1, inputs[IN_L_INPUT].getChannels(), inputs[IN_R_INPUT].getChannels()}); + const int maxFxReturnChannels = std::max({1, inputs[FROM_FX_L_INPUT].getChannels(), inputs[FROM_FX_R_INPUT].getChannels()}); - latchMode = (LatchMode) params[LAUNCH_MODE_PARAM].getValue(); + const LatchMode latchMode = (LatchMode) params[LAUNCH_MODE_PARAM].getValue(); const ReturnMode returnMode = (ReturnMode) params[MODE_PARAM].getValue(); const bool launchCvTriggered = launchCvTrigger.process(inputs[LAUNCH_INPUT].getVoltage()); - const bool launchButtonPressed = launchButtonTrigger.process(params[LAUNCH_BUTTON_PARAM].getValue()); + const bool launchButtonPressed = launchButtonTrigger.process(launchButtonHeld); // logical or (high if either high) const float launchValue = std::max(launchCvTrigger.isHigh(), launchButtonTrigger.isHigh()); if (latchMode == LatchMode::TOGGLE_MODE) { const bool risingEdge = launchCvTriggered || launchButtonPressed; - // TODO: sometimes misses? if (risingEdge) { active = !active; } @@ -96,36 +102,91 @@ struct Bypass : Module { const float fxGain = std::pow(10, params[FX_GAIN_PARAM].getValue() / 20.0f); const float sendActive = clickFilter.process(args.sampleTime, (latchMode == LatchMode::TOGGLE_MODE) ? active : launchValue); - for (int c = 0; c < maxChannels; c += 4) { - + for (int c = 0; c < maxInputChannels; c += 4) { const float_4 inL = inputs[IN_L_INPUT].getPolyVoltageSimd(c); const float_4 inR = inputs[IN_R_INPUT].getNormalPolyVoltageSimd(inL, c); + // we start be assuming that FXs can be polyphonic, but recognise that often they are not outputs[TOFX_L_OUTPUT].setVoltageSimd(inL * fxGain * sendActive, c); outputs[TOFX_R_OUTPUT].setVoltageSimd(inR * fxGain * sendActive, c); + } + // fx send polyphony is set by input polyphony + outputs[TOFX_L_OUTPUT].setChannels(maxInputChannels); + outputs[TOFX_R_OUTPUT].setChannels(maxInputChannels); + + float_4 dryLeft, dryRight; + for (int c = 0; c < maxFxReturnChannels; c += 4) { + + const bool fxMonophonic = (maxInputChannels == 1); + if (fxMonophonic) { + // if the return fx is monophonic, mix down dry inputs to monophonic also + dryLeft = inputs[IN_L_INPUT].getVoltageSum(); + dryRight = inputs[IN_R_INPUT].isConnected() ? inputs[IN_R_INPUT].getVoltageSum() : inputs[IN_L_INPUT].getVoltageSum(); + } + else { + // if the return fx is polyphonic, then we don't need to do anything special + dryLeft = inputs[IN_L_INPUT].getPolyVoltageSimd(c); + dryRight = inputs[IN_R_INPUT].getNormalPolyVoltageSimd(dryLeft, c); + } const float_4 fxLeftReturn = inputs[FROM_FX_L_INPUT].getPolyVoltageSimd(c); const float_4 fxRightReturn = inputs[FROM_FX_R_INPUT].getPolyVoltageSimd(c); if (returnMode == ReturnMode::HARD_MODE) { - outputs[OUT_L_OUTPUT].setVoltageSimd(inL * (1 - sendActive) + sendActive * fxLeftReturn, c); - outputs[OUT_R_OUTPUT].setVoltageSimd(inR * (1 - sendActive) + sendActive * fxRightReturn, c); + outputs[OUT_L_OUTPUT].setVoltageSimd(dryLeft * (1 - sendActive) + sendActive * fxLeftReturn, c); + outputs[OUT_R_OUTPUT].setVoltageSimd(dryRight * (1 - sendActive) + sendActive * fxRightReturn, c); } else { - outputs[OUT_L_OUTPUT].setVoltageSimd(inL * (1 - sendActive) + fxLeftReturn, c); - outputs[OUT_R_OUTPUT].setVoltageSimd(inR * (1 - sendActive) + fxRightReturn, c); + outputs[OUT_L_OUTPUT].setVoltageSimd(dryLeft * (1 - sendActive) + fxLeftReturn, c); + outputs[OUT_R_OUTPUT].setVoltageSimd(dryRight * (1 - sendActive) + fxRightReturn, c); } } + // output polyphony is set by fx return polyphony + outputs[OUT_L_OUTPUT].setChannels(maxFxReturnChannels); + outputs[OUT_R_OUTPUT].setChannels(maxFxReturnChannels); - outputs[OUT_R_OUTPUT].setVoltage(sendActive); + lights[LAUNCH_LED].setSmoothBrightness(sendActive, args.sampleTime); + } +}; - lights[LAUNCH_LED].setBrightness(sendActive); +/** From VCV Free */ +struct VCVBezelBig : app::SvgSwitch { + VCVBezelBig() { + addFrame(Svg::load(asset::plugin(pluginInstance, "res/components/VCVBezelBig.svg"))); } }; +template +struct VCVBezelLightBig : TBase { + VCVBezelLightBig() { + this->borderColor = color::WHITE_TRANSPARENT; + this->bgColor = color::WHITE_TRANSPARENT; + this->box.size = mm2px(math::Vec(9, 9)); + } +}; -using BefacoRedLightButton = LightButton>>; +struct RecordButton : LightButton> { + // Instead of using onAction() which is called on mouse up, handle on mouse down + void onDragStart(const event::DragStart& e) override { + Bypass* module = dynamic_cast(this->module); + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + if (module) { + module->launchButtonHeld = true; + } + } + LightButton::onDragStart(e); + } + + void onDragEnd(const event::DragEnd& e) override { + Bypass* module = dynamic_cast(this->module); + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + if (module) { + module->launchButtonHeld = false; + } + } + } +}; struct BypassWidget : ModuleWidget { @@ -142,7 +203,7 @@ struct BypassWidget : ModuleWidget { addParam(createParamCentered(mm2px(Vec(10.0, 78.903)), module, Bypass::FX_GAIN_PARAM)); addParam(createParam(mm2px(Vec(13.8, 91.6)), module, Bypass::LAUNCH_MODE_PARAM)); - launchParam = createLightParamCentered(mm2px(Vec(10.0, 111.287)), module, Bypass::LAUNCH_BUTTON_PARAM, Bypass::LAUNCH_LED); + launchParam = createLightParamCentered(mm2px(Vec(10.0, 111.287)), module, Bypass::LAUNCH_BUTTON_PARAM, Bypass::LAUNCH_LED); addParam(launchParam); addInput(createInputCentered(mm2px(Vec(15.016, 15.03)), module, Bypass::IN_R_INPUT)); @@ -157,16 +218,22 @@ struct BypassWidget : ModuleWidget { addOutput(createOutputCentered(mm2px(Vec(14.957, 53.824)), module, Bypass::OUT_R_OUTPUT)); } - void draw(const DrawArgs& args) override { + // for context menu + struct SlewTimeSider : ui::Slider { + explicit SlewTimeSider(ParamQuantity* q_) { + quantity = q_; + this->box.size.x = 200.0f; + } + }; + void appendContextMenu(Menu* menu) override { Bypass* module = dynamic_cast(this->module); + assert(module); - if (module != nullptr) { - launchParam->momentary = module->latchMode == Bypass::LatchMode::MOMENTARY_MODE; - launchParam->latch = !launchParam->momentary; - } + menu->addChild(new MenuSeparator()); + + menu->addChild(new SlewTimeSider(module->slewTimeParam)); - ModuleWidget::draw(args); } }; diff --git a/src/plugin.cpp b/src/plugin.cpp index 48745f7..1b48a6e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -32,4 +32,5 @@ void init(rack::Plugin *p) { p->addModel(modelVoltio); p->addModel(modelOctaves); p->addModel(modelBypass); + p->addModel(modelBandit); } diff --git a/src/plugin.hpp b/src/plugin.hpp index 1c8a4fa..70ad26f 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -33,6 +33,7 @@ extern Model* modelMidiThing; extern Model* modelVoltio; extern Model* modelOctaves; extern Model* modelBypass; +extern Model* modelBandit; struct Knurlie : SvgScrew { Knurlie() {