diff --git a/res/Muxlicer.svg b/res/Muxlicer.svg
index 4ff8734..e70ac12 100644
--- a/res/Muxlicer.svg
+++ b/res/Muxlicer.svg
@@ -68,9 +68,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="1.4"
- inkscape:cx="199.2469"
- inkscape:cy="196.57393"
+ inkscape:zoom="3.959798"
+ inkscape:cx="135.50855"
+ inkscape:cy="65.391117"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
@@ -1365,6 +1365,77 @@
style="display:inline;fill:none;stroke:#ffffff;stroke-width:0.33399057px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new"
d="m 39.016225,193.94652 a 7.3426011,7.3441482 0 0 1 0.0046,7.33713"
id="path8000" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
getClockOptions() {
+ return std::vector {-16, -8, -4, -3, -2, 1, 2, 3, 4, 8, 16};
+}
+
+std::string getClockOptionString(const int clockOption) {
+ return (clockOption < 0) ? ("x 1/" + std::to_string(-clockOption)) : ("x " + std::to_string(clockOption));
+}
+
struct Muxlicer : Module {
enum ParamIds {
PLAY_PARAM,
@@ -247,6 +255,7 @@ struct Muxlicer : Module {
uint32_t runIndex; // which step are we on (0 to 7)
uint32_t addressIndex = 0;
bool reset = false;
+ bool tapped = 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
@@ -257,24 +266,39 @@ struct Muxlicer : Module {
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 resetTimer; // leave a grace period before advancing the step
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)
+ bool usingExternalClock = false; // is there a clock plugged into clock in (external) or not (internal)
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
+ ModeCOMIO modeCOMIO = COM_8_IN_1_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
+ struct TapTempoKnobParamQuantity : ParamQuantity {
+ std::string getDisplayValueString() override {
+ if (module != nullptr) {
+ const std::vector clockOptions = getClockOptions();
+ const int clockOptionIndex = clamp(int(ParamQuantity::getValue()), 0, clockOptions.size());
+ return getClockOptionString(clockOptions[clockOptionIndex]);
+ }
+ else {
+ return "";
+ }
+ }
+ };
+
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");
+ const int numClockOptions = getClockOptions().size();
+ configParam(Muxlicer::TAP_TEMPO_PARAM, 0, numClockOptions - 1, numClockOptions / 2, "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));
@@ -291,7 +315,7 @@ struct Muxlicer : Module {
void process(const ProcessArgs& args) override {
- const bool usingExternalClock = inputs[CLOCK_INPUT].isConnected();
+ usingExternalClock = inputs[CLOCK_INPUT].isConnected();
bool externalClockPulseReceived = false;
// a clock pulse does two things: 1) sets the internal clock (based on timing between two pulses), which
@@ -300,10 +324,15 @@ struct Muxlicer : Module {
externalClockPulseReceived = true;
}
// this can also be sent by tap tempo
- else if (!usingExternalClock && tapTempoTrigger.process(params[TAP_TEMPO_PARAM].getValue())) {
+ else if (!usingExternalClock && tapTempoTrigger.process(tapped)) {
externalClockPulseReceived = true;
+ tapped = false;
}
+ const std::vector clockOptions = getClockOptions();
+ const int clockOptionFromDial = clockOptions[int(params[TAP_TEMPO_PARAM].getValue())];
+ mainClockMultDiv.multDiv = clockOptionFromDial;
+
if (resetTrigger.process(rescale(inputs[RESET_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
reset = true;
if (playState == STATE_STOPPED) {
@@ -350,11 +379,13 @@ struct Muxlicer : Module {
if (reset) {
runIndex = 0;
reset = false;
+ resetTimer.trigger();
}
+ // see https://vcvrack.com/manual/VoltageStandards#Timing
+ const bool resetGracePeriodActive = resetTimer.process(args.sampleTime);
if (dividedMultipliedClockPulseReceived) {
-
- if (isSequenceAdvancing) {
+ if (isSequenceAdvancing && !resetGracePeriodActive) {
runIndex++;
if (runIndex >= 8) {
// both play modes will reset to step 0 and fire an EOC trigger
@@ -432,7 +463,7 @@ struct Muxlicer : Module {
const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
for (int c = 0; c < numActiveEngines; c += 4) {
const float_4 allInValue = inputs[ALL_INPUT].getNormalPolyVoltageSimd((float_4) allInNormalVoltage, c);
- const float_4 stepValue = inputs[MUX_INPUTS + addressIndex].getNormalPolyVoltageSimd(allInValue, c) * stepVolume;
+ const float_4 stepValue = inputs[MUX_INPUTS + addressIndex].getNormalPolyVoltageSimd(allInValue, c) * stepVolume;
outputs[COM_OUTPUT].setVoltageSimd(stepValue, c);
}
outputs[COM_OUTPUT].setChannels(numActiveEngines);
@@ -560,6 +591,7 @@ struct Muxlicer : Module {
};
+
struct MuxlicerWidget : ModuleWidget {
MuxlicerWidget(Muxlicer* module) {
@@ -574,7 +606,8 @@ struct MuxlicerWidget : ModuleWidget {
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(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));
@@ -622,7 +655,7 @@ struct MuxlicerWidget : ModuleWidget {
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);
+ updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_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));
@@ -643,8 +676,8 @@ struct MuxlicerWidget : ModuleWidget {
}
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);
+ // in which case just assume the default (8 in, 1 out)
+ updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_OUT);
}
ModuleWidget::draw(args);
@@ -696,9 +729,7 @@ struct MuxlicerWidget : ModuleWidget {
}
};
- static std::vector getClockOptions() {
- return std::vector {-16, -8, -4, -3, -2, 1, 2, 3, 4, 8, 16};
- }
+
struct OutputClockScalingItem : MenuItem {
Muxlicer* module;
@@ -714,8 +745,8 @@ struct MuxlicerWidget : ModuleWidget {
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));
+ for (int clockOption : getClockOptions()) {
+ std::string optionString = getClockOptionString(clockOption);
OutputClockScalingChildItem* clockItem = createMenuItem(optionString,
CHECKMARK(module->outputClockMultDiv.multDiv == clockOption));
clockItem->clockOutMulDiv = clockOption;
@@ -732,22 +763,28 @@ struct MuxlicerWidget : ModuleWidget {
struct MainClockScalingChildItem : MenuItem {
Muxlicer* module;
- int clockOutMulDiv;
+ int mainClockMulDiv, mainClockMulDivIndex;
+
void onAction(const event::Action& e) override {
- module->mainClockMultDiv.multDiv = clockOutMulDiv;
+ module->mainClockMultDiv.multDiv = mainClockMulDiv;
+ module->params[Muxlicer::TAP_TEMPO_PARAM].setValue(mainClockMulDivIndex);
}
};
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));
+ int i = 0;
+ for (int clockOption : getClockOptions()) {
+ std::string optionString = getClockOptionString(clockOption);
MainClockScalingChildItem* clockItem = createMenuItem(optionString,
CHECKMARK(module->mainClockMultDiv.multDiv == clockOption));
- clockItem->clockOutMulDiv = clockOption;
+
+ clockItem->mainClockMulDiv = clockOption;
+ clockItem->mainClockMulDivIndex = i;
clockItem->module = module;
menu->addChild(clockItem);
+ ++i;
}
return menu;
@@ -761,6 +798,14 @@ struct MuxlicerWidget : ModuleWidget {
}
};
+ struct TapTempoItem : MenuItem {
+ Muxlicer* module;
+ void onAction(const event::Action& e) override {
+ module->tapped = true;
+ e.consume(NULL);
+ }
+ };
+
void appendContextMenu(Menu* menu) override {
Muxlicer* module = dynamic_cast(this->module);
assert(module);
@@ -768,6 +813,17 @@ struct MuxlicerWidget : ModuleWidget {
menu->addChild(new MenuSeparator());
menu->addChild(createMenuLabel("Clock Multiplication/Division"));
+ if (module->usingExternalClock) {
+ menu->addChild(createMenuLabel("Using external clock"));
+
+ }
+ else {
+ TapTempoItem* tapTempoItem = createMenuItem("Tap to set internal tempo...");
+ tapTempoItem->module = module;
+ menu->addChild(tapTempoItem);
+ }
+
+
MainClockScalingItem* mainClockScaleItem = createMenuItem("Input clock", "▸");
mainClockScaleItem->module = module;
menu->addChild(mainClockScaleItem);