Browse Source

Mex: update to allow multiple Mex's

* simpler method adopted (pointer vs producerMessage etc)
* better tooltips

Muxlicer:
* address in can only adjust step on clock ticks
tags/v2.1.0
hemmer 3 years ago
parent
commit
be48d97700
3 changed files with 105 additions and 76 deletions
  1. +72
    -29
      src/Mex.cpp
  2. +30
    -37
      src/Muxlicer.hpp
  3. +3
    -10
      src/plugin.hpp

+ 72
- 29
src/Mex.cpp View File

@@ -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<GateSwitchParamQuantity>(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<Muxlicer*>(leftExpander.module);
}
// * an active Mex is to the left
else if (leftExpander.module->model == modelMex) {
Mex* moduleMex = reinterpret_cast<Mex*>(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<Mex*>(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;
}
}
};


src/Muxlicer.cpp → src/Muxlicer.hpp View File

@@ -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<Muxlicer*>(module);
@@ -360,7 +366,7 @@ struct Muxlicer : Module {
configParam<DivMultKnobParamQuantity>(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() {

+ 3
- 10
src/plugin.hpp View File

@@ -1,3 +1,4 @@
#pragma once
#include <rack.hpp>


@@ -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;
};

};

Loading…
Cancel
Save