@@ -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() { | |||