diff --git a/CHANGELOG.md b/CHANGELOG.md index 89378e7..02a5a77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.3.1 (2020-07-14) +- Rewrite and re-panel Branches. +- Make Branches polyphonic. + ### 1.3.0 (2020-05-29) - Add EQ Filter. diff --git a/plugin.json b/plugin.json index 94df9d6..0e23157 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "slug": "AudibleInstruments", - "version": "1.3.0", + "version": "1.3.1", "license": "GPL-3.0-or-later", "name": "Audible Instruments", "author": "VCV", @@ -155,7 +155,8 @@ "tags": [ "Random", "Dual", - "Hardware clone" + "Hardware clone", + "Polyphonic" ] }, { diff --git a/res/Branches.svg b/res/Branches.svg index 07df61b..40f33d8 100644 --- a/res/Branches.svg +++ b/res/Branches.svg @@ -1,779 +1,512 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Branches.cpp b/src/Branches.cpp index d1dfefa..e39da76 100644 --- a/src/Branches.cpp +++ b/src/Branches.cpp @@ -24,24 +24,87 @@ struct Branches : Module { NUM_OUTPUTS }; enum LightIds { - MODE1_LIGHT, - MODE2_LIGHT, - STATE1_POS_LIGHT, STATE1_NEG_LIGHT, - STATE2_POS_LIGHT, STATE2_NEG_LIGHT, + ENUMS(STATE_LIGHTS, 2 * 2), NUM_LIGHTS }; - dsp::SchmittTrigger gateTriggers[2]; - dsp::SchmittTrigger modeTriggers[2]; + dsp::BooleanTrigger gateTriggers[2][16]; + dsp::BooleanTrigger modeTriggers[2]; bool modes[2] = {}; - bool outcomes[2] = {}; + bool outcomes[2][16] = {}; Branches() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - configParam(THRESHOLD1_PARAM, 0.0, 1.0, 0.5, "Probability 1"); - configParam(MODE1_PARAM, 0.0, 1.0, 0.0, "Mode 1"); - configParam(THRESHOLD2_PARAM, 0.0, 1.0, 0.5, "Probability 2"); - configParam(MODE2_PARAM, 0.0, 1.0, 0.0, "Mode 2"); + configParam(THRESHOLD1_PARAM, 0.0, 1.0, 0.5, "Channel 1 probability", "%", 0, 100); + configParam(MODE1_PARAM, 0.0, 1.0, 0.0, "Channel 1 mode"); + configParam(THRESHOLD2_PARAM, 0.0, 1.0, 0.5, "Channel 2 probability", "%", 0, 100); + configParam(MODE2_PARAM, 0.0, 1.0, 0.0, "Channel 2 mode"); + } + + void process(const ProcessArgs& args) override { + for (int i = 0; i < 2; i++) { + // Get input + Input* input = &inputs[IN1_INPUT + i]; + // 2nd input is normalized to 1st. + if (i == 1 && !input->isConnected()) + input = &inputs[IN1_INPUT + 0]; + int channels = std::max(input->getChannels(), 1); + + // mode button + if (modeTriggers[i].process(params[MODE1_PARAM + i].getValue() > 0.f)) + modes[i] ^= true; + + bool lightA = false; + bool lightB = false; + + // Process triggers + for (int c = 0; c < channels; c++) { + bool gate = input->getVoltage(c) >= 2.f; + if (gateTriggers[i][c].process(gate)) { + // trigger + // We don't have to clamp here because the threshold comparison works without it. + float threshold = params[THRESHOLD1_PARAM + i].getValue() + inputs[P1_INPUT + i].getPolyVoltage(c) / 10.f; + bool toss = (random::uniform() < threshold); + if (!modes[i]) { + // direct modes + outcomes[i][c] = toss; + } + else { + // toggle modes + if (toss) + outcomes[i][c] ^= true; + } + } + + // Output gate logic + bool gateA = !outcomes[i][c] && (modes[i] ? true : gate); + bool gateB = outcomes[i][c] && (modes[i] ? true : gate); + + if (gateA) + lightA = true; + if (gateB) + lightB = true; + + // Set output gates + outputs[OUT1A_OUTPUT + i].setVoltage(gateA ? 10.f : 0.f, c); + outputs[OUT1B_OUTPUT + i].setVoltage(gateB ? 10.f : 0.f, c); + } + + outputs[OUT1A_OUTPUT + i].setChannels(channels); + outputs[OUT1B_OUTPUT + i].setChannels(channels); + + lights[STATE_LIGHTS + i * 2 + 1].setSmoothBrightness(lightA, args.sampleTime); + lights[STATE_LIGHTS + i * 2 + 0].setSmoothBrightness(lightB, args.sampleTime); + } + } + + void onReset() override { + for (int i = 0; i < 2; i++) { + modes[i] = false; + for (int c = 0; c < 16; c++) { + outcomes[i][c] = false; + } + } } json_t* dataToJson() override { @@ -64,52 +127,6 @@ struct Branches : Module { } } } - - void process(const ProcessArgs& args) override { - float gate = 0.0; - for (int i = 0; i < 2; i++) { - // mode button - if (modeTriggers[i].process(params[MODE1_PARAM + i].getValue())) - modes[i] = !modes[i]; - - if (inputs[IN1_INPUT + i].isConnected()) - gate = inputs[IN1_INPUT + i].getVoltage(); - - if (gateTriggers[i].process(gate)) { - // trigger - float r = random::uniform(); - float threshold = clamp(params[THRESHOLD1_PARAM + i].getValue() + inputs[P1_INPUT + i].getVoltage() / 10.f, 0.f, 1.f); - bool toss = (r < threshold); - if (!modes[i]) { - // direct modes - outcomes[i] = toss; - } - else { - // toggle modes - outcomes[i] = (outcomes[i] != toss); - } - - if (!outcomes[i]) - lights[STATE1_POS_LIGHT + 2 * i].value = 1.0; - else - lights[STATE1_NEG_LIGHT + 2 * i].value = 1.0; - } - - lights[STATE1_POS_LIGHT + 2 * i].value *= 1.0 - args.sampleTime * 15.0; - lights[STATE1_NEG_LIGHT + 2 * i].value *= 1.0 - args.sampleTime * 15.0; - lights[MODE1_LIGHT + i].value = modes[i] ? 1.0 : 0.0; - - outputs[OUT1A_OUTPUT + i].setVoltage(outcomes[i] ? 0.0 : gate); - outputs[OUT1B_OUTPUT + i].setVoltage(outcomes[i] ? gate : 0.0); - } - } - - void onReset() override { - for (int i = 0; i < 2; i++) { - modes[i] = false; - outcomes[i] = false; - } - } }; @@ -118,25 +135,26 @@ struct BranchesWidget : ModuleWidget { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Branches.svg"))); - addChild(createWidget(Vec(15, 0))); - addChild(createWidget(Vec(15, 365))); - - addParam(createParam(Vec(24, 64), module, Branches::THRESHOLD1_PARAM)); - addParam(createParam(Vec(69, 58), module, Branches::MODE1_PARAM)); - addInput(createInput(Vec(9, 122), module, Branches::IN1_INPUT)); - addInput(createInput(Vec(55, 122), module, Branches::P1_INPUT)); - addOutput(createOutput(Vec(9, 160), module, Branches::OUT1A_OUTPUT)); - addOutput(createOutput(Vec(55, 160), module, Branches::OUT1B_OUTPUT)); - - addParam(createParam(Vec(24, 220), module, Branches::THRESHOLD2_PARAM)); - addParam(createParam(Vec(69, 214), module, Branches::MODE2_PARAM)); - addInput(createInput(Vec(9, 278), module, Branches::IN2_INPUT)); - addInput(createInput(Vec(55, 278), module, Branches::P2_INPUT)); - addOutput(createOutput(Vec(9, 316), module, Branches::OUT2A_OUTPUT)); - addOutput(createOutput(Vec(55, 316), module, Branches::OUT2B_OUTPUT)); - - addChild(createLight>(Vec(40, 169), module, Branches::STATE1_POS_LIGHT)); - addChild(createLight>(Vec(40, 325), module, Branches::STATE2_POS_LIGHT)); + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(createParamCentered(mm2px(Vec(25.852, 22.24)), module, Branches::MODE1_PARAM)); + addParam(createParamCentered(mm2px(Vec(15.057, 28.595)), module, Branches::THRESHOLD1_PARAM)); + addParam(createParamCentered(mm2px(Vec(25.852, 74.95)), module, Branches::MODE2_PARAM)); + addParam(createParamCentered(mm2px(Vec(15.057, 81.296)), module, Branches::THRESHOLD2_PARAM)); + + addInput(createInputCentered(mm2px(Vec(7.112, 45.74)), module, Branches::IN1_INPUT)); + addInput(createInputCentered(mm2px(Vec(22.991, 45.74)), module, Branches::P1_INPUT)); + addInput(createInputCentered(mm2px(Vec(7.112, 98.44)), module, Branches::IN2_INPUT)); + addInput(createInputCentered(mm2px(Vec(22.991, 98.44)), module, Branches::P2_INPUT)); + + addOutput(createOutputCentered(mm2px(Vec(7.112, 58.44)), module, Branches::OUT1A_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(22.991, 58.44)), module, Branches::OUT1B_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(7.112, 111.14)), module, Branches::OUT2A_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(22.991, 111.14)), module, Branches::OUT2B_OUTPUT)); + + addChild(createLightCentered>(mm2px(Vec(15.052, 58.45)), module, Branches::STATE_LIGHTS + 0 * 2)); + addChild(createLightCentered>(mm2px(Vec(15.052, 111.151)), module, Branches::STATE_LIGHTS + 1 * 2)); } void appendContextMenu(Menu* menu) override { @@ -145,12 +163,12 @@ struct BranchesWidget : ModuleWidget { struct BranchesModeItem : MenuItem { Branches* branches; - int channel; + int i; void onAction(const event::Action& e) override { - branches->modes[channel] ^= 1; + branches->modes[i] ^= 1; } void step() override { - rightText = branches->modes[channel] ? "Toggle" : "Latch"; + rightText = branches->modes[i] ? "Latch" : "Toggle"; MenuItem::step(); } }; @@ -158,8 +176,8 @@ struct BranchesWidget : ModuleWidget { menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuLabel::text, "Channels")); - menu->addChild(construct(&MenuItem::text, "Channel 1 modes", &BranchesModeItem::branches, branches, &BranchesModeItem::channel, 0)); - menu->addChild(construct(&MenuItem::text, "Channel 2 modes", &BranchesModeItem::branches, branches, &BranchesModeItem::channel, 1)); + menu->addChild(construct(&MenuItem::text, "Channel 1 mode", &BranchesModeItem::branches, branches, &BranchesModeItem::i, 0)); + menu->addChild(construct(&MenuItem::text, "Channel 2 mode", &BranchesModeItem::branches, branches, &BranchesModeItem::i, 1)); } };