BogaudioModules

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

Walk2.cpp (17747B)


      1 
      2 #include "Walk2.hpp"
      3 
      4 #define ZOOM_OUT_KEY "zoom_out"
      5 #define GRID_KEY "grid"
      6 #define COLOR_KEY "color"
      7 
      8 extern float zoom;
      9 
     10 void Walk2::reset() {
     11 	_jumpTrigger.reset();
     12 }
     13 
     14 void Walk2::sampleRateChange() {
     15 	_historySteps = (historySeconds * APP->engine->getSampleRate()) / historyPoints;
     16 }
     17 
     18 json_t* Walk2::saveToJson(json_t* root) {
     19 	json_object_set_new(root, ZOOM_OUT_KEY, json_boolean(_zoomOut));
     20 	json_object_set_new(root, GRID_KEY, json_boolean(_drawGrid));
     21 	json_object_set_new(root, COLOR_KEY, json_integer(_traceColor));
     22 	return root;
     23 }
     24 
     25 void Walk2::loadFromJson(json_t* root) {
     26 	json_t* zo = json_object_get(root, ZOOM_OUT_KEY);
     27 	if (zo) {
     28 		_zoomOut = json_is_true(zo);
     29 	}
     30 	json_t* g = json_object_get(root, GRID_KEY);
     31 	if (g) {
     32 		_drawGrid = json_is_true(g);
     33 	}
     34 	json_t* c = json_object_get(root, COLOR_KEY);
     35 	if (c) {
     36 		_traceColor = (TraceColor)json_integer_value(c);
     37 	}
     38 }
     39 
     40 inline float scaleRate(float rate) {
     41 	return 0.2f * powf(rate, 5.0f);
     42 }
     43 
     44 void Walk2::modulate() {
     45 	float sampleRate = APP->engine->getSampleRate();
     46 
     47 	float rateX = params[RATE_X_PARAM].getValue();
     48 	if (inputs[RATE_X_INPUT].isConnected()) {
     49 		rateX *= clamp(inputs[RATE_X_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
     50 	}
     51 	rateX = scaleRate(rateX);
     52 	_walkX.setParams(sampleRate, rateX);
     53 	_slewX.setParams(sampleRate, std::max((1.0f - rateX) * 100.0f, 0.0f), 10.0f);
     54 
     55 	_offsetX = params[OFFSET_X_PARAM].getValue();
     56 	if (inputs[OFFSET_X_INPUT].isConnected()) {
     57 		_offsetX *= clamp(inputs[OFFSET_X_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f);
     58 	}
     59 	_offsetX *= 5.0f;
     60 
     61 	_scaleX = params[SCALE_X_PARAM].getValue();
     62 	if (inputs[SCALE_X_INPUT].isConnected()) {
     63 		_scaleX *= clamp(inputs[SCALE_X_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
     64 	}
     65 
     66 	float rateY = params[RATE_Y_PARAM].getValue();
     67 	if (inputs[RATE_Y_INPUT].isConnected()) {
     68 		rateY *= clamp(inputs[RATE_Y_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
     69 	}
     70 	rateY = scaleRate(rateY);
     71 	_walkY.setParams(sampleRate, rateY);
     72 	_slewY.setParams(sampleRate, std::max((1.0f - rateY) * 100.0f, 0.0f), 10.0f);
     73 
     74 	_offsetY = params[OFFSET_Y_PARAM].getValue();
     75 	if (inputs[OFFSET_Y_INPUT].isConnected()) {
     76 		_offsetY *= clamp(inputs[OFFSET_Y_INPUT].getVoltage() / 5.0f, -1.0f, 1.0f);
     77 	}
     78 	_offsetY *= 5.0f;
     79 
     80 	_scaleY = params[SCALE_Y_PARAM].getValue();
     81 	if (inputs[SCALE_Y_INPUT].isConnected()) {
     82 		_scaleY *= clamp(inputs[SCALE_Y_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
     83 	}
     84 
     85 	float jm = clamp(params[JUMP_MODE_PARAM].getValue(), 0.0f, 2.0f);
     86 	if (jm > 1.5f) {
     87 		_jumpMode = Walk::TRACKHOLD_JUMPMODE;
     88 	}
     89 	else if (jm > 0.5f) {
     90 		_jumpMode = Walk::SAMPLEHOLD_JUMPMODE;
     91 	}
     92 	else {
     93 		_jumpMode = Walk::JUMP_JUMPMODE;
     94 	}
     95 }
     96 
     97 void Walk2::processAlways(const ProcessArgs& args) {
     98 	lights[JUMP_LIGHT].value = _jumpMode == Walk::JUMP_JUMPMODE;
     99 	lights[SAMPLEHOLD_LIGHT].value = _jumpMode == Walk::SAMPLEHOLD_JUMPMODE;
    100 	lights[TRACKHOLD_LIGHT].value = _jumpMode == Walk::TRACKHOLD_JUMPMODE;
    101 }
    102 
    103 void Walk2::processAll(const ProcessArgs& args) {
    104 	Vec* jumpTo = _jumpTo;
    105 	if (jumpTo != NULL) {
    106 		_jumpTo = NULL;
    107 		_lastOutX = jumpTo->x;
    108 		_walkX.tell(jumpTo->x);
    109 		_lastOutY = jumpTo->y;
    110 		_walkY.tell(jumpTo->y);
    111 		delete jumpTo;
    112 	}
    113 
    114 	bool triggered = _jumpTrigger.process(inputs[JUMP_INPUT].getVoltage());
    115 	float outX = _walkX.next();
    116 	float outY = _walkY.next();
    117 
    118 	switch (_jumpMode) {
    119 		case Walk::JUMP_JUMPMODE: {
    120 			if (triggered) {
    121 				_walkX.jump();
    122 				_walkY.jump();
    123 			}
    124 			break;
    125 		}
    126 		case Walk::TRACKHOLD_JUMPMODE: {
    127 			if (_jumpTrigger.isHigh()) {
    128 				_lastOutX = outX;
    129 				_lastOutY = outY;
    130 			}
    131 			else {
    132 				outX = _lastOutX;
    133 				outY = _lastOutY;
    134 			}
    135 			break;
    136 		}
    137 		case Walk::SAMPLEHOLD_JUMPMODE: {
    138 			if (triggered) {
    139 				_lastOutX = outX;
    140 				_lastOutY = outY;
    141 			}
    142 			else {
    143 				outX = _lastOutX;
    144 				outY = _lastOutY;
    145 			}
    146 			break;
    147 		}
    148 	}
    149 
    150 	outX = _slewX.next(outX);
    151 	outX *= _scaleX;
    152 	outX += _offsetX;
    153 	outputs[OUT_X_OUTPUT].setVoltage(outX);
    154 
    155 	outY = _slewY.next(outY);
    156 	outY *= _scaleY;
    157 	outY += _offsetY;
    158 	outputs[OUT_Y_OUTPUT].setVoltage(outY);
    159 
    160 	if (outputs[DISTANCE_OUTPUT].isConnected()) {
    161 		outputs[DISTANCE_OUTPUT].setVoltage(sqrtf(outX*outX + outY*outY) * 0.707107f); // scaling constant is 10 / squrt(200)
    162 	}
    163 
    164 	if (_historyStep == 0) {
    165 		_outsX.push(outX);
    166 		_outsY.push(outY);
    167 	}
    168 	++_historyStep;
    169 	_historyStep %= _historySteps;
    170 }
    171 
    172 struct Walk2Display : DisplayWidget {
    173 	const int _insetAround = 4;
    174 
    175 	const NVGcolor _axisColor = nvgRGBA(0xff, 0xff, 0xff, 0x70);
    176 	const NVGcolor _defaultTraceColor = nvgRGBA(0x00, 0xff, 0x00, 0xee);
    177 
    178 	Walk2* _module;
    179 	const Vec _size;
    180 	const Vec _drawSize;
    181 	int _midX, _midY;
    182 	NVGcolor _traceColor = _defaultTraceColor;
    183 	Vec _dragLast;
    184 
    185 	Walk2Display(
    186 		Walk2* module,
    187 		Vec size
    188 	)
    189 	: DisplayWidget(module)
    190 	, _module(module)
    191 	, _size(size)
    192 	, _drawSize(2 * (_size.x - 2 * _insetAround), 2 * (_size.y - 2 * _insetAround))
    193 	, _midX(_insetAround + _drawSize.x/2)
    194 	, _midY(_insetAround + _drawSize.y/2)
    195 	{
    196 	}
    197 
    198 	void onButton(const event::Button& e) override {
    199 		if (!(e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0)) {
    200 			return;
    201 		}
    202 		e.consume(this);
    203 		_dragLast = e.pos;
    204 		maybeJump(e.pos);
    205 	}
    206 
    207 	void onDragMove(const event::DragMove& e) override {
    208 		float zoom = APP->scene->rackScroll->zoomWidget->zoom;
    209 		_dragLast.x += e.mouseDelta.x / zoom;
    210 		_dragLast.y += e.mouseDelta.y / zoom;
    211 		maybeJump(_dragLast);
    212 	}
    213 
    214 	void maybeJump(Vec pos) {
    215 		if (
    216 			pos.x > _insetAround &&
    217 			pos.x < _size.x - _insetAround &&
    218 			pos.y > _insetAround &&
    219 			pos.y < _size.y - _insetAround
    220 		) {
    221 			float x = 20.0f * ((pos.x - _insetAround) / (float)_drawSize.x);
    222 			x -= 5.0f;
    223 			float y = 20.0f * ((pos.y - _insetAround) / (float)_drawSize.y);
    224 			y = 5.0f - y;
    225 			_module->_jumpTo = new Vec(x, y);
    226 		}
    227 	}
    228 
    229 	void drawOnce(const DrawArgs& args, bool screenshot, bool lit) override {
    230 		float strokeWidth = std::max(1.0f, 3.0f - getZoom());
    231 
    232 		nvgSave(args.vg);
    233 		drawBackground(args);
    234 		nvgScissor(args.vg, _insetAround, _insetAround, _drawSize.x / 2, _drawSize.y / 2);
    235 		if (_module && _module->_zoomOut) {
    236 			nvgScale(args.vg, 0.5f, 0.5f);
    237 			strokeWidth *= 2.0f;
    238 		}
    239 		else {
    240 			float offsetX = _module ? _module->_offsetX : 0.0f;
    241 			float offsetY = _module ? _module->_offsetY : 0.0f;
    242 			float tx = 1.0f + (clamp(offsetX, -5.0f, 5.0f) / 5.0f);
    243 			tx *= -_drawSize.x / 4;
    244 			float ty = 1.0f - (clamp(offsetY, -5.0f, 5.0f) / 5.0f);
    245 			ty *= -_drawSize.y / 4;
    246 			nvgTranslate(args.vg, tx, ty);
    247 		}
    248 		drawAxes(args, strokeWidth);
    249 
    250 		if (lit) {
    251 			switch (_module->_traceColor) {
    252 				case Walk2::ORANGE_TRACE_COLOR: {
    253 					_traceColor = nvgRGBA(0xff, 0x80, 0x00, 0xee);
    254 					break;
    255 				}
    256 				case Walk2::RED_TRACE_COLOR: {
    257 					_traceColor = nvgRGBA(0xff, 0x00, 0x00, 0xee);
    258 					break;
    259 				}
    260 				case Walk2::BLUE_TRACE_COLOR: {
    261 					_traceColor = nvgRGBA(0x00, 0xdd, 0xff, 0xee);
    262 					break;
    263 				}
    264 				case Walk2::GREEN_TRACE_COLOR:
    265 				default: {
    266 					_traceColor = _defaultTraceColor;
    267 				}
    268 			}
    269 			drawTrace(args, _traceColor, _module->_outsX, _module->_outsY);
    270 		}
    271 
    272 		nvgRestore(args.vg);
    273 	}
    274 
    275 	void drawBackground(const DrawArgs& args) {
    276 		nvgSave(args.vg);
    277 		nvgBeginPath(args.vg);
    278 		nvgRect(args.vg, 0, 0, _size.x, _size.y);
    279 		nvgFillColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
    280 		nvgFill(args.vg);
    281 		nvgStrokeColor(args.vg, nvgRGBA(0x50, 0x50, 0x50, 0xff));
    282 		nvgStroke(args.vg);
    283 		nvgRestore(args.vg);
    284 	}
    285 
    286 	void drawAxes(const DrawArgs& args, float strokeWidth) {
    287 		const float shortTick = 4.0f;
    288 		const float longTick = 8.0f;
    289 		float dot = 0.5f * strokeWidth;
    290 
    291 		nvgSave(args.vg);
    292 		nvgStrokeColor(args.vg, _axisColor);
    293 		nvgStrokeWidth(args.vg, strokeWidth);
    294 
    295 		nvgBeginPath(args.vg);
    296 		nvgMoveTo(args.vg, _insetAround, _midY);
    297 		nvgLineTo(args.vg, _insetAround + _drawSize.x, _midY);
    298 		nvgStroke(args.vg);
    299 
    300 		nvgBeginPath(args.vg);
    301 		nvgMoveTo(args.vg, _midX, _insetAround);
    302 		nvgLineTo(args.vg, _midX, _insetAround + _drawSize.y);
    303 		nvgStroke(args.vg);
    304 
    305 		for (int i = 1; i <= 10; ++i) {
    306 			float tick = i % 5 == 0 ? longTick : shortTick;
    307 
    308 			float x = (i * 0.1f) * 0.5f * _drawSize.x;
    309 			nvgBeginPath(args.vg);
    310 			nvgMoveTo(args.vg, _midX + x, _midY - tick);
    311 			nvgLineTo(args.vg, _midX + x, _midY + tick);
    312 			nvgStroke(args.vg);
    313 
    314 			nvgBeginPath(args.vg);
    315 			nvgMoveTo(args.vg, _midX - x, _midY - tick);
    316 			nvgLineTo(args.vg, _midX - x, _midY + tick);
    317 			nvgStroke(args.vg);
    318 
    319 			float y = (i * 0.1f) * 0.5f * _drawSize.y;
    320 			nvgBeginPath(args.vg);
    321 			nvgMoveTo(args.vg, _midX - tick, _midY + y);
    322 			nvgLineTo(args.vg, _midX + tick, _midY + y);
    323 			nvgStroke(args.vg);
    324 
    325 			nvgBeginPath(args.vg);
    326 			nvgMoveTo(args.vg, _midX - tick, _midY - y);
    327 			nvgLineTo(args.vg, _midX + tick, _midY - y);
    328 			nvgStroke(args.vg);
    329 
    330 			if (!_module || _module->_drawGrid) {
    331 				for (int j = 1; j <= 10; ++j) {
    332 					float y = (j * 0.1f) * 0.5f * _drawSize.y;
    333 
    334 					nvgBeginPath(args.vg);
    335 					nvgMoveTo(args.vg, _midX + x - dot, _midY + y);
    336 					nvgLineTo(args.vg, _midX + x + dot, _midY + y);
    337 					nvgStroke(args.vg);
    338 
    339 					nvgBeginPath(args.vg);
    340 					nvgMoveTo(args.vg, _midX - x - dot, _midY + y);
    341 					nvgLineTo(args.vg, _midX - x + dot, _midY + y);
    342 					nvgStroke(args.vg);
    343 
    344 					nvgBeginPath(args.vg);
    345 					nvgMoveTo(args.vg, _midX - x - dot, _midY - y);
    346 					nvgLineTo(args.vg, _midX - x + dot, _midY - y);
    347 					nvgStroke(args.vg);
    348 
    349 					nvgBeginPath(args.vg);
    350 					nvgMoveTo(args.vg, _midX + x - dot, _midY - y);
    351 					nvgLineTo(args.vg, _midX + x + dot, _midY - y);
    352 					nvgStroke(args.vg);
    353 				}
    354 			}
    355 		}
    356 
    357 		if (!_module || _module->_drawGrid) {
    358 			const float tick = shortTick;
    359 			{
    360 				float x = _midX - _drawSize.x / 4;
    361 				float y = _midY - _drawSize.y / 4;
    362 
    363 				nvgBeginPath(args.vg);
    364 				nvgMoveTo(args.vg, x - tick, y);
    365 				nvgLineTo(args.vg, x + tick, y);
    366 				nvgStroke(args.vg);
    367 
    368 				nvgBeginPath(args.vg);
    369 				nvgMoveTo(args.vg, x, y - tick);
    370 				nvgLineTo(args.vg, x, y + tick);
    371 				nvgStroke(args.vg);
    372 			}
    373 			{
    374 				float x = _midX + _drawSize.x / 4;
    375 				float y = _midY - _drawSize.y / 4;
    376 
    377 				nvgBeginPath(args.vg);
    378 				nvgMoveTo(args.vg, x - tick, y);
    379 				nvgLineTo(args.vg, x + tick, y);
    380 				nvgStroke(args.vg);
    381 
    382 				nvgBeginPath(args.vg);
    383 				nvgMoveTo(args.vg, x, y - tick);
    384 				nvgLineTo(args.vg, x, y + tick);
    385 				nvgStroke(args.vg);
    386 			}
    387 			{
    388 				float x = _midX + _drawSize.x / 4;
    389 				float y = _midY + _drawSize.y / 4;
    390 
    391 				nvgBeginPath(args.vg);
    392 				nvgMoveTo(args.vg, x - tick, y);
    393 				nvgLineTo(args.vg, x + tick, y);
    394 				nvgStroke(args.vg);
    395 
    396 				nvgBeginPath(args.vg);
    397 				nvgMoveTo(args.vg, x, y - tick);
    398 				nvgLineTo(args.vg, x, y + tick);
    399 				nvgStroke(args.vg);
    400 			}
    401 			{
    402 				float x = _midX - _drawSize.x / 4;
    403 				float y = _midY + _drawSize.y / 4;
    404 
    405 				nvgBeginPath(args.vg);
    406 				nvgMoveTo(args.vg, x - tick, y);
    407 				nvgLineTo(args.vg, x + tick, y);
    408 				nvgStroke(args.vg);
    409 
    410 				nvgBeginPath(args.vg);
    411 				nvgMoveTo(args.vg, x, y - tick);
    412 				nvgLineTo(args.vg, x, y + tick);
    413 				nvgStroke(args.vg);
    414 			}
    415 		}
    416 
    417 		nvgRestore(args.vg);
    418 	}
    419 
    420 	void drawTrace(const DrawArgs& args, NVGcolor color, HistoryBuffer<float>& x, HistoryBuffer<float>& y) {
    421 		nvgSave(args.vg);
    422 		// nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER);
    423 
    424 		// int n = _module->historyPoints;
    425 		// float beginRadius = std::max(1.0f, 2.0f - getZoom());
    426 		// float endRadius = std::max(0.01f, 0.8f - getZoom());
    427 		// float radiusStep = (beginRadius - endRadius) / (float)n;
    428 		// float radius = beginRadius;
    429 		// float alphaStep = (color.a - 0.1f) / (float)n;
    430 		// for (int i = 0; i < n; ++i) {
    431 		// 	nvgBeginPath(args.vg);
    432 		// 	nvgCircle(args.vg, cvToPixel(_midX, _drawSize.x, x.value(i)), cvToPixel(_midY, _drawSize.y, y.value(i)), radius);
    433 		// 	nvgStrokeColor(args.vg, color);
    434 		// 	nvgFillColor(args.vg, color);
    435 		// 	nvgStroke(args.vg);
    436 		// 	nvgFill(args.vg);
    437 		// 	radius -= radiusStep;
    438 		// 	color.a -= alphaStep;
    439 		// }
    440 
    441 		int n = _module->historyPoints;
    442 		float beginWidth = std::max(1.0f, 4.0f - getZoom());
    443 		float endWidth = std::max(0.5f, 2.0f - getZoom());
    444 		if (_module->_zoomOut) {
    445 			beginWidth *= 2.0f;
    446 			endWidth *= 2.0f;
    447 		}
    448 		float widthStep = (beginWidth - endWidth) / (float)n;
    449 		float width = endWidth;
    450 		float endAlpha = 0.1f;
    451 		float alphaStep = (color.a - endAlpha) / (float)n;
    452 		color.a = endAlpha;
    453 		for (int i = n - 1; i > 0; --i) {
    454 			nvgBeginPath(args.vg);
    455 			nvgMoveTo(args.vg, cvToPixelX(_midX, _drawSize.x, x.value(i - 1)), cvToPixelY(_midY, _drawSize.y, y.value(i - 1)));
    456 			nvgLineTo(args.vg, cvToPixelX(_midX, _drawSize.x, x.value(i)), cvToPixelY(_midY, _drawSize.y, y.value(i)));
    457 			nvgStrokeWidth(args.vg, width);
    458 			nvgStrokeColor(args.vg, color);
    459 			nvgStroke(args.vg);
    460 			width += widthStep;
    461 			color.a += alphaStep;
    462 		}
    463 		nvgBeginPath(args.vg);
    464 		nvgCircle(args.vg, cvToPixelX(_midX, _drawSize.x, x.value(0)), cvToPixelY(_midY, _drawSize.y, y.value(0)), 0.5f * width);
    465 		nvgStrokeColor(args.vg, color);
    466 		nvgFillColor(args.vg, color);
    467 		nvgStroke(args.vg);
    468 		nvgFill(args.vg);
    469 
    470 		nvgRestore(args.vg);
    471 	}
    472 
    473 	inline float cvToPixelX(float mid, float extent, float cv) {
    474 		return mid + 0.05f * extent * cv;
    475 	}
    476 
    477 	inline float cvToPixelY(float mid, float extent, float cv) {
    478 		return mid - 0.05f * extent * cv;
    479 	}
    480 };
    481 
    482 struct Walk2Widget : BGModuleWidget {
    483 	static constexpr int hp = 14;
    484 
    485 	Walk2Widget(Walk2* module) {
    486 		setModule(module);
    487 		box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
    488 		setPanel(box.size, "Walk2");
    489 		createScrews();
    490 
    491 		{
    492 			auto inset = Vec(10, 25);
    493 			int dim = box.size.x - 2*inset.x;
    494 			auto size = Vec(dim, dim);
    495 			auto display = new Walk2Display(module, size);
    496 			display->box.pos = inset;
    497 			display->box.size = size;
    498 			addChild(display);
    499 		}
    500 
    501 		// generated by svg_widgets.rb
    502 		auto rateXParamPosition = Vec(28.0, 240.0);
    503 		auto rateYParamPosition = Vec(151.5, 240.0);
    504 		auto offsetXParamPosition = Vec(75.0, 234.0);
    505 		auto offsetYParamPosition = Vec(119.0, 234.0);
    506 		auto scaleXParamPosition = Vec(75.0, 262.5);
    507 		auto scaleYParamPosition = Vec(119.0, 262.5);
    508 		auto jumpModeParamPosition = Vec(122.0, 341.7);
    509 
    510 		auto offsetXInputPosition = Vec(10.5, 284.0);
    511 		auto scaleXInputPosition = Vec(41.5, 284.0);
    512 		auto rateXInputPosition = Vec(10.5, 323.0);
    513 		auto offsetYInputPosition = Vec(145.5, 284.0);
    514 		auto scaleYInputPosition = Vec(176.5, 284.0);
    515 		auto rateYInputPosition = Vec(145.5, 323.0);
    516 		auto jumpInputPosition = Vec(78.0, 291.0);
    517 
    518 		auto outXOutputPosition = Vec(41.5, 323.0);
    519 		auto outYOutputPosition = Vec(176.5, 323.0);
    520 		auto distanceOutputPosition = Vec(109.0, 291.0);
    521 
    522 		auto jumpLightPosition = Vec(90.5, 333.0);
    523 		auto sampleholdLightPosition = Vec(90.5, 343.0);
    524 		auto trackholdLightPosition = Vec(90.5, 353.0);
    525 		// end generated by svg_widgets.rb
    526 
    527 		addParam(createParam<Knob29>(rateXParamPosition, module, Walk2::RATE_X_PARAM));
    528 		addParam(createParam<Knob29>(rateYParamPosition, module, Walk2::RATE_Y_PARAM));
    529 		addParam(createParam<Knob16>(offsetXParamPosition, module, Walk2::OFFSET_X_PARAM));
    530 		addParam(createParam<Knob16>(offsetYParamPosition, module, Walk2::OFFSET_Y_PARAM));
    531 		addParam(createParam<Knob16>(scaleXParamPosition, module, Walk2::SCALE_X_PARAM));
    532 		addParam(createParam<Knob16>(scaleYParamPosition, module, Walk2::SCALE_Y_PARAM));
    533 		addParam(createParam<StatefulButton9>(jumpModeParamPosition, module, Walk2::JUMP_MODE_PARAM));
    534 
    535 		addInput(createInput<Port24>(offsetXInputPosition, module, Walk2::OFFSET_X_INPUT));
    536 		addInput(createInput<Port24>(scaleXInputPosition, module, Walk2::SCALE_X_INPUT));
    537 		addInput(createInput<Port24>(rateXInputPosition, module, Walk2::RATE_X_INPUT));
    538 		addInput(createInput<Port24>(offsetYInputPosition, module, Walk2::OFFSET_Y_INPUT));
    539 		addInput(createInput<Port24>(scaleYInputPosition, module, Walk2::SCALE_Y_INPUT));
    540 		addInput(createInput<Port24>(rateYInputPosition, module, Walk2::RATE_Y_INPUT));
    541 		addInput(createInput<Port24>(jumpInputPosition, module, Walk2::JUMP_INPUT));
    542 
    543 		addOutput(createOutput<Port24>(outXOutputPosition, module, Walk2::OUT_X_OUTPUT));
    544 		addOutput(createOutput<Port24>(outYOutputPosition, module, Walk2::OUT_Y_OUTPUT));
    545 		addOutput(createOutput<Port24>(distanceOutputPosition, module, Walk2::DISTANCE_OUTPUT));
    546 
    547 		addChild(createLight<BGSmallLight<GreenLight>>(jumpLightPosition, module, Walk2::JUMP_LIGHT));
    548 		addChild(createLight<BGSmallLight<GreenLight>>(sampleholdLightPosition, module, Walk2::SAMPLEHOLD_LIGHT));
    549 		addChild(createLight<BGSmallLight<GreenLight>>(trackholdLightPosition, module, Walk2::TRACKHOLD_LIGHT));
    550 	}
    551 
    552 	void contextMenu(Menu* menu) override {
    553 		auto m = dynamic_cast<Walk2*>(module);
    554 		assert(m);
    555 
    556 		{
    557 			OptionsMenuItem* mi = new OptionsMenuItem("Display range");
    558 			mi->addItem(OptionMenuItem("+/-5V", [m]() { return m->_zoomOut == false; }, [m]() { m->_zoomOut = false; }));
    559 			mi->addItem(OptionMenuItem("+/-10V", [m]() { return m->_zoomOut == true; }, [m]() { m->_zoomOut = true; }));
    560 			OptionsMenuItem::addToMenu(mi, menu);
    561 		}
    562 		menu->addChild(new BoolOptionMenuItem("Show grid", [m]() { return &m->_drawGrid; }));
    563 		{
    564 			OptionsMenuItem* mi = new OptionsMenuItem("Trace color");
    565 			mi->addItem(OptionMenuItem("Green", [m]() { return m->_traceColor == Walk2::GREEN_TRACE_COLOR; }, [m]() { m->_traceColor = Walk2::GREEN_TRACE_COLOR; }));
    566 			mi->addItem(OptionMenuItem("Orange", [m]() { return m->_traceColor == Walk2::ORANGE_TRACE_COLOR; }, [m]() { m->_traceColor = Walk2::ORANGE_TRACE_COLOR; }));
    567 			mi->addItem(OptionMenuItem("Red", [m]() { return m->_traceColor == Walk2::RED_TRACE_COLOR; }, [m]() { m->_traceColor = Walk2::RED_TRACE_COLOR; }));
    568 			mi->addItem(OptionMenuItem("Blue", [m]() { return m->_traceColor == Walk2::BLUE_TRACE_COLOR; }, [m]() { m->_traceColor = Walk2::BLUE_TRACE_COLOR; }));
    569 			OptionsMenuItem::addToMenu(mi, menu);
    570 		}
    571 	}
    572 };
    573 
    574 Model* modelWalk2 = bogaudio::createModel<Walk2, Walk2Widget>("Bogaudio-Walk2", "WALK2", "2D random-walk and X/Y controller", "Random", "Sample and hold", "Controller");