diff --git a/plugin.json b/plugin.json
index aac5c89..37759a7 100644
--- a/plugin.json
+++ b/plugin.json
@@ -203,6 +203,28 @@
"Stereo",
"Polyphonic"
]
+ },
+ {
+ "slug": "Muxlicer",
+ "name": "Muxlicer",
+ "description": "VC adressable sequential switch and sequencer",
+ "manualUrl": "https://www.befaco.org/muxlicer-2/",
+ "modularGridUrl": "https://www.modulargrid.net/e/befaco-muxlicer",
+ "tags": [
+ "Clock generator",
+ "Hardware clone",
+ "Sequencer",
+ "Switch"
+ ]
+ },
+ {
+ "slug": "Mex",
+ "name": "Mex",
+ "description": "Gate Expander for Befaco Muxlicer",
+ "tags": [
+ "Expander",
+ "Hardware clone"
+ ]
}
]
}
\ No newline at end of file
diff --git a/res/BefacoButton_0.svg b/res/BefacoButton_0.svg
new file mode 100644
index 0000000..6429238
--- /dev/null
+++ b/res/BefacoButton_0.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/res/BefacoButton_1.svg b/res/BefacoButton_1.svg
new file mode 100644
index 0000000..483d36f
--- /dev/null
+++ b/res/BefacoButton_1.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/res/BefacoSwitchHoriz_0.svg b/res/BefacoSwitchHoriz_0.svg
new file mode 100644
index 0000000..27ea795
--- /dev/null
+++ b/res/BefacoSwitchHoriz_0.svg
@@ -0,0 +1,101 @@
+
+
diff --git a/res/BefacoSwitchHoriz_1.svg b/res/BefacoSwitchHoriz_1.svg
new file mode 100644
index 0000000..15c8b6b
--- /dev/null
+++ b/res/BefacoSwitchHoriz_1.svg
@@ -0,0 +1,86 @@
+
+
diff --git a/res/BefacoSwitchHoriz_2.svg b/res/BefacoSwitchHoriz_2.svg
new file mode 100644
index 0000000..dfc5355
--- /dev/null
+++ b/res/BefacoSwitchHoriz_2.svg
@@ -0,0 +1,104 @@
+
+
diff --git a/res/Mex.svg b/res/Mex.svg
new file mode 100644
index 0000000..b3024b4
--- /dev/null
+++ b/res/Mex.svg
@@ -0,0 +1,541 @@
+
+
diff --git a/res/Muxlicer.svg b/res/Muxlicer.svg
new file mode 100644
index 0000000..4ff8734
--- /dev/null
+++ b/res/Muxlicer.svg
@@ -0,0 +1,5891 @@
+
+
diff --git a/src/Mex.cpp b/src/Mex.cpp
new file mode 100644
index 0000000..a6e7032
--- /dev/null
+++ b/src/Mex.cpp
@@ -0,0 +1,105 @@
+#include "plugin.hpp"
+
+struct Mex : Module {
+ static const int numSteps = 8;
+ enum ParamIds {
+ ENUMS(STEP_PARAM, numSteps),
+ NUM_PARAMS
+ };
+ enum InputIds {
+ GATE_IN_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ ENUMS(LED, numSteps),
+ NUM_LIGHTS
+ };
+ enum StepState {
+ GATE_IN_MODE,
+ MUTE_MODE,
+ MUXLICER_MODE
+ };
+
+ MexMessage leftMessages[2] = {};
+
+ 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));
+ }
+
+ leftExpander.producerMessage = &(leftMessages[0]);
+ leftExpander.consumerMessage = &(leftMessages[1]);
+ }
+
+ void process(const ProcessArgs& args) override {
+
+ for (int i = 0; i < 8; i++) {
+ lights[i].setBrightness(0.f);
+ }
+
+ if (leftExpander.module && leftExpander.module->model == modelMuxlicer) {
+
+ // Get consumer message
+ MexMessage* message = (MexMessage*) leftExpander.consumerMessage;
+
+ 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 = inputs[GATE_IN_INPUT].getNormalVoltage(message->outputClock);
+ }
+
+ lights[currentStep].setBrightness(gate);
+ }
+
+ outputs[OUT_OUTPUT].setVoltage(gate);
+ }
+ }
+};
+
+
+struct MexWidget : ModuleWidget {
+ MexWidget(Mex* module) {
+ setModule(module);
+ setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Mex.svg")));
+
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+ addParam(createParamCentered(mm2px(Vec(8.088, 13.063)), module, Mex::STEP_PARAM + 0));
+ addParam(createParamCentered(mm2px(Vec(8.088, 25.706)), module, Mex::STEP_PARAM + 1));
+ addParam(createParamCentered(mm2px(Vec(8.088, 38.348)), module, Mex::STEP_PARAM + 2));
+ addParam(createParamCentered(mm2px(Vec(8.088, 50.990)), module, Mex::STEP_PARAM + 3));
+ addParam(createParamCentered(mm2px(Vec(8.088, 63.632)), module, Mex::STEP_PARAM + 4));
+ addParam(createParamCentered(mm2px(Vec(8.088, 76.274)), module, Mex::STEP_PARAM + 5));
+ addParam(createParamCentered(mm2px(Vec(8.088, 88.916)), module, Mex::STEP_PARAM + 6));
+ addParam(createParamCentered(mm2px(Vec(8.088, 101.559)), module, Mex::STEP_PARAM + 7));
+
+ addInput(createInputCentered(mm2px(Vec(4.978, 113.445)), module, Mex::GATE_IN_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(15.014, 113.4)), module, Mex::OUT_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(17.7, 13.063)), module, Mex::LED + 0));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 25.706)), module, Mex::LED + 1));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 38.348)), module, Mex::LED + 2));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 50.990)), module, Mex::LED + 3));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 63.632)), module, Mex::LED + 4));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 76.274)), module, Mex::LED + 5));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 88.916)), module, Mex::LED + 6));
+ addChild(createLightCentered>(mm2px(Vec(17.7, 101.558)), module, Mex::LED + 7));
+ }
+};
+
+
+Model* modelMex = createModel("Mex");
\ No newline at end of file
diff --git a/src/Muxlicer.cpp b/src/Muxlicer.cpp
new file mode 100644
index 0000000..b7a16e7
--- /dev/null
+++ b/src/Muxlicer.cpp
@@ -0,0 +1,811 @@
+#include "plugin.hpp"
+
+// an implementation of a performable, 3-stage switch, i.e. where
+// the state triggers after being dragged a certain distance
+struct BefacoSwitchMomentary : SVGSwitch {
+ BefacoSwitchMomentary() {
+ momentary = true;
+ addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_0.svg")));
+ addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_1.svg")));
+ addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_2.svg")));
+ }
+
+ void onDragStart(const event::DragStart& e) override {
+ latched = false;
+ startMouseY = APP->scene->rack->mousePos.y;
+ ParamWidget::onDragStart(e);
+ }
+
+ void onDragMove(const event::DragMove& e) override {
+
+ float diff = APP->scene->rack->mousePos.y - startMouseY;
+
+ // Once the user has dragged the mouse a "threshold" distance, latch
+ // to disallow further changes of state until the mouse is released.
+ // We don't just setValue(1) (default/rest state) because this creates a
+ // jarring UI experience
+ if (diff < -10 && !latched) {
+ paramQuantity->setValue(2);
+ latched = true;
+ }
+ if (diff > 10 && !latched) {
+ paramQuantity->setValue(0);
+ latched = true;
+ }
+
+ ParamWidget::onDragMove(e);
+ }
+
+ void onDragEnd(const event::DragEnd& e) override {
+ // on release, the switch resets to default/neutral/middle position
+ paramQuantity->setValue(1);
+ latched = false;
+ ParamWidget::onDragEnd(e);
+ }
+
+ // do nothing
+ void randomize() override {}
+
+ float startMouseY = 0.f;
+ bool latched = false;
+};
+
+
+// Class which can yield a divided clock state, specifically where the
+// gate is generated at request time through getGate(), rather than during
+// process() - this means that different divisions of clock can be requested
+// at any point in time. In contrast, the division/multiplication setting for
+// ClockMultDiv cannot easily be changed _during_ a clock tick.
+struct MultiGateClock {
+
+ float remaining = 0.f;
+ float fullPulseLength = 0.f;
+
+ /** Immediately disables the pulse */
+ void reset(float newfullPulseLength) {
+ fullPulseLength = newfullPulseLength;
+ remaining = fullPulseLength;
+ }
+
+ /** Advances the state by `deltaTime`. Returns whether the pulse is in the HIGH state. */
+ bool process(float deltaTime) {
+ if (remaining > 0.f) {
+ remaining -= deltaTime;
+ return true;
+ }
+ return false;
+ }
+
+ float getGate(int gateMode) {
+
+ if (gateMode == 0) {
+ // always on (special case)
+ return 10.f;
+ }
+ else if (gateMode < 0 || remaining <= 0) {
+ // disabled (or elapsed)
+ return 0.f;
+ }
+
+ 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;
+ }
+};
+
+
+// Class for generating a clock sequence after setting a clock multiplication or division,
+// given a stream of clock pulses as the "base" clock.
+// Implementation is heavily inspired by BogAudio RGate, with modification
+struct MultDivClock {
+
+ // convention: negative values are used for division (1/mult), positive for multiplication (x mult)
+ // multDiv = 0 should not be used, but if it is it will result in no modification to the clock
+ int multDiv = 1;
+ float secondsSinceLastClock = -1.0f;
+ float inputClockLengthSeconds = -1.0f;
+
+ // count how many divisions we've had
+ int dividerCount = 0;
+
+ float dividedProgressSeconds = 0.f;
+
+ // returns the gated clock signal
+ float process(float deltaTime, bool clockPulseReceived) {
+
+ if (clockPulseReceived) {
+ // update our record of the incoming clock spacing
+ if (secondsSinceLastClock > 0.0f) {
+ inputClockLengthSeconds = secondsSinceLastClock;
+ }
+ secondsSinceLastClock = 0.0f;
+ }
+
+ float out = 0.f;
+ if (secondsSinceLastClock >= 0.0f) {
+ secondsSinceLastClock += deltaTime;
+
+ // negative values are used for division (x 1/mult), positive for multiplication (x mult)
+ const int division = std::max(-multDiv, 1);
+ const int multiplication = std::max(multDiv, 1);
+
+ if (clockPulseReceived) {
+ if (dividerCount < 1) {
+ dividedProgressSeconds = 0.0f;
+ }
+ else {
+ dividedProgressSeconds += deltaTime;
+ }
+ ++dividerCount;
+ if (dividerCount >= division) {
+ dividerCount = 0;
+ }
+ }
+ else {
+ dividedProgressSeconds += deltaTime;
+ }
+
+ // lengths of the mult/div versions of the clock
+ const float dividedSeconds = inputClockLengthSeconds * (float) division;
+ const float multipliedSeconds = dividedSeconds / (float) multiplication;
+
+ // length of the output gate (s)
+ const float gateSeconds = std::max(0.001f, multipliedSeconds * 0.5f);
+
+ if (dividedProgressSeconds < dividedSeconds) {
+ float multipliedProgressSeconds = dividedProgressSeconds / multipliedSeconds;
+ multipliedProgressSeconds -= (float)(int)multipliedProgressSeconds;
+ multipliedProgressSeconds *= multipliedSeconds;
+ out += (float)(multipliedProgressSeconds <= gateSeconds);
+ }
+ }
+ return out;
+ }
+
+ float getEffectiveClockLength() {
+ // negative values are used for division (x 1/mult), positive for multiplication (x mult)
+ const int division = std::max(-multDiv, 1);
+ const int multiplication = std::max(multDiv, 1);
+
+ // lengths of the mult/div versions of the clock
+ const float dividedSeconds = inputClockLengthSeconds * (float) division;
+ const float multipliedSeconds = dividedSeconds / (float) multiplication;
+
+ return multipliedSeconds;
+ }
+};
+
+struct Muxlicer : Module {
+ enum ParamIds {
+ PLAY_PARAM,
+ ADDRESS_PARAM,
+ GATE_MODE_PARAM,
+ TAP_TEMPO_PARAM,
+ ENUMS(LEVEL_PARAMS, 8),
+ NUM_PARAMS
+ };
+ enum InputIds {
+ GATE_MODE_INPUT,
+ ADDRESS_INPUT,
+ CLOCK_INPUT,
+ RESET_INPUT,
+ COM_INPUT,
+ ENUMS(MUX_INPUTS, 8),
+ ALL_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ CLOCK_OUTPUT,
+ ALL_GATES_OUTPUT,
+ EOC_OUTPUT,
+ ENUMS(GATE_OUTPUTS, 8),
+ ENUMS(MUX_OUTPUTS, 8),
+ COM_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ CLOCK_LIGHT,
+ ENUMS(GATE_LIGHTS, 8),
+ NUM_LIGHTS
+ };
+
+ enum ModeCOMIO {
+ COM_1_IN_8_OUT,
+ COM_8_IN_1_OUT
+ };
+
+ enum PlayState {
+ STATE_PLAY_ONCE,
+ STATE_STOPPED,
+ STATE_PLAY
+ };
+
+ /*
+ This shows how the values of the gate mode knob + CV map onto gate triggers.
+ See also getGateMode()
+ value | description | quadratic only mode
+ -1 no gate | ✔
+ 0 gate (full timestep) | x
+ +1 half timestep | ✔
+ 2 two gates | ✔
+ 3 three gates | x
+ 4 four gates | ✔
+ 5 five gates | x
+ 6 six gates | x
+ 7 seven gates | x
+ 8 eight gates | ✔
+ */
+ int possibleQuadraticGates[5] = {-1, 1, 2, 4, 8};
+ bool quadraticGatesOnly = false;
+
+ PlayState playState = STATE_STOPPED;
+ dsp::BooleanTrigger playStateTrigger;
+
+ uint32_t runIndex; // which step are we on (0 to 7)
+ uint32_t addressIndex = 0;
+ bool reset = false;
+
+ // used to track the clock (e.g. if external clock is not connected). NOTE: this clock
+ // is defined _prior_ to any clock division/multiplication logic
+ float internalClockProgress = 0.f;
+ float internalClockLength = 0.25f;
+
+ float tapTime = 99999; // used to track the time between clock pulses (or taps?)
+ dsp::SchmittTrigger inputClockTrigger; // to detect incoming clock pulses
+ dsp::SchmittTrigger mainClockTrigger; // to detect rising edges from the divided/multiplied version of the clock signal
+ dsp::SchmittTrigger resetTrigger; // to detect the reset signal
+ dsp::PulseGenerator endOfCyclePulse; // fire a signal at the end of cycle
+ dsp::BooleanTrigger tapTempoTrigger; // to only trigger tap tempo when push is first detected
+
+ MultDivClock mainClockMultDiv; // to produce a divided/multiplied version of the (internal or external) clock signal
+ MultDivClock outputClockMultDiv; // to produce a divided/multiplied version of the output clock signal
+ MultiGateClock multiClock; // to easily produce a divided version of the main clock (where division can be changed at any point)
+
+ const static int SEQUENCE_LENGTH = 8;
+ ModeCOMIO modeCOMIO = COM_1_IN_8_OUT; // are we in 1-in-8-out mode, or 8-in-1-out mode
+ int allInNormalVoltage = 10; // what voltage is normalled into the "All In" input, selectable via context menu
+ Module* rightModule; // for the expander
+
+ Muxlicer() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam(Muxlicer::PLAY_PARAM, STATE_PLAY_ONCE, STATE_PLAY, STATE_STOPPED, "Play switch");
+ configParam(Muxlicer::ADDRESS_PARAM, -1.f, 7.f, -1.f, "Address");
+ configParam(Muxlicer::GATE_MODE_PARAM, -1.f, 8.f, 0.f, "Gate mode");
+ configParam(Muxlicer::TAP_TEMPO_PARAM, 0.f, 1.f, 0.f, "Tap tempo");
+
+ for (int i = 0; i < SEQUENCE_LENGTH; ++i) {
+ configParam(Muxlicer::LEVEL_PARAMS + i, 0.0, 1.0, 1.0, string::f("Slider %d", i));
+ }
+
+ onReset();
+ }
+
+ void onReset() override {
+ internalClockLength = 0.250f;
+ internalClockProgress = 0;
+ runIndex = 0;
+ }
+
+ void process(const ProcessArgs& args) override {
+
+ const bool usingExternalClock = inputs[CLOCK_INPUT].isConnected();
+
+ bool externalClockPulseReceived = false;
+ // a clock pulse does two things: sets the internal clock (based on timing between two pulses), and
+ // also synchronises the clock
+ if (usingExternalClock && inputClockTrigger.process(rescale(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
+ externalClockPulseReceived = true;
+ }
+ else if (!usingExternalClock && tapTempoTrigger.process(params[TAP_TEMPO_PARAM].getValue())) {
+ externalClockPulseReceived = true;
+ }
+
+ if (resetTrigger.process(rescale(inputs[RESET_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
+ reset = true;
+ if (playState == STATE_STOPPED) {
+ playState = STATE_PLAY_ONCE;
+ }
+ }
+
+ processPlayResetSwitch();
+
+ // TODO: work out CV scaling/conversion for ADDRESS_INPUT
+ const float address = params[ADDRESS_PARAM].getValue() + inputs[ADDRESS_INPUT].getVoltage();
+ const bool isSequenceAdvancing = 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 after CLOCK_INPUT is disconnected
+ if (externalClockPulseReceived) {
+ // TODO: only want 2.f for tap for tempo, not all external clock
+ if (tapTime < 2.f) {
+ internalClockLength = tapTime;
+ }
+ tapTime = 0;
+ internalClockProgress = 0;
+ }
+ tapTime += args.sampleTime;
+ internalClockProgress += args.sampleTime;
+
+ // track if the internal clock has "ticked"
+ const bool internalClockPulseReceived = (internalClockProgress >= internalClockLength);
+ if (internalClockPulseReceived) {
+ internalClockProgress = 0.f;
+ }
+
+ // we can be in one of two clock modes:
+ // * external (decided by pulses to CLOCK_INPUT)
+ // * internal (decided by internalClockProgress exceeding the internal clock length)
+ //
+ // choose which clock source we are to use
+ const bool clockPulseReceived = usingExternalClock ? externalClockPulseReceived : internalClockPulseReceived;
+ // apply the main clock div/mult logic to whatever clock source we're using - this outputs a gate sequence
+ // so we must use a Schmitt Trigger on the divided/mult'd signal in order to detect when to advance the sequence
+ const bool dividedMultedClockPulseReceived = mainClockTrigger.process(mainClockMultDiv.process(args.sampleTime, clockPulseReceived));
+
+ // reset _doesn't_ reset/sync the clock, it just moves the sequence index marker back to the start
+ if (reset) {
+ runIndex = 0;
+ reset = false;
+ }
+
+ // end of cycle trigger trigger
+ outputs[EOC_OUTPUT].setVoltage(0.f);
+
+ if (dividedMultedClockPulseReceived) {
+
+ if (isSequenceAdvancing) {
+ runIndex++;
+ if (runIndex >= 8) {
+ // both play modes will reset to step 0 and fire an EOC trigger
+ runIndex = 0;
+ endOfCyclePulse.trigger(1e-3);
+
+ // additionally stop if in one shot mode
+ if (playState == STATE_PLAY_ONCE) {
+ playState = STATE_STOPPED;
+ }
+ }
+ }
+
+ multiClock.reset(mainClockMultDiv.getEffectiveClockLength());
+
+ 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, 8 - 1);
+ }
+
+ // Gates
+ for (int i = 0; i < 8; i++) {
+ outputs[GATE_OUTPUTS + i].setVoltage(0.f);
+ lights[GATE_LIGHTS + i].setBrightness(0.f);
+ }
+ outputs[ALL_GATES_OUTPUT].setVoltage(0.f);
+
+ 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);
+ }
+
+
+ if (modeCOMIO == COM_1_IN_8_OUT) {
+ // Mux outputs (all zero, except active step, if playing)
+ for (int i = 0; i < 8; i++) {
+ outputs[MUX_OUTPUTS + i].setVoltage(0.f);
+ }
+
+ if (playState != STATE_STOPPED) {
+ const float com_input = inputs[COM_INPUT].getVoltage();
+ const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
+ outputs[MUX_OUTPUTS + addressIndex].setVoltage(stepVolume * com_input);
+ }
+ }
+ else if (modeCOMIO == COM_8_IN_1_OUT && playState != STATE_STOPPED) {
+ const float allInValue = inputs[ALL_INPUT].getNormalVoltage(allInNormalVoltage);
+ const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
+ float stepValue = inputs[MUX_INPUTS + addressIndex].getNormalVoltage(allInValue) * stepVolume;
+ outputs[COM_OUTPUT].setVoltage(stepValue);
+ }
+
+ const bool isOutputClockHigh = outputClockMultDiv.process(args.sampleTime, clockPulseReceived);
+ outputs[CLOCK_OUTPUT].setVoltage(isOutputClockHigh ? 10.f : 0.f);
+ lights[CLOCK_LIGHT].setBrightness(isOutputClockHigh ? 1.f : 0.f);
+ 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() {
+
+ // if the play switch has effectively been activated for the first time,
+ // i.e. it's not just still being held
+ const bool switchIsActive = params[PLAY_PARAM].getValue() != STATE_STOPPED;
+ if (playStateTrigger.process(switchIsActive) && switchIsActive) {
+
+ // if we were stopped, check for activation (normal or one-shot)
+ if (playState == STATE_STOPPED) {
+ if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
+ playState = STATE_PLAY;
+ }
+ else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
+ playState = STATE_PLAY_ONCE;
+ runIndex = 0;
+ reset = true;
+ }
+ }
+ // otherwise we are in play mode (and we've not just held onto the play switch),
+ // so check for stop or reset
+ else {
+
+ // top switch will stop
+ if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
+ playState = STATE_STOPPED;
+ }
+ // bottom will reset
+ else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
+ reset = true;
+ runIndex = 0;
+ }
+ }
+ }
+ }
+
+ int getGateMode() {
+
+ float gate;
+
+ if (inputs[GATE_MODE_INPUT].isConnected()) {
+ float gateCV = clamp(inputs[GATE_MODE_INPUT].getVoltage(), 0.f, 5.f) / 5.f;
+ float knobAttenuation = rescale(params[GATE_MODE_PARAM].getValue(), -1.f, 8.f, 0.f, 1.f);
+ // todo: check against hardware
+ gate = rescale(gateCV * knobAttenuation, 0.f, 1.f, -1.0f, 8.f);
+ }
+ else {
+ gate = params[GATE_MODE_PARAM].getValue();
+ }
+
+ if (quadraticGatesOnly) {
+ int quadraticGateIndex = int(floor(rescale(gate, -1.f, 8.f, 0.f, 4.99f)));
+ return possibleQuadraticGates[quadraticGateIndex];
+ }
+ else {
+ return clamp((int) roundf(gate), -1, 8);
+ }
+ }
+
+ json_t* dataToJson() override {
+ json_t* rootJ = json_object();
+ json_object_set_new(rootJ, "modeCOMIO", json_integer(modeCOMIO));
+ json_object_set_new(rootJ, "quadraticGatesOnly", json_boolean(quadraticGatesOnly));
+ json_object_set_new(rootJ, "allInNormalVoltage", json_integer(allInNormalVoltage));
+ json_object_set_new(rootJ, "mainClockMultDiv", json_integer(mainClockMultDiv.multDiv));
+ json_object_set_new(rootJ, "outputClockMultDiv", json_integer(outputClockMultDiv.multDiv));
+ json_object_set_new(rootJ, "playState", json_integer(playState));
+
+ return rootJ;
+ }
+
+ void dataFromJson(json_t* rootJ) override {
+ json_t* modeJ = json_object_get(rootJ, "modeCOMIO");
+ modeCOMIO = (Muxlicer::ModeCOMIO) json_integer_value(modeJ);
+
+ json_t* quadraticJ = json_object_get(rootJ, "quadraticGatesOnly");
+ quadraticGatesOnly = json_boolean_value(quadraticJ);
+
+ json_t* allInNormalVoltageJ = json_object_get(rootJ, "allInNormalVoltage");
+ allInNormalVoltage = json_integer_value(allInNormalVoltageJ);
+
+ json_t* mainClockMultDivJ = json_object_get(rootJ, "mainClockMultDiv");
+ mainClockMultDiv.multDiv = json_integer_value(mainClockMultDivJ);
+
+ json_t* outputClockMultDivJ = json_object_get(rootJ, "outputClockMultDiv");
+ outputClockMultDiv.multDiv = json_integer_value(outputClockMultDivJ);
+
+ json_t* playStateJ = json_object_get(rootJ, "playState");
+ playState = (PlayState) json_integer_value(playStateJ);
+ }
+
+};
+
+
+struct MuxlicerWidget : ModuleWidget {
+ MuxlicerWidget(Muxlicer* module) {
+
+ setModule(module);
+ setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Muxlicer.svg")));
+
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+ addParam(createParam(mm2px(Vec(35.72963, 10.008)), module, Muxlicer::PLAY_PARAM));
+ addParam(createParam(mm2px(Vec(3.84112, 10.90256)), module, Muxlicer::ADDRESS_PARAM));
+ addParam(createParam(mm2px(Vec(67.83258, 10.86635)), module, Muxlicer::GATE_MODE_PARAM));
+ addParam(createParam(mm2px(Vec(28.12238, 24.62151)), module, Muxlicer::TAP_TEMPO_PARAM));
+ addParam(createParam(mm2px(Vec(2.32728, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 0));
+ addParam(createParam(mm2px(Vec(12.45595, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 1));
+ addParam(createParam(mm2px(Vec(22.58462, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 2));
+ addParam(createParam(mm2px(Vec(32.7133, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 3));
+ addParam(createParam(mm2px(Vec(42.74195, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 4));
+ addParam(createParam(mm2px(Vec(52.97062, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 5));
+ addParam(createParam(mm2px(Vec(63.0993, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 6));
+ addParam(createParam(mm2px(Vec(73.22797, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 7));
+
+ addInput(createInput(mm2px(Vec(51.568, 11.20189)), module, Muxlicer::GATE_MODE_INPUT));
+ addInput(createInput(mm2px(Vec(21.13974, 11.23714)), module, Muxlicer::ADDRESS_INPUT));
+ addInput(createInput(mm2px(Vec(44.24461, 24.93662)), module, Muxlicer::CLOCK_INPUT));
+ addInput(createInput(mm2px(Vec(12.62135, 24.95776)), module, Muxlicer::RESET_INPUT));
+ addInput(createInput(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_INPUT));
+ addInput(createInput(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_INPUTS + 0));
+ addInput(createInput(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_INPUTS + 1));
+ addInput(createInput(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_INPUTS + 2));
+ addInput(createInput(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_INPUTS + 3));
+ addInput(createInput(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_INPUTS + 4));
+ addInput(createInput(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_INPUTS + 5));
+ addInput(createInput(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_INPUTS + 6));
+ addInput(createInput(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_INPUTS + 7));
+ addInput(createInput(mm2px(Vec(16.11766, 98.09121)), module, Muxlicer::ALL_INPUT));
+
+ addOutput(createOutput(mm2px(Vec(59.8492, 24.95776)), module, Muxlicer::CLOCK_OUTPUT));
+ addOutput(createOutput(mm2px(Vec(56.59663, 98.06252)), module, Muxlicer::ALL_GATES_OUTPUT));
+ addOutput(createOutput(mm2px(Vec(66.72661, 98.07008)), module, Muxlicer::EOC_OUTPUT));
+ addOutput(createOutput(mm2px(Vec(0.89595, 86.78581)), module, Muxlicer::GATE_OUTPUTS + 0));
+ addOutput(createOutput(mm2px(Vec(11.02463, 86.77068)), module, Muxlicer::GATE_OUTPUTS + 1));
+ addOutput(createOutput(mm2px(Vec(21.14758, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 2));
+ addOutput(createOutput(mm2px(Vec(31.27625, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 3));
+ addOutput(createOutput(mm2px(Vec(41.40493, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 4));
+ addOutput(createOutput(mm2px(Vec(51.56803, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 5));
+ addOutput(createOutput(mm2px(Vec(61.69671, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 6));
+ addOutput(createOutput(mm2px(Vec(71.79094, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 7));
+
+ // these blocks are exclusive (for visibility / interactivity) and allows IO and OI within one module
+ addOutput(createOutput(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_OUTPUTS + 0));
+ addOutput(createOutput(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 1));
+ addOutput(createOutput(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 2));
+ addOutput(createOutput(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 3));
+ addOutput(createOutput(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 4));
+ addOutput(createOutput(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 5));
+ addOutput(createOutput(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 6));
+ addOutput(createOutput(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 7));
+ addOutput(createOutput(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_OUTPUT));
+
+ updatePortVisibilityForIOMode(Muxlicer::COM_1_IN_8_OUT);
+
+ addChild(createLight>(mm2px(Vec(71.28361, 28.02644)), module, Muxlicer::CLOCK_LIGHT));
+ addChild(createLight>(mm2px(Vec(3.99336, 81.86801)), module, Muxlicer::GATE_LIGHTS + 0));
+ addChild(createLight>(mm2px(Vec(14.09146, 81.86801)), module, Muxlicer::GATE_LIGHTS + 1));
+ addChild(createLight>(mm2px(Vec(24.22525, 81.86801)), module, Muxlicer::GATE_LIGHTS + 2));
+ addChild(createLight>(mm2px(Vec(34.35901, 81.86801)), module, Muxlicer::GATE_LIGHTS + 3));
+ addChild(createLight>(mm2px(Vec(44.49277, 81.86801)), module, Muxlicer::GATE_LIGHTS + 4));
+ addChild(createLight>(mm2px(Vec(54.62652, 81.86801)), module, Muxlicer::GATE_LIGHTS + 5));
+ addChild(createLight>(mm2px(Vec(64.76028, 81.86801)), module, Muxlicer::GATE_LIGHTS + 6));
+ addChild(createLight>(mm2px(Vec(74.89404, 81.86801)), module, Muxlicer::GATE_LIGHTS + 7));
+ }
+
+ void draw(const DrawArgs& args) override {
+ Muxlicer* module = dynamic_cast(this->module);
+
+ if (module != nullptr) {
+ updatePortVisibilityForIOMode(module->modeCOMIO);
+ }
+ else {
+ // module can be null, e.g. if populating the module browser with screenshots,
+ // in which case just assume the default (1 in, 8 out)
+ updatePortVisibilityForIOMode(Muxlicer::COM_1_IN_8_OUT);
+ }
+
+ ModuleWidget::draw(args);
+ }
+
+ struct IOMenuItem : MenuItem {
+ Muxlicer* module;
+ MuxlicerWidget* widget;
+ void onAction(const event::Action& e) override {
+ module->modeCOMIO = Muxlicer::COM_1_IN_8_OUT;
+ widget->updatePortVisibilityForIOMode(module->modeCOMIO);
+ widget->clearCables();
+ }
+ };
+ struct OIMenuItem : MenuItem {
+ Muxlicer* module;
+ MuxlicerWidget* widget;
+ void onAction(const event::Action& e) override {
+ module->modeCOMIO = Muxlicer::COM_8_IN_1_OUT;
+ widget->updatePortVisibilityForIOMode(module->modeCOMIO);
+ widget->clearCables();
+ }
+ };
+
+ struct OutputRangeChildItem : MenuItem {
+ Muxlicer* module;
+ int allInNormalVoltage;
+ void onAction(const event::Action& e) override {
+ module->allInNormalVoltage = allInNormalVoltage;
+ }
+ };
+
+ struct OutputRangeItem : MenuItem {
+ Muxlicer* module;
+
+ Menu* createChildMenu() override {
+ Menu* menu = new Menu;
+
+ std::vector voltageOptions = {1, 5, 10};
+ for (auto voltageOption : voltageOptions) {
+ OutputRangeChildItem* rangeItem = createMenuItem(std::to_string(voltageOption) + "V",
+ CHECKMARK(module->allInNormalVoltage == voltageOption));
+ rangeItem->allInNormalVoltage = voltageOption;
+ rangeItem->module = module;
+ menu->addChild(rangeItem);
+ }
+
+ return menu;
+ }
+ };
+
+ static std::vector getClockOptions() {
+ return std::vector {-16, -8, -4, -3, -2, 1, 2, 3, 4, 8, 16};
+ }
+
+ struct OutputClockScalingItem : MenuItem {
+ Muxlicer* module;
+
+ struct OutputClockScalingChildItem : MenuItem {
+ Muxlicer* module;
+ int clockOutMulDiv;
+ void onAction(const event::Action& e) override {
+ module->outputClockMultDiv.multDiv = clockOutMulDiv;
+ }
+ };
+
+ Menu* createChildMenu() override {
+ Menu* menu = new Menu;
+
+ for (auto clockOption : getClockOptions()) {
+ std::string optionString = (clockOption < 0) ? ("x 1/" + std::to_string(-clockOption)) : ("x " + std::to_string(clockOption));
+ OutputClockScalingChildItem* clockItem = createMenuItem(optionString,
+ CHECKMARK(module->outputClockMultDiv.multDiv == clockOption));
+ clockItem->clockOutMulDiv = clockOption;
+ clockItem->module = module;
+ menu->addChild(clockItem);
+ }
+
+ return menu;
+ }
+ };
+
+ struct MainClockScalingItem : MenuItem {
+ Muxlicer* module;
+
+ struct MainClockScalingChildItem : MenuItem {
+ Muxlicer* module;
+ int clockOutMulDiv;
+ void onAction(const event::Action& e) override {
+ module->mainClockMultDiv.multDiv = clockOutMulDiv;
+ }
+ };
+
+ Menu* createChildMenu() override {
+ Menu* menu = new Menu;
+
+ for (auto clockOption : getClockOptions()) {
+ std::string optionString = (clockOption < 0) ? ("x 1/" + std::to_string(-clockOption)) : ("x " + std::to_string(clockOption));
+ MainClockScalingChildItem* clockItem = createMenuItem(optionString,
+ CHECKMARK(module->mainClockMultDiv.multDiv == clockOption));
+ clockItem->clockOutMulDiv = clockOption;
+ clockItem->module = module;
+ menu->addChild(clockItem);
+ }
+
+ return menu;
+ }
+ };
+
+ struct QuadraticGatesMenuItem : MenuItem {
+ Muxlicer* module;
+ void onAction(const event::Action& e) override {
+ module->quadraticGatesOnly ^= true;
+ }
+ };
+
+ void appendContextMenu(Menu* menu) override {
+ Muxlicer* module = dynamic_cast(this->module);
+ assert(module);
+
+ menu->addChild(new MenuSeparator());
+ menu->addChild(createMenuLabel("Clock Multiplication/Division"));
+
+ MainClockScalingItem* mainClockScaleItem = createMenuItem("Input clock", "▸");
+ mainClockScaleItem->module = module;
+ menu->addChild(mainClockScaleItem);
+
+ OutputClockScalingItem* outputClockScaleItem = createMenuItem("Output clock", "▸");
+ outputClockScaleItem->module = module;
+ menu->addChild(outputClockScaleItem);
+
+ menu->addChild(new MenuSeparator());
+
+ OutputRangeItem* outputRangeItem = createMenuItem("All In Normalled Value", "▸");
+ outputRangeItem->module = module;
+ menu->addChild(outputRangeItem);
+
+ QuadraticGatesMenuItem* quadraticGatesItem = createMenuItem("Gate Mode: quadratic only", CHECKMARK(module->quadraticGatesOnly));
+ quadraticGatesItem->module = module;
+ menu->addChild(quadraticGatesItem);
+
+ menu->addChild(new MenuSeparator());
+ menu->addChild(createMenuLabel("Input/Output mode"));
+
+ IOMenuItem* ioItem = createMenuItem("1 input ▸ 8 outputs",
+ CHECKMARK(module->modeCOMIO == Muxlicer::COM_1_IN_8_OUT));
+ ioItem->module = module;
+ ioItem->widget = this;
+ menu->addChild(ioItem);
+
+ OIMenuItem* oiItem = createMenuItem("8 inputs ▸ 1 output",
+ CHECKMARK(module->modeCOMIO == Muxlicer::COM_8_IN_1_OUT));
+ oiItem->module = module;
+ oiItem->widget = this;
+ menu->addChild(oiItem);
+ }
+
+ void clearCables() {
+ for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
+ APP->scene->rack->clearCablesOnPort(outputs[i]);
+ }
+ APP->scene->rack->clearCablesOnPort(inputs[Muxlicer::COM_INPUT]);
+
+ for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
+ APP->scene->rack->clearCablesOnPort(inputs[i]);
+ }
+ APP->scene->rack->clearCablesOnPort(outputs[Muxlicer::COM_OUTPUT]);
+ }
+
+ // set ports visibility, either for 1 input -> 8 outputs or 8 inputs -> 1 output
+ void updatePortVisibilityForIOMode(Muxlicer::ModeCOMIO mode) {
+
+ bool visibleToggle = (mode == Muxlicer::COM_1_IN_8_OUT);
+
+ for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
+ outputs[i]->visible = visibleToggle;
+ }
+ inputs[Muxlicer::COM_INPUT]->visible = visibleToggle;
+
+ for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
+ inputs[i]->visible = !visibleToggle;
+ }
+ outputs[Muxlicer::COM_OUTPUT]->visible = !visibleToggle;
+ }
+
+
+};
+
+
+Model* modelMuxlicer = createModel("Muxlicer");
+
diff --git a/src/SamplingModulator.cpp b/src/SamplingModulator.cpp
index 6860ebf..dc96048 100644
--- a/src/SamplingModulator.cpp
+++ b/src/SamplingModulator.cpp
@@ -77,6 +77,7 @@ struct SamplingModulator : Module {
int currentStep = 0;
StepState stepStates[numSteps];
+ dsp::PulseGenerator triggerGenerator;
dsp::SchmittTrigger holdDetector;
dsp::SchmittTrigger clock;
dsp::MinBlepGenerator<16, 32> squareMinBlep;
@@ -171,7 +172,7 @@ struct SamplingModulator : Module {
if (stepStates[currentStep] == STATE_ON) {
const float crossing = -(oldPhase + deltaPhase - 1.0) / deltaPhase;
triggMinBlep.insertDiscontinuity(crossing, +2.f);
-
+ triggerGenerator.trigger();
if (!holdDetector.isHigh()) {
float oldHeldValue = heldValue;
heldValue = inputs[IN_INPUT].getVoltage();
@@ -186,7 +187,6 @@ struct SamplingModulator : Module {
float square = (stepPhase < 0.5) ? 2.f : 0.f;
square += squareMinBlep.process();
-
float trigger = (stepPhase < 0.5 && stepStates[currentStep] == STATE_ON) ? 2.f : 0.f;
trigger += triggMinBlep.process();
@@ -199,7 +199,12 @@ struct SamplingModulator : Module {
}
outputs[CLOCK_OUTPUT].setVoltage(5.f * square);
- outputs[TRIGG_OUTPUT].setVoltage(5.f * trigger);
+ if (params[INT_EXT_PARAM].getValue() == CLOCK_INTERNAL) {
+ outputs[TRIGG_OUTPUT].setVoltage(5.f * trigger);
+ }
+ else {
+ outputs[TRIGG_OUTPUT].setVoltage(10.f * triggerGenerator.process(args.sampleTime));
+ }
for (int i = 0; i < numSteps; i++) {
lights[STEP_LIGHT + i].setBrightness(currentStep == i);
diff --git a/src/plugin.cpp b/src/plugin.cpp
index 06943a9..1f9f5e8 100644
--- a/src/plugin.cpp
+++ b/src/plugin.cpp
@@ -21,4 +21,6 @@ void init(rack::Plugin *p) {
p->addModel(modelMorphader);
p->addModel(modelADSR);
p->addModel(modelSTMix);
+ p->addModel(modelMuxlicer);
+ p->addModel(modelMex);
}
diff --git a/src/plugin.hpp b/src/plugin.hpp
index b4e63e6..12d2ecb 100644
--- a/src/plugin.hpp
+++ b/src/plugin.hpp
@@ -21,6 +21,8 @@ extern Model* modelSamplingModulator;
extern Model* modelMorphader;
extern Model* modelADSR;
extern Model* modelSTMix;
+extern Model* modelMuxlicer;
+extern Model* modelMex;
struct Knurlie : SvgScrew {
Knurlie() {
@@ -108,6 +110,30 @@ struct Crossfader : app::SvgSlider {
}
};
+struct BefacoButton : app::SvgSwitch {
+ BefacoButton() {
+ momentary = true;
+ addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoButton_0.svg")));
+ addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoButton_1.svg")));
+ }
+};
+
+struct BefacoSwitchHorizontal : app::SvgSwitch {
+ 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")));
+ }
+};
+
+struct BefacoTinyKnobSnap : BefacoTinyKnob {
+ BefacoTinyKnobSnap() {
+ snap = true;
+ minAngle = -0.80 * M_PI;
+ maxAngle = 0.80 * M_PI;
+ }
+};
+
template
T sin2pi_pade_05_5_4(T x) {
x -= 0.5f;
@@ -176,3 +202,11 @@ struct ADEnvelope {
private:
float envLinear = 0.f;
};
+
+struct MexMessage {
+ int addressIndex = 0;
+ bool isPlaying = false;
+ float allGates = 0.f;
+ float outputClock = 0.f;
+};
+