diff --git a/src/Mex.cpp b/src/Mex.cpp index a96a662..b84a5f4 100644 --- a/src/Mex.cpp +++ b/src/Mex.cpp @@ -1,4 +1,6 @@ #include "plugin.hpp" +#include "Muxlicer.hpp" + struct Mex : Module { static const int numSteps = 8; @@ -24,18 +26,30 @@ struct Mex : Module { MUXLICER_MODE }; - MexMessage leftMessages[2] = {}; dsp::SchmittTrigger gateInTrigger; + // this expander communicates with the mother module (Muxlicer) purely + // through this pointer (it cannot modify Muxlicer, read-only) + Muxlicer const* mother = nullptr; + + struct GateSwitchParamQuantity : ParamQuantity { + std::string getDisplayValueString() override { + + switch ((StepState) ParamQuantity::getValue()) { + case GATE_IN_MODE: return "Gate in/Clock Out"; + case MUTE_MODE: return "Muted"; + case MUXLICER_MODE: return "All Gates"; + default: return ""; + } + } + }; + Mex() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); for (int i = 0; i < 8; ++i) { - configParam(STEP_PARAM + i, 0.f, 2.f, 0.f, string::f("Step %d", i)); + configParam(STEP_PARAM + i, 0.f, 2.f, 0.f, string::f("Step %d", i + 1)); } - - leftExpander.producerMessage = &(leftMessages[0]); - leftExpander.consumerMessage = &(leftMessages[1]); } void process(const ProcessArgs& args) override { @@ -44,37 +58,66 @@ struct Mex : Module { lights[i].setBrightness(0.f); } - if (leftExpander.module && leftExpander.module->model == modelMuxlicer) { + if (leftExpander.module) { + // this expander is active if: + // * muxlicer is to the left or + if (leftExpander.module->model == modelMuxlicer) { + mother = reinterpret_cast(leftExpander.module); + } + // * an active Mex is to the left + else if (leftExpander.module->model == modelMex) { + Mex* moduleMex = reinterpret_cast(leftExpander.module); + if (moduleMex) { + mother = moduleMex->mother; + } + } + else { + mother = nullptr; + } - // Get consumer message - MexMessage* message = (MexMessage*) leftExpander.consumerMessage; + if (mother) { - float gate = 0.f; + float gate = 0.f; - if (message->isPlaying) { - const int currentStep = clamp(message->addressIndex, 0, 7); - StepState state = (StepState) params[STEP_PARAM + currentStep].getValue(); - if (state == MUXLICER_MODE) { - gate = message->allGates; - } - else if (state == GATE_IN_MODE) { - // gate in will convert non-gate signals to gates (via schmitt trigger) - // if input is present - if (inputs[GATE_IN_INPUT].isConnected()) { - gateInTrigger.process(inputs[GATE_IN_INPUT].getVoltage()); - gate = 10.f * gateInTrigger.isHigh(); + if (mother->playState != Muxlicer::STATE_STOPPED) { + const int currentStep = clamp(mother->addressIndex, 0, 7); + StepState state = (StepState) params[STEP_PARAM + currentStep].getValue(); + if (state == MUXLICER_MODE) { + gate = mother->isAllGatesOutHigh; } - // otherwise the main Muxlicer output clock (including divisions/multiplications) - // is normalled in - else { - gate = message->outputClock; - } + else if (state == GATE_IN_MODE) { + // gate in will convert non-gate signals to gates (via schmitt trigger) + // if input is present + if (inputs[GATE_IN_INPUT].isConnected()) { + gateInTrigger.process(inputs[GATE_IN_INPUT].getVoltage()); + gate = gateInTrigger.isHigh(); + } + // otherwise the main Muxlicer output clock (including divisions/multiplications) + // is normalled in + else { + gate = mother->isOutputClockHigh; + } + } + + lights[currentStep].setBrightness(gate); } - lights[currentStep].setBrightness(gate); - } + outputs[OUT_OUTPUT].setVoltage(gate * 10.f); + + // if there's another Mex to the right, update it to also point at the message we just received, + // i.e. just forward on the message + if (rightExpander.module && rightExpander.module->model == modelMex) { + Mex* moduleMexRight = reinterpret_cast(rightExpander.module); - outputs[OUT_OUTPUT].setVoltage(gate); + // assign current message pointer to the right expander + moduleMexRight->mother = mother; + } + } + } + // if we've become disconnected, i.e. no module to the left, then break the connection + // which will propagate to all expanders to the right + else { + mother = nullptr; } } }; diff --git a/src/Muxlicer.cpp b/src/Muxlicer.hpp similarity index 95% rename from src/Muxlicer.cpp rename to src/Muxlicer.hpp index 8a57d84..74dc9d8 100644 --- a/src/Muxlicer.cpp +++ b/src/Muxlicer.hpp @@ -1,3 +1,4 @@ +#pragma once #include "plugin.hpp" using simd::float_4; @@ -78,21 +79,21 @@ struct MultiGateClock { return false; } - float getGate(int gateMode) { + bool getGate(int gateMode) { if (gateMode == 0) { // always on (special case) - return 10.f; + return true; } else if (gateMode < 0 || remaining <= 0) { // disabled (or elapsed) - return 0.f; + return false; } const float multiGateOnLength = fullPulseLength / ((gateMode > 0) ? (2.f * gateMode) : 1.0f); const bool isOddPulse = int(floor(remaining / multiGateOnLength)) % 2; - return isOddPulse ? 10.f : 0.f; + return isOddPulse; } }; @@ -281,6 +282,11 @@ struct Muxlicer : Module { int allInNormalVoltage = 10; // what voltage is normalled into the "All In" input, selectable via context menu Module* rightModule; // for the expander + // these are class variables, rather than scoped to process(...), to allow expanders to read + // all gate output and clock output + bool isAllGatesOutHigh = false; + bool isOutputClockHigh = false; + struct DivMultKnobParamQuantity : ParamQuantity { std::string getDisplayValueString() override { Muxlicer* moduleMuxlicer = reinterpret_cast(module); @@ -360,7 +366,7 @@ struct Muxlicer : Module { configParam(Muxlicer::DIV_MULT_PARAM, 0, 1, 0.5, "Main clock mult/div"); for (int i = 0; i < SEQUENCE_LENGTH; ++i) { - configParam(Muxlicer::LEVEL_PARAMS + i, 0.0, 1.0, 1.0, string::f("Slider %d", i)); + configParam(Muxlicer::LEVEL_PARAMS + i, 0.0, 1.0, 1.0, string::f("Gain %d", i + 1)); } onReset(); @@ -400,7 +406,7 @@ struct Muxlicer : Module { processPlayResetSwitch(); const float address = params[ADDRESS_PARAM].getValue() + inputs[ADDRESS_INPUT].getVoltage(); - const bool isSequenceAdvancing = address < 0.f; + const bool isAddressInRunMode = address < 0.f; // even if we have an external clock, use its pulses to time/sync the internal clock // so that it will remain running even after CLOCK_INPUT is disconnected @@ -442,7 +448,7 @@ struct Muxlicer : Module { const bool resetGracePeriodActive = resetTimer.process(args.sampleTime); if (dividedMultipliedClockPulseReceived) { - if (isSequenceAdvancing && !resetGracePeriodActive) { + if (isAddressInRunMode && !resetGracePeriodActive) { runIndex++; if (runIndex >= SEQUENCE_LENGTH) { // both play modes will reset to step 0 and fire an EOC trigger @@ -457,18 +463,18 @@ struct Muxlicer : Module { } multiClock.reset(mainClockMultDiv.getEffectiveClockLength()); + if (isAddressInRunMode) { + addressIndex = runIndex; + } + else { + addressIndex = clamp((int) roundf(address), 0, SEQUENCE_LENGTH - 1); + } + for (int i = 0; i < 8; i++) { outputs[GATE_OUTPUTS + i].setVoltage(0.f); } } - if (isSequenceAdvancing) { - addressIndex = runIndex; - } - else { - addressIndex = clamp((int) roundf(address), 0, SEQUENCE_LENGTH - 1); - } - // Gates for (int i = 0; i < 8; i++) { outputs[GATE_OUTPUTS + i].setVoltage(0.f); @@ -479,13 +485,12 @@ struct Muxlicer : Module { multiClock.process(args.sampleTime); const int gateMode = getGateMode(); - if (playState != STATE_STOPPED) { - // current gate output _and_ "All Gates" output get the gate pattern from multiClock - float gateValue = multiClock.getGate(gateMode); - outputs[GATE_OUTPUTS + addressIndex].setVoltage(gateValue); - lights[GATE_LIGHTS + addressIndex].setBrightness(gateValue / 10.f); - outputs[ALL_GATES_OUTPUT].setVoltage(gateValue); - } + // current gate output _and_ "All Gates" output both get the gate pattern from multiClock + // NOTE: isAllGatesOutHigh can also be read by expanders + isAllGatesOutHigh = multiClock.getGate(gateMode) && (playState != STATE_STOPPED); + outputs[GATE_OUTPUTS + addressIndex].setVoltage(isAllGatesOutHigh * 10.f); + lights[GATE_LIGHTS + addressIndex].setBrightness(isAllGatesOutHigh * 1.f); + outputs[ALL_GATES_OUTPUT].setVoltage(isAllGatesOutHigh * 10.f); if (modeCOMIO == COM_1_IN_8_OUT) { const int numActiveEngines = std::max(inputs[ALL_INPUT].getChannels(), inputs[COM_INPUT].getChannels()); @@ -526,26 +531,14 @@ struct Muxlicer : Module { // there is an option to stop output clock when play stops const bool playStateMask = !outputClockFollowsPlayMode || (playState != STATE_STOPPED); - const bool isOutputClockHigh = outputClockMultDiv.process(args.sampleTime, clockPulseReceived) && playStateMask; - outputs[CLOCK_OUTPUT].setVoltage(isOutputClockHigh ? 10.f : 0.f); - lights[CLOCK_LIGHT].setBrightness(isOutputClockHigh ? 1.f : 0.f); + // NOTE: outputClockOut can also be read by expanders + isOutputClockHigh = outputClockMultDiv.process(args.sampleTime, clockPulseReceived) && playStateMask; + outputs[CLOCK_OUTPUT].setVoltage(isOutputClockHigh * 10.f); + lights[CLOCK_LIGHT].setBrightness(isOutputClockHigh * 1.f); // end of cycle trigger trigger outputs[EOC_OUTPUT].setVoltage(endOfCyclePulse.process(args.sampleTime) ? 10.f : 0.f); - if (rightExpander.module && rightExpander.module->model == modelMex) { - // Get message from right expander - MexMessage* message = (MexMessage*) rightExpander.module->leftExpander.producerMessage; - - // Write message - message->addressIndex = addressIndex; - message->allGates = multiClock.getGate(gateMode); - message->outputClock = isOutputClockHigh ? 10.f : 0.f; - message->isPlaying = (playState != STATE_STOPPED); - - // Flip messages at the end of the timestep - rightExpander.module->leftExpander.messageFlipRequested = true; - } } void processPlayResetSwitch() { diff --git a/src/plugin.hpp b/src/plugin.hpp index 9fea9b3..8b7292f 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -1,3 +1,4 @@ +#pragma once #include @@ -138,7 +139,7 @@ struct BefacoButton : app::SvgSwitch { }; struct BefacoSwitchHorizontal : app::SvgSwitch { - BefacoSwitchHorizontal() { + BefacoSwitchHorizontal() { addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoSwitchHoriz_0.svg"))); addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoSwitchHoriz_1.svg"))); addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoSwitchHoriz_2.svg"))); @@ -220,12 +221,4 @@ struct ADEnvelope { private: float envLinear = 0.f; -}; - -struct MexMessage { - int addressIndex = 0; - bool isPlaying = false; - float allGates = 0.f; - float outputClock = 0.f; -}; - +}; \ No newline at end of file