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");