From 3dc334ca2252b39bc7bffef6eebbf273c18cd7b7 Mon Sep 17 00:00:00 2001 From: hemmer <915048+hemmer@users.noreply.github.com> Date: Fri, 1 Nov 2024 06:05:53 +0000 Subject: [PATCH] Update Bypass Tweak return gain taper Gain is return gain not send gain! Add optional saturation --- src/Bypass.cpp | 96 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/src/Bypass.cpp b/src/Bypass.cpp index 334d56d..fbb84a3 100644 --- a/src/Bypass.cpp +++ b/src/Bypass.cpp @@ -20,8 +20,8 @@ struct Bypass : Module { INPUTS_LEN }; enum OutputId { - TOFX_L_OUTPUT, - TOFX_R_OUTPUT, + TO_FX_L_OUTPUT, + TO_FX_R_OUTPUT, OUT_L_OUTPUT, OUT_R_OUTPUT, OUTPUTS_LEN @@ -45,35 +45,44 @@ struct Bypass : Module { dsp::BooleanTrigger latchTrigger; dsp::SlewLimiter clickFilter; bool launchButtonHeld = false; + bool applySaturation = true; + bool active = false; + + struct GainParamQuantity : ParamQuantity { + std::string getDisplayValueString() override { + if (getValue() < 0.f) { + return string::f("%g dB", 30 * getValue()); + } + else { + return string::f("%g dB", 12 * getValue()); + } + } + }; Bypass() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); - configSwitch(MODE_PARAM, 0.f, 1.f, 0.f, "Return mode", {"Hard", "Soft"}); - configParam(FX_GAIN_PARAM, -30.f, 30.f, 0.f, "FX Gain"); + auto switchParam = configSwitch(MODE_PARAM, 0.f, 1.f, 0.f, "Return mode", {"Hard", "Soft"}); + switchParam->description = "In hard mode, Bypass wil cut off any sound coming from the loop.\nWith soft mode, the FX return is still active giving you reverb tails, decaying delay taps etc."; + configParam(FX_GAIN_PARAM, -1.f, 1.f, 0.f, "FX return gain"); 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"); configInput(FROM_FX_R_INPUT, "From FX R"); configInput(LAUNCH_INPUT, "Launch"); - configOutput(TOFX_L_OUTPUT, "To FX L"); - configOutput(TOFX_R_OUTPUT, "To FX R"); + configOutput(TO_FX_L_OUTPUT, "To FX L"); + configOutput(TO_FX_R_OUTPUT, "To FX R"); configOutput(OUT_L_OUTPUT, "Left"); configOutput(OUT_R_OUTPUT, "Right"); configBypass(IN_L_INPUT, OUT_L_OUTPUT); configBypass(IN_R_INPUT, OUT_R_OUTPUT); - - } - bool active = false; void process(const ProcessArgs& args) override { // slew time in secs (so take inverse for lambda) @@ -99,22 +108,25 @@ struct Bypass : Module { } } - const float fxGain = std::pow(10, params[FX_GAIN_PARAM].getValue() / 20.0f); + // FX send section const float sendActive = clickFilter.process(args.sampleTime, (latchMode == LatchMode::TOGGLE_MODE) ? active : launchValue); - 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); + outputs[TO_FX_L_OUTPUT].setVoltageSimd(inL * sendActive, c); + outputs[TO_FX_R_OUTPUT].setVoltageSimd(inR * sendActive, c); } // fx send polyphony is set by input polyphony - outputs[TOFX_L_OUTPUT].setChannels(maxInputChannels); - outputs[TOFX_R_OUTPUT].setChannels(maxInputChannels); + outputs[TO_FX_L_OUTPUT].setChannels(maxInputChannels); + outputs[TO_FX_R_OUTPUT].setChannels(maxInputChannels); + - float_4 dryLeft, dryRight; + // FX return section + const float gainTaper = params[FX_GAIN_PARAM].getValue() < 0.f ? 30 * params[FX_GAIN_PARAM].getValue() : params[FX_GAIN_PARAM].getValue() * 12; + const float fxReturnGain = std::pow(10, gainTaper / 20.0f); + float_4 dryLeft, dryRight, outL, outR; for (int c = 0; c < maxFxReturnChannels; c += 4) { const bool fxMonophonic = (maxInputChannels == 1); @@ -129,24 +141,54 @@ struct Bypass : Module { 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); + const float_4 fxLeftReturn = fxReturnGain * inputs[FROM_FX_L_INPUT].getPolyVoltageSimd(c); + const float_4 fxRightReturn = fxReturnGain * inputs[FROM_FX_R_INPUT].getPolyVoltageSimd(c); if (returnMode == ReturnMode::HARD_MODE) { - outputs[OUT_L_OUTPUT].setVoltageSimd(dryLeft * (1 - sendActive) + sendActive * fxLeftReturn, c); - outputs[OUT_R_OUTPUT].setVoltageSimd(dryRight * (1 - sendActive) + sendActive * fxRightReturn, c); + outL = dryLeft * (1 - sendActive) + sendActive * fxLeftReturn; + outR = dryRight * (1 - sendActive) + sendActive * fxRightReturn; } else { - outputs[OUT_L_OUTPUT].setVoltageSimd(dryLeft * (1 - sendActive) + fxLeftReturn, c); - outputs[OUT_R_OUTPUT].setVoltageSimd(dryRight * (1 - sendActive) + fxRightReturn, c); + outL = dryLeft * (1 - sendActive) + fxLeftReturn; + outR = dryRight * (1 - sendActive) + fxRightReturn; } + + if (applySaturation) { + outL = Saturator::process(outL / 10.f) * 10.f; + outR = Saturator::process(outR / 10.f) * 10.f; + } + + outputs[OUT_L_OUTPUT].setVoltageSimd(outL, c); + outputs[OUT_R_OUTPUT].setVoltageSimd(outR, c); } + // output polyphony is set by fx return polyphony outputs[OUT_L_OUTPUT].setChannels(maxFxReturnChannels); outputs[OUT_R_OUTPUT].setChannels(maxFxReturnChannels); lights[LAUNCH_LED].setSmoothBrightness(sendActive, args.sampleTime); } + + void dataFromJson(json_t* rootJ) override { + json_t* applySaturationJ = json_object_get(rootJ, "applySaturation"); + if (applySaturationJ) { + applySaturation = json_boolean_value(applySaturationJ); + } + + json_t* activeJ = json_object_get(rootJ, "active"); + if (activeJ) { + active = json_boolean_value(activeJ); + } + } + + json_t* dataToJson() override { + json_t* rootJ = json_object(); + + json_object_set_new(rootJ, "applySaturation", json_boolean(applySaturation)); + json_object_set_new(rootJ, "active", json_boolean(active)); + + return rootJ; + } }; /** From VCV Free */ @@ -212,8 +254,8 @@ struct BypassWidget : ModuleWidget { addInput(createInputCentered(mm2px(Vec(6.648, 95.028)), module, Bypass::LAUNCH_INPUT)); addInput(createInputCentered(mm2px(Vec(4.947, 15.03)), module, Bypass::IN_L_INPUT)); - addOutput(createOutputCentered(mm2px(Vec(4.957, 27.961)), module, Bypass::TOFX_L_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(14.957, 27.961)), module, Bypass::TOFX_R_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(4.957, 27.961)), module, Bypass::TO_FX_L_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(14.957, 27.961)), module, Bypass::TO_FX_R_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(4.947, 53.846)), module, Bypass::OUT_L_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(14.957, 53.824)), module, Bypass::OUT_R_OUTPUT)); } @@ -231,7 +273,7 @@ struct BypassWidget : ModuleWidget { assert(module); menu->addChild(new MenuSeparator()); - + menu->addChild(createBoolPtrMenuItem("Soft clip at ±10V", "", &module->applySaturation)); menu->addChild(new SlewTimeSider(module->slewTimeParam)); }