@@ -341,6 +341,20 @@ | |||||
"tags": [ | "tags": [ | ||||
"Hardware clone", | "Hardware clone", | ||||
"Mixer", | "Mixer", | ||||
"Polyphonic", | |||||
"Utility" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Bandit", | |||||
"name": "Bandit", | |||||
"description": "A spectral processing playground.", | |||||
"tags": [ | |||||
"Equalizer", | |||||
"Filter", | |||||
"Hardware clone", | |||||
"Mixer", | |||||
"Polyphonic", | |||||
"Utility" | "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, | FX_GAIN_PARAM, | ||||
LAUNCH_MODE_PARAM, | LAUNCH_MODE_PARAM, | ||||
LAUNCH_BUTTON_PARAM, | LAUNCH_BUTTON_PARAM, | ||||
SLEW_TIME_PARAM, | |||||
PARAMS_LEN | PARAMS_LEN | ||||
}; | }; | ||||
enum InputId { | enum InputId { | ||||
@@ -37,13 +38,13 @@ struct Bypass : Module { | |||||
HARD_MODE, | HARD_MODE, | ||||
SOFT_MODE | SOFT_MODE | ||||
}; | }; | ||||
LatchMode latchMode = LatchMode::MOMENTARY_MODE; | |||||
ReturnMode returnMode = ReturnMode::HARD_MODE; | ReturnMode returnMode = ReturnMode::HARD_MODE; | ||||
ParamQuantity* launchParam; | |||||
ParamQuantity* launchParam, * slewTimeParam; | |||||
dsp::SchmittTrigger launchCvTrigger; | dsp::SchmittTrigger launchCvTrigger; | ||||
dsp::BooleanTrigger launchButtonTrigger; | dsp::BooleanTrigger launchButtonTrigger; | ||||
dsp::BooleanTrigger latchTrigger; | dsp::BooleanTrigger latchTrigger; | ||||
dsp::SlewLimiter clickFilter; | dsp::SlewLimiter clickFilter; | ||||
bool launchButtonHeld = false; | |||||
Bypass() { | Bypass() { | ||||
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | 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)"}); | configSwitch(LAUNCH_MODE_PARAM, 0.f, 1.f, 0.f, "Launch Mode", {"Latch (Toggle)", "Gate (Momentary)"}); | ||||
launchParam = configButton(LAUNCH_BUTTON_PARAM, "Launch"); | 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_L_INPUT, "Left"); | ||||
configInput(IN_R_INPUT, "Right"); | configInput(IN_R_INPUT, "Right"); | ||||
configInput(FROM_FX_L_INPUT, "From FX L"); | configInput(FROM_FX_L_INPUT, "From FX L"); | ||||
@@ -64,30 +68,32 @@ struct Bypass : Module { | |||||
configOutput(OUT_R_OUTPUT, "Right"); | configOutput(OUT_R_OUTPUT, "Right"); | ||||
configBypass(IN_L_INPUT, OUT_L_OUTPUT); | 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; | bool active = false; | ||||
void process(const ProcessArgs& args) override { | 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 ReturnMode returnMode = (ReturnMode) params[MODE_PARAM].getValue(); | ||||
const bool launchCvTriggered = launchCvTrigger.process(inputs[LAUNCH_INPUT].getVoltage()); | 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) | // logical or (high if either high) | ||||
const float launchValue = std::max(launchCvTrigger.isHigh(), launchButtonTrigger.isHigh()); | const float launchValue = std::max(launchCvTrigger.isHigh(), launchButtonTrigger.isHigh()); | ||||
if (latchMode == LatchMode::TOGGLE_MODE) { | if (latchMode == LatchMode::TOGGLE_MODE) { | ||||
const bool risingEdge = launchCvTriggered || launchButtonPressed; | const bool risingEdge = launchCvTriggered || launchButtonPressed; | ||||
// TODO: sometimes misses? | |||||
if (risingEdge) { | if (risingEdge) { | ||||
active = !active; | active = !active; | ||||
} | } | ||||
@@ -96,36 +102,91 @@ struct Bypass : Module { | |||||
const float fxGain = std::pow(10, params[FX_GAIN_PARAM].getValue() / 20.0f); | 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); | 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 inL = inputs[IN_L_INPUT].getPolyVoltageSimd<float_4>(c); | ||||
const float_4 inR = inputs[IN_R_INPUT].getNormalPolyVoltageSimd<float_4>(inL, 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_L_OUTPUT].setVoltageSimd<float_4>(inL * fxGain * sendActive, c); | ||||
outputs[TOFX_R_OUTPUT].setVoltageSimd<float_4>(inR * 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 fxLeftReturn = inputs[FROM_FX_L_INPUT].getPolyVoltageSimd<float_4>(c); | ||||
const float_4 fxRightReturn = inputs[FROM_FX_R_INPUT].getPolyVoltageSimd<float_4>(c); | const float_4 fxRightReturn = inputs[FROM_FX_R_INPUT].getPolyVoltageSimd<float_4>(c); | ||||
if (returnMode == ReturnMode::HARD_MODE) { | 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 { | 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 { | 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(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)); | 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); | addParam(launchParam); | ||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.016, 15.03)), module, Bypass::IN_R_INPUT)); | 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)); | 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); | 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(modelVoltio); | ||||
p->addModel(modelOctaves); | p->addModel(modelOctaves); | ||||
p->addModel(modelBypass); | p->addModel(modelBypass); | ||||
p->addModel(modelBandit); | |||||
} | } |
@@ -33,6 +33,7 @@ extern Model* modelMidiThing; | |||||
extern Model* modelVoltio; | extern Model* modelVoltio; | ||||
extern Model* modelOctaves; | extern Model* modelOctaves; | ||||
extern Model* modelBypass; | extern Model* modelBypass; | ||||
extern Model* modelBandit; | |||||
struct Knurlie : SvgScrew { | struct Knurlie : SvgScrew { | ||||
Knurlie() { | Knurlie() { | ||||