BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

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:
Mplugin.json | 7+++++--
Mres-src/Walk2-src.svg | 31+++++++++++++++++++++++++++++--
Mres/Walk2.svg | 0
Msrc/Walk.cpp | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Walk.hpp | 8++++++++
Msrc/Walk2.cpp | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/Walk2.hpp | 15++++++++++++++-
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&amp;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&amp;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; };