You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

729 lines
21KB

  1. #include <string.h>
  2. #include "JWModules.hpp"
  3. #include "dsp/digital.hpp"
  4. namespace rack_plugin_JW_Modules {
  5. struct XYPad : Module {
  6. enum ParamIds {
  7. X_POS_PARAM,
  8. Y_POS_PARAM,
  9. GATE_PARAM,
  10. OFFSET_X_VOLTS_PARAM,
  11. OFFSET_Y_VOLTS_PARAM,
  12. SCALE_X_PARAM,
  13. SCALE_Y_PARAM,
  14. AUTO_PLAY_PARAM,
  15. PLAY_SPEED_PARAM,
  16. SPEED_MULT_PARAM,
  17. RND_SHAPES_PARAM,
  18. RND_VARIATION_PARAM,
  19. NUM_PARAMS
  20. };
  21. enum InputIds {
  22. PLAY_GATE_INPUT,
  23. PLAY_SPEED_INPUT,
  24. NUM_INPUTS
  25. };
  26. enum OutputIds {
  27. X_OUTPUT,
  28. Y_OUTPUT,
  29. X_INV_OUTPUT,
  30. Y_INV_OUTPUT,
  31. GATE_OUTPUT,
  32. NUM_OUTPUTS
  33. };
  34. enum State {
  35. STATE_IDLE,
  36. STATE_RECORDING,
  37. STATE_AUTO_PLAYING,
  38. STATE_GATE_PLAYING
  39. };
  40. enum LightIds {
  41. AUTO_LIGHT,
  42. NUM_LIGHTS
  43. };
  44. enum Shapes {
  45. RND_SINE,
  46. RND_SQUARE,
  47. RND_RAMP,
  48. RND_LINE,
  49. RND_NOISE,
  50. RND_SINE_MOD,
  51. RND_SPIRAL,
  52. RND_STEPS,
  53. NUM_SHAPES
  54. };
  55. enum PlayModes {
  56. FWD_LOOP,
  57. BWD_LOOP,
  58. FWD_ONE_SHOT,
  59. BWD_ONE_SHOT,
  60. FWD_BWD_LOOP,
  61. BWD_FWD_LOOP,
  62. NUM_PLAY_MODES
  63. };
  64. float minX = 0, minY = 0, maxX = 0, maxY = 0;
  65. float displayWidth = 0, displayHeight = 0;
  66. float ballRadius = 10;
  67. float ballStrokeWidth = 2;
  68. float minVolt = -5, maxVolt = 5;
  69. float recordPhase = 0.0;
  70. float playbackPhase = 0.0;
  71. bool autoPlayOn = false;
  72. bool playingFwd = true;
  73. int state = STATE_IDLE;
  74. int curPlayMode = FWD_LOOP;
  75. int lastRandomShape = RND_STEPS;
  76. SchmittTrigger autoBtnTrigger;
  77. std::vector<Vec> points;
  78. long curPointIdx = 0;
  79. XYPad() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  80. void step() override;
  81. void reset() override {
  82. setState(STATE_IDLE);
  83. points.clear();
  84. defaultPos();
  85. }
  86. void randomize() override {
  87. randomizeShape();
  88. }
  89. void randomizeShape(){
  90. makeShape((lastRandomShape+1)%NUM_SHAPES);
  91. }
  92. void makeShape(int shape){
  93. lastRandomShape = shape;
  94. int stateBefore = state;
  95. setState(STATE_IDLE);
  96. points.clear();
  97. switch(shape){
  98. case RND_SINE: {
  99. float twoPi = 2.0*M_PI;
  100. float cycles = 1 + int(randomUniform() * 13);
  101. bool inside = true;
  102. for(float i=0; i<twoPi * cycles; i+=M_PI/displayWidth*cycles){
  103. float x = rescalefjw(i, 0, twoPi*cycles, minX, maxX);
  104. float y = rescalefjw(sin(i), -1, 1, minY, maxY);
  105. inside = isInView(x, y);
  106. if(inside)addPoint(x, y);
  107. }
  108. }
  109. break;
  110. case RND_SQUARE: {
  111. float twoPi = 2.0*M_PI;
  112. float cycles = 1 + int(randomUniform() * 13);
  113. bool inside = true;
  114. for(float i=0; i<twoPi * cycles; i+=M_PI/displayWidth*cycles){
  115. float x = rescalefjw(i, 0, twoPi*cycles, minX, maxX);
  116. float y = rescalefjw(sin(i)<0, 0, 1, minY, maxY);
  117. inside = isInView(x, y);
  118. if(inside)addPoint(x, y);
  119. }
  120. }
  121. break;
  122. case RND_RAMP: {
  123. float lastY = maxY;
  124. float rate = randomUniform();
  125. bool inside = true;
  126. for(int i=0; i<5000 && inside; i+=2){
  127. float x = minX + i;
  128. lastY -= powf(2, powf(x*0.005, 2)) * rate;
  129. float y = lastY;
  130. inside = isInView(x, y);
  131. if(inside)addPoint(x, y);
  132. }
  133. }
  134. break;
  135. case RND_LINE: {
  136. float startHeight = (randomUniform() * maxY * 0.5) + (maxY * 0.25);
  137. float rate = randomUniform() - 0.5;
  138. bool inside = true;
  139. for(int i=0; i<5000 && inside; i+=2){
  140. float x = minX + i;
  141. float y = startHeight + rate * x;
  142. inside = isInView(x, y);
  143. if(inside)addPoint(x, y);
  144. }
  145. }
  146. break;
  147. case RND_NOISE: {
  148. float midHeight = maxY / 2.0;
  149. float amp = midHeight * 0.9;
  150. bool inside = true;
  151. for(int i=0; i<5000 && inside; i+=2){
  152. float x = minX + i;
  153. float y = (randomUniform()*2-1) * amp + midHeight;
  154. inside = isInView(x, y);
  155. if(inside)addPoint(x, y);
  156. }
  157. }
  158. break;
  159. case RND_SINE_MOD: {
  160. float midHeight = maxY / 2.0;
  161. float amp = midHeight * 0.90 * 0.50;
  162. float rate = randomUniform() * 0.1;
  163. float rateAdder = randomUniform() * 0.001;
  164. float ampAdder = randomUniform() * 0.25;
  165. bool inside = true;
  166. for(int i=0; i<5000 && inside; i+=2){
  167. float x = minX + i;
  168. float y = sin(i*rate) * amp + (maxY / 2.0);
  169. inside = isInView(x, y);
  170. if(inside)addPoint(x, y);
  171. rate+=rateAdder;
  172. amp+=ampAdder;
  173. }
  174. }
  175. break;
  176. case RND_SPIRAL: {
  177. float curX = maxX / 2.0;
  178. float curY = maxY / 2.0;
  179. float radius = 5;
  180. float rate = 1 + (randomUniform()*0.1);
  181. bool inside = true;
  182. for(int i=0; i<5000 && inside; i+=2){
  183. float x = curX + sin(i/10.0) * radius;
  184. float y = curY + cos(i/10.0) * radius;
  185. inside = isInView(x, y);
  186. if(inside)addPoint(x, y);
  187. radius*=rate;
  188. }
  189. }
  190. break;
  191. case RND_STEPS: {
  192. float x = maxX * 0.5;
  193. float y = maxY * 0.5;
  194. enum stateEnum { ST_RIGHT, ST_LEFT, ST_UP, ST_DOWN };
  195. int squSt = ST_RIGHT;
  196. int stepsBeforeStateChange = 5 * int(randomUniform()*5+1);
  197. bool inside = true;
  198. for(int i=0; i<5000 && inside; i+=2){
  199. if(squSt == ST_RIGHT && x < maxX){
  200. x++;
  201. } else if(squSt == ST_LEFT && x > minX){
  202. x--;
  203. } else if(squSt == ST_UP && y > minY){
  204. y--;
  205. } else if(squSt == ST_DOWN && y < maxY){
  206. y++;
  207. }
  208. if(i % stepsBeforeStateChange == 0){
  209. squSt = int(randomUniform() * 4);
  210. }
  211. inside = isInView(x, y);
  212. if(inside)addPoint(x, y);
  213. }
  214. }
  215. break;
  216. }
  217. setCurrentPos(points[0].x, points[0].y);
  218. setState(stateBefore);
  219. }
  220. json_t *toJson() override {
  221. json_t *rootJ = json_object();
  222. json_object_set_new(rootJ, "lastRandomShape", json_integer(lastRandomShape));
  223. json_object_set_new(rootJ, "curPlayMode", json_integer(curPlayMode));
  224. json_object_set_new(rootJ, "autoPlayOn", json_boolean(autoPlayOn));
  225. json_object_set_new(rootJ, "xPos", json_real(params[X_POS_PARAM].value));
  226. json_object_set_new(rootJ, "yPos", json_real(params[Y_POS_PARAM].value));
  227. json_t *pointsArr = json_array();
  228. for(Vec pt : points){
  229. json_t *posArr = json_array();
  230. json_array_append(posArr, json_real(pt.x));
  231. json_array_append(posArr, json_real(pt.y));
  232. json_array_append(pointsArr, posArr);
  233. }
  234. json_object_set_new(rootJ, "points", pointsArr);
  235. return rootJ;
  236. }
  237. void fromJson(json_t *rootJ) override {
  238. lastRandomShape = json_integer_value(json_object_get(rootJ, "lastRandomShape"));
  239. curPlayMode = json_integer_value(json_object_get(rootJ, "curPlayMode"));
  240. json_t *xPosJ = json_object_get(rootJ, "xPos");
  241. json_t *yPosJ = json_object_get(rootJ, "yPos");
  242. setCurrentPos(json_real_value(xPosJ), json_real_value(yPosJ));
  243. json_t *array = json_object_get(rootJ, "points");
  244. if(array){
  245. size_t index;
  246. json_t *value;
  247. json_array_foreach(array, index, value) {
  248. float x = json_real_value(json_array_get(value, 0));
  249. float y = json_real_value(json_array_get(value, 1));
  250. addPoint(x, y);
  251. }
  252. }
  253. json_t *autoPlayOnJ = json_object_get(rootJ, "autoPlayOn");
  254. if (autoPlayOnJ){
  255. autoPlayOn = json_is_true(autoPlayOnJ);
  256. }
  257. lights[AUTO_LIGHT].value = autoPlayOn ? 1.0 : 0.0;
  258. params[AUTO_PLAY_PARAM].value = autoPlayOn ? 1 : 0;
  259. if(autoPlayOn){setState(STATE_AUTO_PLAYING);}
  260. }
  261. void defaultPos() {
  262. params[XYPad::X_POS_PARAM].value = displayWidth / 2.0;
  263. params[XYPad::Y_POS_PARAM].value = displayHeight / 2.0;
  264. }
  265. void setMouseDown(const Vec &pos, bool down){
  266. if(down){
  267. setCurrentPos(pos.x, pos.y);
  268. setState(STATE_RECORDING);
  269. } else {
  270. if(autoPlayOn && !inputs[PLAY_GATE_INPUT].active){ //no auto play if wire connected to play in
  271. setState(STATE_AUTO_PLAYING);
  272. } else {
  273. setState(STATE_IDLE);
  274. }
  275. }
  276. }
  277. void setCurrentPos(float x, float y){
  278. params[X_POS_PARAM].value = clampfjw(x, minX, maxX);
  279. params[Y_POS_PARAM].value = clampfjw(y, minY, maxY);
  280. }
  281. bool isInView(float x, float y){
  282. return x >= minX && x <= maxX && y >= minY && y <= maxY;
  283. }
  284. void addPoint(float x, float y){
  285. points.push_back(Vec(x, y));
  286. }
  287. void updateMinMax(){
  288. float distToMid = ballRadius + ballStrokeWidth;
  289. minX = distToMid;
  290. minY = distToMid;
  291. maxX = displayWidth - distToMid;
  292. maxY = displayHeight - distToMid;
  293. }
  294. bool isStatePlaying() {
  295. return state == STATE_GATE_PLAYING || state == STATE_AUTO_PLAYING;
  296. }
  297. void playback(){
  298. if(isStatePlaying() && points.size() > 0){
  299. params[X_POS_PARAM].value = points[curPointIdx].x;
  300. params[Y_POS_PARAM].value = points[curPointIdx].y;
  301. if(curPlayMode == FWD_LOOP || curPlayMode == FWD_ONE_SHOT){
  302. playingFwd = true;
  303. } else if(curPlayMode == BWD_LOOP || curPlayMode == BWD_ONE_SHOT){
  304. playingFwd = false;
  305. }
  306. curPointIdx += playingFwd ? 1 : -1;
  307. if(curPointIdx >= 0 && curPointIdx < long(points.size())){
  308. params[GATE_PARAM].value = true; //keep gate on
  309. } else {
  310. params[GATE_PARAM].value = false;
  311. if(curPlayMode == FWD_LOOP){
  312. curPointIdx = 0;
  313. } else if(curPlayMode == BWD_LOOP){
  314. curPointIdx = points.size() - 1;
  315. } else if(curPlayMode == FWD_ONE_SHOT || curPlayMode == BWD_ONE_SHOT){
  316. setState(STATE_IDLE);//done playing
  317. curPointIdx = playingFwd ? points.size() - 1 : 0;
  318. } else if(curPlayMode == FWD_BWD_LOOP || curPlayMode == BWD_FWD_LOOP){
  319. playingFwd = !playingFwd; //go the other way now
  320. curPointIdx = playingFwd ? 0 : points.size() - 1;
  321. }
  322. }
  323. }
  324. }
  325. void setState(int newState){
  326. switch(newState){
  327. case STATE_IDLE:
  328. curPointIdx = 0;
  329. params[GATE_PARAM].value = false;
  330. break;
  331. case STATE_RECORDING:
  332. points.clear();
  333. curPointIdx = 0;
  334. params[GATE_PARAM].value = true;
  335. break;
  336. case STATE_AUTO_PLAYING:
  337. params[GATE_PARAM].value = true;
  338. break;
  339. case STATE_GATE_PLAYING:
  340. params[GATE_PARAM].value = true;
  341. break;
  342. }
  343. if(isStatePlaying()){
  344. if(curPlayMode == FWD_LOOP || curPlayMode == FWD_ONE_SHOT){
  345. curPointIdx = 0;
  346. } else if(curPlayMode == BWD_LOOP || curPlayMode == BWD_ONE_SHOT){
  347. curPointIdx = points.size() - 1;
  348. }
  349. }
  350. state = newState;
  351. }
  352. };
  353. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  354. void XYPad::step() {
  355. if (autoBtnTrigger.process(params[AUTO_PLAY_PARAM].value)) {
  356. autoPlayOn = !autoPlayOn;
  357. if(autoPlayOn){
  358. if(!isStatePlaying()){
  359. setState(STATE_AUTO_PLAYING);
  360. }
  361. } else {
  362. //stop when auto turned off
  363. setState(STATE_IDLE);
  364. }
  365. }
  366. lights[AUTO_LIGHT].value = autoPlayOn ? 1.0 : 0.0;
  367. if (inputs[PLAY_GATE_INPUT].active) {
  368. params[AUTO_PLAY_PARAM].value = 0; //disable autoplay if wire connected to play gate
  369. autoPlayOn = false; //disable autoplay if wire connected to play gate
  370. if (inputs[PLAY_GATE_INPUT].value >= 1.0) {
  371. if(!isStatePlaying() && state != STATE_RECORDING){
  372. setState(STATE_GATE_PLAYING);
  373. }
  374. } else {
  375. if(isStatePlaying()){
  376. setState(STATE_IDLE);
  377. }
  378. }
  379. } else if(state == STATE_GATE_PLAYING){//wire removed while playing
  380. setState(STATE_IDLE);
  381. }
  382. if(state == STATE_RECORDING){
  383. float recordClockTime = 50;
  384. recordPhase += recordClockTime / engineGetSampleRate();
  385. if (recordPhase >= 1.0) {
  386. recordPhase -= 1.0;
  387. addPoint(params[X_POS_PARAM].value, params[Y_POS_PARAM].value);
  388. }
  389. } else if(isStatePlaying()){
  390. float playSpeedTotal = clampfjw(inputs[PLAY_SPEED_INPUT].value + params[PLAY_SPEED_PARAM].value, 0, 20);
  391. float playbackClockTime = rescalefjw(playSpeedTotal, 0, 20, 1, 500 * params[SPEED_MULT_PARAM].value);
  392. playbackPhase += playbackClockTime / engineGetSampleRate();
  393. if (playbackPhase >= 1.0) {
  394. playbackPhase -= 1.0;
  395. playback();
  396. }
  397. }
  398. float xOut = rescalefjw(params[X_POS_PARAM].value, minX, maxX, minVolt, maxVolt);
  399. float yOut = rescalefjw(params[Y_POS_PARAM].value, minY, maxY, maxVolt, minVolt); //y is inverted because gui coords
  400. outputs[X_OUTPUT].value = (xOut + params[OFFSET_X_VOLTS_PARAM].value) * params[SCALE_X_PARAM].value;
  401. outputs[Y_OUTPUT].value = (yOut + params[OFFSET_Y_VOLTS_PARAM].value) * params[SCALE_Y_PARAM].value;
  402. float xInvOut = rescalefjw(params[X_POS_PARAM].value, minX, maxX, maxVolt, minVolt);
  403. float yInvOut = rescalefjw(params[Y_POS_PARAM].value, minY, maxY, minVolt, maxVolt); //y is inverted because gui coords
  404. outputs[X_INV_OUTPUT].value = (xInvOut + params[OFFSET_X_VOLTS_PARAM].value) * params[SCALE_X_PARAM].value;
  405. outputs[Y_INV_OUTPUT].value = (yInvOut + params[OFFSET_Y_VOLTS_PARAM].value) * params[SCALE_Y_PARAM].value;
  406. outputs[GATE_OUTPUT].value = params[GATE_PARAM].value * 10;
  407. }
  408. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  409. struct XYPadDisplay : Widget {
  410. XYPad *module;
  411. XYPadDisplay() {}
  412. float initX = 0;
  413. float initY = 0;
  414. float dragX = 0;
  415. float dragY = 0;
  416. void onMouseDown(EventMouseDown &e) override {
  417. if (e.button == 0) {
  418. e.consumed = true;
  419. e.target = this;
  420. initX = e.pos.x;
  421. initY = e.pos.y;
  422. module->setMouseDown(e.pos, true);
  423. }
  424. }
  425. void onMouseMove(EventMouseMove &e) override {
  426. }
  427. void onMouseUp(EventMouseUp &e) override {
  428. if(e.button==0)module->setMouseDown(e.pos, false);
  429. }
  430. void onDragStart(EventDragStart &e) override {
  431. dragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  432. dragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  433. }
  434. void onDragEnd(EventDragEnd &e) override {
  435. module->setMouseDown(Vec(0,0), false);
  436. RACK_PLUGIN_UI_DRAGGED_WIDGET_SET(NULL);
  437. }
  438. void onDragMove(EventDragMove &e) override {
  439. if(module->state == XYPad::STATE_RECORDING){
  440. float newDragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  441. float newDragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  442. module->setCurrentPos(initX+(newDragX-dragX), initY+(newDragY-dragY));
  443. }
  444. }
  445. void draw(NVGcontext *vg) override {
  446. float ballX = module->params[XYPad::X_POS_PARAM].value;
  447. float ballY = module->params[XYPad::Y_POS_PARAM].value;
  448. float invBallX = module->displayWidth-ballX;
  449. float invBallY = module->displayHeight-ballY;
  450. //background
  451. nvgFillColor(vg, nvgRGB(20, 30, 33));
  452. nvgBeginPath(vg);
  453. nvgRect(vg, 0, 0, box.size.x, box.size.y);
  454. nvgFill(vg);
  455. //INVERTED///////////////////////////////////
  456. NVGcolor invertedColor = nvgRGB(20, 50, 53);
  457. NVGcolor ballColor = nvgRGB(25, 150, 252);
  458. //horizontal line
  459. nvgStrokeColor(vg, invertedColor);
  460. nvgBeginPath(vg);
  461. nvgMoveTo(vg, 0, invBallY);
  462. nvgLineTo(vg, box.size.x, invBallY);
  463. nvgStroke(vg);
  464. //vertical line
  465. nvgStrokeColor(vg, invertedColor);
  466. nvgBeginPath(vg);
  467. nvgMoveTo(vg, invBallX, 0);
  468. nvgLineTo(vg, invBallX, box.size.y);
  469. nvgStroke(vg);
  470. //inv ball
  471. nvgFillColor(vg, invertedColor);
  472. nvgStrokeColor(vg, invertedColor);
  473. nvgStrokeWidth(vg, module->ballStrokeWidth);
  474. nvgBeginPath(vg);
  475. nvgCircle(vg, module->displayWidth-ballX, module->displayHeight-ballY, module->ballRadius);
  476. if(module->params[XYPad::GATE_PARAM].value)nvgFill(vg);
  477. nvgStroke(vg);
  478. //POINTS///////////////////////////////////
  479. if(module->points.size() > 0){
  480. nvgStrokeColor(vg, ballColor);
  481. nvgStrokeWidth(vg, 2);
  482. nvgBeginPath(vg);
  483. long lastI = module->points.size() - 1;
  484. for (long i = lastI; i>=0 && i<long(module->points.size()); i--) {
  485. if(i == lastI){
  486. nvgMoveTo(vg, module->points[i].x, module->points[i].y);
  487. } else {
  488. nvgLineTo(vg, module->points[i].x, module->points[i].y);
  489. }
  490. }
  491. nvgStroke(vg);
  492. }
  493. //MAIN///////////////////////////////////
  494. //horizontal line
  495. nvgStrokeColor(vg, nvgRGB(255, 255, 255));
  496. nvgBeginPath(vg);
  497. nvgMoveTo(vg, 0, ballY);
  498. nvgLineTo(vg, box.size.x, ballY);
  499. nvgStroke(vg);
  500. //vertical line
  501. nvgStrokeColor(vg, nvgRGB(255, 255, 255));
  502. nvgBeginPath(vg);
  503. nvgMoveTo(vg, ballX, 0);
  504. nvgLineTo(vg, ballX, box.size.y);
  505. nvgStroke(vg);
  506. //ball
  507. nvgFillColor(vg, ballColor);
  508. nvgStrokeColor(vg, ballColor);
  509. nvgStrokeWidth(vg, module->ballStrokeWidth);
  510. nvgBeginPath(vg);
  511. nvgCircle(vg, ballX, ballY, module->ballRadius);
  512. if(module->params[XYPad::GATE_PARAM].value)nvgFill(vg);
  513. nvgStroke(vg);
  514. }
  515. };
  516. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  517. struct XYPadWidget : ModuleWidget {
  518. XYPadWidget(XYPad *module);
  519. Menu *createContextMenu() override;
  520. };
  521. struct RandomShapeButton : TinyButton {
  522. void onMouseDown(EventMouseDown &e) override {
  523. TinyButton::onMouseDown(e);
  524. XYPadWidget *xyw = this->getAncestorOfType<XYPadWidget>();
  525. XYPad *xyPad = dynamic_cast<XYPad*>(xyw->module);
  526. xyPad->randomizeShape();
  527. }
  528. };
  529. struct RandomVariationButton : TinyButton {
  530. void onMouseDown(EventMouseDown &e) override {
  531. TinyButton::onMouseDown(e);
  532. XYPadWidget *xyw = this->getAncestorOfType<XYPadWidget>();
  533. XYPad *xyPad = dynamic_cast<XYPad*>(xyw->module);
  534. xyPad->makeShape(xyPad->lastRandomShape);
  535. }
  536. };
  537. XYPadWidget::XYPadWidget(XYPad *module) : ModuleWidget(module) {
  538. box.size = Vec(RACK_GRID_WIDTH*24, RACK_GRID_HEIGHT);
  539. SVGPanel *panel = new SVGPanel();
  540. panel->box.size = box.size;
  541. panel->setBackground(SVG::load(assetPlugin(plugin, "res/XYPad.svg")));
  542. addChild(panel);
  543. XYPadDisplay *display = new XYPadDisplay();
  544. display->module = module;
  545. display->box.pos = Vec(2, 40);
  546. display->box.size = Vec(box.size.x - 4, RACK_GRID_HEIGHT - 80);
  547. addChild(display);
  548. module->displayWidth = display->box.size.x;
  549. module->displayHeight = display->box.size.y;
  550. module->updateMinMax();
  551. module->defaultPos();
  552. addChild(Widget::create<Screw_J>(Vec(40, 20)));
  553. addChild(Widget::create<Screw_W>(Vec(55, 20)));
  554. addParam(ParamWidget::create<RandomShapeButton>(Vec(90, 20), module, XYPad::RND_SHAPES_PARAM, 0.0, 1.0, 0.0));
  555. addParam(ParamWidget::create<RandomVariationButton>(Vec(105, 20), module, XYPad::RND_VARIATION_PARAM, 0.0, 1.0, 0.0));
  556. addParam(ParamWidget::create<JwTinyKnob>(Vec(140, 20), module, XYPad::SCALE_X_PARAM, 0.01, 1.0, 0.5));
  557. addParam(ParamWidget::create<JwTinyKnob>(Vec(200, 20), module, XYPad::SCALE_Y_PARAM, 0.01, 1.0, 0.5));
  558. addParam(ParamWidget::create<JwTinyKnob>(Vec(260, 20), module, XYPad::OFFSET_X_VOLTS_PARAM, -5.0, 5.0, 5.0));
  559. addParam(ParamWidget::create<JwTinyKnob>(Vec(320, 20), module, XYPad::OFFSET_Y_VOLTS_PARAM, -5.0, 5.0, 5.0));
  560. ////////////////////////////////////////////////////////////
  561. addInput(Port::create<TinyPJ301MPort>(Vec(25, 360), Port::INPUT, module, XYPad::PLAY_GATE_INPUT));
  562. addParam(ParamWidget::create<TinyButton>(Vec(71, 360), module, XYPad::AUTO_PLAY_PARAM, 0.0, 1.0, 0.0));
  563. addChild(ModuleLightWidget::create<SmallLight<MyBlueValueLight>>(Vec(71+3.75, 360+3.75), module, XYPad::AUTO_LIGHT));
  564. addInput(Port::create<TinyPJ301MPort>(Vec(110, 360), Port::INPUT, module, XYPad::PLAY_SPEED_INPUT));
  565. addParam(ParamWidget::create<JwTinyKnob>(Vec(130, 360), module, XYPad::PLAY_SPEED_PARAM, 0.0, 10.0, 1.0));
  566. addParam(ParamWidget::create<JwTinyKnob>(Vec(157, 360), module, XYPad::SPEED_MULT_PARAM, 1.0, 100.0, 1.0));
  567. addOutput(Port::create<TinyPJ301MPort>(Vec(195, 360), Port::OUTPUT, module, XYPad::X_OUTPUT));
  568. addOutput(Port::create<TinyPJ301MPort>(Vec(220, 360), Port::OUTPUT, module, XYPad::Y_OUTPUT));
  569. addOutput(Port::create<TinyPJ301MPort>(Vec(255, 360), Port::OUTPUT, module, XYPad::X_INV_OUTPUT));
  570. addOutput(Port::create<TinyPJ301MPort>(Vec(280, 360), Port::OUTPUT, module, XYPad::Y_INV_OUTPUT));
  571. addOutput(Port::create<TinyPJ301MPort>(Vec(320, 360), Port::OUTPUT, module, XYPad::GATE_OUTPUT));
  572. }
  573. struct PlayModeItem : MenuItem {
  574. XYPad *xyPad;
  575. int mode;
  576. void onAction(EventAction &e) override {
  577. xyPad->curPlayMode = mode;
  578. xyPad->setState(XYPad::STATE_AUTO_PLAYING);
  579. }
  580. void step() override {
  581. rightText = (xyPad->curPlayMode == mode) ? "✔" : "";
  582. }
  583. };
  584. struct ShapeMenuItem : MenuItem {
  585. XYPad *xyPad;
  586. int shape = -1;
  587. void onAction(EventAction &e) override {
  588. xyPad->makeShape(shape);
  589. }
  590. };
  591. Menu *XYPadWidget::createContextMenu() {
  592. Menu *menu = ModuleWidget::createContextMenu();
  593. {
  594. MenuLabel *spacerLabel = new MenuLabel();
  595. menu->addChild(spacerLabel);
  596. }
  597. XYPad *xyPad = dynamic_cast<XYPad*>(module);
  598. assert(xyPad);
  599. {
  600. PlayModeItem *item = new PlayModeItem();
  601. item->text = "Forward Loop";
  602. item->xyPad = xyPad;
  603. item->mode = XYPad::FWD_LOOP;
  604. menu->addChild(item);
  605. }
  606. {
  607. PlayModeItem *item = new PlayModeItem();
  608. item->text = "Backward Loop";
  609. item->xyPad = xyPad;
  610. item->mode = XYPad::BWD_LOOP;
  611. menu->addChild(item);
  612. }
  613. {
  614. PlayModeItem *item = new PlayModeItem();
  615. item->text = "Forward One-Shot";
  616. item->xyPad = xyPad;
  617. item->mode = XYPad::FWD_ONE_SHOT;
  618. menu->addChild(item);
  619. }
  620. {
  621. PlayModeItem *item = new PlayModeItem();
  622. item->text = "Backward One-Shot";
  623. item->xyPad = xyPad;
  624. item->mode = XYPad::BWD_ONE_SHOT;
  625. menu->addChild(item);
  626. }
  627. {
  628. PlayModeItem *item = new PlayModeItem();
  629. item->text = "Forward-Backward Loop";
  630. item->xyPad = xyPad;
  631. item->mode = XYPad::FWD_BWD_LOOP;
  632. menu->addChild(item);
  633. }
  634. {
  635. PlayModeItem *item = new PlayModeItem();
  636. item->text = "Backward-Forward Loop";
  637. item->xyPad = xyPad;
  638. item->mode = XYPad::BWD_FWD_LOOP;
  639. menu->addChild(item);
  640. }
  641. return menu;
  642. }
  643. } // namespace rack_plugin_JW_Modules
  644. using namespace rack_plugin_JW_Modules;
  645. RACK_PLUGIN_MODEL_INIT(JW_Modules, XYPad) {
  646. Model *modelXYPad = Model::create<XYPad, XYPadWidget>("JW-Modules", "XYPad", "XY Pad", LFO_TAG, ENVELOPE_GENERATOR_TAG, RANDOM_TAG, OSCILLATOR_TAG, SAMPLE_AND_HOLD_TAG);
  647. return modelXYPad;
  648. }