commit d0b2a7bbcde3a48eb4a10e5d3f9dc97467ab3617
parent d361345c974c383d3d39fb5a81603f51f7a5ff6e
Author: Matt Demanett <matt@demanett.net>
Date: Fri, 15 Nov 2019 21:35:08 -0500
ADDR-SEQ, 8:1, 1:8: factor common code to base class, and add "triggered selection" mode. #72
Diffstat:
11 files changed, 208 insertions(+), 205 deletions(-)
diff --git a/README.md b/README.md
@@ -447,7 +447,9 @@ As a multiplexer, it routes an input to the output under control of the SELECT k
Both functions may be used simultaneously: the SELECT+CV value is added to the sequential/clocked value, wrapping around. Note that the STEPS value only affects the sequential value; for example, using a clock input and setting STEPS to 2 will yield an alternation between two adjacent inputs, but this pair can be selected with the SELECT knob or CV.
-On the context (right-click) menu, if option "Select on clock" is selected, then the select value (knob and CV) is checked and used to modify the active step only when a clock is received, rather than continuously.
+On the context (right-click) menu, if option "Select on clock mode" is selected, then the select value (knob and CV) is checked and used to modify the active step only when a clock is received, rather than continuously.
+
+[New in version 1.1.24:] Also on the context menu, option "Triggered select mode" changes how the SELECT feature works, replacing the continuous voltage selection with a second internal sequence that offsets (adds to) the primary sequential switch step. In this mode, the SELECT input exepcts trigger pulses, which advance the secondary sequence, while the SELECT knob sets the length of the secondary sequence (and a trigger at RESET will reset it). Thus different clocks and step lengths can be used to create complex output step patterns. "Select on clock mode" has no effect if "Triggered select mode" is enabled.
_Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphony defined by maximum of the channels present at the CLOCK and select CV inputs.
diff --git a/src/AddrSeq.cpp b/src/AddrSeq.cpp
@@ -27,29 +27,15 @@ void AddrSeq::OutputParamQuantity::setDisplayValue(float v) {
setValue(v);
}
-void AddrSeq::reset() {
- for (int i = 0; i < maxChannels; ++i) {
- _step[i] = 0;
- _clock[i].reset();
- _reset[i].reset();
- }
-}
-
-void AddrSeq::sampleRateChange() {
- for (int i = 0; i < maxChannels; ++i) {
- _timer[i].setParams(APP->engine->getSampleRate(), 0.001f);
- }
-}
-
json_t* AddrSeq::dataToJson() {
- json_t* root = SelectOnClockModule::dataToJson();
+ json_t* root = AddressableSequenceModule::dataToJson();
json_object_set_new(root, RANGE_OFFSET, json_real(_rangeOffset));
json_object_set_new(root, RANGE_SCALE, json_real(_rangeScale));
return root;
}
void AddrSeq::dataFromJson(json_t* root) {
- SelectOnClockModule::dataFromJson(root);
+ AddressableSequenceModule::dataFromJson(root);
json_t* ro = json_object_get(root, RANGE_OFFSET);
if (ro) {
@@ -67,25 +53,15 @@ int AddrSeq::channels() {
}
void AddrSeq::processChannel(const ProcessArgs& args, int c) {
- bool reset = _reset[c].process(inputs[RESET_INPUT].getVoltage());
- if (reset) {
- _timer[c].reset();
- }
- bool timer = _timer[c].next();
- bool clock = _clock[c].process(inputs[CLOCK_INPUT].getPolyVoltage(c)) && !timer;
-
- int steps = clamp(params[STEPS_PARAM].getValue(), 1.0f, 8.0f);
- int reverse = 1 - 2 * (params[DIRECTION_PARAM].getValue() == 0.0f);
- _step[c] = (_step[c] + reverse * clock) % steps;
- _step[c] += (_step[c] < 0) * steps;
- _step[c] -= _step[c] * reset;
- float select = params[SELECT_PARAM].getValue();
- select += clamp(inputs[SELECT_INPUT].getPolyVoltage(c), 0.0f, 10.0f) * 0.1f * 8.0f;
- if (!_selectOnClock || clock) {
- _select[c] = select;
- }
- int step = _step[c] + (int)_select[c];
- step = step % 8;
+ int step = nextStep(
+ c,
+ inputs[RESET_INPUT],
+ inputs[CLOCK_INPUT],
+ params[STEPS_PARAM],
+ params[DIRECTION_PARAM],
+ params[SELECT_PARAM],
+ inputs[SELECT_INPUT]
+ );
float out = params[OUT1_PARAM + step].getValue();
out += _rangeOffset;
@@ -99,7 +75,7 @@ void AddrSeq::processChannel(const ProcessArgs& args, int c) {
}
}
-struct AddrSeqWidget : SelectOnClockModuleWidget {
+struct AddrSeqWidget : AddressableSequenceModuleWidget {
static constexpr int hp = 6;
AddrSeqWidget(AddrSeq* module) {
@@ -205,7 +181,7 @@ struct AddrSeqWidget : SelectOnClockModuleWidget {
};
void appendContextMenu(Menu* menu) override {
- SelectOnClockModuleWidget::appendContextMenu(menu);
+ AddressableSequenceModuleWidget::appendContextMenu(menu);
AddrSeq* m = dynamic_cast<AddrSeq*>(module);
assert(m);
diff --git a/src/AddrSeq.hpp b/src/AddrSeq.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "bogaudio.hpp"
-#include "select_on_clock.hpp"
+#include "addressable_sequence.hpp"
#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -10,7 +10,7 @@ extern Model* modelAddrSeq;
namespace bogaudio {
-struct AddrSeq : SelectOnClockModule {
+struct AddrSeq : AddressableSequenceModule {
enum ParamsIds {
STEPS_PARAM,
DIRECTION_PARAM,
@@ -50,11 +50,6 @@ struct AddrSeq : SelectOnClockModule {
NUM_LIGHTS
};
- Trigger _clock[maxChannels];
- Trigger _reset[maxChannels];
- bogaudio::dsp::Timer _timer[maxChannels];
- int _step[maxChannels];
- float _select[maxChannels] {};
float _rangeOffset = 0.0f;
float _rangeScale = 10.0f;
@@ -76,13 +71,8 @@ struct AddrSeq : SelectOnClockModule {
configParam<OutputParamQuantity>(OUT6_PARAM, -1.0f, 1.0f, 0.0f, "Step 6", " V");
configParam<OutputParamQuantity>(OUT7_PARAM, -1.0f, 1.0f, 0.0f, "Step 7", " V");
configParam<OutputParamQuantity>(OUT8_PARAM, -1.0f, 1.0f, 0.0f, "Step 8", " V");
-
- reset();
- sampleRateChange();
}
- void reset() override;
- void sampleRateChange() override;
json_t* dataToJson() override;
void dataFromJson(json_t* root) override;
int channels() override;
diff --git a/src/EightOne.cpp b/src/EightOne.cpp
@@ -1,44 +1,20 @@
#include "EightOne.hpp"
-void EightOne::reset() {
- for (int i = 0; i < maxChannels; ++i) {
- _step[i] = 0;
- _clock[i].reset();
- _reset[i].reset();
- }
-}
-
-void EightOne::sampleRateChange() {
- for (int i = 0; i < maxChannels; ++i) {
- _timer[i].setParams(APP->engine->getSampleRate(), 0.001f);
- }
-}
-
int EightOne::channels() {
return std::max(1, std::max(inputs[CLOCK_INPUT].getChannels(), inputs[SELECT_INPUT].getChannels()));
}
void EightOne::processChannel(const ProcessArgs& args, int c) {
- bool reset = _reset[c].process(inputs[RESET_INPUT].getVoltage());
- if (reset) {
- _timer[c].reset();
- }
- bool timer = _timer[c].next();
- bool clock = _clock[c].process(inputs[CLOCK_INPUT].getPolyVoltage(c)) && !timer;
-
- int steps = clamp(params[STEPS_PARAM].getValue(), 1.0f, 8.0f);
- int reverse = 1 - 2 * (params[DIRECTION_PARAM].getValue() == 0.0f);
- _step[c] = (_step[c] + reverse * clock) % steps;
- _step[c] += (_step[c] < 0) * steps;
- _step[c] -= _step[c] * reset;
- float select = params[SELECT_PARAM].getValue();
- select += clamp(inputs[SELECT_INPUT].getPolyVoltage(c), 0.0f, 10.0f) * 0.1f * 8.0f;
- if (!_selectOnClock || clock) {
- _select[c] = select;
- }
- int step = _step[c] + (int)_select[c];
- step = step % 8;
+ int step = nextStep(
+ c,
+ inputs[RESET_INPUT],
+ inputs[CLOCK_INPUT],
+ params[STEPS_PARAM],
+ params[DIRECTION_PARAM],
+ params[SELECT_PARAM],
+ inputs[SELECT_INPUT]
+ );
Input& in = inputs[IN1_INPUT + step];
if (_channels > 1) {
@@ -59,7 +35,7 @@ void EightOne::processChannel(const ProcessArgs& args, int c) {
}
}
-struct EightOneWidget : SelectOnClockModuleWidget {
+struct EightOneWidget : AddressableSequenceModuleWidget {
static constexpr int hp = 6;
EightOneWidget(EightOne* module) {
diff --git a/src/EightOne.hpp b/src/EightOne.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "bogaudio.hpp"
-#include "select_on_clock.hpp"
+#include "addressable_sequence.hpp"
#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -10,7 +10,7 @@ extern Model* modelEightOne;
namespace bogaudio {
-struct EightOne : SelectOnClockModule {
+struct EightOne : AddressableSequenceModule {
enum ParamsIds {
STEPS_PARAM,
DIRECTION_PARAM,
@@ -50,24 +50,13 @@ struct EightOne : SelectOnClockModule {
NUM_LIGHTS
};
- Trigger _clock[maxChannels];
- Trigger _reset[maxChannels];
- bogaudio::dsp::Timer _timer[maxChannels];
- int _step[maxChannels];
- float _select[maxChannels] {};
-
EightOne() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(STEPS_PARAM, 1.0f, 8.0f, 8.0f, "Steps");
configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Direction");
configParam(SELECT_PARAM, 0.0f, 7.0f, 0.0f, "Select step");
-
- reset();
- sampleRateChange();
}
- void reset() override;
- void sampleRateChange() override;
int channels() override;
void processChannel(const ProcessArgs& args, int c) override;
};
diff --git a/src/OneEight.cpp b/src/OneEight.cpp
@@ -1,44 +1,20 @@
#include "OneEight.hpp"
-void OneEight::reset() {
- for (int i = 0; i < maxChannels; ++i) {
- _step[i] = 0;
- _clock[i].reset();
- _reset[i].reset();
- }
-}
-
-void OneEight::sampleRateChange() {
- for (int i = 0; i < maxChannels; ++i) {
- _timer[i].setParams(APP->engine->getSampleRate(), 0.001f);
- }
-}
-
int OneEight::channels() {
return std::max(1, std::max(inputs[CLOCK_INPUT].getChannels(), inputs[SELECT_INPUT].getChannels()));
}
void OneEight::processChannel(const ProcessArgs& args, int c) {
- bool reset = _reset[c].process(inputs[RESET_INPUT].getVoltage());
- if (reset) {
- _timer[c].reset();
- }
- bool timer = _timer[c].next();
- bool clock = _clock[c].process(inputs[CLOCK_INPUT].getPolyVoltage(c)) && !timer;
-
- int steps = clamp(params[STEPS_PARAM].getValue(), 1.0f, 8.0f);
- int reverse = 1 - 2 * (params[DIRECTION_PARAM].getValue() == 0.0f);
- _step[c] = (_step[c] + reverse * clock) % steps;
- _step[c] += (_step[c] < 0) * steps;
- _step[c] -= _step[c] * reset;
- float select = params[SELECT_PARAM].getValue();
- select += clamp(inputs[SELECT_INPUT].getPolyVoltage(c), 0.0f, 10.0f) * 0.1f * 8.0f;
- if (!_selectOnClock || clock) {
- _select[c] = select;
- }
- int step = _step[c] + (int)_select[c];
- step = step % 8;
+ int step = nextStep(
+ c,
+ inputs[RESET_INPUT],
+ inputs[CLOCK_INPUT],
+ params[STEPS_PARAM],
+ params[DIRECTION_PARAM],
+ params[SELECT_PARAM],
+ inputs[SELECT_INPUT]
+ );
if (_channels > 1) {
float in = inputs[IN_INPUT].getPolyVoltage(c) + !inputs[IN_INPUT].isConnected() * 10.0f;
@@ -70,7 +46,7 @@ void OneEight::processChannel(const ProcessArgs& args, int c) {
}
}
-struct OneEightWidget : SelectOnClockModuleWidget {
+struct OneEightWidget : AddressableSequenceModuleWidget {
static constexpr int hp = 6;
OneEightWidget(OneEight* module) {
diff --git a/src/OneEight.hpp b/src/OneEight.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "bogaudio.hpp"
-#include "select_on_clock.hpp"
+#include "addressable_sequence.hpp"
#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -10,7 +10,7 @@ extern Model* modelOneEight;
namespace bogaudio {
-struct OneEight : SelectOnClockModule {
+struct OneEight : AddressableSequenceModule {
enum ParamsIds {
STEPS_PARAM,
DIRECTION_PARAM,
@@ -50,24 +50,13 @@ struct OneEight : SelectOnClockModule {
NUM_LIGHTS
};
- Trigger _clock[maxChannels];
- Trigger _reset[maxChannels];
- bogaudio::dsp::Timer _timer[maxChannels];
- int _step[maxChannels];
- float _select[maxChannels] {};
-
OneEight() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(STEPS_PARAM, 1.0f, 8.0f, 8.0f, "Steps");
configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Direction");
configParam(SELECT_PARAM, 0.0f, 7.0f, 0.0f, "Select step");
-
- reset();
- sampleRateChange();
}
- void reset() override;
- void sampleRateChange() override;
int channels() override;
void processChannel(const ProcessArgs& args, int c) override;
};
diff --git a/src/addressable_sequence.cpp b/src/addressable_sequence.cpp
@@ -0,0 +1,79 @@
+
+#include "addressable_sequence.hpp"
+
+#define SELECT_ON_CLOCK "select_on_clock"
+#define TRIGGERED_SELECT "triggered_select"
+
+void AddressableSequenceModule::reset() {
+ for (int i = 0; i < maxChannels; ++i) {
+ _step[i] = 0;
+ _select[i] = 0;
+ _clock[i].reset();
+ _reset[i].reset();
+ _selectTrigger[i].reset();
+ }
+}
+
+void AddressableSequenceModule::sampleRateChange() {
+ float sr = APP->engine->getSampleRate();
+ for (int i = 0; i < maxChannels; ++i) {
+ _timer[i].setParams(sr, 0.001f);
+ }
+}
+
+json_t* AddressableSequenceModule::dataToJson() {
+ json_t* root = json_object();
+ json_object_set_new(root, SELECT_ON_CLOCK, json_boolean(_selectOnClock));
+ json_object_set_new(root, TRIGGERED_SELECT, json_boolean(_triggeredSelect));
+ return root;
+}
+
+void AddressableSequenceModule::dataFromJson(json_t* root) {
+ json_t* s = json_object_get(root, SELECT_ON_CLOCK);
+ if (s) {
+ _selectOnClock = json_is_true(s);
+ }
+
+ json_t* t = json_object_get(root, TRIGGERED_SELECT);
+ if (t) {
+ _triggeredSelect = json_is_true(t);
+ }
+}
+
+int AddressableSequenceModule::nextStep(
+ int c,
+ Input& resetInput,
+ Input& clockInput,
+ Param& stepsParam,
+ Param& directionParam,
+ Param& selectParam,
+ Input& selectInput
+) {
+ bool reset = _reset[c].process(resetInput.getVoltage());
+ if (reset) {
+ _timer[c].reset();
+ }
+ bool timer = _timer[c].next();
+ bool clock = _clock[c].process(clockInput.getPolyVoltage(c)) && !timer;
+
+ int steps = clamp(stepsParam.getValue(), 1.0f, 8.0f);
+ int reverse = 1 - 2 * (directionParam.getValue() == 0.0f);
+ _step[c] = (_step[c] + reverse * clock) % steps;
+ _step[c] += (_step[c] < 0) * steps;
+ _step[c] -= _step[c] * reset;
+
+ float select = selectParam.getValue();
+ if (_triggeredSelect) {
+ if (_selectTrigger[c].process(selectInput.getPolyVoltage(c))) {
+ _select[c] = (1 + (int)_select[c]) % ((int)select + 1);
+ }
+ _select[c] -= _select[c] * reset;
+ }
+ else {
+ select += clamp(selectInput.getPolyVoltage(c), 0.0f, 10.0f) * 0.1f * 8.0f;
+ if (!_selectOnClock || clock) {
+ _select[c] = select;
+ }
+ }
+ return (_step[c] + (int)_select[c]) % 8;
+}
diff --git a/src/addressable_sequence.hpp b/src/addressable_sequence.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "bogaudio.hpp"
+
+using namespace rack;
+
+namespace bogaudio {
+
+struct AddressableSequenceModule : BGModule {
+ Trigger _clock[maxChannels];
+ Trigger _reset[maxChannels];
+ Trigger _selectTrigger[maxChannels];
+ bogaudio::dsp::Timer _timer[maxChannels];
+ int _step[maxChannels];
+ float _select[maxChannels] {};
+ bool _selectOnClock = false;
+ bool _triggeredSelect = false;
+
+ AddressableSequenceModule() {
+ reset();
+ sampleRateChange();
+ }
+
+ void reset() override;
+ void sampleRateChange() override;
+ json_t* dataToJson() override;
+ void dataFromJson(json_t* root) override;
+ int nextStep(
+ int c,
+ Input& resetInput,
+ Input& clockInput,
+ Param& stepsParam,
+ Param& directionParam,
+ Param& selectParam,
+ Input& selectInput
+ );
+};
+
+struct AddressableSequenceModuleWidget : ModuleWidget {
+ struct SelectOnClockMenuItem : MenuItem {
+ AddressableSequenceModule* _module;
+
+ SelectOnClockMenuItem(AddressableSequenceModule* module, const char* label)
+ : _module(module)
+ {
+ this->text = label;
+ }
+
+ void onAction(const event::Action& e) override {
+ _module->_selectOnClock = !_module->_selectOnClock;
+ }
+
+ void step() override {
+ MenuItem::step();
+ rightText = _module->_selectOnClock ? "✔" : "";
+ }
+ };
+
+ struct TriggeredSelectMenuItem : MenuItem {
+ AddressableSequenceModule* _module;
+
+ TriggeredSelectMenuItem(AddressableSequenceModule* module, const char* label)
+ : _module(module)
+ {
+ this->text = label;
+ }
+
+ void onAction(const event::Action& e) override {
+ _module->_triggeredSelect = !_module->_triggeredSelect;
+ }
+
+ void step() override {
+ MenuItem::step();
+ rightText = _module->_triggeredSelect ? "✔" : "";
+ }
+ };
+
+ void appendContextMenu(Menu* menu) override {
+ AddressableSequenceModule* m = dynamic_cast<AddressableSequenceModule*>(module);
+ assert(m);
+ menu->addChild(new MenuLabel());
+ menu->addChild(new SelectOnClockMenuItem(m, "Select on clock mode"));
+ menu->addChild(new TriggeredSelectMenuItem(m, "Triggered select mode"));
+ }
+};
+
+} // namespace bogaudio
diff --git a/src/select_on_clock.cpp b/src/select_on_clock.cpp
@@ -1,17 +0,0 @@
-
-#include "select_on_clock.hpp"
-
-#define SELECT_ON_CLOCK "select_on_clock"
-
-json_t* SelectOnClockModule::dataToJson() {
- json_t* root = json_object();
- json_object_set_new(root, SELECT_ON_CLOCK, json_boolean(_selectOnClock));
- return root;
-}
-
-void SelectOnClockModule::dataFromJson(json_t* root) {
- json_t* s = json_object_get(root, SELECT_ON_CLOCK);
- if (s) {
- _selectOnClock = json_is_true(s);
- }
-}
diff --git a/src/select_on_clock.hpp b/src/select_on_clock.hpp
@@ -1,44 +0,0 @@
-#pragma once
-
-#include "bogaudio.hpp"
-
-using namespace rack;
-
-namespace bogaudio {
-
-struct SelectOnClockModule : BGModule {
- bool _selectOnClock = false;
-
- json_t* dataToJson() override;
- void dataFromJson(json_t* root) override;
-};
-
-struct SelectOnClockModuleWidget : ModuleWidget {
- struct SelectOnClockMenuItem : MenuItem {
- SelectOnClockModule* _module;
-
- SelectOnClockMenuItem(SelectOnClockModule* module, const char* label)
- : _module(module)
- {
- this->text = label;
- }
-
- void onAction(const event::Action& e) override {
- _module->_selectOnClock = !_module->_selectOnClock;
- }
-
- void step() override {
- MenuItem::step();
- rightText = _module->_selectOnClock ? "✔" : "";
- }
- };
-
- void appendContextMenu(Menu* menu) override {
- SelectOnClockModule* m = dynamic_cast<SelectOnClockModule*>(module);
- assert(m);
- menu->addChild(new MenuLabel());
- menu->addChild(new SelectOnClockMenuItem(m, "Select on clock"));
- }
-};
-
-} // namespace bogaudio