#include "Walk2.hpp" #define ZOOM_OUT_KEY "zoom_out" #define GRID_KEY "grid" #define COLOR_KEY "color" void Walk2::onReset() { _jumpTrigger.reset(); _modulationStep = modulationSteps; } void Walk2::onSampleRateChange() { _modulationStep = modulationSteps; _historySteps = (historySeconds * engineGetSampleRate()) / historyPoints; } json_t* Walk2::toJson() { json_t* root = json_object(); json_object_set_new(root, ZOOM_OUT_KEY, json_boolean(_zoomOut)); json_object_set_new(root, GRID_KEY, json_boolean(_drawGrid)); json_object_set_new(root, COLOR_KEY, json_integer(_traceColor)); return root; } void Walk2::fromJson(json_t* root) { json_t* zo = json_object_get(root, ZOOM_OUT_KEY); if (zo) { _zoomOut = json_is_true(zo); } json_t* g = json_object_get(root, GRID_KEY); if (g) { _drawGrid = json_is_true(g); } json_t* c = json_object_get(root, COLOR_KEY); if (c) { _traceColor = (TraceColor)json_integer_value(c); } } inline float scaleRate(float rate) { return 0.2f * powf(rate, 5.0f); } void Walk2::step() { ++_modulationStep; if (_modulationStep >= modulationSteps) { _modulationStep = 0; float sampleRate = engineGetSampleRate(); float rateX = params[RATE_X_PARAM].value; if (inputs[RATE_X_INPUT].active) { rateX *= clamp(inputs[RATE_X_INPUT].value / 10.0f, 0.0f, 1.0f); } rateX = scaleRate(rateX); _walkX.setParams(sampleRate, rateX); _slewX.setParams(sampleRate, std::max((1.0f - rateX) * 100.0f, 0.0f), 10.0f); _offsetX = params[OFFSET_X_PARAM].value; if (inputs[OFFSET_X_INPUT].active) { _offsetX *= clamp(inputs[OFFSET_X_INPUT].value / 5.0f, -1.0f, 1.0f); } _offsetX *= 5.0f; _scaleX = params[SCALE_X_PARAM].value; if (inputs[SCALE_X_INPUT].active) { _scaleX *= clamp(inputs[SCALE_X_INPUT].value / 10.0f, 0.0f, 1.0f); } float rateY = params[RATE_Y_PARAM].value; if (inputs[RATE_Y_INPUT].active) { rateY *= clamp(inputs[RATE_Y_INPUT].value / 10.0f, 0.0f, 1.0f); } rateY = scaleRate(rateY); _walkY.setParams(sampleRate, rateY); _slewY.setParams(sampleRate, std::max((1.0f - rateY) * 100.0f, 0.0f), 10.0f); _offsetY = params[OFFSET_Y_PARAM].value; if (inputs[OFFSET_Y_INPUT].active) { _offsetY *= clamp(inputs[OFFSET_Y_INPUT].value / 5.0f, -1.0f, 1.0f); } _offsetY *= 5.0f; _scaleY = params[SCALE_Y_PARAM].value; if (inputs[SCALE_Y_INPUT].active) { _scaleY *= clamp(inputs[SCALE_Y_INPUT].value / 10.0f, 0.0f, 1.0f); } } Vec* jumpTo = _jumpTo; if (jumpTo != NULL) { _jumpTo = NULL; _walkX.tell(jumpTo->x); _walkY.tell(jumpTo->y); delete jumpTo; } else if (_jumpTrigger.process(inputs[JUMP_INPUT].value)) { _walkX.jump(); _walkY.jump(); } float outX = _slewX.next(_walkX.next()); outX *= _scaleX; outX += _offsetX; outputs[OUT_X_OUTPUT].value = outX; float outY = _slewY.next(_walkY.next()); outY *= _scaleY; outY += _offsetY; outputs[OUT_Y_OUTPUT].value = outY; outputs[DISTANCE_OUTPUT].value = sqrtf(outX*outX + outY*outY) * 0.707107f; // scaling constant is 10 / squrt(200) if (_historyStep == 0) { _outsX.push(outX); _outsY.push(outY); } ++_historyStep; _historyStep %= _historySteps; } struct Walk2Display : TransparentWidget { const int _insetAround = 4; const NVGcolor _axisColor = nvgRGBA(0xff, 0xff, 0xff, 0x70); const NVGcolor _defaultTraceColor = nvgRGBA(0x00, 0xff, 0x00, 0xee); Walk2* _module; const Vec _size; const Vec _drawSize; int _midX, _midY; std::shared_ptr _font; NVGcolor _traceColor = _defaultTraceColor; Walk2Display( Walk2* module, Vec size ) : _module(module) , _size(size) , _drawSize(2 * (_size.x - 2 * _insetAround), 2 * (_size.y - 2 * _insetAround)) , _midX(_insetAround + _drawSize.x/2) , _midY(_insetAround + _drawSize.y/2) , _font(Font::load(assetPlugin(plugin, "res/fonts/inconsolata.ttf"))) { } void onMouseDown(EventMouseDown& e) override { if ( e.pos.x > _insetAround && e.pos.x < _size.x - _insetAround && e.pos.y > _insetAround && e.pos.y < _size.y - _insetAround ) { float x = 20.0f * ((e.pos.x - _insetAround) / (float)_drawSize.x); x -= 5.0f; float y = 20.0f * ((e.pos.y - _insetAround) / (float)_drawSize.y); y = 5.0f - y; _module->_jumpTo = new Vec(x, y); } } void draw(NVGcontext* vg) override { switch (_module->_traceColor) { case Walk2::ORANGE_TRACE_COLOR: { _traceColor = nvgRGBA(0xff, 0x80, 0x00, 0xee); break; } case Walk2::RED_TRACE_COLOR: { _traceColor = nvgRGBA(0xff, 0x00, 0x00, 0xee); break; } case Walk2::BLUE_TRACE_COLOR: { _traceColor = nvgRGBA(0x00, 0xdd, 0xff, 0xee); break; } case Walk2::GREEN_TRACE_COLOR: default: { _traceColor = _defaultTraceColor; } } drawBackground(vg); float strokeWidth = std::max(1.0f, 3 - RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom); nvgSave(vg); nvgScissor(vg, _insetAround, _insetAround, _drawSize.x / 2, _drawSize.y / 2); if (_module->_zoomOut) { nvgScale(vg, 0.5f, 0.5f); strokeWidth *= 2.0f; } else { float tx = 1.0f + (clamp(_module->_offsetX, -5.0f, 5.0f) / 5.0f); tx *= -_drawSize.x / 4; float ty = 1.0f - (clamp(_module->_offsetY, -5.0f, 5.0f) / 5.0f); ty *= -_drawSize.y / 4; nvgTranslate(vg, tx, ty); } drawAxes(vg, strokeWidth); drawTrace(vg, _traceColor, _module->_outsX, _module->_outsY); nvgRestore(vg); } void drawBackground(NVGcontext* vg) { nvgSave(vg); nvgBeginPath(vg); nvgRect(vg, 0, 0, _size.x, _size.y); nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff)); nvgFill(vg); nvgStrokeColor(vg, nvgRGBA(0xc0, 0xc0, 0xc0, 0xff)); nvgStroke(vg); nvgRestore(vg); } void drawAxes(NVGcontext* vg, float strokeWidth) { const float shortTick = 4.0f; const float longTick = 8.0f; float dot = 0.5f * strokeWidth; nvgSave(vg); nvgStrokeColor(vg, _axisColor); nvgStrokeWidth(vg, strokeWidth); nvgBeginPath(vg); nvgMoveTo(vg, _insetAround, _midY); nvgLineTo(vg, _insetAround + _drawSize.x, _midY); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX, _insetAround); nvgLineTo(vg, _midX, _insetAround + _drawSize.y); nvgStroke(vg); for (int i = 1; i <= 10; ++i) { float tick = i % 5 == 0 ? longTick : shortTick; float x = (i * 0.1f) * 0.5f * _drawSize.x; nvgBeginPath(vg); nvgMoveTo(vg, _midX + x, _midY - tick); nvgLineTo(vg, _midX + x, _midY + tick); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX - x, _midY - tick); nvgLineTo(vg, _midX - x, _midY + tick); nvgStroke(vg); float y = (i * 0.1f) * 0.5f * _drawSize.y; nvgBeginPath(vg); nvgMoveTo(vg, _midX - tick, _midY + y); nvgLineTo(vg, _midX + tick, _midY + y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX - tick, _midY - y); nvgLineTo(vg, _midX + tick, _midY - y); nvgStroke(vg); if (_module->_drawGrid) { for (int j = 1; j <= 10; ++j) { float y = (j * 0.1f) * 0.5f * _drawSize.y; nvgBeginPath(vg); nvgMoveTo(vg, _midX + x - dot, _midY + y); nvgLineTo(vg, _midX + x + dot, _midY + y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX - x - dot, _midY + y); nvgLineTo(vg, _midX - x + dot, _midY + y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX - x - dot, _midY - y); nvgLineTo(vg, _midX - x + dot, _midY - y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, _midX + x - dot, _midY - y); nvgLineTo(vg, _midX + x + dot, _midY - y); nvgStroke(vg); } } } if (_module->_drawGrid) { const float tick = shortTick; { float x = _midX - _drawSize.x / 4; float y = _midY - _drawSize.y / 4; nvgBeginPath(vg); nvgMoveTo(vg, x - tick, y); nvgLineTo(vg, x + tick, y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, x, y - tick); nvgLineTo(vg, x, y + tick); nvgStroke(vg); } { float x = _midX + _drawSize.x / 4; float y = _midY - _drawSize.y / 4; nvgBeginPath(vg); nvgMoveTo(vg, x - tick, y); nvgLineTo(vg, x + tick, y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, x, y - tick); nvgLineTo(vg, x, y + tick); nvgStroke(vg); } { float x = _midX + _drawSize.x / 4; float y = _midY + _drawSize.y / 4; nvgBeginPath(vg); nvgMoveTo(vg, x - tick, y); nvgLineTo(vg, x + tick, y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, x, y - tick); nvgLineTo(vg, x, y + tick); nvgStroke(vg); } { float x = _midX - _drawSize.x / 4; float y = _midY + _drawSize.y / 4; nvgBeginPath(vg); nvgMoveTo(vg, x - tick, y); nvgLineTo(vg, x + tick, y); nvgStroke(vg); nvgBeginPath(vg); nvgMoveTo(vg, x, y - tick); nvgLineTo(vg, x, y + tick); nvgStroke(vg); } } nvgRestore(vg); } void drawTrace(NVGcontext* vg, NVGcolor color, HistoryBuffer& x, HistoryBuffer& y) { nvgSave(vg); // nvgGlobalCompositeOperation(vg, NVG_LIGHTER); // int n = _module->historyPoints; // float beginRadius = std::max(1.0f, 2.0f - gRackScene->zoomWidget->zoom); // float endRadius = std::max(0.01f, 0.8f - gRackScene->zoomWidget->zoom); // float radiusStep = (beginRadius - endRadius) / (float)n; // float radius = beginRadius; // float alphaStep = (color.a - 0.1f) / (float)n; // for (int i = 0; i < n; ++i) { // nvgBeginPath(vg); // nvgCircle(vg, cvToPixel(_midX, _drawSize.x, x.value(i)), cvToPixel(_midY, _drawSize.y, y.value(i)), radius); // nvgStrokeColor(vg, color); // nvgFillColor(vg, color); // nvgStroke(vg); // nvgFill(vg); // radius -= radiusStep; // color.a -= alphaStep; // } int n = _module->historyPoints; float beginWidth = std::max(1.0f, 4.0f - RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom); float endWidth = std::max(0.5f, 2.0f - RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom); if (_module->_zoomOut) { beginWidth *= 2.0f; endWidth *= 2.0f; } float widthStep = (beginWidth - endWidth) / (float)n; float width = endWidth; float endAlpha = 0.1f; float alphaStep = (color.a - endAlpha) / (float)n; color.a = endAlpha; for (int i = n - 1; i > 0; --i) { nvgBeginPath(vg); nvgMoveTo(vg, cvToPixelX(_midX, _drawSize.x, x.value(i - 1)), cvToPixelY(_midY, _drawSize.y, y.value(i - 1))); nvgLineTo(vg, cvToPixelX(_midX, _drawSize.x, x.value(i)), cvToPixelY(_midY, _drawSize.y, y.value(i))); nvgStrokeWidth(vg, width); nvgStrokeColor(vg, color); nvgStroke(vg); width += widthStep; color.a += alphaStep; } nvgBeginPath(vg); nvgCircle(vg, cvToPixelX(_midX, _drawSize.x, x.value(0)), cvToPixelY(_midY, _drawSize.y, y.value(0)), 0.5f * width); nvgStrokeColor(vg, color); nvgFillColor(vg, color); nvgStroke(vg); nvgFill(vg); nvgRestore(vg); } inline float cvToPixelX(float mid, float extent, float cv) { return mid + 0.05f * extent * cv; } inline float cvToPixelY(float mid, float extent, float cv) { return mid - 0.05f * extent * cv; } }; struct ZoomOutMenuItem : MenuItem { Walk2* _module; const bool _zoomOut; ZoomOutMenuItem(Walk2* module, const char* label, bool zoomOut) : _module(module) , _zoomOut(zoomOut) { this->text = label; } void onAction(EventAction &e) override { _module->_zoomOut = _zoomOut; } void step() override { rightText = _module->_zoomOut == _zoomOut ? "?" : ""; } }; struct GridMenuItem : MenuItem { Walk2* _module; GridMenuItem(Walk2* module, const char* label) : _module(module) { this->text = label; } void onAction(EventAction &e) override { _module->_drawGrid = !_module->_drawGrid; } void step() override { rightText = _module->_drawGrid ? "?" : ""; } }; struct ColorMenuItem : MenuItem { Walk2* _module; const Walk2::TraceColor _color; ColorMenuItem(Walk2* module, const char* label, Walk2::TraceColor color) : _module(module) , _color(color) { this->text = label; } void onAction(EventAction &e) override { _module->_traceColor = _color; } void step() override { rightText = _module->_traceColor == _color ? "?" : ""; } }; struct Walk2Widget : ModuleWidget { static constexpr int hp = 14; Walk2Widget(Walk2* module) : ModuleWidget(module) { box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/Walk2.svg"))); addChild(panel); } { auto inset = Vec(10, 25); int dim = box.size.x - 2*inset.x; auto size = Vec(dim, dim); auto display = new Walk2Display(module, size); display->box.pos = inset; display->box.size = size; addChild(display); } addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x - 30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x - 30, 365))); // generated by svg_widgets.rb auto rateXParamPosition = Vec(28.0, 240.0); auto rateYParamPosition = Vec(151.5, 240.0); auto offsetXParamPosition = Vec(75.0, 234.0); auto offsetYParamPosition = Vec(119.0, 234.0); auto scaleXParamPosition = Vec(75.0, 262.5); auto scaleYParamPosition = Vec(119.0, 262.5); auto offsetXInputPosition = Vec(10.5, 284.0); auto scaleXInputPosition = Vec(41.5, 284.0); auto rateXInputPosition = Vec(10.5, 323.0); 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 outXOutputPosition = Vec(41.5, 323.0); auto outYOutputPosition = Vec(176.5, 323.0); auto distanceOutputPosition = Vec(109.0, 303.0); // end generated by svg_widgets.rb addParam(ParamWidget::create(rateXParamPosition, module, Walk2::RATE_X_PARAM, 0.0, 1.0, 0.1)); addParam(ParamWidget::create(rateYParamPosition, module, Walk2::RATE_Y_PARAM, 0.0, 1.0, 0.1)); addParam(ParamWidget::create(offsetXParamPosition, module, Walk2::OFFSET_X_PARAM, -1.0, 1.0, 0.0)); addParam(ParamWidget::create(offsetYParamPosition, module, Walk2::OFFSET_Y_PARAM, -1.0, 1.0, 0.0)); addParam(ParamWidget::create(scaleXParamPosition, module, Walk2::SCALE_X_PARAM, 0.0, 1.0, 1.0)); addParam(ParamWidget::create(scaleYParamPosition, module, Walk2::SCALE_Y_PARAM, 0.0, 1.0, 1.0)); addInput(Port::create(offsetXInputPosition, Port::INPUT, module, Walk2::OFFSET_X_INPUT)); addInput(Port::create(scaleXInputPosition, Port::INPUT, module, Walk2::SCALE_X_INPUT)); addInput(Port::create(rateXInputPosition, Port::INPUT, module, Walk2::RATE_X_INPUT)); addInput(Port::create(offsetYInputPosition, Port::INPUT, module, Walk2::OFFSET_Y_INPUT)); addInput(Port::create(scaleYInputPosition, Port::INPUT, module, Walk2::SCALE_Y_INPUT)); addInput(Port::create(rateYInputPosition, Port::INPUT, module, Walk2::RATE_Y_INPUT)); addInput(Port::create(jumpInputPosition, Port::INPUT, module, Walk2::JUMP_INPUT)); addOutput(Port::create(outXOutputPosition, Port::OUTPUT, module, Walk2::OUT_X_OUTPUT)); addOutput(Port::create(outYOutputPosition, Port::OUTPUT, module, Walk2::OUT_Y_OUTPUT)); addOutput(Port::create(distanceOutputPosition, Port::OUTPUT, module, Walk2::DISTANCE_OUTPUT)); } void appendContextMenu(Menu* menu) override { Walk2* w = dynamic_cast(module); assert(w); menu->addChild(new MenuLabel()); menu->addChild(new ZoomOutMenuItem(w, "Display range: +/-5V", false)); menu->addChild(new ZoomOutMenuItem(w, "Display range: +/-10V", true)); menu->addChild(new MenuLabel()); menu->addChild(new GridMenuItem(w, "Show grid")); menu->addChild(new MenuLabel()); menu->addChild(new ColorMenuItem(w, "Trace color: green", Walk2::GREEN_TRACE_COLOR)); menu->addChild(new ColorMenuItem(w, "Trace color: orange", Walk2::ORANGE_TRACE_COLOR)); menu->addChild(new ColorMenuItem(w, "Trace color: red", Walk2::RED_TRACE_COLOR)); menu->addChild(new ColorMenuItem(w, "Trace color: blue", Walk2::BLUE_TRACE_COLOR)); } }; RACK_PLUGIN_MODEL_INIT(Bogaudio, Walk2) { Model* modelWalk2 = createModel("Bogaudio-Walk2", "Walk2", ""); return modelWalk2; }