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.

2039 lines
78KB

  1. //***********************************************************************************************
  2. //Multi-track sequencer module for VCV Rack by Marc Boulé
  3. //
  4. //Based on code from the Fundamental and AudibleInstruments plugins by Andrew Belt
  5. //and graphics from the Component Library by Wes Milholen
  6. //See ./LICENSE.txt for all licenses
  7. //See ./res/fonts/ for font licenses
  8. //
  9. //Acknowledgements: please see README.md
  10. //***********************************************************************************************
  11. #include <algorithm>
  12. #include "FoundrySequencer.hpp"
  13. namespace rack_plugin_ImpromptuModular {
  14. struct Foundry : Module {
  15. enum ParamIds {
  16. EDIT_PARAM,
  17. PHRASE_PARAM,
  18. SEQUENCE_PARAM,
  19. RUN_PARAM,
  20. COPY_PARAM,
  21. PASTE_PARAM,
  22. RESET_PARAM,
  23. ENUMS(OCTAVE_PARAM, 7),
  24. GATE_PARAM,
  25. SLIDE_BTN_PARAM,
  26. AUTOSTEP_PARAM,
  27. ENUMS(KEY_PARAMS, 12),
  28. MODE_PARAM,
  29. CLKRES_PARAM,
  30. TRAN_ROT_PARAM,
  31. GATE_PROB_PARAM,
  32. TIE_PARAM,// Legato
  33. CPMODE_PARAM,
  34. ENUMS(STEP_PHRASE_PARAMS, SequencerKernel::MAX_STEPS),
  35. TRACKDOWN_PARAM,
  36. TRACKUP_PARAM,
  37. VEL_KNOB_PARAM,
  38. SEL_PARAM,
  39. ALLTRACKS_PARAM,
  40. REP_LEN_PARAM,
  41. BEGIN_PARAM,
  42. END_PARAM,
  43. KEY_GATE_PARAM,
  44. ATTACH_PARAM,
  45. VEL_EDIT_PARAM,
  46. WRITEMODE_PARAM,
  47. NUM_PARAMS
  48. };
  49. enum InputIds {
  50. WRITE_INPUT,
  51. ENUMS(CV_INPUTS, Sequencer::NUM_TRACKS),
  52. RESET_INPUT,
  53. ENUMS(CLOCK_INPUTS, Sequencer::NUM_TRACKS),
  54. LEFTCV_INPUT,
  55. RIGHTCV_INPUT,
  56. RUNCV_INPUT,
  57. GATECV_INPUT,
  58. GATEPCV_INPUT,
  59. TIEDCV_INPUT,
  60. SLIDECV_INPUT,
  61. ENUMS(VEL_INPUTS, Sequencer::NUM_TRACKS),
  62. SEQCV_INPUT,
  63. TRKCV_INPUT,
  64. NUM_INPUTS
  65. };
  66. enum OutputIds {
  67. ENUMS(CV_OUTPUTS, Sequencer::NUM_TRACKS),
  68. ENUMS(VEL_OUTPUTS, Sequencer::NUM_TRACKS),
  69. ENUMS(GATE_OUTPUTS, Sequencer::NUM_TRACKS),
  70. NUM_OUTPUTS
  71. };
  72. enum LightIds {
  73. ENUMS(STEP_PHRASE_LIGHTS, SequencerKernel::MAX_STEPS * 3),// room for GreenRedWhite
  74. ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7
  75. ENUMS(KEY_LIGHTS, 12 * 2),// room for GreenRed
  76. RUN_LIGHT,
  77. RESET_LIGHT,
  78. ENUMS(GATE_LIGHT, 2),// room for GreenRed
  79. SLIDE_LIGHT,
  80. ENUMS(GATE_PROB_LIGHT, 2),// room for GreenRed
  81. TIE_LIGHT,
  82. ATTACH_LIGHT,
  83. ENUMS(VEL_PROB_LIGHT, 2),// room for GreenRed
  84. VEL_SLIDE_LIGHT,
  85. ENUMS(WRITECV_LIGHTS, Sequencer::NUM_TRACKS),
  86. ENUMS(WRITECV2_LIGHTS, Sequencer::NUM_TRACKS),
  87. NUM_LIGHTS
  88. };
  89. // Constants
  90. enum EditPSDisplayStateIds {DISP_NORMAL, DISP_MODE_SEQ, DISP_MODE_SONG, DISP_LEN, DISP_REPS, DISP_TRANSPOSE, DISP_ROTATE, DISP_PPQN, DISP_DELAY, DISP_COPY_SEQ, DISP_PASTE_SEQ, DISP_COPY_SONG, DISP_PASTE_SONG, DISP_COPY_SONG_CUST};
  91. static constexpr float warningTime = 0.7f;// seconds
  92. // Need to save
  93. int panelTheme = 0;
  94. int expansion = 0;
  95. int velocityMode;
  96. bool velocityBipol;
  97. bool holdTiedNotes;
  98. bool autoseq;
  99. bool autostepLen;
  100. bool showSharp;
  101. bool multiTracks;
  102. int seqCVmethod;// 0 is 0-10V, 1 is C2-D7#, 2 is TrigIncr
  103. bool running;
  104. bool resetOnRun;
  105. bool attached;
  106. int velEditMode;// 0 is velocity, 1 is gate-prob, 2 is slide-rate
  107. int writeMode;// 0 is both, 1 is CV only, 2 is CV2 only
  108. Sequencer seq;
  109. // No need to save
  110. int displayState;
  111. long clockIgnoreOnReset;
  112. long tiedWarning;// 0 when no warning, positive downward step counter timer when warning
  113. long attachedWarning;// 0 when no warning, positive downward step counter timer when warning
  114. long revertDisplay;
  115. long showLenInSteps;
  116. bool multiSteps;
  117. int clkInSources[Sequencer::NUM_TRACKS];// first index is always 0 and will never change
  118. int cpSeqLength;
  119. int cpSongStart;// no need to initialize
  120. unsigned int lightRefreshCounter = 0;
  121. float resetLight = 0.0f;
  122. int sequenceKnob = 0;
  123. int velocityKnob = 0;
  124. int phraseKnob = 0;
  125. Trigger resetTrigger;
  126. Trigger leftTrigger;
  127. Trigger rightTrigger;
  128. Trigger runningTrigger;
  129. Trigger clockTriggers[SequencerKernel::MAX_STEPS];
  130. Trigger keyTriggers[12];
  131. Trigger octTriggers[7];
  132. Trigger gate1Trigger;
  133. Trigger tiedTrigger;
  134. Trigger gateProbTrigger;
  135. Trigger slideTrigger;
  136. Trigger writeTrigger;
  137. Trigger copyTrigger;
  138. Trigger pasteTrigger;
  139. Trigger modeTrigger;
  140. Trigger rotateTrigger;
  141. Trigger transposeTrigger;
  142. Trigger stepTriggers[SequencerKernel::MAX_STEPS];
  143. Trigger clkResTrigger;
  144. Trigger trackIncTrigger;
  145. Trigger trackDeccTrigger;
  146. Trigger beginTrigger;
  147. Trigger endTrigger;
  148. Trigger repLenTrigger;
  149. Trigger attachedTrigger;
  150. Trigger seqCVTrigger;
  151. Trigger selTrigger;
  152. Trigger allTrigger;
  153. Trigger velEditTrigger;
  154. Trigger writeModeTrigger;
  155. inline bool isEditingSequence(void) {return params[EDIT_PARAM].value < 0.5f;}
  156. inline bool isEditingGates(void) {return params[KEY_GATE_PARAM].value < 0.5f;}
  157. inline int getCPMode(void) {
  158. if (params[CPMODE_PARAM].value > 1.5f) return 2000;// this means end, and code should never loop up to this count. This value should be bigger than max(MAX_STEPS, MAX_PHRASES)
  159. if (params[CPMODE_PARAM].value < 0.5f) return 4;
  160. return 8;
  161. }
  162. Foundry() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  163. seq.construct(&holdTiedNotes, &velocityMode);
  164. onReset();
  165. }
  166. // widgets are not yet created when module is created (and when onReset() is called by constructor)
  167. // onReset() is also called when right-click initialization of module
  168. void onReset() override {
  169. autoseq = false;
  170. autostepLen = false;
  171. showSharp = true;
  172. running = true;
  173. velocityMode = 0;
  174. velocityBipol = false;
  175. holdTiedNotes = true;
  176. displayState = DISP_NORMAL;
  177. tiedWarning = 0l;
  178. attachedWarning = 0l;
  179. revertDisplay = 0l;
  180. showLenInSteps = 0l;
  181. resetOnRun = false;
  182. attached = false;
  183. multiSteps = false;
  184. multiTracks = false;
  185. seqCVmethod = 0;
  186. cpSeqLength = getCPMode();
  187. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  188. clkInSources[trkn] = 0;
  189. }
  190. velEditMode = 0;
  191. writeMode = 0;
  192. seq.reset();
  193. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  194. }
  195. void onRandomize() override {
  196. cpSeqLength = getCPMode();
  197. if (isEditingSequence() && !attached)
  198. seq.randomize();
  199. else if (attached)
  200. attachedWarning = (long) (warningTime * engineGetSampleRate() / displayRefreshStepSkips);
  201. }
  202. json_t *toJson() override {
  203. json_t *rootJ = json_object();
  204. // panelTheme
  205. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  206. // expansion
  207. json_object_set_new(rootJ, "expansion", json_integer(expansion));
  208. // velocityMode
  209. json_object_set_new(rootJ, "velocityMode", json_integer(velocityMode));
  210. // velocityBipol
  211. json_object_set_new(rootJ, "velocityBipol", json_integer(velocityBipol));
  212. // autostepLen
  213. json_object_set_new(rootJ, "autostepLen", json_boolean(autostepLen));
  214. // multiTracks
  215. json_object_set_new(rootJ, "multiTracks", json_boolean(multiTracks));
  216. // autoseq
  217. json_object_set_new(rootJ, "autoseq", json_boolean(autoseq));
  218. // holdTiedNotes
  219. json_object_set_new(rootJ, "holdTiedNotes", json_boolean(holdTiedNotes));
  220. // showSharp
  221. json_object_set_new(rootJ, "showSharp", json_boolean(showSharp));
  222. // seqCVmethod
  223. json_object_set_new(rootJ, "seqCVmethod", json_integer(seqCVmethod));
  224. // running
  225. json_object_set_new(rootJ, "running", json_boolean(running));
  226. // resetOnRun
  227. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  228. // attached
  229. json_object_set_new(rootJ, "attached", json_boolean(attached));
  230. // velEditMode
  231. json_object_set_new(rootJ, "velEditMode", json_integer(velEditMode));
  232. // writeMode
  233. json_object_set_new(rootJ, "writeMode", json_integer(writeMode));
  234. seq.toJson(rootJ);
  235. return rootJ;
  236. }
  237. void fromJson(json_t *rootJ) override {
  238. // panelTheme
  239. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  240. if (panelThemeJ)
  241. panelTheme = json_integer_value(panelThemeJ);
  242. // expansion
  243. json_t *expansionJ = json_object_get(rootJ, "expansion");
  244. if (expansionJ)
  245. expansion = json_integer_value(expansionJ);
  246. // velocityMode
  247. json_t *velocityModeJ = json_object_get(rootJ, "velocityMode");
  248. if (velocityModeJ)
  249. velocityMode = json_integer_value(velocityModeJ);
  250. // velocityBipol
  251. json_t *velocityBipolJ = json_object_get(rootJ, "velocityBipol");
  252. if (velocityBipolJ)
  253. velocityBipol = json_integer_value(velocityBipolJ);
  254. // autostepLen
  255. json_t *autostepLenJ = json_object_get(rootJ, "autostepLen");
  256. if (autostepLenJ)
  257. autostepLen = json_is_true(autostepLenJ);
  258. // multiTracks
  259. json_t *multiTracksJ = json_object_get(rootJ, "multiTracks");
  260. if (multiTracksJ)
  261. multiTracks = json_is_true(multiTracksJ);
  262. // autoseq
  263. json_t *autoseqJ = json_object_get(rootJ, "autoseq");
  264. if (autoseqJ)
  265. autoseq = json_is_true(autoseqJ);
  266. // holdTiedNotes
  267. json_t *holdTiedNotesJ = json_object_get(rootJ, "holdTiedNotes");
  268. if (holdTiedNotesJ)
  269. holdTiedNotes = json_is_true(holdTiedNotesJ);
  270. // showSharp
  271. json_t *showSharpJ = json_object_get(rootJ, "showSharp");
  272. if (showSharpJ)
  273. showSharp = json_is_true(showSharpJ);
  274. // seqCVmethod
  275. json_t *seqCVmethodJ = json_object_get(rootJ, "seqCVmethod");
  276. if (seqCVmethodJ)
  277. seqCVmethod = json_integer_value(seqCVmethodJ);
  278. // running
  279. json_t *runningJ = json_object_get(rootJ, "running");
  280. if (runningJ)
  281. running = json_is_true(runningJ);
  282. // resetOnRun
  283. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  284. if (resetOnRunJ)
  285. resetOnRun = json_is_true(resetOnRunJ);
  286. // attached
  287. json_t *attachedJ = json_object_get(rootJ, "attached");
  288. if (attachedJ)
  289. attached = json_is_true(attachedJ);
  290. // velEditMode
  291. json_t *velEditModeJ = json_object_get(rootJ, "velEditMode");
  292. if (velEditModeJ)
  293. velEditMode = json_integer_value(velEditModeJ);
  294. // writeMode
  295. json_t *writeModeJ = json_object_get(rootJ, "writeMode");
  296. if (writeModeJ)
  297. writeMode = json_integer_value(writeModeJ);
  298. seq.fromJson(rootJ);
  299. // Initialize dependants after everything loaded
  300. cpSeqLength = getCPMode();
  301. seq.initRun();
  302. }
  303. void step() override {
  304. const float sampleRate = engineGetSampleRate();
  305. static const float revertDisplayTime = 0.7f;// seconds
  306. static const float showLenInStepsTime = 2.0f;// seconds
  307. //********** Buttons, knobs, switches and inputs **********
  308. bool editingSequence = isEditingSequence();
  309. // Run button
  310. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) {// no input refresh here, don't want to introduce startup skew
  311. running = !running;
  312. if (running) {
  313. if (resetOnRun)
  314. seq.initRun();
  315. if (resetOnRun || clockIgnoreOnRun)
  316. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * sampleRate);
  317. }
  318. displayState = DISP_NORMAL;
  319. //multiSteps = false;
  320. //multiTracks = false;
  321. }
  322. if ((lightRefreshCounter & userInputsStepSkipMask) == 0) {
  323. // Seq CV input
  324. if (inputs[SEQCV_INPUT].active) {
  325. if (seqCVmethod == 0) {// 0-10 V
  326. int newSeq = (int)( inputs[SEQCV_INPUT].value * ((float)SequencerKernel::MAX_SEQS - 1.0f) / 10.0f + 0.5f );
  327. seq.setSeqIndexEdit(clamp(newSeq, 0, SequencerKernel::MAX_SEQS - 1));
  328. }
  329. else if (seqCVmethod == 1) {// C2-D7#
  330. int newSeq = (int)( (inputs[SEQCV_INPUT].value + 2.0f) * 12.0f + 0.5f );
  331. seq.setSeqIndexEdit(clamp(newSeq, 0, SequencerKernel::MAX_SEQS - 1));
  332. }
  333. else {// TrigIncr
  334. if (seqCVTrigger.process(inputs[SEQCV_INPUT].value))
  335. seq.setSeqIndexEdit(clamp(seq.getSeqIndexEdit() + 1, 0, SequencerKernel::MAX_SEQS - 1));
  336. }
  337. }
  338. // Track CV input
  339. if (inputs[TRKCV_INPUT].active) {
  340. int newTrk = (int)( inputs[TRKCV_INPUT].value * (2.0f * (float)Sequencer::NUM_TRACKS - 1.0f) / 10.0f + 0.5f );
  341. seq.setTrackIndexEdit(abs(newTrk) % Sequencer::NUM_TRACKS);
  342. if (newTrk > 3)
  343. multiTracks = true;
  344. else
  345. multiTracks = false;
  346. }
  347. // Attach button
  348. if (attachedTrigger.process(params[ATTACH_PARAM].value)) {
  349. attached = !attached;
  350. displayState = DISP_NORMAL;
  351. multiSteps = false;
  352. multiTracks = false;
  353. }
  354. if (attached) {//if (running && attached) {
  355. seq.attach();
  356. }
  357. // Copy
  358. if (copyTrigger.process(params[COPY_PARAM].value)) {
  359. if (!attached) {
  360. multiTracks = false;
  361. if (editingSequence) {
  362. seq.copySequence(cpSeqLength);
  363. displayState = DISP_COPY_SEQ;
  364. }
  365. else {
  366. int cpMode = getCPMode();
  367. if (cpMode == 2000) {
  368. if (displayState != DISP_COPY_SONG_CUST) {// first click to set cpSongStart
  369. cpSongStart = seq.getPhraseIndexEdit();
  370. displayState = DISP_COPY_SONG_CUST;
  371. }
  372. else {// second click do the copy
  373. seq.copySong(cpSongStart, max(1, seq.getPhraseIndexEdit() - cpSongStart + 1));
  374. displayState = DISP_COPY_SONG;
  375. }
  376. }
  377. else {
  378. seq.copySong(seq.getPhraseIndexEdit(), cpMode);
  379. displayState = DISP_COPY_SONG;
  380. }
  381. }
  382. if (displayState != DISP_COPY_SONG_CUST)
  383. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  384. }
  385. else
  386. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  387. }
  388. // Paste
  389. if (pasteTrigger.process(params[PASTE_PARAM].value)) {
  390. if (!attached) {
  391. if (editingSequence) {
  392. seq.pasteSequence(multiTracks);
  393. displayState = DISP_PASTE_SEQ;
  394. }
  395. else {
  396. if (displayState != DISP_COPY_SONG_CUST) {
  397. seq.pasteSong(multiTracks);
  398. displayState = DISP_PASTE_SONG;
  399. }
  400. }
  401. if (displayState != DISP_COPY_SONG_CUST)
  402. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  403. }
  404. else
  405. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  406. }
  407. // Write input (must be before Left and Right in case route gate simultaneously to Right and Write for example)
  408. // (write must be to correct step)
  409. bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value);
  410. if (writeTrig) {
  411. if (editingSequence && !attached) {
  412. int multiStepsCount = multiSteps ? cpSeqLength : 1;
  413. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  414. if (trkn == seq.getTrackIndexEdit() || multiTracks) {
  415. if (inputs[VEL_INPUTS + trkn].active && ((writeMode & 0x1) == 0)) { // must be before seq.writeCV() below, so that editing CV2 can be grabbed
  416. float maxVel = (velocityMode > 0 ? 127.0f : 200.0f);
  417. float capturedCV = inputs[VEL_INPUTS + trkn].value + (velocityBipol ? 5.0f : 0.0f);
  418. int intVel = (int)(capturedCV * maxVel / 10.0f + 0.5f);
  419. seq.setVelocityVal(trkn, clamp(intVel, 0, 200), multiStepsCount, false);
  420. }
  421. if (inputs[CV_INPUTS + trkn].active && ((writeMode & 0x2) == 0)) {
  422. seq.writeCV(trkn, clamp(inputs[CV_INPUTS + trkn].value, -10.0f, 10.0f), multiStepsCount, sampleRate, false);
  423. }
  424. }
  425. }
  426. seq.setEditingGateKeyLight(-1);
  427. if (params[AUTOSTEP_PARAM].value > 0.5f)
  428. seq.autostep(autoseq && !inputs[SEQCV_INPUT].active, autostepLen);
  429. }
  430. else if (attached)
  431. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  432. }
  433. // Left and right CV inputs
  434. int delta = 0;
  435. if (leftTrigger.process(inputs[LEFTCV_INPUT].value)) {
  436. delta = -1;
  437. }
  438. if (rightTrigger.process(inputs[RIGHTCV_INPUT].value)) {
  439. delta = +1;
  440. }
  441. if (delta != 0) {
  442. if (editingSequence && !attached) {
  443. if (displayState == DISP_NORMAL) {
  444. seq.moveStepIndexEditWithEditingGate(delta, writeTrig, sampleRate);
  445. }
  446. }
  447. else if (attached)
  448. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  449. }
  450. // Step button presses
  451. int stepPressed = -1;
  452. for (int i = 0; i < SequencerKernel::MAX_STEPS; i++) {
  453. if (stepTriggers[i].process(params[STEP_PHRASE_PARAMS + i].value))
  454. stepPressed = i;
  455. }
  456. if (stepPressed != -1) {
  457. if (editingSequence && !attached) {
  458. if (displayState == DISP_LEN) {
  459. seq.setLength(stepPressed + 1, multiTracks);
  460. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  461. }
  462. else {
  463. if (multiSteps && (getCPMode() == 2000) && (stepPressed >= seq.getStepIndexEdit())) {
  464. cpSeqLength = stepPressed - seq.getStepIndexEdit() + 1;
  465. }
  466. else {
  467. showLenInSteps = (long) (showLenInStepsTime * sampleRate / displayRefreshStepSkips);
  468. seq.setStepIndexEdit(stepPressed, sampleRate);
  469. displayState = DISP_NORMAL; // leave this here, the if has it also, but through the revert mechanism
  470. if (multiSteps && (getCPMode() == 2000)) {
  471. multiSteps = false;
  472. cpSeqLength = 2000;
  473. }
  474. }
  475. }
  476. }
  477. else if (attached)
  478. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  479. }
  480. // Mode button
  481. if (modeTrigger.process(params[MODE_PARAM].value)) {
  482. if (!attached) {
  483. if (displayState != DISP_MODE_SEQ && displayState != DISP_MODE_SONG)
  484. displayState = editingSequence ? DISP_MODE_SEQ : DISP_MODE_SONG;
  485. else
  486. displayState = DISP_NORMAL;
  487. }
  488. else
  489. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  490. }
  491. // Clk res/delay button
  492. if (clkResTrigger.process(params[CLKRES_PARAM].value)) {
  493. if (!attached) {
  494. if (displayState != DISP_PPQN && displayState != DISP_DELAY)
  495. displayState = DISP_PPQN;
  496. else if (displayState == DISP_PPQN)
  497. displayState = DISP_DELAY;
  498. else
  499. displayState = DISP_NORMAL;
  500. }
  501. else
  502. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  503. }
  504. // Transpose/Rotate button
  505. if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) {
  506. if (editingSequence && !attached) {
  507. if (displayState != DISP_TRANSPOSE && displayState != DISP_ROTATE) {
  508. displayState = DISP_TRANSPOSE;
  509. }
  510. else if (displayState == DISP_TRANSPOSE) {
  511. displayState = DISP_ROTATE;
  512. }
  513. else
  514. displayState = DISP_NORMAL;
  515. }
  516. else if (attached)
  517. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  518. }
  519. // Begin/End buttons
  520. if (beginTrigger.process(params[BEGIN_PARAM].value)) {
  521. if (!editingSequence && !attached) {
  522. seq.setBegin(multiTracks);
  523. displayState = DISP_NORMAL;
  524. }
  525. else if (attached)
  526. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  527. }
  528. if (endTrigger.process(params[END_PARAM].value)) {
  529. if (!editingSequence && !attached) {
  530. seq.setEnd(multiTracks);
  531. displayState = DISP_NORMAL;
  532. }
  533. else if (attached)
  534. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  535. }
  536. // Rep/Len button
  537. if (repLenTrigger.process(params[REP_LEN_PARAM].value)) {
  538. if (!attached) {
  539. if (displayState != DISP_LEN && displayState != DISP_REPS)
  540. displayState = editingSequence ? DISP_LEN : DISP_REPS;
  541. else
  542. displayState = DISP_NORMAL;
  543. }
  544. else
  545. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  546. }
  547. // Track Inc/Dec buttons
  548. if (trackIncTrigger.process(params[TRACKUP_PARAM].value)) {
  549. if (!inputs[TRKCV_INPUT].active) {
  550. seq.incTrackIndexEdit();
  551. }
  552. }
  553. if (trackDeccTrigger.process(params[TRACKDOWN_PARAM].value)) {
  554. if (!inputs[TRKCV_INPUT].active) {
  555. seq.decTrackIndexEdit();
  556. }
  557. }
  558. // All button
  559. if (allTrigger.process(params[ALLTRACKS_PARAM].value)) {
  560. if (!inputs[TRKCV_INPUT].active) {
  561. if (!attached) {
  562. multiTracks = !multiTracks;
  563. }
  564. else {
  565. multiTracks = false;
  566. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  567. }
  568. }
  569. }
  570. // Sel button
  571. if (selTrigger.process(params[SEL_PARAM].value)) {
  572. if (!attached && editingSequence)
  573. multiSteps = !multiSteps;
  574. else if (attached) {
  575. multiSteps = false;
  576. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  577. }
  578. }
  579. // Vel mode button
  580. if (velEditTrigger.process(params[VEL_EDIT_PARAM].value)) {
  581. if (attached || editingSequence) {
  582. if (velEditMode < 2)
  583. velEditMode++;
  584. else {
  585. velEditMode = 0;
  586. }
  587. }
  588. }
  589. // Write mode button
  590. if (writeModeTrigger.process(params[WRITEMODE_PARAM].value)) {
  591. if (attached || editingSequence) {
  592. if (++writeMode > 2)
  593. writeMode =0;
  594. }
  595. }
  596. // Velocity edit knob
  597. float velParamValue = params[VEL_KNOB_PARAM].value;
  598. int newVelocityKnob = (int)roundf(velParamValue * 30.0f);
  599. if (velParamValue == 0.0f)// true when constructor or fromJson() occured
  600. velocityKnob = newVelocityKnob;
  601. int deltaVelKnob = newVelocityKnob - velocityKnob;
  602. if (deltaVelKnob != 0) {
  603. if (abs(deltaVelKnob) <= 3) {// avoid discontinuous step (initialize for example)
  604. // any changes in here should may also require right click behavior to be updated in the knob's onMouseDown()
  605. if (editingSequence && !attached) {
  606. int mutliStepsCount = multiSteps ? cpSeqLength : 1;
  607. if (velEditMode == 2) {
  608. seq.modSlideVal(deltaVelKnob, mutliStepsCount, multiTracks);
  609. }
  610. else if (velEditMode == 1) {
  611. seq.modGatePVal(deltaVelKnob, mutliStepsCount, multiTracks);
  612. }
  613. else {
  614. seq.modVelocityVal(deltaVelKnob, mutliStepsCount, multiTracks);
  615. }
  616. displayState = DISP_NORMAL;
  617. }
  618. else if (attached)
  619. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  620. }
  621. velocityKnob = newVelocityKnob;
  622. }
  623. // Sequence edit knob
  624. float seqParamValue = params[SEQUENCE_PARAM].value;
  625. int newSequenceKnob = (int)roundf(seqParamValue * 7.0f);
  626. if (seqParamValue == 0.0f)// true when constructor or fromJson() occured
  627. sequenceKnob = newSequenceKnob;
  628. int deltaSeqKnob = newSequenceKnob - sequenceKnob;
  629. if (deltaSeqKnob != 0) {
  630. if (abs(deltaSeqKnob) <= 3) {// avoid discontinuous step (initialize for example)
  631. // any changes in here should may also require right click behavior to be updated in the knob's onMouseDown()
  632. if (displayState == DISP_LEN) {
  633. seq.modLength(deltaSeqKnob, multiTracks);
  634. }
  635. else if (displayState == DISP_TRANSPOSE) {
  636. seq.transposeSeq(deltaSeqKnob, multiTracks);
  637. }
  638. else if (displayState == DISP_ROTATE) {
  639. seq.rotateSeq(deltaSeqKnob, multiTracks);
  640. }
  641. else if (displayState == DISP_REPS) {
  642. seq.modPhraseReps(deltaSeqKnob, multiTracks);
  643. }
  644. else if (displayState == DISP_PPQN || displayState == DISP_DELAY) {
  645. }
  646. else if (!attached) {
  647. if (editingSequence) {
  648. if (!inputs[SEQCV_INPUT].active) {
  649. seq.moveSeqIndexEdit(deltaSeqKnob);
  650. if (displayState != DISP_MODE_SEQ)
  651. displayState = DISP_NORMAL;
  652. }
  653. }
  654. else {// editing song
  655. seq.modPhraseSeqNum(deltaSeqKnob, multiTracks);
  656. displayState = DISP_NORMAL;
  657. }
  658. }
  659. else if (attached)
  660. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  661. }
  662. sequenceKnob = newSequenceKnob;
  663. }
  664. // Phrase edit knob
  665. float phraseParamValue = params[PHRASE_PARAM].value;
  666. int newPhraseKnob = (int)roundf(phraseParamValue * 7.0f);
  667. if (phraseParamValue == 0.0f)// true when constructor or fromJson() occured
  668. phraseKnob = newPhraseKnob;
  669. int deltaPhrKnob = newPhraseKnob - phraseKnob;
  670. if (deltaPhrKnob != 0) {
  671. if (abs(deltaPhrKnob) <= 3) {// avoid discontinuous step (initialize for example)
  672. // any changes in here should may also require right click behavior to be updated in the knob's onMouseDown()
  673. if (displayState == DISP_MODE_SEQ) {
  674. seq.modRunModeSeq(deltaPhrKnob, multiTracks);
  675. }
  676. else if (displayState == DISP_PPQN) {
  677. seq.modPulsesPerStep(deltaPhrKnob, multiTracks);
  678. }
  679. else if (displayState == DISP_DELAY) {
  680. seq.modDelay(deltaPhrKnob, multiTracks);
  681. }
  682. else if (displayState == DISP_MODE_SONG) {
  683. seq.modRunModeSong(deltaPhrKnob, multiTracks);
  684. }
  685. else if (!editingSequence && !attached && displayState != DISP_PPQN && displayState != DISP_DELAY) {
  686. seq.movePhraseIndexEdit(deltaPhrKnob);
  687. if (displayState != DISP_REPS && displayState != DISP_COPY_SONG_CUST)
  688. displayState = DISP_NORMAL;
  689. }
  690. else if (attached)
  691. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  692. }
  693. phraseKnob = newPhraseKnob;
  694. }
  695. // Octave buttons
  696. for (int octn = 0; octn < 7; octn++) {
  697. if (octTriggers[octn].process(params[OCTAVE_PARAM + octn].value)) {
  698. if (editingSequence && !attached && displayState != DISP_PPQN) {
  699. if (seq.applyNewOctave(6 - octn, multiSteps ? cpSeqLength : 1, sampleRate, multiTracks))
  700. tiedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  701. }
  702. else if (attached)
  703. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  704. displayState = DISP_NORMAL;
  705. }
  706. }
  707. // Keyboard buttons
  708. for (int keyn = 0; keyn < 12; keyn++) {
  709. if (keyTriggers[keyn].process(params[KEY_PARAMS + keyn].value)) {
  710. displayState = DISP_NORMAL;
  711. if (editingSequence && !attached && displayState != DISP_PPQN) {
  712. bool autostepClick = params[KEY_PARAMS + keyn].value > 1.5f;// if right-click
  713. if (isEditingGates()) {
  714. if (!seq.setGateType(keyn, multiSteps ? cpSeqLength : 1, sampleRate, autostepClick, multiTracks))
  715. displayState = DISP_PPQN;
  716. }
  717. else {
  718. if (seq.applyNewKey(keyn, multiSteps ? cpSeqLength : 1, sampleRate, autostepClick, multiTracks))
  719. tiedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  720. }
  721. }
  722. else if (attached)
  723. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  724. }
  725. }
  726. // Gate, GateProb, Slide and Tied buttons
  727. if (gate1Trigger.process(params[GATE_PARAM].value + inputs[GATECV_INPUT].value)) {
  728. if (editingSequence && !attached) {
  729. seq.toggleGate(multiSteps ? cpSeqLength : 1, multiTracks);
  730. }
  731. else if (attached)
  732. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  733. displayState = DISP_NORMAL;
  734. }
  735. if (gateProbTrigger.process(params[GATE_PROB_PARAM].value + inputs[GATEPCV_INPUT].value)) {
  736. if (editingSequence && !attached) {
  737. if (seq.toggleGateP(multiSteps ? cpSeqLength : 1, multiTracks))
  738. tiedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  739. else if (seq.getAttribute().getGateP())
  740. velEditMode = 1;
  741. }
  742. else if (attached)
  743. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  744. displayState = DISP_NORMAL;
  745. }
  746. if (slideTrigger.process(params[SLIDE_BTN_PARAM].value + inputs[SLIDECV_INPUT].value)) {
  747. if (editingSequence && !attached) {
  748. if (seq.toggleSlide(multiSteps ? cpSeqLength : 1, multiTracks))
  749. tiedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  750. else if (seq.getAttribute().getSlide())
  751. velEditMode = 2;
  752. }
  753. else if (attached)
  754. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  755. displayState = DISP_NORMAL;
  756. }
  757. if (tiedTrigger.process(params[TIE_PARAM].value + inputs[TIEDCV_INPUT].value)) {
  758. if (editingSequence && !attached) {
  759. seq.toggleTied(multiSteps ? cpSeqLength : 1, multiTracks);// will clear other attribs if new state is on
  760. }
  761. else if (attached)
  762. attachedWarning = (long) (warningTime * sampleRate / displayRefreshStepSkips);
  763. displayState = DISP_NORMAL;
  764. }
  765. calcClkInSources();
  766. }// userInputs refresh
  767. //********** Clock and reset **********
  768. // Clock
  769. if (running && clockIgnoreOnReset == 0l) {
  770. bool clockTrigged[Sequencer::NUM_TRACKS];
  771. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  772. clockTrigged[trkn] = clockTriggers[trkn].process(inputs[CLOCK_INPUTS + trkn].value);
  773. if (clockTrigged[clkInSources[trkn]]) {
  774. seq.clockStep(trkn);
  775. }
  776. }
  777. seq.step();
  778. }
  779. // Reset
  780. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  781. seq.initRun();
  782. resetLight = 1.0f;
  783. displayState = DISP_NORMAL;
  784. //multiSteps = false;
  785. //multiTracks = false;
  786. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * sampleRate);
  787. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++)
  788. clockTriggers[trkn].reset();
  789. if (inputs[SEQCV_INPUT].active && seqCVmethod == 2)
  790. seq.setSeqIndexEdit(0);
  791. }
  792. //********** Outputs and lights **********
  793. // CV, gate and velocity outputs
  794. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  795. outputs[CV_OUTPUTS + trkn].value = seq.calcCvOutputAndDecSlideStepsRemain(trkn, running);
  796. bool retriggingOnReset = (clockIgnoreOnReset != 0l && retrigGatesOnReset);
  797. outputs[GATE_OUTPUTS + trkn].value = seq.calcGateOutput(trkn, running && !retriggingOnReset, clockTriggers[clkInSources[trkn]], sampleRate);
  798. outputs[VEL_OUTPUTS + trkn].value = seq.calcVelOutput(trkn, running && !retriggingOnReset) - (velocityBipol ? 5.0f : 0.0f);
  799. }
  800. // lights
  801. lightRefreshCounter++;
  802. if (lightRefreshCounter >= displayRefreshStepSkips) {
  803. lightRefreshCounter = 0;
  804. // Prepare values to visualize
  805. StepAttributes attributesVisual;
  806. float cvVisual;
  807. if (editingSequence || attached) {
  808. attributesVisual = seq.getAttribute();
  809. cvVisual = seq.getCV();
  810. }
  811. else {
  812. attributesVisual.clear();// clears everything, but just buttons used below
  813. cvVisual = 0.0f;// not used
  814. }
  815. bool editingGates = isEditingGates();
  816. // Step lights
  817. for (int stepn = 0; stepn < SequencerKernel::MAX_STEPS; stepn++) {
  818. float red = 0.0f;
  819. float green = 0.0f;
  820. float white = 0.0f;
  821. if ((displayState == DISP_COPY_SEQ) || (displayState == DISP_PASTE_SEQ)) {
  822. int startCP = seq.getStepIndexEdit();
  823. if (stepn >= startCP && stepn < (startCP + seq.getLengthSeqCPbug()))
  824. green = 0.5f;
  825. }
  826. else if (displayState == DISP_TRANSPOSE) {
  827. red = 0.5f;
  828. }
  829. else if (displayState == DISP_ROTATE) {
  830. red = (stepn == seq.getStepIndexEdit() ? 1.0f : (stepn < seq.getLength() ? 0.2f : 0.0f));
  831. }
  832. else if (displayState == DISP_LEN) {
  833. int seqEnd = seq.getLength() - 1;
  834. if (stepn < seqEnd)
  835. green = 0.1f;
  836. else if (stepn == seqEnd)
  837. green = 1.0f;
  838. }
  839. else if (editingSequence && !attached) {
  840. if (multiSteps) {
  841. if (stepn >= seq.getStepIndexEdit() && stepn < (seq.getStepIndexEdit() + cpSeqLength))
  842. red = 0.2f;
  843. }
  844. else if (!running && showLenInSteps > 0l && stepn < seq.getLength()) {
  845. green = 0.01f;
  846. }
  847. if (stepn == seq.getStepIndexEdit()) {
  848. if (red == 0.0f) // don't overwrite light multistep with full red
  849. red = 1.0f;
  850. green = 0.0f;
  851. }
  852. if (running && red != 0.2f && stepn == seq.getStepIndexRun(seq.getTrackIndexEdit())) {
  853. green = 1.0f;
  854. }
  855. white = 1.0f;// signal for override below
  856. }
  857. else if (attached) {
  858. // all active light green, current track is bright yellow
  859. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  860. bool trknIsUsed = outputs[CV_OUTPUTS + trkn].active || outputs[GATE_OUTPUTS + trkn].active || outputs[VEL_OUTPUTS + trkn].active;
  861. if (stepn == seq.getStepIndexRun(trkn) && trknIsUsed)
  862. green = 0.1f;
  863. }
  864. if (green > 0.2f)
  865. green = 0.2f;
  866. if (stepn == seq.getStepIndexEdit()) {
  867. green = 1.0f;
  868. red = 1.0f;
  869. }
  870. white = 1.0f;// signal for override below
  871. }
  872. if (white == 1.0f)
  873. white = ((green == 0.0f && red == 0.0f && (editingSequence || attached) && seq.getAttribute(seq.getTrackIndexEdit(), stepn).getGate() && displayState != DISP_MODE_SEQ && displayState != DISP_PPQN && displayState != DISP_DELAY) ? 0.04f : 0.0f);
  874. if (editingSequence && white != 0.0f) {
  875. green = 0.02f; white = 0.0f;
  876. }
  877. setGreenRed(STEP_PHRASE_LIGHTS + stepn * 3, green, red);
  878. //if (white != 0.0f && seq.getAttribute(seq.getTrackIndexEdit(), stepn).getGateP()) white = 0.01f;
  879. lights[STEP_PHRASE_LIGHTS + stepn * 3 + 2].value = white;
  880. }
  881. // Octave lights
  882. int octLightIndex = (int) floor(cvVisual + 3.0f);
  883. for (int i = 0; i < 7; i++) {
  884. float red = 0.0f;
  885. if (editingSequence || attached) {
  886. if (tiedWarning > 0l) {
  887. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (warningTime * sampleRate / displayRefreshStepSkips));
  888. red = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f;
  889. }
  890. else
  891. red = (i == (6 - octLightIndex) ? 1.0f : 0.0f);// no lights when outside of range
  892. }
  893. lights[OCTAVE_LIGHTS + i].value = red;
  894. }
  895. // Keyboard lights
  896. float keyCV = cvVisual + 10.0f;// to properly handle negative note voltages
  897. int keyLightIndex = clamp( (int)((keyCV - floor(keyCV)) * 12.0f + 0.5f), 0, 11);
  898. for (int i = 0; i < 12; i++) {
  899. float green = 0.0f;
  900. float red = 0.0f;
  901. if (displayState == DISP_PPQN) {
  902. if (seq.keyIndexToGateTypeEx(i) != -1) {
  903. green = 1.0f;
  904. red = 1.0f;
  905. }
  906. }
  907. else if (editingSequence || attached) {
  908. if (editingGates) {
  909. green = 1.0f;
  910. red = 0.2f;
  911. unsigned long editingType = seq.getEditingType();
  912. if (editingType > 0ul) {
  913. if (i == seq.getEditingGateKeyLight()) {
  914. float dimMult = ((float) editingType / (float)(Sequencer::gateTime * sampleRate / displayRefreshStepSkips));
  915. green *= dimMult;
  916. red *= dimMult;
  917. }
  918. else {
  919. green = 0.0f;
  920. red = 0.0f;
  921. }
  922. }
  923. else {
  924. int modeLightIndex = seq.getGateType();
  925. if (i != modeLightIndex) {// show dim note if gatetype is different than note
  926. green = 0.0f;
  927. red = (i == keyLightIndex ? 0.1f : 0.0f);
  928. }
  929. }
  930. }
  931. else {
  932. if (tiedWarning > 0l) {
  933. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (warningTime * sampleRate / displayRefreshStepSkips));
  934. red = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f;
  935. }
  936. else {
  937. red = seq.calcKeyLightWithEditing(i, keyLightIndex, sampleRate);
  938. }
  939. }
  940. }
  941. setGreenRed(KEY_LIGHTS + i * 2, green, red);
  942. }
  943. // Gate, Tied, GateProb, and Slide lights
  944. if (!attributesVisual.getGate())
  945. setGreenRed(GATE_LIGHT, 0.0f, 0.0f);
  946. else
  947. setGreenRed(GATE_LIGHT, editingGates ? 1.0f : 0.0f, editingGates ? 0.2f : 1.0f);
  948. if (tiedWarning > 0l) {
  949. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (warningTime * sampleRate / displayRefreshStepSkips));
  950. lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f;
  951. }
  952. else
  953. lights[TIE_LIGHT].value = attributesVisual.getTied() ? 1.0f : 0.0f;
  954. if (attributesVisual.getGateP())
  955. setGreenRed(GATE_PROB_LIGHT, 1.0f, 1.0f);
  956. else
  957. setGreenRed(GATE_PROB_LIGHT, 0.0f, 0.0f);
  958. lights[SLIDE_LIGHT].value = attributesVisual.getSlide() ? 1.0f : 0.0f;
  959. // Reset light
  960. lights[RESET_LIGHT].value = resetLight;
  961. resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips;
  962. // Run light
  963. lights[RUN_LIGHT].value = (running ? 1.0f : 0.0f);
  964. // Attach light
  965. if (attachedWarning > 0l) {
  966. bool warningFlashState = calcWarningFlash(attachedWarning, (long) (warningTime * sampleRate / displayRefreshStepSkips));
  967. lights[ATTACH_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f;
  968. }
  969. else
  970. lights[ATTACH_LIGHT].value = (attached ? 1.0f : 0.0f);
  971. // Velocity edit mode lights
  972. if (editingSequence || attached) {
  973. setGreenRed(VEL_PROB_LIGHT, velEditMode == 1 ? 1.0f : 0.0f, velEditMode == 1 ? 1.0f : 0.0f);
  974. lights[VEL_SLIDE_LIGHT].value = (velEditMode == 2 ? 1.0f : 0.0f);
  975. }
  976. else {
  977. setGreenRed(VEL_PROB_LIGHT, 0.0f, 0.0f);
  978. lights[VEL_SLIDE_LIGHT].value = 0.0f;
  979. }
  980. // CV writing lights
  981. for (int trkn = 0; trkn < Sequencer::NUM_TRACKS; trkn++) {
  982. if (editingSequence && !attached) {
  983. lights[WRITECV_LIGHTS + trkn].value = (((writeMode & 0x2) == 0) && (multiTracks || seq.getTrackIndexEdit() == trkn)) ? 1.0f : 0.0f;
  984. lights[WRITECV2_LIGHTS + trkn].value = (((writeMode & 0x1) == 0) && (multiTracks || seq.getTrackIndexEdit() == trkn)) ? 1.0f : 0.0f;
  985. }
  986. else {
  987. lights[WRITECV_LIGHTS + trkn].value = 0.0f;
  988. lights[WRITECV2_LIGHTS + trkn].value = 0.0f;
  989. }
  990. }
  991. seq.stepEditingGate();// also steps editingType
  992. if (tiedWarning > 0l)
  993. tiedWarning--;
  994. if (attachedWarning > 0l)
  995. attachedWarning--;
  996. if (showLenInSteps > 0l)
  997. showLenInSteps--;
  998. if (revertDisplay > 0l) {
  999. if (revertDisplay == 1)
  1000. displayState = DISP_NORMAL;
  1001. revertDisplay--;
  1002. }
  1003. }// lightRefreshCounter
  1004. if (clockIgnoreOnReset > 0l)
  1005. clockIgnoreOnReset--;
  1006. }// step()
  1007. inline void setGreenRed(int id, float green, float red) {
  1008. lights[id + 0].value = green;
  1009. lights[id + 1].value = red;
  1010. }
  1011. inline void calcClkInSources() {
  1012. // index 0 is always 0 so nothing to do for it
  1013. for (int trkn = 1; trkn < Sequencer::NUM_TRACKS; trkn++) {
  1014. if (inputs[CLOCK_INPUTS + trkn].active)
  1015. clkInSources[trkn] = trkn;
  1016. else
  1017. clkInSources[trkn] = clkInSources[trkn - 1];
  1018. }
  1019. }
  1020. };
  1021. struct FoundryWidget : ModuleWidget {
  1022. Foundry *module;
  1023. DynamicSVGPanel *panel;
  1024. int oldExpansion;
  1025. int expWidth = 105;
  1026. IMPort* expPorts[12];
  1027. template <int NUMCHAR>
  1028. struct DisplayWidget : TransparentWidget {// a centered display, must derive from this
  1029. Foundry *module;
  1030. std::shared_ptr<Font> font;
  1031. char displayStr[NUMCHAR + 1];
  1032. static const int textFontSize = 15;
  1033. static constexpr float textOffsetY = 19.9f; // 18.2f for 14 pt, 19.7f for 15pt
  1034. void runModeToStr(int num) {
  1035. if (num >= 0 && num < SequencerKernel::NUM_MODES)
  1036. snprintf(displayStr, 4, "%s", SequencerKernel::modeLabels[num].c_str());
  1037. }
  1038. DisplayWidget(Vec _pos, Vec _size, Foundry *_module) {
  1039. box.size = _size;
  1040. box.pos = _pos.minus(_size.div(2));
  1041. module = _module;
  1042. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  1043. }
  1044. void draw(NVGcontext *vg) override {
  1045. NVGcolor textColor = prepareDisplay(vg, &box, textFontSize);
  1046. nvgFontFaceId(vg, font->handle);
  1047. nvgTextLetterSpacing(vg, -0.4);
  1048. Vec textPos = Vec(5.7f, textOffsetY);
  1049. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  1050. std::string initString(NUMCHAR,'~');
  1051. nvgText(vg, textPos.x, textPos.y, initString.c_str(), NULL);
  1052. nvgFillColor(vg, textColor);
  1053. char overlayChar = printText();
  1054. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  1055. if (overlayChar != 0) {
  1056. displayStr[0] = overlayChar;
  1057. displayStr[1] = 0;
  1058. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  1059. }
  1060. }
  1061. virtual char printText() = 0;
  1062. };
  1063. struct VelocityDisplayWidget : DisplayWidget<4> {
  1064. VelocityDisplayWidget(Vec _pos, Vec _size, Foundry *_module) : DisplayWidget(_pos, _size, _module) {};
  1065. void draw(NVGcontext *vg) override {
  1066. static const float offsetXfrac = 3.5f;
  1067. NVGcolor textColor = prepareDisplay(vg, &box, textFontSize);
  1068. nvgFontFaceId(vg, font->handle);
  1069. nvgTextLetterSpacing(vg, -0.4);
  1070. Vec textPos = Vec(6.3f, textOffsetY);
  1071. char useRed = printText();
  1072. if (useRed == 1)
  1073. textColor = nvgRGB(0xE0, 0xD0, 0x30);
  1074. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  1075. nvgText(vg, textPos.x, textPos.y, "~", NULL);
  1076. std::string initString(".~~");
  1077. nvgText(vg, textPos.x + offsetXfrac, textPos.y, initString.c_str(), NULL);
  1078. if (useRed == 1)
  1079. textColor = nvgRGB(0xFF, 0x2C, 0x20);
  1080. nvgFillColor(vg, textColor);
  1081. nvgText(vg, textPos.x + offsetXfrac, textPos.y, &displayStr[1], NULL);
  1082. displayStr[1] = 0;
  1083. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  1084. }
  1085. char printText() override {
  1086. char ret = 0;// used for a color instead of overlay char. 0 = default (green), 1 = red
  1087. if (module->isEditingSequence() || module->attached) {
  1088. StepAttributes attributesVal = module->seq.getAttribute();
  1089. if (module->velEditMode == 2) {
  1090. int slide = attributesVal.getSlideVal();
  1091. if ( slide >= 100)
  1092. snprintf(displayStr, 5, " 1");
  1093. else if (slide >= 1)
  1094. snprintf(displayStr, 5, "0.%02u", (unsigned) slide);
  1095. else
  1096. snprintf(displayStr, 5, " 0");
  1097. }
  1098. else if (module->velEditMode == 1) {
  1099. int prob = attributesVal.getGatePVal();
  1100. if ( prob >= 100)
  1101. snprintf(displayStr, 5, " 1");
  1102. else if (prob >= 1)
  1103. snprintf(displayStr, 5, "0.%02u", (unsigned) prob);
  1104. else
  1105. snprintf(displayStr, 5, " 0");
  1106. }
  1107. else {
  1108. unsigned int velocityDisp = (unsigned)(attributesVal.getVelocityVal());
  1109. if (module->velocityMode > 0) {// velocity is 0-127 or semitone
  1110. if (module->velocityMode == 2)// semitone
  1111. printNote(((float)velocityDisp)/12.0f - (module->velocityBipol ? 5.0f : 0.0f), &displayStr[1], true);// given str pointer must be 4 chars (3 display and one end of string)
  1112. else// 0-127
  1113. snprintf(displayStr, 5, " %3u", min(velocityDisp, 127));
  1114. displayStr[0] = displayStr[1];
  1115. displayStr[1] = ' ';
  1116. }
  1117. else {// velocity is 0-10V
  1118. float cvValPrint = (float)velocityDisp;
  1119. cvValPrint /= 20.0f;
  1120. if (module->velocityBipol) {
  1121. if (cvValPrint < 5.0f)
  1122. ret = 1;
  1123. cvValPrint = fabsf(cvValPrint - 5.0f);
  1124. }
  1125. if (cvValPrint > 9.975f)
  1126. snprintf(displayStr, 5, " 10");
  1127. else if (cvValPrint < 0.025f)
  1128. snprintf(displayStr, 5, " 0");
  1129. else {
  1130. snprintf(displayStr, 5, "%3.2f", cvValPrint);// Three-wide, two positions after the decimal, left-justified
  1131. displayStr[1] = '.';// in case locals in printf
  1132. }
  1133. }
  1134. }
  1135. }
  1136. else
  1137. snprintf(displayStr, 5, " - ");
  1138. return ret;
  1139. }
  1140. };
  1141. struct SeqEditDisplayWidget : DisplayWidget<3> {
  1142. SeqEditDisplayWidget(Vec _pos, Vec _size, Foundry *_module) : DisplayWidget(_pos, _size, _module) {};
  1143. char printText() override {
  1144. switch (module->displayState) {
  1145. case Foundry::DISP_PPQN :
  1146. case Foundry::DISP_DELAY :
  1147. snprintf(displayStr, 4, " - ");
  1148. break;
  1149. case Foundry::DISP_REPS :
  1150. snprintf(displayStr, 4, "R%2u", (unsigned) module->seq.getPhraseReps());
  1151. break;
  1152. case Foundry::DISP_COPY_SEQ :
  1153. snprintf(displayStr, 4, "CPY");
  1154. break;
  1155. case Foundry::DISP_PASTE_SEQ :
  1156. snprintf(displayStr, 4, "PST");
  1157. break;
  1158. case Foundry::DISP_LEN :
  1159. snprintf(displayStr, 4, "L%2u", (unsigned) module->seq.getLength());
  1160. break;
  1161. case Foundry::DISP_TRANSPOSE :
  1162. {
  1163. int tranOffset = module->seq.getTransposeOffset();
  1164. snprintf(displayStr, 4, "+%2u", (unsigned) abs(tranOffset));
  1165. if (tranOffset < 0)
  1166. displayStr[0] = '-';
  1167. }
  1168. break;
  1169. case Foundry::DISP_ROTATE :
  1170. {
  1171. int rotOffset = module->seq.getRotateOffset();
  1172. snprintf(displayStr, 4, ")%2u", (unsigned) abs(rotOffset));
  1173. if (rotOffset < 0)
  1174. displayStr[0] = '(';
  1175. }
  1176. break;
  1177. default :
  1178. // two paths below are equivalent when attached, so no need to check attached
  1179. if (module->isEditingSequence()) {
  1180. snprintf(displayStr, 4, " %2u", (unsigned)(module->seq.getSeqIndexEdit() + 1) );
  1181. // if (module->displayState == Foundry::DISP_MODE_SEQ) {// Arrow
  1182. // displayStr[0] = displayStr[1];
  1183. // displayStr[1] = displayStr[2];
  1184. // displayStr[2] = ')';
  1185. // }
  1186. break;
  1187. }
  1188. else
  1189. snprintf(displayStr, 4, " %2u", (unsigned)(module->seq.getPhraseSeq() + 1) );
  1190. }
  1191. return 0;
  1192. }
  1193. };
  1194. struct PhrEditDisplayWidget : DisplayWidget<3> {
  1195. PhrEditDisplayWidget(Vec _pos, Vec _size, Foundry *_module) : DisplayWidget(_pos, _size, _module) {};
  1196. char printText() override {
  1197. char overlayChar = 0;// extra char to print an end symbol overlaped (begin symbol done in here)
  1198. if (module->displayState == Foundry::DISP_COPY_SONG) {
  1199. snprintf(displayStr, 4, "CPY");
  1200. }
  1201. else if (module->displayState == Foundry::DISP_PASTE_SONG) {
  1202. snprintf(displayStr, 4, "PST");
  1203. }
  1204. else if (module->displayState == Foundry::DISP_MODE_SONG) {
  1205. runModeToStr(module->seq.getRunModeSong());
  1206. }
  1207. else if (module->displayState == Foundry::DISP_PPQN) {
  1208. snprintf(displayStr, 4, "x%2u", (unsigned) module->seq.getPulsesPerStep());
  1209. }
  1210. else if (module->displayState == Foundry::DISP_DELAY) {
  1211. snprintf(displayStr, 4, "D%2u", (unsigned) module->seq.getDelay());
  1212. }
  1213. else if (module->displayState == Foundry::DISP_MODE_SEQ) {
  1214. runModeToStr(module->seq.getRunModeSeq());
  1215. }
  1216. else {
  1217. if (module->isEditingSequence()) {
  1218. snprintf(displayStr, 4, " - ");
  1219. }
  1220. else { // editing song
  1221. int phrn = module->seq.getPhraseIndexEdit(); // good whether attached or not
  1222. int phrBeg = module->seq.getBegin();
  1223. int phrEnd = module->seq.getEnd();
  1224. snprintf(displayStr, 4, " %2u", (unsigned)(phrn + 1));
  1225. bool begHere = (phrn == phrBeg);
  1226. bool endHere = (phrn == phrEnd);
  1227. if (begHere) {
  1228. displayStr[0] = '{';
  1229. if (endHere)
  1230. overlayChar = '}';
  1231. }
  1232. else if (endHere) {
  1233. displayStr[0] = '}';
  1234. overlayChar = '_';
  1235. }
  1236. else if (phrn < phrEnd && phrn > phrBeg)
  1237. displayStr[0] = '_';
  1238. if (module->displayState == Foundry::DISP_COPY_SONG_CUST) {
  1239. overlayChar = 0;
  1240. displayStr[0] = (time(0) & 0x1) ? 'C' : ' ';
  1241. }
  1242. }
  1243. }
  1244. return overlayChar;
  1245. }
  1246. };
  1247. struct TrackDisplayWidget : DisplayWidget<2> {
  1248. TrackDisplayWidget(Vec _pos, Vec _size, Foundry *_module) : DisplayWidget(_pos, _size, _module) {};
  1249. char printText() override {
  1250. int trkn = module->seq.getTrackIndexEdit();
  1251. if (module->multiTracks)
  1252. snprintf(displayStr, 3, "%c%c", (unsigned)(trkn + 0x41), ((module->multiTracks && (time(0) & 0x1)) ? '*' : ' '));
  1253. else {
  1254. snprintf(displayStr, 3, " %c", (unsigned)(trkn + 0x41));
  1255. // if (module->displayState == Foundry::DISP_MODE_SONG || // Arrow
  1256. // module->displayState == Foundry::DISP_PPQN || module->displayState == Foundry::DISP_DELAY)
  1257. // displayStr[0] = '(';
  1258. }
  1259. return 0;
  1260. }
  1261. };
  1262. struct PanelThemeItem : MenuItem {
  1263. Foundry *module;
  1264. int theme;
  1265. void onAction(EventAction &e) override {
  1266. module->panelTheme = theme;
  1267. }
  1268. void step() override {
  1269. rightText = (module->panelTheme == theme) ? "✔" : "";
  1270. }
  1271. };
  1272. struct ExpansionItem : MenuItem {
  1273. Foundry *module;
  1274. void onAction(EventAction &e) override {
  1275. module->expansion = module->expansion == 1 ? 0 : 1;
  1276. }
  1277. };
  1278. struct ResetOnRunItem : MenuItem {
  1279. Foundry *module;
  1280. void onAction(EventAction &e) override {
  1281. module->resetOnRun = !module->resetOnRun;
  1282. }
  1283. };
  1284. struct AutoStepLenItem : MenuItem {
  1285. Foundry *module;
  1286. void onAction(EventAction &e) override {
  1287. module->autostepLen = !module->autostepLen;
  1288. }
  1289. };
  1290. struct AutoseqItem : MenuItem {
  1291. Foundry *module;
  1292. void onAction(EventAction &e) override {
  1293. module->autoseq = !module->autoseq;
  1294. }
  1295. };
  1296. struct SeqCVmethodItem : MenuItem {
  1297. Foundry *module;
  1298. void onAction(EventAction &e) override {
  1299. module->seqCVmethod++;
  1300. if (module->seqCVmethod > 2)
  1301. module->seqCVmethod = 0;
  1302. }
  1303. void step() override {
  1304. if (module->seqCVmethod == 0)
  1305. text = "Seq CV in: <0-10V>, C2-D7#, Trig-Incr";
  1306. else if (module->seqCVmethod == 1)
  1307. text = "Seq CV in: 0-10V, <C2-D7#>, Trig-Incr";
  1308. else
  1309. text = "Seq CV in: 0-10V, C2-D7#, <Trig-Incr>";
  1310. }
  1311. };
  1312. struct VelModeItem : MenuItem {
  1313. Foundry *module;
  1314. void onAction(EventAction &e) override {
  1315. module->velocityMode++;
  1316. if (module->velocityMode > 2)
  1317. module->velocityMode = 0;
  1318. }
  1319. void step() override {
  1320. if (module->velocityMode == 0)
  1321. text = "CV2 mode: <Volts>, 0-127, Notes";
  1322. else if (module->velocityMode == 1)
  1323. text = "CV2 mode: Volts, <0-127>, Notes";
  1324. else
  1325. text = "CV2 mode: Volts, 0-127, <Notes>";
  1326. }
  1327. };
  1328. struct VelBipolItem : MenuItem {
  1329. Foundry *module;
  1330. void onAction(EventAction &e) override {
  1331. module->velocityBipol = !module->velocityBipol;
  1332. }
  1333. };
  1334. struct HoldTiedItem : MenuItem {
  1335. Foundry *module;
  1336. void onAction(EventAction &e) override {
  1337. module->holdTiedNotes = !module->holdTiedNotes;
  1338. }
  1339. };
  1340. Menu *createContextMenu() override {
  1341. Menu *menu = ModuleWidget::createContextMenu();
  1342. MenuLabel *spacerLabel = new MenuLabel();
  1343. menu->addChild(spacerLabel);
  1344. Foundry *module = dynamic_cast<Foundry*>(this->module);
  1345. assert(module);
  1346. MenuLabel *themeLabel = new MenuLabel();
  1347. themeLabel->text = "Panel Theme";
  1348. menu->addChild(themeLabel);
  1349. PanelThemeItem *lightItem = new PanelThemeItem();
  1350. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  1351. lightItem->module = module;
  1352. lightItem->theme = 0;
  1353. menu->addChild(lightItem);
  1354. PanelThemeItem *metalItem = new PanelThemeItem();
  1355. metalItem->text = "Liquid metal";// ImpromptuModular.hpp
  1356. metalItem->module = module;
  1357. metalItem->theme = 1;
  1358. menu->addChild(metalItem);
  1359. PanelThemeItem *darkItem = new PanelThemeItem();
  1360. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  1361. darkItem->module = module;
  1362. darkItem->theme = 2;
  1363. menu->addChild(darkItem);
  1364. menu->addChild(new MenuLabel());// empty line
  1365. MenuLabel *settingsLabel = new MenuLabel();
  1366. settingsLabel->text = "Settings";
  1367. menu->addChild(settingsLabel);
  1368. ResetOnRunItem *rorItem = MenuItem::create<ResetOnRunItem>("Reset on run", CHECKMARK(module->resetOnRun));
  1369. rorItem->module = module;
  1370. menu->addChild(rorItem);
  1371. AutoStepLenItem *astlItem = MenuItem::create<AutoStepLenItem>("AutoStep write bounded by seq length", CHECKMARK(module->autostepLen));
  1372. astlItem->module = module;
  1373. menu->addChild(astlItem);
  1374. AutoseqItem *aseqItem = MenuItem::create<AutoseqItem>("AutoSeq when writing via CV inputs", CHECKMARK(module->autoseq));
  1375. aseqItem->module = module;
  1376. menu->addChild(aseqItem);
  1377. HoldTiedItem *holdItem = MenuItem::create<HoldTiedItem>("Hold tied notes", CHECKMARK(module->holdTiedNotes));
  1378. holdItem->module = module;
  1379. menu->addChild(holdItem);
  1380. VelBipolItem *bipolItem = MenuItem::create<VelBipolItem>("CV2 bipolar", CHECKMARK(module->velocityBipol));
  1381. bipolItem->module = module;
  1382. menu->addChild(bipolItem);
  1383. VelModeItem *velItem = MenuItem::create<VelModeItem>("CV2 mode: ", "");
  1384. velItem->module = module;
  1385. menu->addChild(velItem);
  1386. SeqCVmethodItem *seqcvItem = MenuItem::create<SeqCVmethodItem>("Seq CV in: ", "");
  1387. seqcvItem->module = module;
  1388. menu->addChild(seqcvItem);
  1389. menu->addChild(new MenuLabel());// empty line
  1390. MenuLabel *expansionLabel = new MenuLabel();
  1391. expansionLabel->text = "Expansion module";
  1392. menu->addChild(expansionLabel);
  1393. std::string expansionMenuLabel32EX(expansionMenuLabel);
  1394. std::replace( expansionMenuLabel32EX.begin(), expansionMenuLabel32EX.end(), '4', '7');
  1395. ExpansionItem *expItem = MenuItem::create<ExpansionItem>(expansionMenuLabel32EX, CHECKMARK(module->expansion != 0));
  1396. expItem->module = module;
  1397. menu->addChild(expItem);
  1398. return menu;
  1399. }
  1400. void step() override {
  1401. if(module->expansion != oldExpansion) {
  1402. if (oldExpansion != -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks
  1403. for (int i = 0; i < 12; i++)
  1404. RACK_PLUGIN_UI_RACKWIDGET->wireContainer->removeAllWires(expPorts[i]);
  1405. module->writeMode = 0;
  1406. }
  1407. oldExpansion = module->expansion;
  1408. }
  1409. box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth;
  1410. Widget::step();
  1411. }
  1412. struct CKSSNotify : CKSS {// Not randomizable
  1413. CKSSNotify() {}
  1414. void randomize() override {}
  1415. void onChange(EventChange &e) override {
  1416. ((Foundry*)(module))->displayState = Foundry::DISP_NORMAL;
  1417. if (paramId != Foundry::KEY_GATE_PARAM) {
  1418. ((Foundry*)(module))->multiSteps = false;
  1419. }
  1420. SVGSwitch::onChange(e);
  1421. }
  1422. };
  1423. struct CKSSHThreeNotify : CKSSHThree {
  1424. CKSSHThreeNotify() {};
  1425. void onChange(EventChange &e) override {
  1426. ((Foundry*)(module))->displayState = Foundry::DISP_NORMAL;
  1427. SVGSwitch::onChange(e);
  1428. }
  1429. };
  1430. struct CPModeSwitch : CKSSThreeInvNoRandom {// Not randomizable
  1431. CPModeSwitch() {};
  1432. void onChange(EventChange &e) override {
  1433. SVGSwitch::onChange(e);
  1434. Foundry* module = dynamic_cast<Foundry*>(this->module);
  1435. module->cpSeqLength = module->getCPMode();
  1436. if (module->displayState == Foundry::DISP_COPY_SONG_CUST)
  1437. module->displayState = Foundry::DISP_NORMAL;
  1438. }
  1439. };
  1440. struct VelocityKnob : IMMediumKnobInf {
  1441. VelocityKnob() {};
  1442. void onMouseDown(EventMouseDown &e) override {// from ParamWidget.cpp
  1443. Foundry* module = dynamic_cast<Foundry*>(this->module);
  1444. if (e.button == 1) {
  1445. // same code structure below as in velocity knob in main step()
  1446. if (module->isEditingSequence() && !module->attached) {
  1447. int multiStepsCount = module->multiSteps ? module->getCPMode() : 1;
  1448. if (module->velEditMode == 2) {
  1449. module->seq.initSlideVal(multiStepsCount, module->multiTracks);
  1450. }
  1451. else if (module->velEditMode == 1) {
  1452. module->seq.initGatePVal(multiStepsCount, module->multiTracks);
  1453. }
  1454. else {
  1455. module->seq.initVelocityVal(multiStepsCount, module->multiTracks);
  1456. }
  1457. module->displayState = Foundry::DISP_NORMAL;
  1458. }
  1459. }
  1460. ParamWidget::onMouseDown(e);
  1461. }
  1462. };
  1463. struct SequenceKnob : IMMediumKnobInf {
  1464. SequenceKnob() {};
  1465. void onMouseDown(EventMouseDown &e) override {// from ParamWidget.cpp
  1466. Foundry* module = dynamic_cast<Foundry*>(this->module);
  1467. if (e.button == 1) {
  1468. // same code structure below as in sequence knob in main step()
  1469. if (module->displayState == Foundry::DISP_LEN) {
  1470. module->seq.initLength(module->multiTracks);
  1471. }
  1472. else if (module->displayState == Foundry::DISP_TRANSPOSE) {
  1473. module->seq.unTransposeSeq(module->multiTracks);
  1474. }
  1475. else if (module->displayState == Foundry::DISP_ROTATE) {
  1476. module->seq.unRotateSeq(module->multiTracks);
  1477. }
  1478. else if (module->displayState == Foundry::DISP_REPS) {
  1479. module->seq.initPhraseReps(module->multiTracks);
  1480. }
  1481. else if (!module->attached) {
  1482. if (module->isEditingSequence()) {
  1483. if (!module->inputs[Foundry::SEQCV_INPUT].active)
  1484. module->seq.setSeqIndexEdit(0);
  1485. }
  1486. else {// editing song
  1487. module->seq.initPhraseSeqNum(module->multiTracks);
  1488. }
  1489. module->displayState = Foundry::DISP_NORMAL;
  1490. }
  1491. }
  1492. ParamWidget::onMouseDown(e);
  1493. }
  1494. };
  1495. struct PhraseKnob : IMMediumKnobInf {
  1496. PhraseKnob() {};
  1497. void onMouseDown(EventMouseDown &e) override {// from ParamWidget.cpp
  1498. Foundry* module = dynamic_cast<Foundry*>(this->module);
  1499. if (e.button == 1) {
  1500. // same code structure below as in phrase knob in main step()
  1501. if (module->displayState == Foundry::DISP_MODE_SEQ) {
  1502. module->seq.initRunModeSeq(module->multiTracks);
  1503. }
  1504. else if (module->displayState == Foundry::DISP_PPQN) {
  1505. module->seq.initPulsesPerStep(module->multiTracks);
  1506. }
  1507. else if (module->displayState == Foundry::DISP_DELAY) {
  1508. module->seq.initDelay(module->multiTracks);
  1509. }
  1510. else if (module->displayState == Foundry::DISP_MODE_SONG) {
  1511. module->seq.initRunModeSong(module->multiTracks);
  1512. }
  1513. else if (!module->isEditingSequence() && !module->attached) {
  1514. module->seq.setPhraseIndexEdit(0);
  1515. module->displayState = Foundry::DISP_NORMAL;
  1516. }
  1517. }
  1518. ParamWidget::onMouseDown(e);
  1519. }
  1520. };
  1521. FoundryWidget(Foundry *module) : ModuleWidget(module) {
  1522. this->module = module;
  1523. oldExpansion = -1;
  1524. // Main panel from Inkscape
  1525. panel = new DynamicSVGPanel();
  1526. panel->mode = &module->panelTheme;
  1527. panel->expWidth = &expWidth;
  1528. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Foundry.svg")));
  1529. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Foundry_metal.svg")));
  1530. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/Foundry_dark.svg")));
  1531. box.size = panel->box.size;
  1532. box.size.x = box.size.x - (1 - module->expansion) * expWidth;
  1533. addChild(panel);
  1534. // Screws
  1535. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  1536. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  1537. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 0), &module->panelTheme));
  1538. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 365), &module->panelTheme));
  1539. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme));
  1540. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme));
  1541. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-expWidth + 15, 0), &module->panelTheme));
  1542. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-expWidth + 15, 365), &module->panelTheme));
  1543. // ****** Top row ******
  1544. static const int rowRulerT0 = 56;
  1545. static const int columnRulerT0 = 25;// Step/Phase LED buttons
  1546. static const int columnRulerT1 = 373;// Select (multi-steps)
  1547. static const int columnRulerT2 = 426;// Copy-paste-select mode switch
  1548. //static const int columnRulerT3 = 463;// Copy paste buttons (not needed when align to track display)
  1549. static const int columnRulerT5 = 538;// Edit mode switch (and overview switch also)
  1550. static const int stepsOffsetY = 10;
  1551. static const int posLEDvsButton = 26;
  1552. // Step LED buttons
  1553. int posX = columnRulerT0;
  1554. static int spacingSteps = 20;
  1555. static int spacingSteps4 = 4;
  1556. const int numX = SequencerKernel::MAX_STEPS / 2;
  1557. for (int x = 0; x < numX; x++) {
  1558. // First row
  1559. addParam(createParamCentered<LEDButton>(Vec(posX, rowRulerT0 - stepsOffsetY), module, Foundry::STEP_PHRASE_PARAMS + x, 0.0f, 1.0f, 0.0f));
  1560. addChild(createLightCentered<MediumLight<GreenRedWhiteLight>>(Vec(posX, rowRulerT0 - stepsOffsetY), module, Foundry::STEP_PHRASE_LIGHTS + (x * 3)));
  1561. // Second row
  1562. addParam(createParamCentered<LEDButton>(Vec(posX, rowRulerT0 + stepsOffsetY), module, Foundry::STEP_PHRASE_PARAMS + x + numX, 0.0f, 1.0f, 0.0f));
  1563. addChild(createLightCentered<MediumLight<GreenRedWhiteLight>>(Vec(posX, rowRulerT0 + stepsOffsetY), module, Foundry::STEP_PHRASE_LIGHTS + ((x + numX) * 3)));
  1564. // step position to next location and handle groups of four
  1565. posX += spacingSteps;
  1566. if ((x + 1) % 4 == 0)
  1567. posX += spacingSteps4;
  1568. }
  1569. // Sel button
  1570. addParam(createDynamicParamCentered<IMPushButton>(Vec(columnRulerT1, rowRulerT0), module, Foundry::SEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1571. // Copy-paste and select mode switch (3 position)
  1572. addParam(createParamCentered<CPModeSwitch>(Vec(columnRulerT2, rowRulerT0), module, Foundry::CPMODE_PARAM, 0.0f, 2.0f, 0.0f)); // 0.0f is top position
  1573. // Copy/paste buttons
  1574. // see under Track display
  1575. // Main switch
  1576. addParam(createParamCentered<CKSSNotify>(Vec(columnRulerT5, rowRulerT0), module, Foundry::EDIT_PARAM, 0.0f, 1.0f, 0.0f));// 1.0f is top position
  1577. // ****** Octave and keyboard area ******
  1578. // Octave LED buttons
  1579. static const int octLightsIntY = 20;
  1580. static const int rowRulerOct = 111;
  1581. for (int i = 0; i < 7; i++) {
  1582. addParam(createParamCentered<LEDButton>(Vec(columnRulerT0, rowRulerOct + i * octLightsIntY), module, Foundry::OCTAVE_PARAM + i, 0.0f, 1.0f, 0.0f));
  1583. addChild(createLightCentered<MediumLight<RedLight>>(Vec(columnRulerT0, rowRulerOct + i * octLightsIntY), module, Foundry::OCTAVE_LIGHTS + i));
  1584. }
  1585. // Keys and Key lights
  1586. static const int keyNudgeX = 2;
  1587. static const int KeyBlackY = 103;
  1588. static const int KeyWhiteY = 141;
  1589. static const int offsetKeyLEDx = 6;
  1590. static const int offsetKeyLEDy = 16;
  1591. // Black keys and lights
  1592. addParam(createParam<InvisibleKeySmall>( Vec(65+keyNudgeX, KeyBlackY), module, Foundry::KEY_PARAMS + 1, 0.0, 1.0, 0.0));
  1593. addChild(createLight<MediumLight<GreenRedLight>>(Vec(65+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 1 * 2));
  1594. addParam(createParam<InvisibleKeySmall>( Vec(93+keyNudgeX, KeyBlackY), module, Foundry::KEY_PARAMS + 3, 0.0, 1.0, 0.0));
  1595. addChild(createLight<MediumLight<GreenRedLight>>(Vec(93+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 3 * 2));
  1596. addParam(createParam<InvisibleKeySmall>( Vec(150+keyNudgeX, KeyBlackY), module, Foundry::KEY_PARAMS + 6, 0.0, 1.0, 0.0));
  1597. addChild(createLight<MediumLight<GreenRedLight>>(Vec(150+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 6 * 2));
  1598. addParam(createParam<InvisibleKeySmall>( Vec(178+keyNudgeX, KeyBlackY), module, Foundry::KEY_PARAMS + 8, 0.0, 1.0, 0.0));
  1599. addChild(createLight<MediumLight<GreenRedLight>>(Vec(178+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 8 * 2));
  1600. addParam(createParam<InvisibleKeySmall>( Vec(206+keyNudgeX, KeyBlackY), module, Foundry::KEY_PARAMS + 10, 0.0, 1.0, 0.0));
  1601. addChild(createLight<MediumLight<GreenRedLight>>(Vec(206+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 10 * 2));
  1602. // White keys and lights
  1603. addParam(createParam<InvisibleKeySmall>( Vec(51+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 0, 0.0, 1.0, 0.0));
  1604. addChild(createLight<MediumLight<GreenRedLight>>(Vec(51+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 0 * 2));
  1605. addParam(createParam<InvisibleKeySmall>( Vec(79+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 2, 0.0, 1.0, 0.0));
  1606. addChild(createLight<MediumLight<GreenRedLight>>(Vec(79+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 2 * 2));
  1607. addParam(createParam<InvisibleKeySmall>( Vec(107+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 4, 0.0, 1.0, 0.0));
  1608. addChild(createLight<MediumLight<GreenRedLight>>(Vec(107+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 4 * 2));
  1609. addParam(createParam<InvisibleKeySmall>( Vec(136+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 5, 0.0, 1.0, 0.0));
  1610. addChild(createLight<MediumLight<GreenRedLight>>(Vec(136+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 5 * 2));
  1611. addParam(createParam<InvisibleKeySmall>( Vec(164+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 7, 0.0, 1.0, 0.0));
  1612. addChild(createLight<MediumLight<GreenRedLight>>(Vec(164+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 7 * 2));
  1613. addParam(createParam<InvisibleKeySmall>( Vec(192+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 9, 0.0, 1.0, 0.0));
  1614. addChild(createLight<MediumLight<GreenRedLight>>(Vec(192+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 9 * 2));
  1615. addParam(createParam<InvisibleKeySmall>( Vec(220+keyNudgeX, KeyWhiteY), module, Foundry::KEY_PARAMS + 11, 0.0, 1.0, 0.0));
  1616. addChild(createLight<MediumLight<GreenRedLight>>(Vec(220+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, Foundry::KEY_LIGHTS + 11 * 2));
  1617. // ****** Right side control area ******
  1618. static const int rowRulerDisp = 110;
  1619. static const int rowRulerKnobs = 145;
  1620. static const int rowRulerSmallButtons = 189;
  1621. static const int displayWidths = 48; // 43 for 14pt, 46 for 15pt
  1622. static const int displayHeights = 24; // 22 for 14pt, 24 for 15pt
  1623. static const int displaySpacingX = 62;
  1624. // Velocity display
  1625. static const int colRulerVel = 289;
  1626. static const int trkButtonsOffsetX = 14;
  1627. addChild(new VelocityDisplayWidget(Vec(colRulerVel, rowRulerDisp), Vec(displayWidths + 4, displayHeights), module));// 3 characters
  1628. // Velocity knob
  1629. addParam(createDynamicParamCentered<VelocityKnob>(Vec(colRulerVel, rowRulerKnobs), module, Foundry::VEL_KNOB_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  1630. // Veocity mode button and lights
  1631. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerVel - trkButtonsOffsetX - 2, rowRulerSmallButtons), module, Foundry::VEL_EDIT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1632. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerVel + 4, rowRulerSmallButtons), module, Foundry::VEL_PROB_LIGHT));
  1633. addChild(createLightCentered<MediumLight<RedLight>>(Vec(colRulerVel + 20, rowRulerSmallButtons), module, Foundry::VEL_SLIDE_LIGHT));
  1634. // Seq edit display
  1635. static const int colRulerEditSeq = colRulerVel + displaySpacingX + 3;
  1636. addChild(new SeqEditDisplayWidget(Vec(colRulerEditSeq, rowRulerDisp), Vec(displayWidths, displayHeights), module));// 5 characters
  1637. // Sequence-edit knob
  1638. addParam(createDynamicParamCentered<SequenceKnob>(Vec(colRulerEditSeq, rowRulerKnobs), module, Foundry::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  1639. // Transpose/rotate button
  1640. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerEditSeq, rowRulerSmallButtons), module, Foundry::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1641. // Phrase edit display
  1642. static const int colRulerEditPhr = colRulerEditSeq + displaySpacingX + 1;
  1643. addChild(new PhrEditDisplayWidget(Vec(colRulerEditPhr, rowRulerDisp), Vec(displayWidths, displayHeights), module));// 5 characters
  1644. // Phrase knob
  1645. addParam(createDynamicParamCentered<PhraseKnob>(Vec(colRulerEditPhr, rowRulerKnobs), module, Foundry::PHRASE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  1646. // Begin/end buttons
  1647. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerEditPhr - trkButtonsOffsetX, rowRulerSmallButtons), module, Foundry::BEGIN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1648. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerEditPhr + trkButtonsOffsetX, rowRulerSmallButtons), module, Foundry::END_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1649. // Track display
  1650. static const int colRulerTrk = colRulerEditPhr + displaySpacingX;
  1651. addChild(new TrackDisplayWidget(Vec(colRulerTrk, rowRulerDisp), Vec(displayWidths - 13, displayHeights), module));// 2 characters
  1652. // Track buttons
  1653. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerTrk + trkButtonsOffsetX, rowRulerKnobs), module, Foundry::TRACKUP_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1654. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerTrk - trkButtonsOffsetX, rowRulerKnobs), module, Foundry::TRACKDOWN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1655. // AllTracks button
  1656. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerTrk, rowRulerSmallButtons - 12), module, Foundry::ALLTRACKS_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1657. // Copy/paste buttons
  1658. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerTrk - trkButtonsOffsetX, rowRulerT0), module, Foundry::COPY_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1659. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerTrk + trkButtonsOffsetX, rowRulerT0), module, Foundry::PASTE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1660. // Attach button and light
  1661. addParam(createDynamicParamCentered<IMPushButton>(Vec(columnRulerT5 - 10, rowRulerDisp + 4), module, Foundry::ATTACH_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1662. addChild(createLightCentered<MediumLight<RedLight>>(Vec(columnRulerT5 + 10, rowRulerDisp + 4), module, Foundry::ATTACH_LIGHT));
  1663. // ****** Gate and slide section ******
  1664. static const int rowRulerMB0 = rowRulerOct + 6 * octLightsIntY;
  1665. static const int columnRulerMB3 = colRulerVel - displaySpacingX;
  1666. static const int columnRulerMB2 = colRulerVel - 2 * displaySpacingX;
  1667. static const int columnRulerMB1 = colRulerVel - 3 * displaySpacingX;
  1668. // Key mode LED buttons
  1669. static const int colRulerKM = 61;
  1670. addParam(createParamCentered<CKSSNotify>(Vec(colRulerKM, rowRulerMB0), module, Foundry::KEY_GATE_PARAM, 0.0f, 1.0f, 1.0f));
  1671. // Gate 1 light and button
  1672. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(columnRulerMB1 + posLEDvsButton, rowRulerMB0), module, Foundry::GATE_LIGHT));
  1673. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(columnRulerMB1, rowRulerMB0), module, Foundry::GATE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1674. // Tie light and button
  1675. addChild(createLightCentered<MediumLight<RedLight>>(Vec(columnRulerMB2 + posLEDvsButton, rowRulerMB0), module, Foundry::TIE_LIGHT));
  1676. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(columnRulerMB2, rowRulerMB0), module, Foundry::TIE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1677. // Gate 1 probability light and button
  1678. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(columnRulerMB3 + posLEDvsButton, rowRulerMB0), module, Foundry::GATE_PROB_LIGHT));
  1679. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(columnRulerMB3, rowRulerMB0), module, Foundry::GATE_PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1680. // Slide light and button
  1681. addChild(createLightCentered<MediumLight<RedLight>>(Vec(colRulerVel + posLEDvsButton, rowRulerMB0), module, Foundry::SLIDE_LIGHT));
  1682. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerVel, rowRulerMB0), module, Foundry::SLIDE_BTN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1683. // Mode button
  1684. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerEditPhr, rowRulerMB0), module, Foundry::MODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1685. // Rep/Len button
  1686. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerEditSeq, rowRulerMB0), module, Foundry::REP_LEN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1687. // Clk res
  1688. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerTrk, rowRulerMB0), module, Foundry::CLKRES_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1689. // Reset and run LED buttons
  1690. static const int colRulerResetRun = columnRulerT5;
  1691. // Run LED bezel and light
  1692. addParam(createParamCentered<LEDBezel>(Vec(colRulerResetRun, rowRulerSmallButtons - 6), module, Foundry::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  1693. addChild(createLightCentered<MuteLight<GreenLight>>(Vec(colRulerResetRun, rowRulerSmallButtons - 6), module, Foundry::RUN_LIGHT));
  1694. // Reset LED bezel and light
  1695. addParam(createParamCentered<LEDBezel>(Vec(colRulerResetRun, rowRulerMB0), module, Foundry::RESET_PARAM, 0.0f, 1.0f, 0.0f));
  1696. addChild(createLightCentered<MuteLight<GreenLight>>(Vec(colRulerResetRun, rowRulerMB0), module, Foundry::RESET_LIGHT));
  1697. // ****** Bottom two rows ******
  1698. static const int rowRulerBLow = 335;
  1699. static const int rowRulerBHigh = 286;
  1700. static const int bottomJackSpacingX = 46;
  1701. static const int columnRulerB0 = 32;
  1702. static const int columnRulerB1 = columnRulerB0 + bottomJackSpacingX;
  1703. static const int columnRulerB2 = columnRulerB1 + bottomJackSpacingX;
  1704. static const int columnRulerB3 = columnRulerB2 + bottomJackSpacingX;
  1705. static const int columnRulerB4 = columnRulerB3 + bottomJackSpacingX;
  1706. static const int columnRulerB5 = columnRulerB4 + bottomJackSpacingX;
  1707. static const int columnRulerB6 = columnRulerB5 + bottomJackSpacingX;
  1708. static const int columnRulerB7 = columnRulerB6 + bottomJackSpacingX;
  1709. static const int columnRulerB8 = columnRulerB7 + bottomJackSpacingX;
  1710. static const int columnRulerB9 = columnRulerB8 + bottomJackSpacingX;
  1711. static const int columnRulerB10 = columnRulerB9 + bottomJackSpacingX;
  1712. static const int columnRulerB11 = columnRulerB10 + bottomJackSpacingX;
  1713. // Autostep and write
  1714. addParam(createParamCentered<CKSSNoRandom>(Vec(columnRulerB0, rowRulerBHigh), module, Foundry::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f));
  1715. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB0, rowRulerBLow), Port::INPUT, module, Foundry::WRITE_INPUT, &module->panelTheme));
  1716. // CV IN inputs
  1717. static const int writeLEDoffsetX = 16;
  1718. static const int writeLEDoffsetY = 18;
  1719. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB1, rowRulerBHigh), Port::INPUT, module, Foundry::CV_INPUTS + 0, &module->panelTheme));
  1720. addChild(createLightCentered<SmallLight<RedLight>>(Vec(columnRulerB1 + writeLEDoffsetX, rowRulerBHigh + writeLEDoffsetY), module, Foundry::WRITECV_LIGHTS + 0));
  1721. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB2, rowRulerBHigh), Port::INPUT, module, Foundry::CV_INPUTS + 2, &module->panelTheme));
  1722. addChild(createLightCentered<SmallLight<RedLight>>(Vec(columnRulerB2 - writeLEDoffsetX, rowRulerBHigh + writeLEDoffsetY), module, Foundry::WRITECV_LIGHTS + 2));
  1723. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB1, rowRulerBLow), Port::INPUT, module, Foundry::CV_INPUTS + 1, &module->panelTheme));
  1724. addChild(createLightCentered<SmallLight<RedLight>>(Vec(columnRulerB1 + writeLEDoffsetX, rowRulerBLow - writeLEDoffsetY), module, Foundry::WRITECV_LIGHTS + 1));
  1725. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB2, rowRulerBLow), Port::INPUT, module, Foundry::CV_INPUTS + 3, &module->panelTheme));
  1726. addChild(createLightCentered<SmallLight<RedLight>>(Vec(columnRulerB2 - writeLEDoffsetX, rowRulerBLow - writeLEDoffsetY), module, Foundry::WRITECV_LIGHTS + 3));
  1727. // Clock+CV+Gate+Vel outputs
  1728. // Track A
  1729. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB3, rowRulerBHigh), Port::INPUT, module, Foundry::CLOCK_INPUTS + 0, &module->panelTheme));
  1730. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB4, rowRulerBHigh), Port::OUTPUT, module, Foundry::CV_OUTPUTS + 0, &module->panelTheme));
  1731. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB5, rowRulerBHigh), Port::OUTPUT, module, Foundry::GATE_OUTPUTS + 0, &module->panelTheme));
  1732. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB6, rowRulerBHigh), Port::OUTPUT, module, Foundry::VEL_OUTPUTS + 0, &module->panelTheme));
  1733. // Track C
  1734. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB7, rowRulerBHigh), Port::INPUT, module, Foundry::CLOCK_INPUTS + 2, &module->panelTheme));
  1735. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB8, rowRulerBHigh), Port::OUTPUT, module, Foundry::CV_OUTPUTS + 2, &module->panelTheme));
  1736. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB9, rowRulerBHigh), Port::OUTPUT, module, Foundry::GATE_OUTPUTS + 2, &module->panelTheme));
  1737. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB10, rowRulerBHigh), Port::OUTPUT, module, Foundry::VEL_OUTPUTS + 2, &module->panelTheme));
  1738. //
  1739. // Track B
  1740. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB3, rowRulerBLow), Port::INPUT, module, Foundry::CLOCK_INPUTS + 1, &module->panelTheme));
  1741. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB4, rowRulerBLow), Port::OUTPUT, module, Foundry::CV_OUTPUTS + 1, &module->panelTheme));
  1742. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB5, rowRulerBLow), Port::OUTPUT, module, Foundry::GATE_OUTPUTS + 1, &module->panelTheme));
  1743. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB6, rowRulerBLow), Port::OUTPUT, module, Foundry::VEL_OUTPUTS + 1, &module->panelTheme));
  1744. // Track D
  1745. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB7, rowRulerBLow), Port::INPUT, module, Foundry::CLOCK_INPUTS + 3, &module->panelTheme));
  1746. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB8, rowRulerBLow), Port::OUTPUT, module, Foundry::CV_OUTPUTS + 3, &module->panelTheme));
  1747. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB9, rowRulerBLow), Port::OUTPUT, module, Foundry::GATE_OUTPUTS + 3, &module->panelTheme));
  1748. addOutput(createDynamicPortCentered<IMPort>(Vec(columnRulerB10, rowRulerBLow), Port::OUTPUT, module, Foundry::VEL_OUTPUTS + 3, &module->panelTheme));
  1749. // Run and reset inputs
  1750. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB11, rowRulerBHigh), Port::INPUT, module, Foundry::RUNCV_INPUT, &module->panelTheme));
  1751. addInput(createDynamicPortCentered<IMPort>(Vec(columnRulerB11, rowRulerBLow), Port::INPUT, module, Foundry::RESET_INPUT, &module->panelTheme));
  1752. // Expansion module
  1753. static const int rowRulerExpTop = 67;
  1754. static const int rowSpacingExp = 51;
  1755. static const int colRulerExp = panel->box.size.x - expWidth / 2;
  1756. static const int colOffsetX = 23;
  1757. addInput(expPorts[4] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Foundry::SEQCV_INPUT, &module->panelTheme));
  1758. addInput(expPorts[5] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Foundry::TRKCV_INPUT, &module->panelTheme));
  1759. // Step arrow CV inputs
  1760. addInput(expPorts[6] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Foundry::LEFTCV_INPUT, &module->panelTheme));
  1761. addInput(expPorts[7] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Foundry::RIGHTCV_INPUT, &module->panelTheme));
  1762. addInput(expPorts[0] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Foundry::GATECV_INPUT, &module->panelTheme));
  1763. addInput(expPorts[1] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Foundry::TIEDCV_INPUT, &module->panelTheme));
  1764. addInput(expPorts[2] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Foundry::GATEPCV_INPUT, &module->panelTheme));
  1765. addInput(expPorts[3] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Foundry::SLIDECV_INPUT, &module->panelTheme));
  1766. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerExp, rowRulerExpTop + 180), module, Foundry::WRITEMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1767. // Velocity inputs
  1768. addInput(expPorts[8] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerBHigh), Port::INPUT, module, Foundry::VEL_INPUTS + 0, &module->panelTheme));
  1769. addChild(createLightCentered<SmallLight<RedLight>>(Vec(colRulerExp - colOffsetX + writeLEDoffsetX, rowRulerBHigh + writeLEDoffsetY), module, Foundry::WRITECV2_LIGHTS + 0));
  1770. addInput(expPorts[9] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerBHigh), Port::INPUT, module, Foundry::VEL_INPUTS + 2, &module->panelTheme));
  1771. addChild(createLightCentered<SmallLight<RedLight>>(Vec(colRulerExp + colOffsetX - writeLEDoffsetX, rowRulerBHigh + writeLEDoffsetY), module, Foundry::WRITECV2_LIGHTS + 2));
  1772. addInput(expPorts[10] = createDynamicPortCentered<IMPort>(Vec(colRulerExp - colOffsetX, rowRulerBLow), Port::INPUT, module, Foundry::VEL_INPUTS + 1, &module->panelTheme));
  1773. addChild(createLightCentered<SmallLight<RedLight>>(Vec(colRulerExp - colOffsetX + writeLEDoffsetX, rowRulerBLow - writeLEDoffsetY), module, Foundry::WRITECV2_LIGHTS + 1));
  1774. addInput(expPorts[11] = createDynamicPortCentered<IMPort>(Vec(colRulerExp + colOffsetX, rowRulerBLow), Port::INPUT, module, Foundry::VEL_INPUTS + 3, &module->panelTheme));
  1775. addChild(createLightCentered<SmallLight<RedLight>>(Vec(colRulerExp + colOffsetX - writeLEDoffsetX, rowRulerBLow - writeLEDoffsetY), module, Foundry::WRITECV2_LIGHTS + 3));
  1776. }
  1777. };
  1778. } // namespace rack_plugin_ImpromptuModular
  1779. using namespace rack_plugin_ImpromptuModular;
  1780. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, Foundry) {
  1781. Model *modelFoundry = Model::create<Foundry, FoundryWidget>("Impromptu Modular", "Foundry", "SEQ - Foundry", SEQUENCER_TAG);
  1782. return modelFoundry;
  1783. }
  1784. /*CHANGE LOG
  1785. 0.6.16:
  1786. add gate status feedback in steps (white lights)
  1787. 0.6.15:
  1788. save ALL state and don't init ALL nor SEL on run or reset
  1789. allow END in seq mode to choose custom seq lengths
  1790. add proper CV2 monitoring when not running and editing sequences
  1791. fix TKA song runmode slaving
  1792. add right-click menu option to bound AutoStep writes by sequence lengths
  1793. 0.6.14:
  1794. allow ctrl-right-click of notes to copy note/gate-type over to next step (not just move to next step)
  1795. add CV IN and CV2 IN LEDs, and make CV2 button, when returning to CV2 mode, toggle which CV or CV2 groups can be written (see LEDs)
  1796. add CV2 bipol option in right click menu; display notes for semitone CV2 mode (now called "Notes")
  1797. rotate offsets are now persistent and stored in the sequencer
  1798. expand TRACK cv input to allow starred tracks (ALL) to be selected, and write CVs only to selected track when not starred
  1799. 0.6.13:
  1800. created
  1801. */