| @@ -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" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,150 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
| <!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||
| <svg | |||
| version="1.0" | |||
| id="svg64581" | |||
| x="0px" | |||
| y="0px" | |||
| width="34.015747" | |||
| height="34.015747" | |||
| viewBox="0 0 34.015747 34.015748" | |||
| enable-background="new 0 0 36.02325 36.0188" | |||
| xml:space="preserve" | |||
| sodipodi:docname="VCVBezelBig.svg" | |||
| inkscape:version="1.3.2 (091e20e, 2023-11-25)" | |||
| xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
| xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
| xmlns="http://www.w3.org/2000/svg" | |||
| xmlns:svg="http://www.w3.org/2000/svg"><defs | |||
| id="defs7" /> | |||
| <sodipodi:namedview | |||
| bordercolor="#666666" | |||
| borderopacity="1.0" | |||
| fit-margin-bottom="0" | |||
| fit-margin-left="0" | |||
| fit-margin-right="0" | |||
| fit-margin-top="0" | |||
| id="base" | |||
| inkscape:current-layer="svg64581" | |||
| inkscape:cx="-101.01525" | |||
| inkscape:cy="10.101525" | |||
| inkscape:document-units="mm" | |||
| inkscape:pageopacity="0.0" | |||
| inkscape:pageshadow="2" | |||
| inkscape:window-height="1301" | |||
| inkscape:window-maximized="0" | |||
| inkscape:window-width="2560" | |||
| inkscape:window-x="0" | |||
| inkscape:window-y="25" | |||
| inkscape:zoom="1.979899" | |||
| pagecolor="#ffffff" | |||
| showgrid="false" | |||
| inkscape:showpageshadow="2" | |||
| inkscape:pagecheckerboard="0" | |||
| inkscape:deskcolor="#d1d1d1"> | |||
| </sodipodi:namedview> | |||
| <g | |||
| id="g7" | |||
| transform="matrix(0.94421282,0,0,0.94421046,3.5880087e-4,9.4421046e-5)"> | |||
| <g | |||
| id="g5959-5_106_" | |||
| transform="translate(301.93513,1189.951)"> | |||
| <path | |||
| id="path5961-3_112_" | |||
| inkscape:connector-curvature="0" | |||
| d="m -265.91,-1171.937 c 0,9.9474 -8.06683,18.0115 -18.01407,18.0115 -9.94595,0 -18.01144,-8.0641 -18.01144,-18.0115 0,-9.9513 8.06549,-18.0141 18.01141,-18.0141 9.94726,10e-5 18.0141,8.0628 18.0141,18.0141" /> | |||
| </g> | |||
| <g | |||
| id="g5959-5_105_" | |||
| transform="translate(301.93513,1189.951)"> | |||
| <linearGradient | |||
| id="path5961-3_1_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-590.1001" | |||
| y1="-2191.8545" | |||
| x2="-557.04907" | |||
| y2="-2191.8545" | |||
| gradientTransform="rotate(90,-938.70655,-1537.0705)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#787878" | |||
| id="stop1" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#474747" | |||
| id="stop2" /> | |||
| </linearGradient> | |||
| <path | |||
| id="path5961-3_111_" | |||
| inkscape:connector-curvature="0" | |||
| fill="url(#path5961-3_1_)" | |||
| d="m -300.44827,-1171.9395 c 0,-9.1261 7.40079,-16.5244 16.52676,-16.5244 9.12473,0 16.52429,7.3984 16.52429,16.5244 0,9.1297 -7.39957,16.5268 -16.52432,16.5268 -9.12595,0 -16.52673,-7.3971 -16.52673,-16.5268" | |||
| style="fill:url(#path5961-3_1_)" /> | |||
| </g> | |||
| <g | |||
| opacity="0.21" | |||
| id="g4"> | |||
| <g | |||
| id="g5959-5_104_" | |||
| transform="translate(301.93513,1189.951)"> | |||
| <linearGradient | |||
| id="path5961-3_2_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-299.58844" | |||
| y1="-1111.948" | |||
| x2="-268.70374" | |||
| y2="-1111.948" | |||
| gradientTransform="rotate(-90,-314.02952,-1142.0548)"> | |||
| <stop | |||
| offset="0.00559" | |||
| style="stop-color:#6B6B6B" | |||
| id="stop3" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#DEDEDE" | |||
| id="stop4" /> | |||
| </linearGradient> | |||
| <path | |||
| id="path5961-3_110_" | |||
| inkscape:connector-curvature="0" | |||
| fill="url(#path5961-3_2_)" | |||
| d="m -283.92389,-1156.496 c -8.52792,0 -15.44122,-6.9156 -15.44122,-15.4434 0,-8.5267 6.9133,-15.4412 15.44122,-15.4412 8.53131,0 15.44348,6.9145 15.44348,15.4412 0,8.5278 -6.91214,15.4434 -15.44348,15.4434" | |||
| style="fill:url(#path5961-3_2_)" /> | |||
| </g> | |||
| </g> | |||
| <g | |||
| id="g6"> | |||
| <g | |||
| id="g5959-5_103_" | |||
| transform="translate(301.93513,1189.951)"> | |||
| <linearGradient | |||
| id="path5961-3_3_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-298.98605" | |||
| y1="-1111.948" | |||
| x2="-269.30612" | |||
| y2="-1111.948" | |||
| gradientTransform="rotate(-90,-314.02952,-1142.0548)"> | |||
| <stop | |||
| offset="0.00559" | |||
| style="stop-color:#5B5B5B" | |||
| id="stop5" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#6C6C6C" | |||
| id="stop6" /> | |||
| </linearGradient> | |||
| <path | |||
| id="path5961-3_109_" | |||
| inkscape:connector-curvature="0" | |||
| fill="url(#path5961-3_3_)" | |||
| d="m -283.92386,-1157.0984 c -8.19525,0 -14.83887,-6.6459 -14.83887,-14.8409 0,-8.1941 6.64362,-14.839 14.83887,-14.839 8.19852,0 14.84106,6.6449 14.84106,14.8388 1e-5,8.1952 -6.64251,14.8411 -14.84106,14.8411" | |||
| style="fill:url(#path5961-3_3_)" /> | |||
| </g> | |||
| </g> | |||
| </g> | |||
| </svg> | |||
| @@ -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<float_4> 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<float_4>::Type::LOWPASS, lowFc, Q, V); | |||
| filterLowMid[i].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, lowMidFc, Q, V); | |||
| filterHighMid[i].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, highMidFc, Q, V); | |||
| filterHigh[i].setParameters(dsp::TBiquadFilter<float_4>::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<float_4>(c); | |||
| const float_4 inLowMid = inputs[LOW_MID_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inHighMid = inputs[HIGH_MID_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inHigh = inputs[HIGH_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| // bypass sums all inputs to the output | |||
| outputs[MIX_OUTPUT].setVoltageSimd<float_4>(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<float_4>(c); | |||
| const float_4 inLowMid = inputs[LOW_MID_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inHighMid = inputs[HIGH_MID_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inHigh = inputs[HIGH_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 lowGain = params[LOW_GAIN_PARAM].getValue() * inputs[LOW_CV_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f; | |||
| const float_4 outLow = filterLow[c / 4].process((inLow + inAll) * lowGain); | |||
| outputs[LOW_OUTPUT].setVoltageSimd<float_4>(outLow, c); | |||
| const float_4 lowMidGain = params[LOW_MID_GAIN_PARAM].getValue() * inputs[LOW_MID_CV_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f; | |||
| const float_4 outLowMid = filterLowMid[c / 4].process((inLowMid + inAll) * lowMidGain); | |||
| outputs[LOW_MID_OUTPUT].setVoltageSimd<float_4>(outLowMid, c); | |||
| const float_4 highMidGain = params[HIGH_MID_GAIN_PARAM].getValue() * inputs[HIGH_MID_CV_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f; | |||
| const float_4 outHighMid = filterHighMid[c / 4].process((inHighMid + inAll) * highMidGain); | |||
| outputs[HIGH_MID_OUTPUT].setVoltageSimd<float_4>(outHighMid, c); | |||
| const float_4 highGain = params[HIGH_GAIN_PARAM].getValue() * inputs[HIGH_CV_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f; | |||
| const float_4 outHigh = filterHigh[c / 4].process((inHigh + inAll) * highGain); | |||
| outputs[HIGH_OUTPUT].setVoltageSimd<float_4>(outHigh, c); | |||
| const float_4 fxReturnSum = inputs[LOW_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) + | |||
| inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) + | |||
| inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) + | |||
| inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| outputs[MIX_OUTPUT].setVoltageSimd<float_4>(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<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addParam(createParam<BefacoSlidePot>(mm2px(Vec(3.062, 51.365)), module, Bandit::LOW_GAIN_PARAM)); | |||
| addParam(createParam<BefacoSlidePot>(mm2px(Vec(13.23, 51.365)), module, Bandit::LOW_MID_GAIN_PARAM)); | |||
| addParam(createParam<BefacoSlidePot>(mm2px(Vec(23.398, 51.365)), module, Bandit::HIGH_MID_GAIN_PARAM)); | |||
| addParam(createParam<BefacoSlidePot>(mm2px(Vec(33.566, 51.365)), module, Bandit::HIGH_GAIN_PARAM)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.038, 14.5)), module, Bandit::LOW_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.178, 14.5)), module, Bandit::LOW_MID_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.253, 14.5)), module, Bandit::HIGH_MID_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.328, 14.5)), module, Bandit::HIGH_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.045, 40.34)), module, Bandit::LOW_RETURN_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.118, 40.34)), module, Bandit::LOW_MID_RETURN_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.19, 40.338)), module, Bandit::HIGH_MID_RETURN_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.263, 40.34)), module, Bandit::HIGH_RETURN_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.038, 101.229)), module, Bandit::LOW_CV_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.113, 101.229)), module, Bandit::LOW_MID_CV_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.187, 101.231)), module, Bandit::HIGH_MID_CV_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.263, 101.229)), module, Bandit::HIGH_CV_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(10.075, 113.502)), module, Bandit::ALL_INPUT)); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.15, 113.5)), module, Bandit::ALL_CV_INPUT)); | |||
| addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.045, 27.248)), module, Bandit::LOW_OUTPUT)); | |||
| addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.118, 27.256)), module, Bandit::LOW_MID_OUTPUT)); | |||
| addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.19, 27.256)), module, Bandit::HIGH_MID_OUTPUT)); | |||
| addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.263, 27.256)), module, Bandit::HIGH_OUTPUT)); | |||
| addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(30.225, 113.5)), module, Bandit::MIX_OUTPUT)); | |||
| addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(37.781, 111.125)), module, Bandit::MIX_CLIP_LIGHT)); | |||
| addChild(createLightCentered<MediumLight<RedGreenBlueLight>>(mm2px(Vec(37.781, 115.875)), module, Bandit::MIX_LIGHT)); | |||
| } | |||
| }; | |||
| Model* modelBandit = createModel<Bandit, BanditWidget>("Bandit"); | |||
| @@ -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<float_4>(c); | |||
| const float_4 inR = inputs[IN_R_INPUT].getNormalPolyVoltageSimd<float_4>(inL, c); | |||
| // we start be assuming that FXs can be polyphonic, but recognise that often they are not | |||
| outputs[TOFX_L_OUTPUT].setVoltageSimd<float_4>(inL * fxGain * sendActive, c); | |||
| outputs[TOFX_R_OUTPUT].setVoltageSimd<float_4>(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<float_4>(c); | |||
| dryRight = inputs[IN_R_INPUT].getNormalPolyVoltageSimd<float_4>(dryLeft, c); | |||
| } | |||
| const float_4 fxLeftReturn = inputs[FROM_FX_L_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| const float_4 fxRightReturn = inputs[FROM_FX_R_INPUT].getPolyVoltageSimd<float_4>(c); | |||
| if (returnMode == ReturnMode::HARD_MODE) { | |||
| outputs[OUT_L_OUTPUT].setVoltageSimd<float_4>(inL * (1 - sendActive) + sendActive * fxLeftReturn, c); | |||
| outputs[OUT_R_OUTPUT].setVoltageSimd<float_4>(inR * (1 - sendActive) + sendActive * fxRightReturn, c); | |||
| outputs[OUT_L_OUTPUT].setVoltageSimd<float_4>(dryLeft * (1 - sendActive) + sendActive * fxLeftReturn, c); | |||
| outputs[OUT_R_OUTPUT].setVoltageSimd<float_4>(dryRight * (1 - sendActive) + sendActive * fxRightReturn, c); | |||
| } | |||
| else { | |||
| outputs[OUT_L_OUTPUT].setVoltageSimd<float_4>(inL * (1 - sendActive) + fxLeftReturn, c); | |||
| outputs[OUT_R_OUTPUT].setVoltageSimd<float_4>(inR * (1 - sendActive) + fxRightReturn, c); | |||
| outputs[OUT_L_OUTPUT].setVoltageSimd<float_4>(dryLeft * (1 - sendActive) + fxLeftReturn, c); | |||
| outputs[OUT_R_OUTPUT].setVoltageSimd<float_4>(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 <typename TBase> | |||
| 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<BefacoButton, VeryLargeSimpleLight<TRedLight<app::ModuleLightWidget>>>; | |||
| struct RecordButton : LightButton<VCVBezelBig, VCVBezelLightBig<RedLight>> { | |||
| // 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<Bypass*>(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<Bypass*>(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<BefacoTinyKnobWhite>(mm2px(Vec(10.0, 78.903)), module, Bypass::FX_GAIN_PARAM)); | |||
| addParam(createParam<CKSSNarrow>(mm2px(Vec(13.8, 91.6)), module, Bypass::LAUNCH_MODE_PARAM)); | |||
| launchParam = createLightParamCentered<BefacoRedLightButton>(mm2px(Vec(10.0, 111.287)), module, Bypass::LAUNCH_BUTTON_PARAM, Bypass::LAUNCH_LED); | |||
| launchParam = createLightParamCentered<RecordButton>(mm2px(Vec(10.0, 111.287)), module, Bypass::LAUNCH_BUTTON_PARAM, Bypass::LAUNCH_LED); | |||
| addParam(launchParam); | |||
| addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.016, 15.03)), module, Bypass::IN_R_INPUT)); | |||
| @@ -157,16 +218,22 @@ struct BypassWidget : ModuleWidget { | |||
| addOutput(createOutputCentered<BefacoOutputPort>(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<Bypass*>(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); | |||
| } | |||
| }; | |||
| @@ -32,4 +32,5 @@ void init(rack::Plugin *p) { | |||
| p->addModel(modelVoltio); | |||
| p->addModel(modelOctaves); | |||
| p->addModel(modelBypass); | |||
| p->addModel(modelBandit); | |||
| } | |||
| @@ -33,6 +33,7 @@ extern Model* modelMidiThing; | |||
| extern Model* modelVoltio; | |||
| extern Model* modelOctaves; | |||
| extern Model* modelBypass; | |||
| extern Model* modelBandit; | |||
| struct Knurlie : SvgScrew { | |||
| Knurlie() { | |||