commit c4dc8c4a069f0c092c5cd3af439886f3e7b04afe
parent d204c00c6b918a84d3d54aa4d1419dd8e6e17378
Author: Matt Demanett <matt@demanett.net>
Date: Thu, 9 Jul 2020 23:27:01 -0400
WALK, WALK2: add modes to turn "jump" into sample or track and hold. On WALK2, track mouse drags, not just clicks, to force the output position -- turns the module into an X/Y controller.
Diffstat:
7 files changed, 215 insertions(+), 24 deletions(-)
diff --git a/plugin.json b/plugin.json
@@ -354,10 +354,12 @@
{
"slug": "Bogaudio-Walk2",
"name": "WALK2",
- "description": "2-channel random-walk CV source",
+ "description": "2D random-walk and X/Y controller",
"manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#walk2",
"tags": [
- "Random"
+ "Random",
+ "Sample and hold",
+ "Controller"
]
},
{
@@ -367,6 +369,7 @@
"manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#walk",
"tags": [
"Random",
+ "Sample and hold",
"Polyphonic"
]
},
diff --git a/res-src/Walk2-src.svg b/res-src/Walk2-src.svg
@@ -83,6 +83,16 @@
</g>
</symbol>
+ <symbol id="button-small" viewBox="0 0 9px 9px">
+ <g transform="translate(4.5 4.5)">
+ <circle r="4" stroke-width="1" stroke="#00f" fill="#f00" />
+ </g>
+ </symbol>
+
+ <symbol id="light-small" viewBox="0 0 6.4px 6.4px">
+ <rect width="6.4" height="6.4" fill="#0f0" />
+ </symbol>
+
<symbol id="input" viewBox="0 0 24px 24px">
<g transform="translate(12 12)">
<circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" />
@@ -206,13 +216,30 @@
<text font-size="6pt" letter-spacing="1.5px" transform="translate(37 75)">OUT</text>
</g>
- <g transform="translate(74.5 300)">
+ <g transform="translate(74.5 288)">
<rect width="61" height="40" rx="5" fill="#fafafa" />
<rect width="22" height="40" rx="5" fill="#bbb" transform="translate(40)" />
<rect width="20" height="40" fill="#bbb" transform="translate(31)" />
<use id="JUMP_INPUT" xlink:href="#input" transform="translate(3.5 3)" />
<use id="DISTANCE_OUTPUT" xlink:href="#output" transform="translate(34.5 3)" />
- <text font-size="6pt" letter-spacing="1.5px" transform="translate(2 36)">JUMP</text>
+ <text font-size="6pt" letter-spacing="1.5px" transform="translate(4 36)">TRIG</text>
<text font-size="6pt" letter-spacing="1.5px" transform="translate(35.5 36)">DIST</text>
</g>
+
+ <g transform="translate(96 332)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(-10 24.5) rotate(-90)">TRIG</text>
+ <g transform="translate(2 0)">
+ <use id="JUMP_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="1px" transform="translate(1 6.7)">JUMP</text>
+ </g>
+ <g transform="translate(2 10)">
+ <use id="SAMPLEHOLD_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="0.5px" transform="translate(1 6.7)">S&H</text>
+ </g>
+ <g transform="translate(2 20)">
+ <use id="TRACKHOLD_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="1px" transform="translate(1 6.7)">T&H</text>
+ </g>
+ <use id="JUMP_MODE_PARAM" xlink:href="#button-small" transform="translate(26 9.7)" />
+ </g>
</svg>
diff --git a/res/Walk2.svg b/res/Walk2.svg
Binary files differ.
diff --git a/src/Walk.cpp b/src/Walk.cpp
@@ -2,6 +2,10 @@
#include "Walk.hpp"
#define POLY_INPUT "poly_input"
+#define JUMP_MODE "jump_mode"
+#define JUMP_MODE_JUMP "jump"
+#define JUMP_MODE_TRACKHOLD "track_and_hold"
+#define JUMP_MODE_SAMPLEHOLD "sample_and_hold"
void Walk::reset() {
for (int i = 0; i < maxChannels; ++i) {
@@ -18,6 +22,20 @@ void Walk::sampleRateChange() {
json_t* Walk::dataToJson() {
json_t* root = json_object();
json_object_set_new(root, POLY_INPUT, json_integer(_polyInputID));
+ switch (_jumpMode) {
+ case JUMP_JUMPMODE: {
+ json_object_set_new(root, JUMP_MODE, json_string(JUMP_MODE_JUMP));
+ break;
+ }
+ case TRACKHOLD_JUMPMODE: {
+ json_object_set_new(root, JUMP_MODE, json_string(JUMP_MODE_TRACKHOLD));
+ break;
+ }
+ case SAMPLEHOLD_JUMPMODE: {
+ json_object_set_new(root, JUMP_MODE, json_string(JUMP_MODE_SAMPLEHOLD));
+ break;
+ }
+ }
return root;
}
@@ -26,6 +44,19 @@ void Walk::dataFromJson(json_t* root) {
if (p) {
_polyInputID = json_integer_value(p);
}
+
+ json_t* jm = json_object_get(root, JUMP_MODE);
+ if (jm) {
+ if (strcmp(json_string_value(jm), JUMP_MODE_JUMP) == 0) {
+ _jumpMode = JUMP_JUMPMODE;
+ }
+ else if (strcmp(json_string_value(jm), JUMP_MODE_TRACKHOLD) == 0) {
+ _jumpMode = TRACKHOLD_JUMPMODE;
+ }
+ else if (strcmp(json_string_value(jm), JUMP_MODE_SAMPLEHOLD) == 0) {
+ _jumpMode = SAMPLEHOLD_JUMPMODE;
+ }
+ }
}
int Walk::channels() {
@@ -57,13 +88,40 @@ void Walk::modulateChannel(int c) {
}
void Walk::processChannel(const ProcessArgs& args, int c) {
- if (_jumpTrigger[c].process(inputs[JUMP_INPUT].getPolyVoltage(c))) {
- _walk[c].jump();
+ float triggered = _jumpTrigger[c].process(inputs[JUMP_INPUT].getPolyVoltage(c));
+ float out = _walk[c].next();
+
+ switch (_jumpMode) {
+ case JUMP_JUMPMODE: {
+ if (triggered) {
+ _walk[c].jump();
+ }
+ break;
+ }
+ case TRACKHOLD_JUMPMODE: {
+ if (_jumpTrigger[c].isHigh()) {
+ _lastOut[c] = out;
+ }
+ else {
+ out = _lastOut[c];
+ }
+ break;
+ }
+ case SAMPLEHOLD_JUMPMODE: {
+ if (triggered) {
+ _lastOut[c] = out;
+ }
+ else {
+ out = _lastOut[c];
+ }
+ break;
+ }
}
- float out = _slew[c].next(_walk[c].next());
+ out = _slew[c].next(out);
out *= _scale[c];
out += _offset[c];
+
outputs[OUT_OUTPUT].setChannels(_channels);
outputs[OUT_OUTPUT].setVoltage(out, c);
}
@@ -114,13 +172,20 @@ struct WalkWidget : ModuleWidget {
auto m = dynamic_cast<Walk*>(module);
assert(m);
menu->addChild(new MenuLabel());
+
OptionsMenuItem* p = new OptionsMenuItem("Polyphony channels from");
p->addItem(OptionMenuItem("RATE input", [m]() { return m->_polyInputID == Walk::RATE_INPUT; }, [m]() { m->_polyInputID = Walk::RATE_INPUT; }));
p->addItem(OptionMenuItem("OFFSET input", [m]() { return m->_polyInputID == Walk::OFFSET_INPUT; }, [m]() { m->_polyInputID = Walk::OFFSET_INPUT; }));
p->addItem(OptionMenuItem("SCALE input", [m]() { return m->_polyInputID == Walk::SCALE_INPUT; }, [m]() { m->_polyInputID = Walk::SCALE_INPUT; }));
p->addItem(OptionMenuItem("JUMP input", [m]() { return m->_polyInputID == Walk::JUMP_INPUT; }, [m]() { m->_polyInputID = Walk::JUMP_INPUT; }));
OptionsMenuItem::addToMenu(p, menu);
+
+ OptionsMenuItem* jm = new OptionsMenuItem("Jump input action");
+ jm->addItem(OptionMenuItem("Jump", [m]() { return m->_jumpMode == Walk::JUMP_JUMPMODE; }, [m]() { m->_jumpMode = Walk::JUMP_JUMPMODE; }));
+ jm->addItem(OptionMenuItem("Sample and hold", [m]() { return m->_jumpMode == Walk::SAMPLEHOLD_JUMPMODE; }, [m]() { m->_jumpMode = Walk::SAMPLEHOLD_JUMPMODE; }));
+ jm->addItem(OptionMenuItem("Track and hold", [m]() { return m->_jumpMode == Walk::TRACKHOLD_JUMPMODE; }, [m]() { m->_jumpMode = Walk::TRACKHOLD_JUMPMODE; }));
+ OptionsMenuItem::addToMenu(jm, menu);
}
};
-Model* modelWalk = bogaudio::createModel<Walk, WalkWidget>("Bogaudio-Walk", "WALK", "Random-walk CV source", "Random", "Polyphonic");
+Model* modelWalk = bogaudio::createModel<Walk, WalkWidget>("Bogaudio-Walk", "WALK", "Random-walk CV source", "Random", "Sample and hold", "Polyphonic");
diff --git a/src/Walk.hpp b/src/Walk.hpp
@@ -31,12 +31,20 @@ struct Walk : BGModule {
NUM_OUTPUTS
};
+ enum JumpMode {
+ JUMP_JUMPMODE,
+ TRACKHOLD_JUMPMODE,
+ SAMPLEHOLD_JUMPMODE
+ };
+
float _offset[maxChannels] {};
float _scale[maxChannels] {};
Trigger _jumpTrigger[maxChannels];
RandomWalk _walk[maxChannels];
bogaudio::dsp::SlewLimiter _slew[maxChannels];
+ float _lastOut[maxChannels] {};
int _polyInputID = RATE_INPUT;
+ JumpMode _jumpMode = JUMP_JUMPMODE;
Walk() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
diff --git a/src/Walk2.cpp b/src/Walk2.cpp
@@ -82,32 +82,85 @@ void Walk2::modulate() {
if (inputs[SCALE_Y_INPUT].isConnected()) {
_scaleY *= clamp(inputs[SCALE_Y_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
}
+
+ float jm = clamp(params[JUMP_MODE_PARAM].getValue(), 0.0f, 2.0f);
+ if (jm > 1.5f) {
+ _jumpMode = Walk::TRACKHOLD_JUMPMODE;
+ }
+ else if (jm > 0.5f) {
+ _jumpMode = Walk::SAMPLEHOLD_JUMPMODE;
+ }
+ else {
+ _jumpMode = Walk::JUMP_JUMPMODE;
+ }
+}
+
+void Walk2::processAlways(const ProcessArgs& args) {
+ lights[JUMP_LIGHT].value = _jumpMode == Walk::JUMP_JUMPMODE;
+ lights[SAMPLEHOLD_LIGHT].value = _jumpMode == Walk::SAMPLEHOLD_JUMPMODE;
+ lights[TRACKHOLD_LIGHT].value = _jumpMode == Walk::TRACKHOLD_JUMPMODE;
}
void Walk2::processChannel(const ProcessArgs& args, int _c) {
Vec* jumpTo = _jumpTo;
if (jumpTo != NULL) {
_jumpTo = NULL;
+ _lastOutX = jumpTo->x;
_walkX.tell(jumpTo->x);
+ _lastOutY = jumpTo->y;
_walkY.tell(jumpTo->y);
delete jumpTo;
}
- else if (_jumpTrigger.process(inputs[JUMP_INPUT].getVoltage())) {
- _walkX.jump();
- _walkY.jump();
+
+ bool triggered = _jumpTrigger.process(inputs[JUMP_INPUT].getVoltage());
+ float outX = _walkX.next();
+ float outY = _walkY.next();
+
+ switch (_jumpMode) {
+ case Walk::JUMP_JUMPMODE: {
+ if (triggered) {
+ _walkX.jump();
+ _walkY.jump();
+ }
+ break;
+ }
+ case Walk::TRACKHOLD_JUMPMODE: {
+ if (_jumpTrigger.isHigh()) {
+ _lastOutX = outX;
+ _lastOutY = outY;
+ }
+ else {
+ outX = _lastOutX;
+ outY = _lastOutY;
+ }
+ break;
+ }
+ case Walk::SAMPLEHOLD_JUMPMODE: {
+ if (triggered) {
+ _lastOutX = outX;
+ _lastOutY = outY;
+ }
+ else {
+ outX = _lastOutX;
+ outY = _lastOutY;
+ }
+ break;
+ }
}
- float outX = _slewX.next(_walkX.next());
+ outX = _slewX.next(outX);
outX *= _scaleX;
outX += _offsetX;
outputs[OUT_X_OUTPUT].setVoltage(outX);
- float outY = _slewY.next(_walkY.next());
+ outY = _slewY.next(outY);
outY *= _scaleY;
outY += _offsetY;
outputs[OUT_Y_OUTPUT].setVoltage(outY);
- outputs[DISTANCE_OUTPUT].setVoltage(sqrtf(outX*outX + outY*outY) * 0.707107f); // scaling constant is 10 / squrt(200)
+ if (outputs[DISTANCE_OUTPUT].isConnected()) {
+ outputs[DISTANCE_OUTPUT].setVoltage(sqrtf(outX*outX + outY*outY) * 0.707107f); // scaling constant is 10 / squrt(200)
+ }
if (_historyStep == 0) {
_outsX.push(outX);
@@ -129,6 +182,7 @@ struct Walk2Display : TransparentWidget {
int _midX, _midY;
std::shared_ptr<Font> _font;
NVGcolor _traceColor = _defaultTraceColor;
+ Vec _dragLast;
Walk2Display(
Walk2* module,
@@ -147,16 +201,27 @@ struct Walk2Display : TransparentWidget {
if (!(e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0)) {
return;
}
+ e.consume(this);
+ _dragLast = e.pos;
+ maybeJump(e.pos);
+ }
+
+ void onDragMove(const event::DragMove& e) override {
+ _dragLast.x += e.mouseDelta.x;
+ _dragLast.y += e.mouseDelta.y;
+ maybeJump(_dragLast);
+ }
+ void maybeJump(Vec pos) {
if (
- e.pos.x > _insetAround &&
- e.pos.x < _size.x - _insetAround &&
- e.pos.y > _insetAround &&
- e.pos.y < _size.y - _insetAround
+ pos.x > _insetAround &&
+ pos.x < _size.x - _insetAround &&
+ pos.y > _insetAround &&
+ pos.y < _size.y - _insetAround
) {
- float x = 20.0f * ((e.pos.x - _insetAround) / (float)_drawSize.x);
+ float x = 20.0f * ((pos.x - _insetAround) / (float)_drawSize.x);
x -= 5.0f;
- float y = 20.0f * ((e.pos.y - _insetAround) / (float)_drawSize.y);
+ float y = 20.0f * ((pos.y - _insetAround) / (float)_drawSize.y);
y = 5.0f - y;
_module->_jumpTo = new Vec(x, y);
}
@@ -451,6 +516,7 @@ struct Walk2Widget : ModuleWidget {
auto offsetYParamPosition = Vec(119.0, 234.0);
auto scaleXParamPosition = Vec(75.0, 262.5);
auto scaleYParamPosition = Vec(119.0, 262.5);
+ auto jumpModeParamPosition = Vec(122.0, 341.7);
auto offsetXInputPosition = Vec(10.5, 284.0);
auto scaleXInputPosition = Vec(41.5, 284.0);
@@ -458,11 +524,15 @@ struct Walk2Widget : ModuleWidget {
auto offsetYInputPosition = Vec(145.5, 284.0);
auto scaleYInputPosition = Vec(176.5, 284.0);
auto rateYInputPosition = Vec(145.5, 323.0);
- auto jumpInputPosition = Vec(78.0, 303.0);
+ auto jumpInputPosition = Vec(78.0, 291.0);
auto outXOutputPosition = Vec(41.5, 323.0);
auto outYOutputPosition = Vec(176.5, 323.0);
- auto distanceOutputPosition = Vec(109.0, 303.0);
+ auto distanceOutputPosition = Vec(109.0, 291.0);
+
+ auto jumpLightPosition = Vec(90.5, 333.0);
+ auto sampleholdLightPosition = Vec(90.5, 343.0);
+ auto trackholdLightPosition = Vec(90.5, 353.0);
// end generated by svg_widgets.rb
addParam(createParam<Knob29>(rateXParamPosition, module, Walk2::RATE_X_PARAM));
@@ -471,6 +541,7 @@ struct Walk2Widget : ModuleWidget {
addParam(createParam<Knob16>(offsetYParamPosition, module, Walk2::OFFSET_Y_PARAM));
addParam(createParam<Knob16>(scaleXParamPosition, module, Walk2::SCALE_X_PARAM));
addParam(createParam<Knob16>(scaleYParamPosition, module, Walk2::SCALE_Y_PARAM));
+ addParam(createParam<StatefulButton9>(jumpModeParamPosition, module, Walk2::JUMP_MODE_PARAM));
addInput(createInput<Port24>(offsetXInputPosition, module, Walk2::OFFSET_X_INPUT));
addInput(createInput<Port24>(scaleXInputPosition, module, Walk2::SCALE_X_INPUT));
@@ -483,6 +554,10 @@ struct Walk2Widget : ModuleWidget {
addOutput(createOutput<Port24>(outXOutputPosition, module, Walk2::OUT_X_OUTPUT));
addOutput(createOutput<Port24>(outYOutputPosition, module, Walk2::OUT_Y_OUTPUT));
addOutput(createOutput<Port24>(distanceOutputPosition, module, Walk2::DISTANCE_OUTPUT));
+
+ addChild(createLight<SmallLight<GreenLight>>(jumpLightPosition, module, Walk2::JUMP_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(sampleholdLightPosition, module, Walk2::SAMPLEHOLD_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(trackholdLightPosition, module, Walk2::TRACKHOLD_LIGHT));
}
void appendContextMenu(Menu* menu) override {
@@ -508,4 +583,4 @@ struct Walk2Widget : ModuleWidget {
}
};
-Model* modelWalk2 = bogaudio::createModel<Walk2, Walk2Widget>("Bogaudio-Walk2", "WALK2", "2-channel random-walk CV source", "Random");
+Model* modelWalk2 = bogaudio::createModel<Walk2, Walk2Widget>("Bogaudio-Walk2", "WALK2", "2D random-walk and X/Y controller", "Random", "Sample and hold", "Controller");
diff --git a/src/Walk2.hpp b/src/Walk2.hpp
@@ -6,6 +6,7 @@
#include "dsp/buffer.hpp"
#include "dsp/noise.hpp"
#include "dsp/signal.hpp"
+#include "Walk.hpp"
using namespace bogaudio::dsp;
@@ -21,6 +22,7 @@ struct Walk2 : BGModule {
OFFSET_Y_PARAM,
SCALE_X_PARAM,
SCALE_Y_PARAM,
+ JUMP_MODE_PARAM,
NUM_PARAMS
};
@@ -42,6 +44,13 @@ struct Walk2 : BGModule {
NUM_OUTPUTS
};
+ enum LightsIds {
+ JUMP_LIGHT,
+ SAMPLEHOLD_LIGHT,
+ TRACKHOLD_LIGHT,
+ NUM_LIGHTS
+ };
+
const float historySeconds = 1.0f;
const int historyPoints = 100;
int _historySteps;
@@ -54,6 +63,8 @@ struct Walk2 : BGModule {
Trigger _jumpTrigger;
HistoryBuffer<float> _outsX, _outsY;
std::atomic<Vec*> _jumpTo;
+ Walk::JumpMode _jumpMode = Walk::JUMP_JUMPMODE;
+ float _lastOutX = 0.0f, _lastOutY = 0.0f;
enum TraceColor {
GREEN_TRACE_COLOR,
@@ -70,13 +81,14 @@ struct Walk2 : BGModule {
, _outsY(historyPoints, 0.0f)
, _jumpTo(NULL)
{
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(RATE_X_PARAM, 0.0f, 1.0f, 0.1f, "Rate X", "%", 0.0f, 100.0f);
configParam(RATE_Y_PARAM, 0.0f, 1.0f, 0.1f, "Rate Y", "%", 0.0f, 100.0f);
configParam(OFFSET_X_PARAM, -1.0f, 1.0f, 0.0f, "Offset X", " V", 0.0f, 5.0f);
configParam(OFFSET_Y_PARAM, -1.0f, 1.0f, 0.0f, "Offset Y", " V", 0.0f, 5.0f);
configParam(SCALE_X_PARAM, 0.0f, 1.0f, 1.0f, "Scale X", "%", 0.0f, 100.0f);
configParam(SCALE_Y_PARAM, 0.0f, 1.0f, 1.0f, "Scale Y", "%", 0.0f, 100.0f);
+ configParam(JUMP_MODE_PARAM, 0.0f, 2.0f, 0.0f, "TRIG action");
}
void reset() override;
@@ -84,6 +96,7 @@ struct Walk2 : BGModule {
json_t* dataToJson() override;
void dataFromJson(json_t* root) override;
void modulate() override;
+ void processAlways(const ProcessArgs& args) override;
void processChannel(const ProcessArgs& args, int _c) override;
};