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.

797 lines
25KB

  1. #include <string.h>
  2. #include <algorithm>
  3. #include "JWModules.hpp"
  4. #include "dsp/digital.hpp"
  5. namespace rack_plugin_JW_Modules {
  6. #define ROWS 32
  7. #define COLS 32
  8. #define CELLS 1024
  9. #define POLY 16
  10. #define HW 11.75 //cell height and width
  11. struct NoteSeq : Module,QuantizeUtils {
  12. enum ParamIds {
  13. STEP_BTN_PARAM,
  14. LENGTH_KNOB_PARAM,
  15. PLAY_MODE_KNOB_PARAM,
  16. RESET_BTN_PARAM,
  17. CLEAR_BTN_PARAM,
  18. RND_MODE_KNOB_PARAM,
  19. RND_TRIG_BTN_PARAM,
  20. RND_AMT_KNOB_PARAM,
  21. ROT_RIGHT_BTN_PARAM,
  22. ROT_LEFT_BTN_PARAM,
  23. FLIP_HORIZ_BTN_PARAM,
  24. FLIP_VERT_BTN_PARAM,
  25. SHIFT_UP_BTN_PARAM,
  26. SHIFT_DOWN_BTN_PARAM,
  27. LIFE_ON_SWITCH_PARAM,
  28. LIFE_SPEED_KNOB_PARAM,
  29. SCALE_KNOB_PARAM,
  30. NOTE_KNOB_PARAM,
  31. OCTAVE_KNOB_PARAM,
  32. LOW_HIGH_SWITCH_PARAM,
  33. HIGHEST_NOTE_PARAM,
  34. LOWEST_NOTE_PARAM,
  35. INCLUDE_INACTIVE_PARAM,
  36. NUM_PARAMS
  37. };
  38. enum InputIds {
  39. CLOCK_INPUT,
  40. RESET_INPUT,
  41. CLEAR_INPUT,
  42. RND_TRIG_INPUT,
  43. RND_AMT_INPUT,
  44. ROT_RIGHT_INPUT,
  45. ROT_LEFT_INPUT,
  46. FLIP_HORIZ_INPUT,
  47. FLIP_VERT_INPUT,
  48. SHIFT_UP_INPUT,
  49. SHIFT_DOWN_INPUT,
  50. HIGHEST_NOTE_INPUT,
  51. LOWEST_NOTE_INPUT,
  52. NUM_INPUTS
  53. };
  54. enum OutputIds {
  55. VOCT_MAIN_OUTPUT,
  56. GATE_MAIN_OUTPUT = VOCT_MAIN_OUTPUT + POLY,
  57. MIN_VOCT_OUTPUT = GATE_MAIN_OUTPUT + POLY,
  58. MIN_GATE_OUTPUT,
  59. MID_VOCT_OUTPUT,
  60. MID_GATE_OUTPUT,
  61. MAX_VOCT_OUTPUT,
  62. MAX_GATE_OUTPUT,
  63. RND_VOCT_OUTPUT,
  64. RND_GATE_OUTPUT,
  65. NUM_OUTPUTS
  66. };
  67. enum LightIds {
  68. GATES_LIGHT,
  69. NUM_LIGHTS = GATES_LIGHT + POLY,
  70. };
  71. enum PlayMode {
  72. PM_FWD_LOOP,
  73. PM_BWD_LOOP,
  74. PM_FWD_BWD_LOOP,
  75. PM_BWD_FWD_LOOP,
  76. PM_RANDOM_POS,
  77. NUM_PLAY_MODES
  78. };
  79. enum RndMode {
  80. RND_BASIC,
  81. RND_EUCLID,
  82. RND_SIN_WAVE,
  83. RND_LIFE_GLIDERS,
  84. NUM_RND_MODES
  85. };
  86. enum ShiftDirection {
  87. DIR_UP,
  88. DIR_DOWN
  89. };
  90. enum RotateDirection {
  91. DIR_LEFT,
  92. DIR_RIGHT
  93. };
  94. enum FlipDirection {
  95. DIR_HORIZ,
  96. DIR_VERT
  97. };
  98. float displayWidth = 0, displayHeight = 0;
  99. float rate = 1.0 / engineGetSampleRate();
  100. float lifeRate = 0.5 * engineGetSampleRate();
  101. long lifeCounter = 0;
  102. int seqPos = 0;
  103. float rndFloat0to1AtClockStep = randomUniform();
  104. bool goingForward = true;
  105. bool *cells = new bool[CELLS];
  106. SchmittTrigger clockTrig, resetTrig, clearTrig;
  107. SchmittTrigger rndTrig, shiftUpTrig, shiftDownTrig;
  108. SchmittTrigger rotateRightTrig, rotateLeftTrig, flipHorizTrig, flipVertTrig;
  109. PulseGenerator gatePulse;
  110. NoteSeq() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  111. reset();
  112. }
  113. ~NoteSeq() {
  114. delete [] cells;
  115. }
  116. void randomize() override {
  117. randomizeCells();
  118. }
  119. void reset() override {
  120. resetSeq();
  121. clearCells();
  122. }
  123. void onSampleRateChange() override {
  124. rate = 1.0 / engineGetSampleRate();
  125. }
  126. json_t *toJson() override {
  127. json_t *rootJ = json_object();
  128. json_t *cellsJ = json_array();
  129. for (int i = 0; i < CELLS; i++) {
  130. json_t *cellJ = json_integer((int) cells[i]);
  131. json_array_append_new(cellsJ, cellJ);
  132. }
  133. json_object_set_new(rootJ, "cells", cellsJ);
  134. return rootJ;
  135. }
  136. void fromJson(json_t *rootJ) override {
  137. json_t *cellsJ = json_object_get(rootJ, "cells");
  138. if (cellsJ) {
  139. for (int i = 0; i < CELLS; i++) {
  140. json_t *cellJ = json_array_get(cellsJ, i);
  141. if (cellJ)
  142. cells[i] = json_integer_value(cellJ);
  143. }
  144. }
  145. }
  146. //TODO maybe add length input
  147. //TODO maybe add start pos knob and input
  148. void step() override {
  149. if(params[LIFE_ON_SWITCH_PARAM].value){
  150. if(lifeCounter % int(params[LIFE_SPEED_KNOB_PARAM].value) == 0){
  151. stepLife();
  152. lifeCounter = 1;
  153. }
  154. }
  155. if (clearTrig.process(params[CLEAR_BTN_PARAM].value + inputs[CLEAR_INPUT].value)) { clearCells(); }
  156. if (rndTrig.process(params[RND_TRIG_BTN_PARAM].value + inputs[RND_TRIG_INPUT].value)) { randomizeCells(); }
  157. if (rotateRightTrig.process(params[ROT_RIGHT_BTN_PARAM].value + inputs[ROT_RIGHT_INPUT].value)) { rotateCells(DIR_RIGHT); }
  158. if (rotateLeftTrig.process(params[ROT_LEFT_BTN_PARAM].value + inputs[ROT_LEFT_INPUT].value)) { rotateCells(DIR_LEFT); }
  159. if (flipHorizTrig.process(params[FLIP_HORIZ_BTN_PARAM].value + inputs[FLIP_HORIZ_INPUT].value)) { flipCells(DIR_HORIZ); }
  160. if (flipVertTrig.process(params[FLIP_VERT_BTN_PARAM].value + inputs[FLIP_VERT_INPUT].value)) { flipCells(DIR_VERT); }
  161. if (shiftUpTrig.process(params[SHIFT_UP_BTN_PARAM].value + inputs[SHIFT_UP_INPUT].value)) { shiftCells(DIR_UP); }
  162. if (shiftDownTrig.process(params[SHIFT_DOWN_BTN_PARAM].value + inputs[SHIFT_DOWN_INPUT].value)) { shiftCells(DIR_DOWN); }
  163. if (resetTrig.process(params[RESET_BTN_PARAM].value + inputs[RESET_INPUT].value)) {
  164. resetSeq();
  165. }
  166. if (clockTrig.process(inputs[CLOCK_INPUT].value + params[STEP_BTN_PARAM].value)) {
  167. clockStep();
  168. }
  169. bool pulse = gatePulse.process(1.0 / engineGetSampleRate());
  170. ////////////////////////////////////////////// POLY OUTPUTS //////////////////////////////////////////////
  171. std::vector<int> polyYVals = getYValsFromBottomAtSeqPos(params[INCLUDE_INACTIVE_PARAM].value, pulse);
  172. for(int i=0;i<POLY;i++){ //param # starts from bottom
  173. bool hasVal = i < int(polyYVals.size());
  174. bool cellActive = hasVal && cells[iFromXY(seqPos, ROWS - polyYVals[i] - 1)];
  175. if(cellActive){
  176. outputs[VOCT_MAIN_OUTPUT + i].value = closestVoltageForRow(polyYVals[i]);
  177. }
  178. outputs[GATE_MAIN_OUTPUT + i].value = pulse && cellActive ? 10.0 : 0.0;
  179. lights[GATES_LIGHT + i].value = cellActive ? 1.0 : 0.0;
  180. }
  181. ////////////////////////////////////////////// MONO OUTPUTS //////////////////////////////////////////////
  182. std::vector<int> monoYVals = getYValsFromBottomAtSeqPos(false, pulse);
  183. bool atLeastOne = int(monoYVals.size()) > 0;
  184. if(outputs[MIN_VOCT_OUTPUT].active && atLeastOne){
  185. std::vector<int>::iterator pos = std::min_element(monoYVals.begin(), monoYVals.end());
  186. outputs[MIN_VOCT_OUTPUT].value = closestVoltageForRow(*pos);
  187. }
  188. if(outputs[MIN_GATE_OUTPUT].active){
  189. outputs[MIN_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0;
  190. }
  191. if(outputs[MID_VOCT_OUTPUT].active && atLeastOne){
  192. std::vector<int>::iterator minPos = std::min_element(monoYVals.begin(), monoYVals.end());
  193. std::vector<int>::iterator maxPos = std::max_element(monoYVals.begin(), monoYVals.end());
  194. outputs[MID_VOCT_OUTPUT].value = closestVoltageForRow((*minPos + *maxPos) * 0.5);
  195. }
  196. if(outputs[MID_GATE_OUTPUT].active){
  197. outputs[MID_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0;
  198. }
  199. if(outputs[MAX_VOCT_OUTPUT].active && atLeastOne){
  200. std::vector<int>::iterator pos = std::max_element(monoYVals.begin(), monoYVals.end());
  201. outputs[MAX_VOCT_OUTPUT].value = closestVoltageForRow(*pos);
  202. }
  203. if(outputs[MAX_GATE_OUTPUT].active){
  204. outputs[MAX_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0;
  205. }
  206. if(outputs[RND_VOCT_OUTPUT].active && atLeastOne){
  207. outputs[RND_VOCT_OUTPUT].value = closestVoltageForRow(monoYVals[int(rndFloat0to1AtClockStep * monoYVals.size())]);
  208. }
  209. if(outputs[RND_GATE_OUTPUT].active){
  210. outputs[RND_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0;
  211. }
  212. }
  213. std::vector<int> getYValsFromBottomAtSeqPos(bool includeInactive, bool pulse){
  214. std::vector<int> v;
  215. for(int i=CELLS-1;i>=0;i--){
  216. int x = xFromI(i);
  217. if(x == seqPos && (cells[i] || includeInactive)){
  218. int y = yFromI(i);
  219. int yFromBottom = ROWS - 1 - y;
  220. if(yFromBottom <= getFinalHighestNote1to32()-1 &&
  221. yFromBottom >= getFinalLowestNote1to32()-1){
  222. v.push_back(yFromBottom);
  223. }
  224. }
  225. }
  226. // if(pulse){
  227. // printf("getActiveYValsAtSeqPos ");
  228. // for(int n : v) { printf("%i ", n); }
  229. // printf("\n\n");
  230. // }
  231. return v;
  232. }
  233. int getFinalHighestNote1to32(){
  234. int inputOffset = inputs[HIGHEST_NOTE_INPUT].active ? clampijw(int(rescalefjw(inputs[HIGHEST_NOTE_INPUT].value, -5, 5, -16, 16)), 1, 32) : 0;
  235. return params[HIGHEST_NOTE_PARAM].value + inputOffset;
  236. }
  237. int getFinalLowestNote1to32(){
  238. int inputOffset = inputs[LOWEST_NOTE_INPUT].active ? clampijw(int(rescalefjw(inputs[LOWEST_NOTE_INPUT].value, -5, 5, -16, 16)), 1, 32) : 0;
  239. return params[LOWEST_NOTE_PARAM].value + inputOffset;
  240. }
  241. float closestVoltageForRow(int cellYFromBottom){
  242. int octave = params[OCTAVE_KNOB_PARAM].value;
  243. int rootNote = params[NOTE_KNOB_PARAM].value;
  244. int scale = params[SCALE_KNOB_PARAM].value;
  245. return closestVoltageInScale(octave + (cellYFromBottom * 0.0833), rootNote, scale);
  246. }
  247. void clockStep(){
  248. gatePulse.trigger(1e-3);
  249. lifeCounter++;
  250. rndFloat0to1AtClockStep = randomUniform();
  251. //iterate seq pos
  252. int curPlayMode = int(params[PLAY_MODE_KNOB_PARAM].value);
  253. int seqLen = int(params[NoteSeq::LENGTH_KNOB_PARAM].value);
  254. if(curPlayMode == PM_FWD_LOOP){
  255. seqPos = (seqPos + 1) % seqLen;
  256. goingForward = true;
  257. } else if(curPlayMode == PM_BWD_LOOP){
  258. seqPos = seqPos > 0 ? seqPos - 1 : seqLen - 1;
  259. goingForward = false;
  260. } else if(curPlayMode == PM_FWD_BWD_LOOP || curPlayMode == PM_BWD_FWD_LOOP){
  261. if(goingForward){
  262. if(seqPos < seqLen - 1){
  263. seqPos++;
  264. } else {
  265. seqPos--;
  266. goingForward = false;
  267. }
  268. } else {
  269. if(seqPos > 0){
  270. seqPos--;
  271. } else {
  272. seqPos++;
  273. goingForward = true;
  274. }
  275. }
  276. } else if(curPlayMode == PM_RANDOM_POS){
  277. seqPos = int(randomUniform() * seqLen);
  278. }
  279. }
  280. void resetSeq(){
  281. int curPlayMode = int(params[PLAY_MODE_KNOB_PARAM].value);
  282. if(curPlayMode == PM_FWD_LOOP || curPlayMode == PM_FWD_BWD_LOOP || curPlayMode == PM_RANDOM_POS){
  283. seqPos = 0;
  284. } else if(curPlayMode == PM_BWD_LOOP || curPlayMode == PM_BWD_FWD_LOOP){
  285. seqPos = int(params[NoteSeq::LENGTH_KNOB_PARAM].value) - 1;
  286. }
  287. }
  288. void rotateCells(RotateDirection dir){
  289. bool *newCells = new bool[CELLS];
  290. for(int x=0; x < COLS; x++){
  291. for(int y=0; y < ROWS; y++){
  292. switch(dir){
  293. case DIR_RIGHT:
  294. newCells[iFromXY(x, y)] = cells[iFromXY(y, COLS - x - 1)];
  295. break;
  296. case DIR_LEFT:
  297. newCells[iFromXY(x, y)] = cells[iFromXY(COLS - y - 1, x)];
  298. break;
  299. }
  300. }
  301. }
  302. cells = newCells;
  303. }
  304. void flipCells(FlipDirection dir){
  305. bool *newCells = new bool[CELLS];
  306. for(int x=0; x < COLS; x++){
  307. for(int y=0; y < ROWS; y++){
  308. switch(dir){
  309. case DIR_HORIZ:
  310. newCells[iFromXY(x, y)] = cells[iFromXY(COLS - 1 - x, y)];
  311. break;
  312. case DIR_VERT:
  313. newCells[iFromXY(x, y)] = cells[iFromXY(x, ROWS - 1 - y)];
  314. break;
  315. }
  316. }
  317. }
  318. cells = newCells;
  319. }
  320. void shiftCells(ShiftDirection dir){
  321. bool *newCells = new bool[CELLS];
  322. for(int x=0; x < COLS; x++){
  323. for(int y=0; y < ROWS; y++){
  324. switch(dir){
  325. case DIR_UP:
  326. newCells[iFromXY(x, y)] = cells[iFromXY(x, y==ROWS-1 ? 0 : y + 1)];
  327. break;
  328. case DIR_DOWN:
  329. newCells[iFromXY(x, y)] = cells[iFromXY(x, y==0 ? ROWS-1 : y - 1)];
  330. break;
  331. }
  332. }
  333. }
  334. cells = newCells;
  335. }
  336. void clearCells() {
  337. for(int i=0;i<CELLS;i++){
  338. cells[i] = false;
  339. }
  340. }
  341. void randomizeCells() {
  342. clearCells();
  343. float rndAmt = params[RND_AMT_KNOB_PARAM].value + inputs[RND_AMT_INPUT].value*0.1;
  344. switch(int(params[RND_MODE_KNOB_PARAM].value)){
  345. case RND_BASIC:{
  346. for(int i=0;i<CELLS;i++){
  347. setCellOn(xFromI(i), yFromI(i), randomUniform() < rndAmt);
  348. }
  349. break;
  350. }
  351. case RND_EUCLID:{
  352. for(int y=0; y < ROWS; y++){
  353. if(randomUniform() < rndAmt){
  354. int div = int(randomUniform() * COLS * 0.5) + 1;
  355. for(int x=0; x < COLS; x++){
  356. setCellOn(x, y, x % div == 0);
  357. }
  358. }
  359. }
  360. break;
  361. }
  362. case RND_SIN_WAVE:{
  363. int sinCount = int(rndAmt * 3) + 1;
  364. for(int i=0;i<sinCount;i++){
  365. float angle = 0;
  366. float angleInc = randomUniform();
  367. float offset = ROWS * 0.5;
  368. for(int x=0;x<COLS;x+=1){
  369. int y = int(offset + (sinf(angle)*(offset)));
  370. setCellOn(x, y, true);
  371. angle+=angleInc;
  372. }
  373. }
  374. break;
  375. }
  376. case RND_LIFE_GLIDERS:{
  377. int gliderCount = int(rndAmt * 20);
  378. int size = 3;
  379. for(int i=0;i<gliderCount;i++){
  380. int x = size + int(randomUniform() * (COLS-size*2));
  381. int y = size + int(randomUniform() * (ROWS-size*2));
  382. if(randomUniform() < 0.5){
  383. //down
  384. if(randomUniform() < 0.5){
  385. //right
  386. setCellOn(x, y, true);
  387. setCellOn(x+1, y+1, true);
  388. setCellOn(x+1, y+2, true);
  389. setCellOn(x, y+2, true);
  390. setCellOn(x-1, y+2, true);
  391. } else {
  392. //left
  393. setCellOn(x, y, true);
  394. setCellOn(x-1, y+1, true);
  395. setCellOn(x+1, y+2, true);
  396. setCellOn(x, y+2, true);
  397. setCellOn(x-1, y+2, true);
  398. }
  399. } else {
  400. //up
  401. if(randomUniform() < 0.5){
  402. //right
  403. setCellOn(x, y, true);
  404. setCellOn(x+1, y-1, true);
  405. setCellOn(x+1, y-2, true);
  406. setCellOn(x, y-2, true);
  407. setCellOn(x-1, y-2, true);
  408. } else {
  409. //left
  410. setCellOn(x, y, true);
  411. setCellOn(x-1, y-1, true);
  412. setCellOn(x+1, y-2, true);
  413. setCellOn(x, y-2, true);
  414. setCellOn(x-1, y-2, true);
  415. }
  416. }
  417. }
  418. break;
  419. }
  420. }
  421. }
  422. void stepLife(){
  423. bool *newCells = new bool[CELLS];
  424. for(int x=0; x < COLS; x++){
  425. for(int y=0; y < ROWS; y++){
  426. int cellIdx = iFromXY(x, y);
  427. int neighbors = getNeighborCount(x,y);
  428. newCells[cellIdx] = cells[cellIdx];
  429. if(neighbors<2 || neighbors>3) {
  430. newCells[cellIdx] = false;
  431. } else if (neighbors==3 && !cells[cellIdx]) {
  432. newCells[cellIdx] = true;
  433. }
  434. }
  435. }
  436. cells = newCells;
  437. }
  438. int getNeighborCount(int x, int y){
  439. int result = 0;
  440. for(int i=-1; i<=1; i++){
  441. for(int j=-1; j<=1; j++){
  442. int newX = x + i;
  443. int newY = y + j;
  444. if(newX==x && newY==y)continue;
  445. if(newX>=0 && newY>=0 && newX<COLS && newY<ROWS &&
  446. cells[iFromXY(newX, newY)]) result++;
  447. }
  448. }
  449. return result;
  450. }
  451. void setCellOnByDisplayPos(float displayX, float displayY, bool on){
  452. setCellOn(int(displayX / HW), int(displayY / HW), on);
  453. }
  454. void setCellOn(int cellX, int cellY, bool on){
  455. if(cellX >= 0 && cellX < COLS &&
  456. cellY >=0 && cellY < ROWS){
  457. cells[iFromXY(cellX, cellY)] = on;
  458. }
  459. }
  460. bool isCellOnByDisplayPos(float displayX, float displayY){
  461. return isCellOn(int(displayX / HW), int(displayY / HW));
  462. }
  463. bool isCellOn(int cellX, int cellY){
  464. return cells[iFromXY(cellX, cellY)];
  465. }
  466. int iFromXY(int cellX, int cellY){
  467. return cellX + cellY * ROWS;
  468. }
  469. int xFromI(int cellI){
  470. return cellI % COLS;
  471. }
  472. int yFromI(int cellI){
  473. return cellI / COLS;
  474. }
  475. };
  476. struct NoteSeqDisplay : Widget {
  477. NoteSeq *module;
  478. bool currentlyTurningOn = false;
  479. float initX = 0;
  480. float initY = 0;
  481. float dragX = 0;
  482. float dragY = 0;
  483. NoteSeqDisplay(){}
  484. void onMouseDown(EventMouseDown &e) override {
  485. if (e.button == 0) {
  486. e.consumed = true;
  487. e.target = this;
  488. initX = e.pos.x;
  489. initY = e.pos.y;
  490. currentlyTurningOn = !module->isCellOnByDisplayPos(e.pos.x, e.pos.y);
  491. module->setCellOnByDisplayPos(e.pos.x, e.pos.y, currentlyTurningOn);
  492. }
  493. }
  494. void onMouseMove(EventMouseMove &e) override {}
  495. void onMouseUp(EventMouseUp &e) override {}
  496. void onDragStart(EventDragStart &e) override {
  497. dragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  498. dragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  499. }
  500. void onDragEnd(EventDragEnd &e) override {}
  501. void onDragMove(EventDragMove &e) override {
  502. float newDragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x;
  503. float newDragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y;
  504. module->setCellOnByDisplayPos(initX+(newDragX-dragX), initY+(newDragY-dragY), currentlyTurningOn);
  505. }
  506. void draw(NVGcontext *vg) override {
  507. //background
  508. nvgFillColor(vg, nvgRGB(20, 30, 33));
  509. nvgBeginPath(vg);
  510. nvgRect(vg, 0, 0, box.size.x, box.size.y);
  511. nvgFill(vg);
  512. //grid
  513. nvgStrokeColor(vg, nvgRGB(60, 70, 73));
  514. for(int i=1;i<COLS;i++){
  515. nvgStrokeWidth(vg, (i % 4 == 0) ? 2 : 1);
  516. nvgBeginPath(vg);
  517. nvgMoveTo(vg, i * HW, 0);
  518. nvgLineTo(vg, i * HW, box.size.y);
  519. nvgStroke(vg);
  520. }
  521. for(int i=1;i<ROWS;i++){
  522. nvgStrokeWidth(vg, (i % 4 == 0) ? 2 : 1);
  523. nvgBeginPath(vg);
  524. nvgMoveTo(vg, 0, i * HW);
  525. nvgLineTo(vg, box.size.x, i * HW);
  526. nvgStroke(vg);
  527. }
  528. //cells
  529. nvgFillColor(vg, nvgRGB(25, 150, 252)); //blue
  530. for(int i=0;i<CELLS;i++){
  531. if(module->cells[i]){
  532. int y = i / ROWS;
  533. int x = i % COLS;
  534. nvgBeginPath(vg);
  535. nvgRect(vg, x * HW, y * HW, HW, HW);
  536. nvgFill(vg);
  537. }
  538. }
  539. nvgStrokeWidth(vg, 2);
  540. //highest note line
  541. float rowHighLimitY = (32-module->getFinalHighestNote1to32()) * HW;
  542. nvgStrokeColor(vg, nvgRGB(255, 151, 9));//orange
  543. nvgBeginPath(vg);
  544. nvgMoveTo(vg, 0, rowHighLimitY);
  545. nvgLineTo(vg, box.size.x, rowHighLimitY);
  546. nvgStroke(vg);
  547. //lowest note line
  548. float rowLowLimitY = (33-module->getFinalLowestNote1to32()) * HW;
  549. nvgStrokeColor(vg, nvgRGB(255, 243, 9));//yellow
  550. nvgBeginPath(vg);
  551. nvgMoveTo(vg, 0, rowLowLimitY);
  552. nvgLineTo(vg, box.size.x, rowLowLimitY);
  553. nvgStroke(vg);
  554. //seq length line
  555. float colLimitX = module->params[NoteSeq::LENGTH_KNOB_PARAM].value * HW;
  556. nvgStrokeColor(vg, nvgRGB(144, 26, 252));//purple
  557. nvgBeginPath(vg);
  558. nvgMoveTo(vg, colLimitX, 0);
  559. nvgLineTo(vg, colLimitX, box.size.y);
  560. nvgStroke(vg);
  561. //seq pos
  562. nvgStrokeColor(vg, nvgRGB(255, 255, 255));
  563. nvgBeginPath(vg);
  564. nvgRect(vg, module->seqPos * HW, 0, HW, box.size.y);
  565. nvgStroke(vg);
  566. }
  567. };
  568. struct PlayModeKnob : JwSmallSnapKnob {
  569. PlayModeKnob(){}
  570. std::string formatCurrentValue() override {
  571. switch(int(value)){
  572. case NoteSeq::PM_FWD_LOOP:return "→";
  573. case NoteSeq::PM_BWD_LOOP:return "←";
  574. case NoteSeq::PM_FWD_BWD_LOOP:return "→←";
  575. case NoteSeq::PM_BWD_FWD_LOOP:return "←→";
  576. case NoteSeq::PM_RANDOM_POS:return "*";
  577. }
  578. return "";
  579. }
  580. };
  581. struct RndModeKnob : JwSmallSnapKnob {
  582. RndModeKnob(){}
  583. std::string formatCurrentValue() override {
  584. switch(int(value)){
  585. case NoteSeq::RND_BASIC:return "Basic";
  586. case NoteSeq::RND_EUCLID:return "Euclid";
  587. case NoteSeq::RND_SIN_WAVE:return "Sine";
  588. case NoteSeq::RND_LIFE_GLIDERS:return "Gliders";
  589. }
  590. return "";
  591. }
  592. };
  593. struct NoteSeqWidget : ModuleWidget {
  594. NoteSeqWidget(NoteSeq *module);
  595. };
  596. NoteSeqWidget::NoteSeqWidget(NoteSeq *module) : ModuleWidget(module) {
  597. box.size = Vec(RACK_GRID_WIDTH*48, RACK_GRID_HEIGHT);
  598. SVGPanel *panel = new SVGPanel();
  599. panel->box.size = box.size;
  600. panel->setBackground(SVG::load(assetPlugin(plugin, "res/NoteSeq.svg")));
  601. addChild(panel);
  602. NoteSeqDisplay *display = new NoteSeqDisplay();
  603. display->module = module;
  604. display->box.pos = Vec(172, 2);
  605. display->box.size = Vec(376, RACK_GRID_HEIGHT - 4);
  606. addChild(display);
  607. module->displayWidth = display->box.size.x;
  608. module->displayHeight = display->box.size.y;
  609. addChild(Widget::create<Screw_J>(Vec(16, 1)));
  610. addChild(Widget::create<Screw_J>(Vec(16, 365)));
  611. addChild(Widget::create<Screw_W>(Vec(box.size.x-29, 1)));
  612. addChild(Widget::create<Screw_W>(Vec(box.size.x-29, 365)));
  613. ///////////////////////////////////////////////////// LEFT SIDE /////////////////////////////////////////////////////
  614. //row 1
  615. addInput(Port::create<TinyPJ301MPort>(Vec(25, 40), Port::INPUT, module, NoteSeq::CLOCK_INPUT));
  616. addParam(ParamWidget::create<SmallButton>(Vec(58, 35), module, NoteSeq::STEP_BTN_PARAM, 0.0, 1.0, 0.0));
  617. addParam(ParamWidget::create<JwSmallSnapKnob>(Vec(92, 35), module, NoteSeq::LENGTH_KNOB_PARAM, 1.0, 32.0, 32.0));
  618. PlayModeKnob *playModeKnob = dynamic_cast<PlayModeKnob*>(ParamWidget::create<PlayModeKnob>(Vec(126, 35), module, NoteSeq::PLAY_MODE_KNOB_PARAM, 0.0, NoteSeq::NUM_PLAY_MODES - 1, 0.0));
  619. CenteredLabel* const playModeLabel = new CenteredLabel;
  620. playModeLabel->box.pos = Vec(69.5, 35);
  621. playModeLabel->text = "";
  622. playModeKnob->connectLabel(playModeLabel);
  623. addChild(playModeLabel);
  624. addParam(playModeKnob);
  625. //row 2
  626. addInput(Port::create<TinyPJ301MPort>(Vec(10, 92), Port::INPUT, module, NoteSeq::RESET_INPUT));
  627. addParam(ParamWidget::create<SmallButton>(Vec(30, 87), module, NoteSeq::RESET_BTN_PARAM, 0.0, 1.0, 0.0));
  628. addInput(Port::create<TinyPJ301MPort>(Vec(60, 92), Port::INPUT, module, NoteSeq::CLEAR_INPUT));
  629. addParam(ParamWidget::create<SmallButton>(Vec(80, 87), module, NoteSeq::CLEAR_BTN_PARAM, 0.0, 1.0, 0.0));
  630. RndModeKnob *rndModeKnob = dynamic_cast<RndModeKnob*>(ParamWidget::create<RndModeKnob>(Vec(120, 87), module, NoteSeq::RND_MODE_KNOB_PARAM, 0.0, NoteSeq::NUM_RND_MODES - 1, 0.0));
  631. CenteredLabel* const rndModeLabel = new CenteredLabel;
  632. rndModeLabel->box.pos = Vec(67, 61);
  633. rndModeLabel->text = "";
  634. rndModeKnob->connectLabel(rndModeLabel);
  635. addChild(rndModeLabel);
  636. addParam(rndModeKnob);
  637. //row 3
  638. addInput(Port::create<TinyPJ301MPort>(Vec(60, 150), Port::INPUT, module, NoteSeq::RND_TRIG_INPUT));
  639. addParam(ParamWidget::create<SmallButton>(Vec(80, 145), module, NoteSeq::RND_TRIG_BTN_PARAM, 0.0, 1.0, 0.0));
  640. addInput(Port::create<TinyPJ301MPort>(Vec(118, 150), Port::INPUT, module, NoteSeq::RND_AMT_INPUT));
  641. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(138, 145), module, NoteSeq::RND_AMT_KNOB_PARAM, 0.0, 1.0, 0.1));
  642. addInput(Port::create<TinyPJ301MPort>(Vec(60, 201), Port::INPUT, module, NoteSeq::SHIFT_UP_INPUT));
  643. addParam(ParamWidget::create<SmallButton>(Vec(80, 196), module, NoteSeq::SHIFT_UP_BTN_PARAM, 0.0, 1.0, 0.0));
  644. addInput(Port::create<TinyPJ301MPort>(Vec(118, 201), Port::INPUT, module, NoteSeq::SHIFT_DOWN_INPUT));
  645. addParam(ParamWidget::create<SmallButton>(Vec(138, 196), module, NoteSeq::SHIFT_DOWN_BTN_PARAM, 0.0, 1.0, 0.0));
  646. addInput(Port::create<TinyPJ301MPort>(Vec(60, 252), Port::INPUT, module, NoteSeq::ROT_RIGHT_INPUT));
  647. addParam(ParamWidget::create<SmallButton>(Vec(80, 247), module, NoteSeq::ROT_RIGHT_BTN_PARAM, 0.0, 1.0, 0.0));
  648. addInput(Port::create<TinyPJ301MPort>(Vec(118, 252), Port::INPUT, module, NoteSeq::ROT_LEFT_INPUT));
  649. addParam(ParamWidget::create<SmallButton>(Vec(138, 247), module, NoteSeq::ROT_LEFT_BTN_PARAM, 0.0, 1.0, 0.0));
  650. addInput(Port::create<TinyPJ301MPort>(Vec(60, 304), Port::INPUT, module, NoteSeq::FLIP_HORIZ_INPUT));
  651. addParam(ParamWidget::create<SmallButton>(Vec(80, 299), module, NoteSeq::FLIP_HORIZ_BTN_PARAM, 0.0, 1.0, 0.0));
  652. addInput(Port::create<TinyPJ301MPort>(Vec(118, 304), Port::INPUT, module, NoteSeq::FLIP_VERT_INPUT));
  653. addParam(ParamWidget::create<SmallButton>(Vec(138, 299), module, NoteSeq::FLIP_VERT_BTN_PARAM, 0.0, 1.0, 0.0));
  654. addParam(ParamWidget::create<JwHorizontalSwitch>(Vec(68, 345), module, NoteSeq::LIFE_ON_SWITCH_PARAM, 0.0, 1.0, 0.0));
  655. addParam(ParamWidget::create<JwSmallSnapKnob>(Vec(125, 345), module, NoteSeq::LIFE_SPEED_KNOB_PARAM, 16.0, 1.0, 4.0));
  656. ///////////////////////////////////////////////////// RIGHT SIDE /////////////////////////////////////////////////////
  657. float outputRowTop = 35.0;
  658. float outputRowDist = 21.0;
  659. for(int i=0;i<POLY;i++){
  660. int paramIdx = POLY - i - 1;
  661. addOutput(Port::create<TinyPJ301MPort>(Vec(559.081, outputRowTop + i * outputRowDist), Port::OUTPUT, module, NoteSeq::VOCT_MAIN_OUTPUT + paramIdx)); //param # from bottom up
  662. addChild(ModuleLightWidget::create<SmallLight<MyBlueValueLight>>(Vec(580, (outputRowTop+3) + i * outputRowDist), module, NoteSeq::GATES_LIGHT + paramIdx));
  663. addOutput(Port::create<TinyPJ301MPort>(Vec(591.858, outputRowTop + i * outputRowDist), Port::OUTPUT, module, NoteSeq::GATE_MAIN_OUTPUT + paramIdx)); //param # from bottom up
  664. }
  665. addOutput(Port::create<TinyPJ301MPort>(Vec(656.303, outputRowTop), Port::OUTPUT, module, NoteSeq::MIN_VOCT_OUTPUT));
  666. addOutput(Port::create<TinyPJ301MPort>(Vec(689.081, outputRowTop), Port::OUTPUT, module, NoteSeq::MIN_GATE_OUTPUT));
  667. addOutput(Port::create<TinyPJ301MPort>(Vec(656.303, outputRowTop + outputRowDist), Port::OUTPUT, module, NoteSeq::MID_VOCT_OUTPUT));
  668. addOutput(Port::create<TinyPJ301MPort>(Vec(689.081, outputRowTop + outputRowDist), Port::OUTPUT, module, NoteSeq::MID_GATE_OUTPUT));
  669. addOutput(Port::create<TinyPJ301MPort>(Vec(656.303, outputRowTop + 2*outputRowDist), Port::OUTPUT, module, NoteSeq::MAX_VOCT_OUTPUT));
  670. addOutput(Port::create<TinyPJ301MPort>(Vec(689.081, outputRowTop + 2*outputRowDist), Port::OUTPUT, module, NoteSeq::MAX_GATE_OUTPUT));
  671. addOutput(Port::create<TinyPJ301MPort>(Vec(656.303, outputRowTop + 3*outputRowDist), Port::OUTPUT, module, NoteSeq::RND_VOCT_OUTPUT));
  672. addOutput(Port::create<TinyPJ301MPort>(Vec(689.081, outputRowTop + 3*outputRowDist), Port::OUTPUT, module, NoteSeq::RND_GATE_OUTPUT));
  673. addInput(Port::create<TinyPJ301MPort>(Vec(643, 152), Port::INPUT, module, NoteSeq::HIGHEST_NOTE_INPUT));
  674. addParam(ParamWidget::create<JwSmallSnapKnob>(Vec(663, 147), module, NoteSeq::HIGHEST_NOTE_PARAM, 1.0, 32.0, 32.0));
  675. addInput(Port::create<TinyPJ301MPort>(Vec(643, 195), Port::INPUT, module, NoteSeq::LOWEST_NOTE_INPUT));
  676. addParam(ParamWidget::create<JwSmallSnapKnob>(Vec(663, 190), module, NoteSeq::LOWEST_NOTE_PARAM, 1.0, 32.0, 1.0));
  677. addParam(ParamWidget::create<JwHorizontalSwitch>(Vec(654, 236), module, NoteSeq::INCLUDE_INACTIVE_PARAM, 0.0, 1.0, 0.0));
  678. addParam(ParamWidget::create<JwSmallSnapKnob>(Vec(652, 276), module, NoteSeq::OCTAVE_KNOB_PARAM, -5.0, 7.0, 2.0));
  679. ///// NOTE AND SCALE CONTROLS /////
  680. float pitchParamYVal = 320;
  681. float labelY = 178;
  682. NoteKnob *noteKnob = dynamic_cast<NoteKnob*>(ParamWidget::create<NoteKnob>(Vec(626, pitchParamYVal), module, NoteSeq::NOTE_KNOB_PARAM, 0.0, QuantizeUtils::NUM_NOTES-1, QuantizeUtils::NOTE_C));
  683. CenteredLabel* const noteLabel = new CenteredLabel;
  684. noteLabel->box.pos = Vec(319, labelY);
  685. noteLabel->text = "";
  686. noteKnob->connectLabel(noteLabel);
  687. addChild(noteLabel);
  688. addParam(noteKnob);
  689. ScaleKnob *scaleKnob = dynamic_cast<ScaleKnob*>(ParamWidget::create<ScaleKnob>(Vec(677, pitchParamYVal), module, NoteSeq::SCALE_KNOB_PARAM, 0.0, QuantizeUtils::NUM_SCALES-1, QuantizeUtils::MINOR));
  690. CenteredLabel* const scaleLabel = new CenteredLabel;
  691. scaleLabel->box.pos = Vec(345, labelY);
  692. scaleLabel->text = "";
  693. scaleKnob->connectLabel(scaleLabel);
  694. addChild(scaleLabel);
  695. addParam(scaleKnob);
  696. }
  697. } // namespace rack_plugin_JW_Modules
  698. using namespace rack_plugin_JW_Modules;
  699. RACK_PLUGIN_MODEL_INIT(JW_Modules, NoteSeq) {
  700. Model *modelNoteSeq = Model::create<NoteSeq, NoteSeqWidget>("JW-Modules", "NoteSeq", "NoteSeq", SEQUENCER_TAG);
  701. return modelNoteSeq;
  702. }