diff --git a/src/Cosmos.cpp b/src/Cosmos.cpp index eaf7e33..7c483f7 100644 --- a/src/Cosmos.cpp +++ b/src/Cosmos.cpp @@ -19,10 +19,10 @@ struct Cosmos : Module { TZ_CLIPPER_OUTPUT, OR_GATE_OUTPUT, AND_GATE_OUTPUT, - MAX_OUTPUT, - MIN_OUTPUT, + OR_OUTPUT, + AND_OUTPUT, SUM_OUTPUT, - ORG_TRIG_OUTPUT, + OR_TRIG_OUTPUT, X_OUTPUT, Y_OUTPUT, AND_TRIG_OUTPUT, @@ -31,8 +31,8 @@ struct Cosmos : Module { INV_Y_OUTPUT, NAND_TRIG_OUTPUT, DIFF_OUTPUT, - INV_MAX_OUTPUT, - INV_MIN_OUTPUT, + NOR_OUTPUT, + NAND_OUTPUT, NOR_GATE_OUTPUT, NAND_GATE_OUTPUT, INV_TZ_CLIPPER_OUTPUT, @@ -56,18 +56,27 @@ struct Cosmos : Module { LIGHTS_LEN }; + // for boolean gates dsp::TSchmittTrigger logicalOrSchmitt[4]; dsp::TSchmittTrigger logicalAndSchmitt[4]; dsp::TSchmittTrigger logicalXorSchmitt[4]; + // in theory we could use above for negated versions, but need falling edge detection too + dsp::TSchmittTrigger logicalNorSchmitt[4]; + dsp::TSchmittTrigger logicalNandSchmitt[4]; + dsp::TSchmittTrigger logicalXNorSchmitt[4]; + // for outputting triggers PulseGenerator_4 logicalOrTrigger[4]; PulseGenerator_4 logicalAndTrigger[4]; PulseGenerator_4 logicalXorTrigger[4]; + PulseGenerator_4 logicalNorTrigger[4]; + PulseGenerator_4 logicalNandTrigger[4]; + PulseGenerator_4 logicalXnorTrigger[4]; Cosmos() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); - configParam(PAD_X_PARAM, 0.f, 1.f, 0.f, ""); - configParam(PAD_Y_PARAM, 0.f, 1.f, 0.f, ""); + configParam(PAD_X_PARAM, 0.f, 1.f, 0.f, "Pad X"); + configParam(PAD_Y_PARAM, 0.f, 1.f, 0.f, "Pad Y"); configInput(X_INPUT, "X"); configInput(Y_INPUT, "Y"); configOutput(XOR_GATE_OUTPUT, "XOR gate"); @@ -75,10 +84,10 @@ struct Cosmos : Module { configOutput(TZ_CLIPPER_OUTPUT, "Through-zero clipper"); configOutput(OR_GATE_OUTPUT, "OR gate"); configOutput(AND_GATE_OUTPUT, "AND gate"); - configOutput(MAX_OUTPUT, "Maximum"); - configOutput(MIN_OUTPUT, "Minimum"); - configOutput(SUM_OUTPUT, "Sum"); - configOutput(ORG_TRIG_OUTPUT, "OR-gate trigger"); + configOutput(OR_OUTPUT, "OR (maximum)"); + configOutput(AND_OUTPUT, "AND (minimum)"); + configOutput(SUM_OUTPUT, "Sum (mean)"); + configOutput(OR_TRIG_OUTPUT, "OR-gate trigger"); configOutput(X_OUTPUT, "X"); configOutput(Y_OUTPUT, "Y"); configOutput(AND_TRIG_OUTPUT, "AND trigger"); @@ -87,8 +96,8 @@ struct Cosmos : Module { configOutput(INV_Y_OUTPUT, "Y (inverted)"); configOutput(NAND_TRIG_OUTPUT, "NAND trigger"); configOutput(DIFF_OUTPUT, "Difference"); - configOutput(INV_MAX_OUTPUT, "Maximum (inverted)"); - configOutput(INV_MIN_OUTPUT, "Minimum (inverted)"); + configOutput(NOR_OUTPUT, "NOR (maximum inverted)"); + configOutput(NAND_OUTPUT, "NAND (minimum inverted)"); configOutput(NOR_GATE_OUTPUT, "NOR gate"); configOutput(NAND_GATE_OUTPUT, "NAND gate"); configOutput(INV_TZ_CLIPPER_OUTPUT, "Ternary clipper (inverted)"); @@ -99,50 +108,79 @@ struct Cosmos : Module { void process(const ProcessArgs& args) override { const int numActivePolyphonyChannels = std::max({1, inputs[X_INPUT].getChannels(), inputs[Y_INPUT].getChannels()}); + + // schmitt trigger thresholds - normally high threshold is greater that low (to get hystersis) but then boolean gate outs aren't perfect inverted copies + const float lowThreshold = 0.1; + const float highThreshold = 1.0; for (int c = 0; c < numActivePolyphonyChannels; c += 4) { const float_4 x = inputs[X_INPUT].getPolyVoltage(c); const float_4 y = inputs[Y_INPUT].getPolyVoltage(c); - + // main outputs outputs[X_OUTPUT].setVoltageSimd(x, c); outputs[Y_OUTPUT].setVoltageSimd(y, c); - - outputs[SUM_OUTPUT].setVoltageSimd(x + y, c); - outputs[DIFF_OUTPUT].setVoltageSimd(x - y, c); + outputs[SUM_OUTPUT].setVoltageSimd(0.5 * (x + y), c); const float_4 analogueOr = ifelse(x > y, x, y); - outputs[MAX_OUTPUT].setVoltageSimd(analogueOr, c); const float_4 analogueAnd = ifelse(x > y, y, x); - outputs[MIN_OUTPUT].setVoltageSimd(analogueAnd, c); - const float_4 sign_x = ifelse(x < 0, -1.f, 1.f); const float_4 analogueXor = ifelse(abs(y) < abs(x), -y * sign_x, ifelse(y < 0, x, -x)); - outputs[TZ_CLIPPER_OUTPUT].setVoltageSimd(analogueXor, c); - outputs[INV_X_OUTPUT].setVoltageSimd(-x, c); - outputs[INV_Y_OUTPUT].setVoltageSimd(-y, c); + outputs[OR_OUTPUT].setVoltageSimd(analogueOr, c); + outputs[AND_OUTPUT].setVoltageSimd(analogueAnd, c); + outputs[TZ_CLIPPER_OUTPUT].setVoltageSimd(analogueXor, c); // gate/trigger outputs - const float_4 orTrig = logicalOrSchmitt[c].process(analogueOr); + const float_4 orTrig = logicalOrSchmitt[c].process(analogueOr, lowThreshold, highThreshold); logicalOrTrigger[c].trigger(orTrig, 1e-3); const float_4 orTriggerHigh = logicalOrTrigger[c].process(args.sampleTime); outputs[OR_GATE_OUTPUT].setVoltageSimd(ifelse(logicalOrSchmitt[c].isHigh(), 10.f, 0.f), c); - outputs[ORG_TRIG_OUTPUT].setVoltageSimd(ifelse(orTriggerHigh, 10.f, 0.f), c); + outputs[OR_TRIG_OUTPUT].setVoltageSimd(ifelse(orTriggerHigh, 10.f, 0.f), c); - const float_4 andTrig = logicalAndSchmitt[c].process(analogueAnd); + const float_4 andTrig = logicalAndSchmitt[c].process(analogueAnd, lowThreshold, highThreshold); logicalAndTrigger[c].trigger(andTrig, 1e-3); const float_4 andTriggerHigh = logicalAndTrigger[c].process(args.sampleTime); outputs[AND_GATE_OUTPUT].setVoltageSimd(ifelse(logicalAndSchmitt[c].isHigh(), 10.f, 0.f), c); outputs[AND_TRIG_OUTPUT].setVoltageSimd(ifelse(andTriggerHigh, 10.f, 0.f), c); - const float_4 xorTrig = logicalXorSchmitt[c].process(analogueXor); + const float_4 xorTrig = logicalXorSchmitt[c].process(analogueXor, lowThreshold, highThreshold); logicalXorTrigger[c].trigger(xorTrig, 1e-3); const float_4 xorTriggerHigh = logicalXorTrigger[c].process(args.sampleTime); outputs[XOR_GATE_OUTPUT].setVoltageSimd(ifelse(logicalXorSchmitt[c].isHigh(), 10.f, 0.f), c); outputs[XOR_TRIG_OUTPUT].setVoltageSimd(ifelse(xorTriggerHigh, 10.f, 0.f), c); - + + + // inverse outputs + outputs[INV_X_OUTPUT].setVoltageSimd(-x, c); + outputs[INV_Y_OUTPUT].setVoltageSimd(-y, c); + outputs[DIFF_OUTPUT].setVoltageSimd(0.5 * (x - y), c); + + const float_4 analogueNor = -analogueOr; + const float_4 analogueNand = -analogueAnd; + const float_4 analogueXnor = -analogueXor; + outputs[NOR_OUTPUT].setVoltageSimd(analogueNor, c); + outputs[NAND_OUTPUT].setVoltageSimd(analogueNand, c); + outputs[INV_TZ_CLIPPER_OUTPUT].setVoltageSimd(analogueXnor, c); + + const float_4 norTrig = logicalNorSchmitt[c].process(analogueNor, lowThreshold, highThreshold); + logicalNorTrigger[c].trigger(norTrig, 1e-3); + const float_4 norTriggerHigh = logicalNorTrigger[c].process(args.sampleTime); + outputs[NOR_GATE_OUTPUT].setVoltageSimd(ifelse(logicalOrSchmitt[c].isHigh(), 10.f, 0.f), c); + outputs[NOR_TRIG_OUTPUT].setVoltageSimd(ifelse(norTriggerHigh, 10.f, 0.f), c); + + const float_4 nandTrig = logicalNandSchmitt[c].process(analogueNand, lowThreshold, highThreshold); + logicalNandTrigger[c].trigger(nandTrig, 1e-3); + const float_4 nandTriggerHigh = logicalNandTrigger[c].process(args.sampleTime); + outputs[NAND_GATE_OUTPUT].setVoltageSimd(ifelse(logicalNandSchmitt[c].isHigh(), 10.f, 0.f), c); + outputs[NAND_TRIG_OUTPUT].setVoltageSimd(ifelse(nandTriggerHigh, 10.f, 0.f), c); + + const float_4 xnorTrig = logicalXNorSchmitt[c].process(analogueXnor, lowThreshold, highThreshold); + logicalXnorTrigger[c].trigger(xnorTrig, 1e-3); + const float_4 xnorTriggerHigh = logicalXnorTrigger[c].process(args.sampleTime); + outputs[XNOR_GATE_OUTPUT].setVoltageSimd(ifelse(logicalXNorSchmitt[c].isHigh(), 10.f, 0.f), c); + outputs[XNOR_TRIG_OUTPUT].setVoltageSimd(ifelse(xnorTriggerHigh, 10.f, 0.f), c); } outputs[X_OUTPUT].setChannels(numActivePolyphonyChannels); @@ -172,10 +210,10 @@ struct CosmosWidget : ModuleWidget { addOutput(createOutputCentered(mm2px(Vec(35.329, 21.201)), module, Cosmos::TZ_CLIPPER_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(10.428, 26.725)), module, Cosmos::OR_GATE_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(60.23, 26.725)), module, Cosmos::AND_GATE_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(17.67, 39.245)), module, Cosmos::MAX_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(52.986, 39.245)), module, Cosmos::MIN_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(17.67, 39.245)), module, Cosmos::OR_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(52.986, 39.245)), module, Cosmos::AND_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(35.329, 46.26)), module, Cosmos::SUM_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(10.44, 51.775)), module, Cosmos::ORG_TRIG_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(10.44, 51.775)), module, Cosmos::OR_TRIG_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(24.889, 51.775)), module, Cosmos::X_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(45.757, 51.775)), module, Cosmos::Y_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(60.206, 51.775)), module, Cosmos::AND_TRIG_OUTPUT)); @@ -184,8 +222,8 @@ struct CosmosWidget : ModuleWidget { addOutput(createOutputCentered(mm2px(Vec(45.769, 76.816)), module, Cosmos::INV_Y_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(60.218, 76.816)), module, Cosmos::NAND_TRIG_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(35.329, 82.331)), module, Cosmos::DIFF_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(17.672, 89.346)), module, Cosmos::INV_MAX_OUTPUT)); - addOutput(createOutputCentered(mm2px(Vec(52.989, 89.346)), module, Cosmos::INV_MIN_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(17.672, 89.346)), module, Cosmos::NOR_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(52.989, 89.346)), module, Cosmos::NAND_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(10.428, 101.866)), module, Cosmos::NOR_GATE_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(60.23, 101.865)), module, Cosmos::NAND_GATE_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(35.329, 107.39)), module, Cosmos::INV_TZ_CLIPPER_OUTPUT));