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.

1784 lines
71KB

  1. //***********************************************************************************************
  2. //Multi-phrase 32 step 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. //Module inspired by the SA-100 Stepper Acid sequencer by Transistor Sounds Labs
  10. //
  11. //Acknowledgements: please see README.md
  12. //***********************************************************************************************
  13. #include "ImpromptuModular.hpp"
  14. #include "PhraseSeqUtil.hpp"
  15. namespace rack_plugin_ImpromptuModular {
  16. struct PhraseSeq32 : Module {
  17. enum ParamIds {
  18. LEFT_PARAM,
  19. RIGHT_PARAM,
  20. RIGHT8_PARAM,// not used
  21. EDIT_PARAM,
  22. SEQUENCE_PARAM,
  23. RUN_PARAM,
  24. COPY_PARAM,
  25. PASTE_PARAM,
  26. RESET_PARAM,
  27. ENUMS(OCTAVE_PARAM, 7),
  28. GATE1_PARAM,
  29. GATE2_PARAM,
  30. SLIDE_BTN_PARAM,
  31. SLIDE_KNOB_PARAM,
  32. ATTACH_PARAM,
  33. AUTOSTEP_PARAM,
  34. ENUMS(KEY_PARAMS, 12),
  35. RUNMODE_PARAM,
  36. TRAN_ROT_PARAM,
  37. GATE1_KNOB_PARAM,
  38. GATE1_PROB_PARAM,
  39. TIE_PARAM,// Legato
  40. CPMODE_PARAM,
  41. ENUMS(STEP_PHRASE_PARAMS, 32),
  42. CONFIG_PARAM,
  43. // -- 0.6.4 ^^
  44. NUM_PARAMS
  45. };
  46. enum InputIds {
  47. WRITE_INPUT,
  48. CV_INPUT,
  49. RESET_INPUT,
  50. CLOCK_INPUT,
  51. LEFTCV_INPUT,
  52. RIGHTCV_INPUT,
  53. RUNCV_INPUT,
  54. SEQCV_INPUT,
  55. // -- 0.6.4 ^^
  56. MODECV_INPUT,
  57. GATE1CV_INPUT,
  58. GATE2CV_INPUT,
  59. TIEDCV_INPUT,
  60. SLIDECV_INPUT,
  61. NUM_INPUTS
  62. };
  63. enum OutputIds {
  64. CVA_OUTPUT,
  65. GATE1A_OUTPUT,
  66. GATE2A_OUTPUT,
  67. CVB_OUTPUT,
  68. GATE1B_OUTPUT,
  69. GATE2B_OUTPUT,
  70. NUM_OUTPUTS
  71. };
  72. enum LightIds {
  73. ENUMS(STEP_PHRASE_LIGHTS, 32 * 2),// room for GreenRed
  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(GATE1_LIGHT, 2),// room for GreenRed
  79. ENUMS(GATE2_LIGHT, 2),// room for GreenRed
  80. SLIDE_LIGHT,
  81. ATTACH_LIGHT,
  82. GATE1_PROB_LIGHT,
  83. TIE_LIGHT,
  84. NUM_LIGHTS
  85. };
  86. enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_LENGTH, DISP_TRANSPOSE, DISP_ROTATE};
  87. // Need to save
  88. int panelTheme = 0;
  89. int expansion = 0;
  90. int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 6, 12, 24 PPS (Pulses per step)
  91. bool running;
  92. int runModeSeq[32];
  93. int runModeSong;
  94. //
  95. int sequence;
  96. int lengths[32];//1 to 32
  97. //
  98. int phrase[32];// This is the song (series of phases; a phrase is a patten number)
  99. int phrases;//1 to 32
  100. //
  101. float cv[32][32];// [-3.0 : 3.917]. First index is patten number, 2nd index is step
  102. int attributes[32][32];// First index is patten number, 2nd index is step (see enum AttributeBitMasks for details)
  103. //
  104. bool resetOnRun;
  105. bool attached;
  106. // No need to save
  107. float resetLight = 0.0f;
  108. int stepIndexEdit;
  109. int stepIndexRun;
  110. int phraseIndexEdit;
  111. int phraseIndexRun;
  112. long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste
  113. unsigned long editingGate;// 0 when no edit gate, downward step counter timer when edit gate
  114. float editingGateCV;// no need to initialize, this is a companion to editingGate (output this only when editingGate > 0)
  115. int editingGateKeyLight;// no need to initialize, this is a companion to editingGate (use this only when editingGate > 0)
  116. int editingChannel;// 0 means channel A, 1 means channel B. no need to initialize, this is a companion to editingGate
  117. int stepIndexRunHistory;// no need to initialize
  118. int phraseIndexRunHistory;// no need to initialize
  119. int displayState;
  120. unsigned long slideStepsRemain[2];// 0 when no slide under way, downward step counter when sliding
  121. float slideCVdelta[2];// no need to initialize, this is a companion to slideStepsRemain
  122. float cvCPbuffer[32];// copy paste buffer for CVs
  123. int attributesCPbuffer[32];// copy paste buffer for attributes
  124. int lengthCPbuffer;
  125. int modeCPbuffer;
  126. int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste)
  127. int transposeOffset;// no need to initialize, this is companion to displayMode = DISP_TRANSPOSE
  128. int rotateOffset;// no need to initialize, this is companion to displayMode = DISP_ROTATE
  129. long clockIgnoreOnReset;
  130. const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays)
  131. unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed)
  132. long tiedWarning;// 0 when no warning, positive downward step counter timer when warning
  133. int sequenceKnob = 0;
  134. int gate1Code[2];
  135. int gate2Code[2];
  136. bool attachedChanB;
  137. long revertDisplay;
  138. long editingGateLength;// 0 when no info, positive downward step counter timer when gate1, negative upward when gate2
  139. long editGateLengthTimeInitMult;// multiplier for extended setting of advanced gates
  140. long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn
  141. int ppqnCount;
  142. int lightRefreshCounter;
  143. static constexpr float CONFIG_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget
  144. int stepConfigLast;
  145. static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget
  146. bool editingSequence;
  147. bool editingSequenceLast;
  148. SchmittTrigger resetTrigger;
  149. SchmittTrigger leftTrigger;
  150. SchmittTrigger rightTrigger;
  151. SchmittTrigger runningTrigger;
  152. SchmittTrigger clockTrigger;
  153. SchmittTrigger octTriggers[7];
  154. SchmittTrigger octmTrigger;
  155. SchmittTrigger gate1Trigger;
  156. SchmittTrigger gate1ProbTrigger;
  157. SchmittTrigger gate2Trigger;
  158. SchmittTrigger slideTrigger;
  159. SchmittTrigger keyTriggers[12];
  160. SchmittTrigger writeTrigger;
  161. SchmittTrigger attachedTrigger;
  162. SchmittTrigger copyTrigger;
  163. SchmittTrigger pasteTrigger;
  164. SchmittTrigger modeTrigger;
  165. SchmittTrigger rotateTrigger;
  166. SchmittTrigger transposeTrigger;
  167. SchmittTrigger tiedTrigger;
  168. SchmittTrigger stepTriggers[32];
  169. HoldDetect modeHoldDetect;
  170. HoldDetect gate1HoldDetect;
  171. HoldDetect gate2HoldDetect;
  172. inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;}
  173. inline bool getGate1(int seq, int step) {return getGate1a(attributes[seq][step]);}
  174. inline bool getGate1P(int seq, int step) {return getGate1Pa(attributes[seq][step]);}
  175. inline bool getGate2(int seq, int step) {return getGate2a(attributes[seq][step]);}
  176. inline bool getSlide(int seq, int step) {return getSlideA(attributes[seq][step]);}
  177. inline bool getTied(int seq, int step) {return getTiedA(attributes[seq][step]);}
  178. inline int getGate1Mode(int seq, int step) {return getGate1aMode(attributes[seq][step]);}
  179. inline int getGate2Mode(int seq, int step) {return getGate2aMode(attributes[seq][step]);}
  180. inline void setGate1Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE1MODE; attributes[seq][step] |= (gateMode << gate1ModeShift);}
  181. inline void setGate2Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE2MODE; attributes[seq][step] |= (gateMode << gate2ModeShift);}
  182. PhraseSeq32() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  183. onReset();
  184. }
  185. // widgets are not yet created when module is created (and when onReset() is called by constructor)
  186. // onReset() is also called when right-click initialization of module
  187. void onReset() override {
  188. int stepConfig = 1;// 2x16
  189. if (CONFIG_PARAM_INIT_VALUE < 0.5f)// 1x32
  190. stepConfig = 2;
  191. stepConfigLast = stepConfig;
  192. pulsesPerStep = 1;
  193. running = false;
  194. runModeSong = MODE_FWD;
  195. stepIndexEdit = 0;
  196. phraseIndexEdit = 0;
  197. sequence = 0;
  198. phrases = 4;
  199. for (int i = 0; i < 32; i++) {
  200. for (int s = 0; s < 32; s++) {
  201. cv[i][s] = 0.0f;
  202. attributes[i][s] = ATT_MSK_GATE1;
  203. }
  204. runModeSeq[i] = MODE_FWD;
  205. phrase[i] = 0;
  206. lengths[i] = 16 * stepConfig;
  207. cvCPbuffer[i] = 0.0f;
  208. attributesCPbuffer[i] = ATT_MSK_GATE1;
  209. }
  210. initRun(stepConfig, true);
  211. lengthCPbuffer = 32;
  212. modeCPbuffer = MODE_FWD;
  213. countCP = 32;
  214. editingGate = 0ul;
  215. infoCopyPaste = 0l;
  216. displayState = DISP_NORMAL;
  217. slideStepsRemain[0] = 0ul;
  218. slideStepsRemain[1] = 0ul;
  219. attached = true;
  220. clockPeriod = 0ul;
  221. tiedWarning = 0ul;
  222. attachedChanB = false;
  223. revertDisplay = 0l;
  224. editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f;
  225. editingSequenceLast = editingSequence;
  226. resetOnRun = false;
  227. modeHoldDetect.reset();
  228. gate1HoldDetect.reset();
  229. gate2HoldDetect.reset();
  230. editGateLengthTimeInitMult = 1l;
  231. editingPpqn = 0l;
  232. lightRefreshCounter = 0;
  233. }
  234. // widgets randomized before onRandomize() is called
  235. void onRandomize() override {
  236. int stepConfig = 1;// 2x16
  237. if (params[CONFIG_PARAM].value < 0.5f)// 1x32
  238. stepConfig = 2;
  239. stepConfigLast = stepConfig;
  240. running = false;
  241. runModeSong = randomu32() % 5;
  242. stepIndexEdit = 0;
  243. phraseIndexEdit = 0;
  244. sequence = randomu32() % 32;
  245. phrases = 1 + (randomu32() % 32);
  246. for (int i = 0; i < 32; i++) {
  247. for (int s = 0; s < 32; s++) {
  248. cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f;
  249. attributes[i][s] = randomu32() & 0x1FFF;// 5 bit for normal attributes + 2 * 4 bits for advanced gate modes
  250. if (getTied(i,s)) {
  251. attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied
  252. applyTiedStep(i, s, lengths[i]);
  253. }
  254. }
  255. runModeSeq[i] = randomu32() % NUM_MODES;
  256. phrase[i] = randomu32() % 32;
  257. lengths[i] = 1 + (randomu32() % (16 * stepConfig));
  258. cvCPbuffer[i] = 0.0f;
  259. attributesCPbuffer[i] = ATT_MSK_GATE1;
  260. }
  261. initRun(stepConfig, true);
  262. lengthCPbuffer = 32;
  263. modeCPbuffer = MODE_FWD;
  264. countCP = 32;
  265. editingGate = 0ul;
  266. infoCopyPaste = 0l;
  267. displayState = DISP_NORMAL;
  268. slideStepsRemain[0] = 0ul;
  269. slideStepsRemain[1] = 0ul;
  270. attached = true;
  271. clockPeriod = 0ul;
  272. tiedWarning = 0ul;
  273. attachedChanB = false;
  274. revertDisplay = 0l;
  275. editingSequence = isEditingSequence();
  276. editingSequenceLast = editingSequence;
  277. resetOnRun = false;
  278. modeHoldDetect.reset();
  279. gate1HoldDetect.reset();
  280. gate2HoldDetect.reset();
  281. editGateLengthTimeInitMult = 1l;
  282. editingPpqn = 0l;
  283. }
  284. void initRun(int stepConfig, bool hard) {// run button activated or run edge in run input jack or edit mode toggled
  285. if (hard)
  286. phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0);
  287. int seq = (editingSequence ? sequence : phrase[phraseIndexRun]);
  288. if (hard)
  289. stepIndexRun = (runModeSeq[seq] == MODE_REV ? lengths[seq] - 1 : 0);
  290. ppqnCount = 0;
  291. for (int i = 0; i < 2; i += stepConfig) {
  292. gate1Code[i] = calcGate1Code(attributes[seq][(i * 16) + stepIndexRun], 0, pulsesPerStep, params[GATE1_KNOB_PARAM].value);
  293. gate2Code[i] = calcGate2Code(attributes[seq][(i * 16) + stepIndexRun], 0, pulsesPerStep);
  294. }
  295. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  296. editingGateLength = 0l;
  297. }
  298. json_t *toJson() override {
  299. json_t *rootJ = json_object();
  300. // panelTheme
  301. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  302. // expansion
  303. json_object_set_new(rootJ, "expansion", json_integer(expansion));
  304. // pulsesPerStep
  305. json_object_set_new(rootJ, "pulsesPerStep", json_integer(pulsesPerStep));
  306. // running
  307. json_object_set_new(rootJ, "running", json_boolean(running));
  308. // runModeSeq
  309. json_t *runModeSeqJ = json_array();
  310. for (int i = 0; i < 16; i++)
  311. json_array_insert_new(runModeSeqJ, i, json_integer(runModeSeq[i]));
  312. json_object_set_new(rootJ, "runModeSeq2", runModeSeqJ);// "2" appended so no break patches
  313. // runModeSong
  314. json_object_set_new(rootJ, "runModeSong", json_integer(runModeSong));
  315. // sequence
  316. json_object_set_new(rootJ, "sequence", json_integer(sequence));
  317. // lengths
  318. json_t *lengthsJ = json_array();
  319. for (int i = 0; i < 32; i++)
  320. json_array_insert_new(lengthsJ, i, json_integer(lengths[i]));
  321. json_object_set_new(rootJ, "lengths", lengthsJ);
  322. // phrase
  323. json_t *phraseJ = json_array();
  324. for (int i = 0; i < 32; i++)
  325. json_array_insert_new(phraseJ, i, json_integer(phrase[i]));
  326. json_object_set_new(rootJ, "phrase", phraseJ);
  327. // phrases
  328. json_object_set_new(rootJ, "phrases", json_integer(phrases));
  329. // CV
  330. json_t *cvJ = json_array();
  331. for (int i = 0; i < 32; i++)
  332. for (int s = 0; s < 32; s++) {
  333. json_array_insert_new(cvJ, s + (i * 32), json_real(cv[i][s]));
  334. }
  335. json_object_set_new(rootJ, "cv", cvJ);
  336. // attributes
  337. json_t *attributesJ = json_array();
  338. for (int i = 0; i < 32; i++)
  339. for (int s = 0; s < 32; s++) {
  340. json_array_insert_new(attributesJ, s + (i * 32), json_integer(attributes[i][s]));
  341. }
  342. json_object_set_new(rootJ, "attributes", attributesJ);
  343. // attached
  344. json_object_set_new(rootJ, "attached", json_boolean(attached));
  345. // resetOnRun
  346. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  347. return rootJ;
  348. }
  349. // widgets loaded before this fromJson() is called
  350. void fromJson(json_t *rootJ) override {
  351. // panelTheme
  352. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  353. if (panelThemeJ)
  354. panelTheme = json_integer_value(panelThemeJ);
  355. // expansion
  356. json_t *expansionJ = json_object_get(rootJ, "expansion");
  357. if (expansionJ)
  358. expansion = json_integer_value(expansionJ);
  359. // pulsesPerStep
  360. json_t *pulsesPerStepJ = json_object_get(rootJ, "pulsesPerStep");
  361. if (pulsesPerStepJ)
  362. pulsesPerStep = json_integer_value(pulsesPerStepJ);
  363. // running
  364. json_t *runningJ = json_object_get(rootJ, "running");
  365. if (runningJ)
  366. running = json_is_true(runningJ);
  367. // runModeSeq
  368. json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq2");// "2" appended so no break patches
  369. if (runModeSeqJ) {
  370. for (int i = 0; i < 16; i++)
  371. {
  372. json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i);
  373. if (runModeSeqArrayJ)
  374. runModeSeq[i] = json_integer_value(runModeSeqArrayJ);
  375. }
  376. }
  377. // runModeSong
  378. json_t *runModeSongJ = json_object_get(rootJ, "runModeSong");
  379. if (runModeSongJ)
  380. runModeSong = json_integer_value(runModeSongJ);
  381. // sequence
  382. json_t *sequenceJ = json_object_get(rootJ, "sequence");
  383. if (sequenceJ)
  384. sequence = json_integer_value(sequenceJ);
  385. // lengths
  386. json_t *lengthsJ = json_object_get(rootJ, "lengths");
  387. if (lengthsJ)
  388. for (int i = 0; i < 32; i++)
  389. {
  390. json_t *lengthsArrayJ = json_array_get(lengthsJ, i);
  391. if (lengthsArrayJ)
  392. lengths[i] = json_integer_value(lengthsArrayJ);
  393. }
  394. // phrase
  395. json_t *phraseJ = json_object_get(rootJ, "phrase");
  396. if (phraseJ)
  397. for (int i = 0; i < 32; i++)
  398. {
  399. json_t *phraseArrayJ = json_array_get(phraseJ, i);
  400. if (phraseArrayJ)
  401. phrase[i] = json_integer_value(phraseArrayJ);
  402. }
  403. // phrases
  404. json_t *phrasesJ = json_object_get(rootJ, "phrases");
  405. if (phrasesJ)
  406. phrases = json_integer_value(phrasesJ);
  407. // CV
  408. json_t *cvJ = json_object_get(rootJ, "cv");
  409. if (cvJ) {
  410. for (int i = 0; i < 32; i++)
  411. for (int s = 0; s < 32; s++) {
  412. json_t *cvArrayJ = json_array_get(cvJ, s + (i * 32));
  413. if (cvArrayJ)
  414. cv[i][s] = json_real_value(cvArrayJ);
  415. }
  416. }
  417. // attributes
  418. json_t *attributesJ = json_object_get(rootJ, "attributes");
  419. if (attributesJ) {
  420. for (int i = 0; i < 32; i++)
  421. for (int s = 0; s < 32; s++) {
  422. json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 32));
  423. if (attributesArrayJ)
  424. attributes[i][s] = json_integer_value(attributesArrayJ);
  425. }
  426. }
  427. // attached
  428. json_t *attachedJ = json_object_get(rootJ, "attached");
  429. if (attachedJ)
  430. attached = json_is_true(attachedJ);
  431. // resetOnRun
  432. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  433. if (resetOnRunJ)
  434. resetOnRun = json_is_true(resetOnRunJ);
  435. // Initialize dependants after everything loaded (widgets already loaded when reach here)
  436. int stepConfig = 1;// 2x16
  437. if (params[CONFIG_PARAM].value < 0.5f)// 1x32
  438. stepConfig = 2;
  439. stepConfigLast = stepConfig;
  440. initRun(stepConfig, true);
  441. editingSequence = isEditingSequence();
  442. editingSequenceLast = editingSequence;
  443. }
  444. void rotateSeq(int seqNum, bool directionRight, int seqLength, bool chanB_16) {
  445. // set chanB_16 to false to rotate chan A in 2x16 config (length will be <= 16) or single chan in 1x32 config (length will be <= 32)
  446. // set chanB_16 to true to rotate chan B in 2x16 config (length must be <= 16)
  447. float rotCV;
  448. int rotAttributes;
  449. int iStart = chanB_16 ? 16 : 0;
  450. int iEnd = iStart + seqLength - 1;
  451. int iRot = iStart;
  452. int iDelta = 1;
  453. if (directionRight) {
  454. iRot = iEnd;
  455. iDelta = -1;
  456. }
  457. rotCV = cv[seqNum][iRot];
  458. rotAttributes = attributes[seqNum][iRot];
  459. for ( ; ; iRot += iDelta) {
  460. if (iDelta == 1 && iRot >= iEnd) break;
  461. if (iDelta == -1 && iRot <= iStart) break;
  462. cv[seqNum][iRot] = cv[seqNum][iRot + iDelta];
  463. attributes[seqNum][iRot] = attributes[seqNum][iRot + iDelta];
  464. }
  465. cv[seqNum][iRot] = rotCV;
  466. attributes[seqNum][iRot] = rotAttributes;
  467. }
  468. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  469. void step() override {
  470. float sampleRate = engineGetSampleRate();
  471. static const float gateTime = 0.4f;// seconds
  472. static const float copyPasteInfoTime = 0.5f;// seconds
  473. static const float revertDisplayTime = 0.7f;// seconds
  474. static const float tiedWarningTime = 0.7f;// seconds
  475. static const float holdDetectTime = 2.0f;// seconds
  476. static const float editGateLengthTime = 4.0f;// seconds
  477. //********** Buttons, knobs, switches and inputs **********
  478. // Notes:
  479. // * a tied step's attributes can not be modified by any of the following:
  480. // write input, oct and keyboard buttons, gate1Prob and slide buttons
  481. // however, paste, transpose, rotate obviously can, and gate1/2 can be turned back on if desired.
  482. // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps)
  483. // Config switch
  484. int stepConfig = 1;// 2x16
  485. if (params[CONFIG_PARAM].value < 0.5f)// 1x32
  486. stepConfig = 2;
  487. // Config: set lengths to their new max when move switch
  488. if (stepConfigLast != stepConfig) {
  489. for (int i = 0; i < 32; i++)
  490. lengths[i] = 16 * stepConfig;
  491. attachedChanB = false;
  492. stepConfigLast = stepConfig;
  493. }
  494. // Edit mode
  495. editingSequence = isEditingSequence();// true = editing sequence, false = editing song
  496. if (editingSequenceLast != editingSequence) {
  497. if (running)
  498. initRun(stepConfig, true);
  499. displayState = DISP_NORMAL;
  500. editingSequenceLast = editingSequence;
  501. }
  502. // Seq CV input
  503. if (inputs[SEQCV_INPUT].active) {
  504. sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (32.0f - 1.0f) / 10.0f), 0.0f, (32.0f - 1.0f) );
  505. }
  506. // Mode CV input
  507. if (inputs[MODECV_INPUT].active) {
  508. if (editingSequence)
  509. runModeSeq[sequence] = (int) clamp( round(inputs[MODECV_INPUT].value * ((float)NUM_MODES - 1.0f) / 10.0f), 0.0f, (float)NUM_MODES - 1.0f );
  510. }
  511. // Run button
  512. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) {
  513. running = !running;
  514. if (running)
  515. initRun(stepConfig, resetOnRun);
  516. displayState = DISP_NORMAL;
  517. }
  518. // Attach button
  519. if (attachedTrigger.process(params[ATTACH_PARAM].value)) {
  520. if (running) {
  521. attached = !attached;
  522. if (attached && editingSequence && stepConfig == 1 )
  523. attachedChanB = stepIndexEdit >= 16;
  524. }
  525. displayState = DISP_NORMAL;
  526. }
  527. if (running && attached) {
  528. if (editingSequence)
  529. stepIndexEdit = stepIndexRun + ((attachedChanB && stepConfig == 1) ? 16 : 0);
  530. else
  531. phraseIndexEdit = phraseIndexRun;
  532. }
  533. // Copy button
  534. if (copyTrigger.process(params[COPY_PARAM].value)) {
  535. if (editingSequence) {
  536. infoCopyPaste = (long) (copyPasteInfoTime * sampleRate / displayRefreshStepSkips);
  537. int sStart = stepIndexEdit;
  538. int sCount = 32;
  539. if (params[CPMODE_PARAM].value > 1.5f)// all
  540. sStart = 0;
  541. else if (params[CPMODE_PARAM].value < 0.5f)// 4
  542. sCount = 4;
  543. else// 8
  544. sCount = 8;
  545. countCP = sCount;
  546. for (int i = 0, s = sStart; i < countCP; i++, s++) {
  547. if (s >= 32) s = 0;
  548. cvCPbuffer[i] = cv[sequence][s];
  549. attributesCPbuffer[i] = attributes[sequence][s];
  550. if ((--sCount) <= 0)
  551. break;
  552. }
  553. lengthCPbuffer = lengths[sequence];
  554. modeCPbuffer = runModeSeq[sequence];
  555. }
  556. displayState = DISP_NORMAL;
  557. }
  558. // Paste button
  559. if (pasteTrigger.process(params[PASTE_PARAM].value)) {
  560. if (editingSequence) {
  561. infoCopyPaste = (long) (-1 * copyPasteInfoTime * sampleRate / displayRefreshStepSkips);
  562. int sStart = ((countCP == 32) ? 0 : stepIndexEdit);
  563. int sCount = countCP;
  564. for (int i = 0, s = sStart; i < countCP; i++, s++) {
  565. if (s >= 32) break;
  566. cv[sequence][s] = cvCPbuffer[i];
  567. attributes[sequence][s] = attributesCPbuffer[i];
  568. if ((--sCount) <= 0)
  569. break;
  570. }
  571. if (params[CPMODE_PARAM].value > 1.5f) {// all
  572. lengths[sequence] = lengthCPbuffer;
  573. if (lengths[sequence] > 16 * stepConfig)
  574. lengths[sequence] = 16 * stepConfig;
  575. runModeSeq[sequence] = modeCPbuffer;
  576. }
  577. }
  578. displayState = DISP_NORMAL;
  579. }
  580. // Write input (must be before Left and Right in case route gate simultaneously to Right and Write for example)
  581. // (write must be to correct step)
  582. bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value);
  583. if (writeTrig) {
  584. if (editingSequence) {
  585. cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value;
  586. // Extra CVs from expansion panel:
  587. if (inputs[TIEDCV_INPUT].active)
  588. setTiedA(&attributes[sequence][stepIndexEdit], inputs[TIEDCV_INPUT].value > 1.0f);
  589. if (inputs[GATE1CV_INPUT].active)
  590. setGate1a(&attributes[sequence][stepIndexEdit], inputs[GATE1CV_INPUT].value > 1.0f);
  591. if (inputs[GATE2CV_INPUT].active)
  592. setGate2a(&attributes[sequence][stepIndexEdit], inputs[GATE2CV_INPUT].value > 1.0f);
  593. if (inputs[SLIDECV_INPUT].active)
  594. setSlideA(&attributes[sequence][stepIndexEdit], inputs[SLIDECV_INPUT].value > 1.0f);
  595. applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]);
  596. editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips);
  597. editingGateCV = cv[sequence][stepIndexEdit];
  598. editingGateKeyLight = -1;
  599. editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0;
  600. // Autostep (after grab all active inputs)
  601. if (params[AUTOSTEP_PARAM].value > 0.5f)
  602. stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32);
  603. }
  604. displayState = DISP_NORMAL;
  605. }
  606. // Left and right CV inputs
  607. int delta = 0;
  608. if (leftTrigger.process(inputs[LEFTCV_INPUT].value)) {
  609. delta = -1;
  610. if (displayState != DISP_LENGTH)
  611. displayState = DISP_NORMAL;
  612. }
  613. if (rightTrigger.process(inputs[RIGHTCV_INPUT].value)) {
  614. delta = +1;
  615. if (displayState != DISP_LENGTH)
  616. displayState = DISP_NORMAL;
  617. }
  618. if (delta != 0) {
  619. if (displayState == DISP_LENGTH) {
  620. if (editingSequence) {
  621. lengths[sequence] += delta;
  622. if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig);
  623. if (lengths[sequence] < 1 ) lengths[sequence] = 1;
  624. lengths[sequence] = ((lengths[sequence] - 1) % (16 * stepConfig)) + 1;
  625. }
  626. else {
  627. phrases += delta;
  628. if (phrases > 32) phrases = 32;
  629. if (phrases < 1 ) phrases = 1;
  630. }
  631. }
  632. else {
  633. if (!running || !attached) {// don't move heads when attach and running
  634. if (editingSequence) {
  635. stepIndexEdit += delta;
  636. if (stepIndexEdit < 0)
  637. stepIndexEdit = ((stepConfig == 1) ? 16 : 0) + lengths[sequence] - 1;
  638. if (stepIndexEdit >= 32)
  639. stepIndexEdit = 0;
  640. if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step
  641. if (!writeTrig) {// in case autostep when simultaneous writeCV and stepCV (keep what was done in Write Input block above)
  642. editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips);
  643. editingGateCV = cv[sequence][stepIndexEdit];
  644. editingGateKeyLight = -1;
  645. editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0;
  646. }
  647. }
  648. }
  649. else
  650. phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 32);
  651. }
  652. }
  653. }
  654. // Step button presses
  655. int stepPressed = -1;
  656. for (int i = 0; i < 32; i++) {
  657. if (stepTriggers[i].process(params[STEP_PHRASE_PARAMS + i].value))
  658. stepPressed = i;
  659. }
  660. if (stepPressed != -1) {
  661. if (displayState == DISP_LENGTH) {
  662. if (editingSequence)
  663. lengths[sequence] = (stepPressed % (16 * stepConfig)) + 1;
  664. else
  665. phrases = stepPressed + 1;
  666. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  667. }
  668. else {
  669. if (!running || !attached) {// not running or detached
  670. if (editingSequence) {
  671. stepIndexEdit = stepPressed;
  672. if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step
  673. editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips);
  674. editingGateCV = cv[sequence][stepIndexEdit];
  675. editingGateKeyLight = -1;
  676. editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0;
  677. }
  678. }
  679. else {
  680. phraseIndexEdit = stepPressed;
  681. }
  682. }
  683. else {// attached and running
  684. if (editingSequence) {
  685. if ((stepPressed < 16) && attachedChanB)
  686. attachedChanB = false;
  687. if ((stepPressed >= 16) && !attachedChanB)
  688. attachedChanB = true;
  689. }
  690. }
  691. displayState = DISP_NORMAL;
  692. }
  693. }
  694. // Mode/Length button
  695. if (modeTrigger.process(params[RUNMODE_PARAM].value)) {
  696. if (displayState == DISP_NORMAL || displayState == DISP_TRANSPOSE || displayState == DISP_ROTATE)
  697. displayState = DISP_LENGTH;
  698. else if (displayState == DISP_LENGTH)
  699. displayState = DISP_MODE;
  700. else
  701. displayState = DISP_NORMAL;
  702. //if (!running) {
  703. modeHoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips));
  704. //}
  705. }
  706. // Transpose/Rotate button
  707. if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) {
  708. if (editingSequence) {
  709. if (displayState == DISP_NORMAL || displayState == DISP_MODE || displayState == DISP_LENGTH) {
  710. displayState = DISP_TRANSPOSE;
  711. transposeOffset = 0;
  712. }
  713. else if (displayState == DISP_TRANSPOSE) {
  714. displayState = DISP_ROTATE;
  715. rotateOffset = 0;
  716. }
  717. else
  718. displayState = DISP_NORMAL;
  719. }
  720. }
  721. // Sequence knob
  722. float seqParamValue = params[SEQUENCE_PARAM].value;
  723. int newSequenceKnob = int(seqParamValue * 7.0f + 0.5f);
  724. if (seqParamValue == 0.0f)// true when constructor or fromJson() occured
  725. sequenceKnob = newSequenceKnob;
  726. int deltaKnob = newSequenceKnob - sequenceKnob;
  727. if (deltaKnob != 0) {
  728. if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example)
  729. if (editingPpqn != 0) {
  730. pulsesPerStep = indexToPps(ppsToIndex(pulsesPerStep) + deltaKnob);// indexToPps() does clamping
  731. editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips);
  732. }
  733. else if (displayState == DISP_MODE) {
  734. if (editingSequence) {
  735. if (!inputs[MODECV_INPUT].active) {
  736. runModeSeq[sequence] += deltaKnob;
  737. if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0;
  738. if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1;
  739. }
  740. }
  741. else {
  742. runModeSong += deltaKnob;
  743. if (runModeSong < 0) runModeSong = 0;
  744. if (runModeSong >= 5) runModeSong = 5 - 1;
  745. }
  746. }
  747. else if (displayState == DISP_LENGTH) {
  748. if (editingSequence) {
  749. lengths[sequence] += deltaKnob;
  750. if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig);
  751. if (lengths[sequence] < 1 ) lengths[sequence] = 1;
  752. }
  753. else {
  754. phrases += deltaKnob;
  755. if (phrases > 32) phrases = 32;
  756. if (phrases < 1 ) phrases = 1;
  757. }
  758. }
  759. else if (displayState == DISP_TRANSPOSE) {
  760. if (editingSequence) {
  761. transposeOffset += deltaKnob;
  762. if (transposeOffset > 99) transposeOffset = 99;
  763. if (transposeOffset < -99) transposeOffset = -99;
  764. // Tranpose by this number of semi-tones: deltaKnob
  765. float transposeOffsetCV = ((float)(deltaKnob))/12.0f;
  766. if (stepConfig == 1){ // 2x16 (transpose only the 16 steps corresponding to row where stepIndexEdit is located)
  767. int offset = stepIndexEdit < 16 ? 0 : 16;
  768. for (int s = offset; s < offset + 16; s++)
  769. cv[sequence][s] += transposeOffsetCV;
  770. }
  771. else { // 1x32 (transpose all 32 steps)
  772. for (int s = 0; s < 32; s++)
  773. cv[sequence][s] += transposeOffsetCV;
  774. }
  775. }
  776. }
  777. else if (displayState == DISP_ROTATE) {
  778. if (editingSequence) {
  779. rotateOffset += deltaKnob;
  780. if (rotateOffset > 99) rotateOffset = 99;
  781. if (rotateOffset < -99) rotateOffset = -99;
  782. if (deltaKnob > 0 && deltaKnob < 99) {// Rotate right, 99 is safety
  783. for (int i = deltaKnob; i > 0; i--)
  784. rotateSeq(sequence, true, lengths[sequence], stepConfig == 1 && stepIndexEdit >= 16);
  785. }
  786. if (deltaKnob < 0 && deltaKnob > -99) {// Rotate left, 99 is safety
  787. for (int i = deltaKnob; i < 0; i++)
  788. rotateSeq(sequence, false, lengths[sequence], stepConfig == 1 && stepIndexEdit >= 16);
  789. }
  790. }
  791. }
  792. else {// DISP_NORMAL
  793. if (editingSequence) {
  794. if (!inputs[SEQCV_INPUT].active) {
  795. sequence += deltaKnob;
  796. if (sequence < 0) sequence = 0;
  797. if (sequence >= 32) sequence = (32 - 1);
  798. }
  799. }
  800. else {
  801. // // no roll over
  802. // phrase[phraseIndexEdit] += deltaKnob;
  803. // if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0;
  804. // if (phrase[phraseIndexEdit] >= 32) phrase[phraseIndexEdit] = (32 - 1);
  805. // roll over
  806. int newPhrase = phrase[phraseIndexEdit] + deltaKnob;
  807. if (newPhrase < 0)
  808. newPhrase += (1 - newPhrase / 32) * 32;// newPhrase now positive
  809. newPhrase = newPhrase % 32;
  810. phrase[phraseIndexEdit] = newPhrase;
  811. }
  812. }
  813. }
  814. sequenceKnob = newSequenceKnob;
  815. }
  816. // Octave buttons
  817. int newOct = -1;
  818. for (int i = 0; i < 7; i++) {
  819. if (octTriggers[i].process(params[OCTAVE_PARAM + i].value)) {
  820. newOct = 6 - i;
  821. displayState = DISP_NORMAL;
  822. }
  823. }
  824. if (newOct >= 0 && newOct <= 6) {
  825. if (editingSequence) {
  826. if (getTied(sequence,stepIndexEdit))
  827. tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips);
  828. else {
  829. float newCV = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages
  830. newCV = newCV - floor(newCV) + (float) (newOct - 3);
  831. if (newCV >= -3.0f && newCV < 4.0f) {
  832. cv[sequence][stepIndexEdit] = newCV;
  833. applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]);
  834. }
  835. editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips);
  836. editingGateCV = cv[sequence][stepIndexEdit];
  837. editingGateKeyLight = -1;
  838. editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0;
  839. }
  840. }
  841. }
  842. // Keyboard buttons
  843. for (int i = 0; i < 12; i++) {
  844. if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) {
  845. if (editingSequence) {
  846. if (editingGateLength != 0l) {
  847. int newMode = keyIndexToGateMode(i, pulsesPerStep);
  848. if (editingGateLength > 0l) {
  849. if (newMode != -1)
  850. setGate1Mode(sequence, stepIndexEdit, newMode);
  851. else
  852. editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips);
  853. editingGateLength = ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult);
  854. }
  855. else {
  856. if (newMode != -1)
  857. setGate2Mode(sequence, stepIndexEdit, newMode);
  858. else
  859. editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips);
  860. editingGateLength = -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult);
  861. }
  862. }
  863. else if (getTied(sequence,stepIndexEdit)) {
  864. if (params[KEY_PARAMS + i].value > 1.5f)
  865. stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32);
  866. else
  867. tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips);
  868. }
  869. else {
  870. cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f;
  871. applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]);
  872. editingGate = (unsigned long) (gateTime * sampleRate / displayRefreshStepSkips);
  873. editingGateCV = cv[sequence][stepIndexEdit];
  874. editingGateKeyLight = -1;
  875. editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0;
  876. if (params[KEY_PARAMS + i].value > 1.5f) {
  877. stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32);
  878. editingGateKeyLight = i;
  879. }
  880. }
  881. }
  882. displayState = DISP_NORMAL;
  883. }
  884. }
  885. // Gate1, Gate1Prob, Gate2, Slide and Tied buttons
  886. if (gate1Trigger.process(params[GATE1_PARAM].value)) {
  887. if (editingSequence) {
  888. toggleGate1a(&attributes[sequence][stepIndexEdit]);
  889. //if (!running) {
  890. if (pulsesPerStep != 1) {
  891. editingGateLength = getGate1(sequence,stepIndexEdit) ? ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l;
  892. gate1HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips));
  893. }
  894. //}
  895. }
  896. displayState = DISP_NORMAL;
  897. }
  898. if (gate1ProbTrigger.process(params[GATE1_PROB_PARAM].value)) {
  899. if (editingSequence) {
  900. if (getTied(sequence,stepIndexEdit))
  901. tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips);
  902. else
  903. toggleGate1Pa(&attributes[sequence][stepIndexEdit]);
  904. }
  905. displayState = DISP_NORMAL;
  906. }
  907. if (gate2Trigger.process(params[GATE2_PARAM].value)) {
  908. if (editingSequence) {
  909. toggleGate2a(&attributes[sequence][stepIndexEdit]);
  910. //if (!running) {
  911. if (pulsesPerStep != 1) {
  912. editingGateLength = getGate2(sequence,stepIndexEdit) ? -1l * ((long) (editGateLengthTime * sampleRate / displayRefreshStepSkips) * editGateLengthTimeInitMult) : 0l;
  913. gate2HoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips));
  914. }
  915. //}
  916. }
  917. displayState = DISP_NORMAL;
  918. }
  919. if (slideTrigger.process(params[SLIDE_BTN_PARAM].value)) {
  920. if (editingSequence) {
  921. if (getTied(sequence,stepIndexEdit))
  922. tiedWarning = (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips);
  923. else
  924. toggleSlideA(&attributes[sequence][stepIndexEdit]);
  925. }
  926. displayState = DISP_NORMAL;
  927. }
  928. if (tiedTrigger.process(params[TIE_PARAM].value)) {
  929. if (editingSequence) {
  930. toggleTiedA(&attributes[sequence][stepIndexEdit]);
  931. if (getTied(sequence,stepIndexEdit)) {
  932. setGate1a(&attributes[sequence][stepIndexEdit], false);
  933. setGate2a(&attributes[sequence][stepIndexEdit], false);
  934. setSlideA(&attributes[sequence][stepIndexEdit], false);
  935. applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]);
  936. }
  937. }
  938. displayState = DISP_NORMAL;
  939. }
  940. //********** Clock and reset **********
  941. // Clock
  942. if (clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  943. if (running && clockIgnoreOnReset == 0l) {
  944. ppqnCount++;
  945. if (ppqnCount >= pulsesPerStep)
  946. ppqnCount = 0;
  947. int newSeq = sequence;// good value when editingSequence, overwrite if not editingSequence
  948. if (ppqnCount == 0) {
  949. float slideFromCV[2] = {0.0f, 0.0f};
  950. if (editingSequence) {
  951. for (int i = 0; i < 2; i += stepConfig)
  952. slideFromCV[i] = cv[sequence][(i * 16) + stepIndexRun];
  953. moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory);
  954. }
  955. else {
  956. for (int i = 0; i < 2; i += stepConfig)
  957. slideFromCV[i] = cv[phrase[phraseIndexRun]][(i * 16) + stepIndexRun];
  958. if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) {
  959. moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory);
  960. stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed
  961. }
  962. newSeq = phrase[phraseIndexRun];
  963. }
  964. // Slide
  965. for (int i = 0; i < 2; i += stepConfig) {
  966. if (getSlide(newSeq, (i * 16) + stepIndexRun)) {
  967. // activate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit)
  968. slideStepsRemain[i] = (unsigned long) (((float)clockPeriod * pulsesPerStep) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating)
  969. //slideStepsRemain[i] = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide
  970. float slideToCV = cv[newSeq][(i * 16) + stepIndexRun];
  971. slideCVdelta[i] = (slideToCV - slideFromCV[i])/(float)slideStepsRemain[i];
  972. }
  973. }
  974. }
  975. else {
  976. if (!editingSequence)
  977. newSeq = phrase[phraseIndexRun];
  978. }
  979. for (int i = 0; i < 2; i += stepConfig) {
  980. if (gate1Code[i] != -1 || ppqnCount == 0)
  981. gate1Code[i] = calcGate1Code(attributes[newSeq][(i * 16) + stepIndexRun], ppqnCount, pulsesPerStep, params[GATE1_KNOB_PARAM].value);
  982. gate2Code[i] = calcGate2Code(attributes[newSeq][(i * 16) + stepIndexRun], ppqnCount, pulsesPerStep);
  983. }
  984. }
  985. clockPeriod = 0ul;
  986. }
  987. clockPeriod++;
  988. // Reset
  989. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  990. //stepIndexEdit = 0;
  991. //sequence = 0;
  992. initRun(stepConfig, true);// must be after sequence reset
  993. resetLight = 1.0f;
  994. displayState = DISP_NORMAL;
  995. clockTrigger.reset();
  996. }
  997. //********** Outputs and lights **********
  998. // CV and gates outputs
  999. int seq = editingSequence ? (sequence) : (running ? phrase[phraseIndexRun] : phrase[phraseIndexEdit]);
  1000. int step = editingSequence ? (running ? stepIndexRun : stepIndexEdit) : (stepIndexRun);
  1001. if (running) {
  1002. float slideOffset[2];
  1003. for (int i = 0; i < 2; i += stepConfig)
  1004. slideOffset[i] = (slideStepsRemain[i] > 0ul ? (slideCVdelta[i] * (float)slideStepsRemain[i]) : 0.0f);
  1005. outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0];
  1006. outputs[GATE1A_OUTPUT].value = calcGate(gate1Code[0], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f;
  1007. outputs[GATE2A_OUTPUT].value = calcGate(gate2Code[0], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f;
  1008. if (stepConfig == 1) {
  1009. outputs[CVB_OUTPUT].value = cv[seq][16 + step] - slideOffset[1];
  1010. outputs[GATE1B_OUTPUT].value = calcGate(gate1Code[1], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f;
  1011. outputs[GATE2B_OUTPUT].value = calcGate(gate2Code[1], clockTrigger, clockPeriod, sampleRate) ? 10.0f : 0.0f;
  1012. }
  1013. else {
  1014. outputs[CVB_OUTPUT].value = 0.0f;
  1015. outputs[GATE1B_OUTPUT].value = 0.0f;
  1016. outputs[GATE2B_OUTPUT].value = 0.0f;
  1017. }
  1018. }
  1019. else {// not running
  1020. if (editingChannel == 0) {
  1021. outputs[CVA_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step];
  1022. outputs[GATE1A_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f;
  1023. outputs[GATE2A_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f;
  1024. outputs[CVB_OUTPUT].value = 0.0f;
  1025. outputs[GATE1B_OUTPUT].value = 0.0f;
  1026. outputs[GATE2B_OUTPUT].value = 0.0f;
  1027. }
  1028. else {
  1029. outputs[CVA_OUTPUT].value = 0.0f;
  1030. outputs[GATE1A_OUTPUT].value = 0.0f;
  1031. outputs[GATE2A_OUTPUT].value = 0.0f;
  1032. outputs[CVB_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step];
  1033. outputs[GATE1B_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f;
  1034. outputs[GATE2B_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f;
  1035. }
  1036. }
  1037. for (int i = 0; i < 2; i++)
  1038. if (slideStepsRemain[i] > 0ul)
  1039. slideStepsRemain[i]--;
  1040. lightRefreshCounter++;
  1041. if (lightRefreshCounter > displayRefreshStepSkips) {
  1042. lightRefreshCounter = 0;
  1043. // Step/phrase lights
  1044. if (infoCopyPaste != 0l) {
  1045. for (int i = 0; i < 32; i++) {
  1046. if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 32) )
  1047. lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval
  1048. else
  1049. lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing)
  1050. lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing)
  1051. }
  1052. }
  1053. else {
  1054. for (int i = 0; i < 32; i++) {
  1055. int col = (stepConfig == 1 ? (i & 0xF) : i);//i % (16 * stepConfig);// optimized
  1056. if (displayState == DISP_LENGTH) {
  1057. if (editingSequence) {
  1058. if (col < (lengths[sequence] - 1))
  1059. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f);
  1060. else if (col == (lengths[sequence] - 1))
  1061. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 1.0f, 0.0f);
  1062. else
  1063. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.0f, 0.0f);
  1064. }
  1065. else {
  1066. if (i < phrases - 1)
  1067. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f);
  1068. else
  1069. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, (i == phrases - 1) ? 1.0f : 0.0f, 0.0f);
  1070. }
  1071. }
  1072. else {// normal led display (i.e. not length)
  1073. float red = 0.0f;
  1074. float green = 0.0f;
  1075. // Run cursor (green)
  1076. if (editingSequence)
  1077. green = ((running && (col == stepIndexRun)) ? 1.0f : 0.0f);
  1078. else {
  1079. green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f);
  1080. green += ((running && (col == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f);
  1081. green = clamp(green, 0.0f, 1.0f);
  1082. }
  1083. // Edit cursor (red)
  1084. if (editingSequence)
  1085. red = (i == stepIndexEdit ? 1.0f : 0.0f);
  1086. else
  1087. red = (i == phraseIndexEdit ? 1.0f : 0.0f);
  1088. setGreenRed(STEP_PHRASE_LIGHTS + i * 2, green, red);
  1089. }
  1090. }
  1091. }
  1092. // Octave lights
  1093. float octCV = 0.0f;
  1094. if (editingSequence)
  1095. octCV = cv[sequence][stepIndexEdit];
  1096. else
  1097. octCV = cv[phrase[phraseIndexEdit]][stepIndexRun];
  1098. int octLightIndex = (int) floor(octCV + 3.0f);
  1099. for (int i = 0; i < 7; i++) {
  1100. if (!editingSequence && (!attached || !running || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3])
  1101. // [1] makes no sense, can't mod steps and stepping though seq that may not be playing
  1102. // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display
  1103. // [3] makes no sense, which sequence would be displayed, top or bottom row!
  1104. lights[OCTAVE_LIGHTS + i].value = 0.0f;
  1105. else {
  1106. if (tiedWarning > 0l) {
  1107. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips));
  1108. lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f;
  1109. }
  1110. else
  1111. lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f);
  1112. }
  1113. }
  1114. // Keyboard lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations)
  1115. float cvValOffset;
  1116. if (editingSequence)
  1117. cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages
  1118. else
  1119. cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages
  1120. int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f);
  1121. if (editingGateLength != 0 && editingSequence) {
  1122. int modeLightIndex = gateModeToKeyLightIndex(attributes[sequence][stepIndexEdit], editingGateLength > 0l);
  1123. for (int i = 0; i < 12; i++) {
  1124. if (i == modeLightIndex) {
  1125. lights[KEY_LIGHTS + i * 2 + 0].value = editingGateLength > 0l ? 1.0f : 0.2f;
  1126. lights[KEY_LIGHTS + i * 2 + 1].value = editingGateLength > 0l ? 0.2f : 1.0f;
  1127. }
  1128. else {
  1129. lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f;
  1130. if (i == keyLightIndex)
  1131. lights[KEY_LIGHTS + i * 2 + 1].value = 0.1f;
  1132. else
  1133. lights[KEY_LIGHTS + i * 2 + 1].value = 0.0f;
  1134. }
  1135. }
  1136. }
  1137. else {
  1138. for (int i = 0; i < 12; i++) {
  1139. lights[KEY_LIGHTS + i * 2 + 0].value = 0.0f;
  1140. if (!editingSequence && (!attached || !running || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3])
  1141. // [1] makes no sense, can't mod steps and stepping though seq that may not be playing
  1142. // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display
  1143. // [3] makes no sense, which sequence would be displayed, top or bottom row!
  1144. lights[KEY_LIGHTS + i * 2 + 1].value = 0.0f;
  1145. else {
  1146. if (tiedWarning > 0l) {
  1147. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips));
  1148. lights[KEY_LIGHTS + i * 2 + 1].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f;
  1149. }
  1150. else {
  1151. if (editingGate > 0ul && editingGateKeyLight != -1)
  1152. lights[KEY_LIGHTS + i * 2 + 1].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * sampleRate / displayRefreshStepSkips)) : 0.0f);
  1153. else
  1154. lights[KEY_LIGHTS + i * 2 + 1].value = (i == keyLightIndex ? 1.0f : 0.0f);
  1155. }
  1156. }
  1157. }
  1158. }
  1159. // Gate1, Gate1Prob, Gate2, Slide and Tied lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations)
  1160. int attributesVal = attributes[sequence][stepIndexEdit];
  1161. if (!editingSequence)
  1162. attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun];
  1163. //
  1164. setGateLight(getGate1a(attributesVal), GATE1_LIGHT);
  1165. setGateLight(getGate2a(attributesVal), GATE2_LIGHT);
  1166. lights[GATE1_PROB_LIGHT].value = getGate1Pa(attributesVal) ? 1.0f : 0.0f;
  1167. lights[SLIDE_LIGHT].value = getSlideA(attributesVal) ? 1.0f : 0.0f;
  1168. if (tiedWarning > 0l) {
  1169. bool warningFlashState = calcWarningFlash(tiedWarning, (long) (tiedWarningTime * sampleRate / displayRefreshStepSkips));
  1170. lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f;
  1171. }
  1172. else
  1173. lights[TIE_LIGHT].value = getTiedA(attributesVal) ? 1.0f : 0.0f;
  1174. // Attach light
  1175. lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f;
  1176. // Reset light
  1177. lights[RESET_LIGHT].value = resetLight;
  1178. resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips;
  1179. // Run light
  1180. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  1181. if (editingGate > 0ul)
  1182. editingGate--;
  1183. if (infoCopyPaste != 0l) {
  1184. if (infoCopyPaste > 0l)
  1185. infoCopyPaste --;
  1186. if (infoCopyPaste < 0l)
  1187. infoCopyPaste ++;
  1188. }
  1189. if (editingGateLength != 0l) {
  1190. if (editingGateLength > 0l)
  1191. editingGateLength --;
  1192. if (editingGateLength < 0l)
  1193. editingGateLength ++;
  1194. }
  1195. if (editingPpqn > 0l)
  1196. editingPpqn--;
  1197. if (tiedWarning > 0l)
  1198. tiedWarning--;
  1199. if (modeHoldDetect.process(params[RUNMODE_PARAM].value)) {
  1200. displayState = DISP_NORMAL;
  1201. editingPpqn = (long) (editGateLengthTime * sampleRate / displayRefreshStepSkips);
  1202. }
  1203. if (gate1HoldDetect.process(params[GATE1_PARAM].value)) {
  1204. toggleGate1a(&attributes[sequence][stepIndexEdit]);
  1205. editGateLengthTimeInitMult = 1;
  1206. }
  1207. if (gate2HoldDetect.process(params[GATE2_PARAM].value)) {
  1208. toggleGate2a(&attributes[sequence][stepIndexEdit]);
  1209. editGateLengthTimeInitMult = 100;
  1210. }
  1211. if (revertDisplay > 0l) {
  1212. if (revertDisplay == 1)
  1213. displayState = DISP_NORMAL;
  1214. revertDisplay--;
  1215. }
  1216. }// lightRefreshCounter
  1217. if (clockIgnoreOnReset > 0l)
  1218. clockIgnoreOnReset--;
  1219. }// step()
  1220. void setGreenRed(int id, float green, float red) {
  1221. lights[id + 0].value = green;
  1222. lights[id + 1].value = red;
  1223. }
  1224. void applyTiedStep(int seqNum, int indexTied, int seqLength) {
  1225. // Start on indexTied and loop until seqLength
  1226. // Called because either:
  1227. // case A: tied was activated for given step
  1228. // case B: the given step's CV was modified
  1229. // These cases are mutually exclusive
  1230. // copy previous CV over to current step if tied
  1231. if (getTied(seqNum,indexTied) && (indexTied > 0))
  1232. cv[seqNum][indexTied] = cv[seqNum][indexTied - 1];
  1233. // Affect downstream CVs of subsequent tied note chain (can be 0 length if next note is not tied)
  1234. for (int i = indexTied + 1; i < seqLength && getTied(seqNum,i); i++)
  1235. cv[seqNum][i] = cv[seqNum][indexTied];
  1236. }
  1237. int calcNewGateMode(int currentGateMode, int deltaKnob) {
  1238. return clamp(currentGateMode + deltaKnob, 0, NUM_GATES - 1);
  1239. }
  1240. inline void setGateLight(bool gateOn, int lightIndex) {
  1241. if (!gateOn) {
  1242. lights[lightIndex + 0].value = 0.0f;
  1243. lights[lightIndex + 1].value = 0.0f;
  1244. }
  1245. else if (pulsesPerStep == 1) {
  1246. lights[lightIndex + 0].value = 0.0f;
  1247. lights[lightIndex + 1].value = 1.0f;
  1248. }
  1249. else {
  1250. lights[lightIndex + 0].value = lightIndex == GATE1_LIGHT ? 1.0f : 0.2f;
  1251. lights[lightIndex + 1].value = lightIndex == GATE1_LIGHT ? 0.2f : 1.0f;
  1252. }
  1253. }
  1254. };
  1255. struct PhraseSeq32Widget : ModuleWidget {
  1256. PhraseSeq32 *module;
  1257. DynamicSVGPanel *panel;
  1258. int oldExpansion;
  1259. int expWidth = 60;
  1260. IMPort* expPorts[5];
  1261. struct SequenceDisplayWidget : TransparentWidget {
  1262. PhraseSeq32 *module;
  1263. std::shared_ptr<Font> font;
  1264. char displayStr[4];
  1265. SequenceDisplayWidget() {
  1266. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  1267. }
  1268. void runModeToStr(int num) {
  1269. if (num >= 0 && num < NUM_MODES)
  1270. snprintf(displayStr, 4, "%s", modeLabels[num].c_str());
  1271. }
  1272. void draw(NVGcontext *vg) override {
  1273. NVGcolor textColor = prepareDisplay(vg, &box);
  1274. nvgFontFaceId(vg, font->handle);
  1275. //nvgTextLetterSpacing(vg, 2.5);
  1276. Vec textPos = Vec(6, 24);
  1277. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  1278. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  1279. nvgFillColor(vg, textColor);
  1280. if (module->infoCopyPaste != 0l) {
  1281. if (module->infoCopyPaste > 0l)
  1282. snprintf(displayStr, 4, "CPY");
  1283. else
  1284. snprintf(displayStr, 4, "PST");
  1285. }
  1286. else if (module->editingPpqn != 0ul) {
  1287. snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep);
  1288. }
  1289. else if (module->displayState == PhraseSeq32::DISP_MODE) {
  1290. if (module->editingSequence)
  1291. runModeToStr(module->runModeSeq[module->sequence]);
  1292. else
  1293. runModeToStr(module->runModeSong);
  1294. }
  1295. else if (module->displayState == PhraseSeq32::DISP_LENGTH) {
  1296. if (module->editingSequence)
  1297. snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]);
  1298. else
  1299. snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases);
  1300. }
  1301. else if (module->displayState == PhraseSeq32::DISP_TRANSPOSE) {
  1302. snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset));
  1303. if (module->transposeOffset < 0)
  1304. displayStr[0] = '-';
  1305. }
  1306. else if (module->displayState == PhraseSeq32::DISP_ROTATE) {
  1307. snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset));
  1308. if (module->rotateOffset < 0)
  1309. displayStr[0] = '(';
  1310. }
  1311. else {// DISP_NORMAL
  1312. snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ?
  1313. module->sequence : module->phrase[module->phraseIndexEdit]) + 1 );
  1314. }
  1315. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  1316. }
  1317. };
  1318. struct PanelThemeItem : MenuItem {
  1319. PhraseSeq32 *module;
  1320. int theme;
  1321. void onAction(EventAction &e) override {
  1322. module->panelTheme = theme;
  1323. }
  1324. void step() override {
  1325. rightText = (module->panelTheme == theme) ? "✔" : "";
  1326. }
  1327. };
  1328. struct ExpansionItem : MenuItem {
  1329. PhraseSeq32 *module;
  1330. void onAction(EventAction &e) override {
  1331. module->expansion = module->expansion == 1 ? 0 : 1;
  1332. }
  1333. };
  1334. struct ResetOnRunItem : MenuItem {
  1335. PhraseSeq32 *module;
  1336. void onAction(EventAction &e) override {
  1337. module->resetOnRun = !module->resetOnRun;
  1338. }
  1339. };
  1340. Menu *createContextMenu() override {
  1341. Menu *menu = ModuleWidget::createContextMenu();
  1342. MenuLabel *spacerLabel = new MenuLabel();
  1343. menu->addChild(spacerLabel);
  1344. PhraseSeq32 *module = dynamic_cast<PhraseSeq32*>(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 *darkItem = new PanelThemeItem();
  1355. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  1356. darkItem->module = module;
  1357. darkItem->theme = 1;
  1358. menu->addChild(darkItem);
  1359. menu->addChild(new MenuLabel());// empty line
  1360. MenuLabel *settingsLabel = new MenuLabel();
  1361. settingsLabel->text = "Settings";
  1362. menu->addChild(settingsLabel);
  1363. ResetOnRunItem *rorItem = MenuItem::create<ResetOnRunItem>("Reset on Run", CHECKMARK(module->resetOnRun));
  1364. rorItem->module = module;
  1365. menu->addChild(rorItem);
  1366. menu->addChild(new MenuLabel());// empty line
  1367. MenuLabel *expansionLabel = new MenuLabel();
  1368. expansionLabel->text = "Expansion module";
  1369. menu->addChild(expansionLabel);
  1370. ExpansionItem *expItem = MenuItem::create<ExpansionItem>(expansionMenuLabel, CHECKMARK(module->expansion != 0));
  1371. expItem->module = module;
  1372. menu->addChild(expItem);
  1373. return menu;
  1374. }
  1375. void step() override {
  1376. if(module->expansion != oldExpansion) {
  1377. if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks
  1378. for (int i = 0; i < 5; i++)
  1379. rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]);
  1380. }
  1381. oldExpansion = module->expansion;
  1382. }
  1383. box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth;
  1384. Widget::step();
  1385. }
  1386. PhraseSeq32Widget(PhraseSeq32 *module) : ModuleWidget(module) {
  1387. this->module = module;
  1388. oldExpansion = -1;
  1389. // Main panel from Inkscape
  1390. panel = new DynamicSVGPanel();
  1391. panel->mode = &module->panelTheme;
  1392. panel->expWidth = &expWidth;
  1393. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PhraseSeq32.svg")));
  1394. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/PhraseSeq32_dark.svg")));
  1395. box.size = panel->box.size;
  1396. box.size.x = box.size.x - (1 - module->expansion) * expWidth;
  1397. addChild(panel);
  1398. // Screws
  1399. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  1400. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  1401. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 0), &module->panelTheme));
  1402. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 365), &module->panelTheme));
  1403. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme));
  1404. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme));
  1405. // ****** Top row ******
  1406. static const int rowRulerT0 = 48;
  1407. static const int columnRulerT0 = 18;//30;// Step/Phase LED buttons
  1408. static const int columnRulerT3 = 377;// Attach
  1409. static const int columnRulerT4 = 430;// Config
  1410. // Step/Phrase LED buttons
  1411. int posX = columnRulerT0;
  1412. static int spacingSteps = 20;
  1413. static int spacingSteps4 = 4;
  1414. for (int x = 0; x < 16; x++) {
  1415. // First row
  1416. addParam(ParamWidget::create<LEDButton>(Vec(posX, rowRulerT0 - 10 + 3 - 4.4f), module, PhraseSeq32::STEP_PHRASE_PARAMS + x, 0.0f, 1.0f, 0.0f));
  1417. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(posX + 4.4f, rowRulerT0 - 10 + 3), module, PhraseSeq32::STEP_PHRASE_LIGHTS + (x * 2)));
  1418. // Second row
  1419. addParam(ParamWidget::create<LEDButton>(Vec(posX, rowRulerT0 + 10 + 3 - 4.4f), module, PhraseSeq32::STEP_PHRASE_PARAMS + x + 16, 0.0f, 1.0f, 0.0f));
  1420. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(posX + 4.4f, rowRulerT0 + 10 + 3), module, PhraseSeq32::STEP_PHRASE_LIGHTS + ((x + 16) * 2)));
  1421. // step position to next location and handle groups of four
  1422. posX += spacingSteps;
  1423. if ((x + 1) % 4 == 0)
  1424. posX += spacingSteps4;
  1425. }
  1426. // Attach button and light
  1427. addParam(ParamWidget::create<TL1105>(Vec(columnRulerT3 - 4, rowRulerT0 - 6 + 2 + offsetTL1105), module, PhraseSeq32::ATTACH_PARAM, 0.0f, 1.0f, 0.0f));
  1428. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(columnRulerT3 + 12 + offsetMediumLight, rowRulerT0 - 6 + offsetMediumLight), module, PhraseSeq32::ATTACH_LIGHT));
  1429. // Config switch
  1430. addParam(ParamWidget::create<CKSS>(Vec(columnRulerT4 + hOffsetCKSS + 1, rowRulerT0 - 6 + vOffsetCKSS), module, PhraseSeq32::CONFIG_PARAM, 0.0f, 1.0f, PhraseSeq32::CONFIG_PARAM_INIT_VALUE));
  1431. // ****** Octave and keyboard area ******
  1432. // Octave LED buttons
  1433. static const float octLightsIntY = 20.0f;
  1434. for (int i = 0; i < 7; i++) {
  1435. addParam(ParamWidget::create<LEDButton>(Vec(15 + 3, 82 + 24 + i * octLightsIntY- 4.4f), module, PhraseSeq32::OCTAVE_PARAM + i, 0.0f, 1.0f, 0.0f));
  1436. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(15 + 3 + 4.4f, 82 + 24 + i * octLightsIntY), module, PhraseSeq32::OCTAVE_LIGHTS + i));
  1437. }
  1438. // Keys and Key lights
  1439. static const int keyNudgeX = 7;
  1440. static const int KeyBlackY = 103;
  1441. static const int KeyWhiteY = 141;
  1442. static const int offsetKeyLEDx = 6;
  1443. static const int offsetKeyLEDy = 16;
  1444. // Black keys and lights
  1445. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(65+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 1, 0.0, 1.0, 0.0));
  1446. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(65+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 1 * 2));
  1447. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(93+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 3, 0.0, 1.0, 0.0));
  1448. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(93+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 3 * 2));
  1449. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(150+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 6, 0.0, 1.0, 0.0));
  1450. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(150+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 6 * 2));
  1451. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(178+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 8, 0.0, 1.0, 0.0));
  1452. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(178+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 8 * 2));
  1453. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(206+keyNudgeX, KeyBlackY), module, PhraseSeq32::KEY_PARAMS + 10, 0.0, 1.0, 0.0));
  1454. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(206+keyNudgeX+offsetKeyLEDx, KeyBlackY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 10 * 2));
  1455. // White keys and lights
  1456. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(51+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 0, 0.0, 1.0, 0.0));
  1457. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(51+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 0 * 2));
  1458. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(79+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 2, 0.0, 1.0, 0.0));
  1459. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(79+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 2 * 2));
  1460. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(107+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 4, 0.0, 1.0, 0.0));
  1461. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(107+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 4 * 2));
  1462. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(136+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 5, 0.0, 1.0, 0.0));
  1463. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(136+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 5 * 2));
  1464. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(164+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 7, 0.0, 1.0, 0.0));
  1465. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(164+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 7 * 2));
  1466. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(192+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 9, 0.0, 1.0, 0.0));
  1467. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(192+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 9 * 2));
  1468. addParam(ParamWidget::create<InvisibleKeySmall>( Vec(220+keyNudgeX, KeyWhiteY), module, PhraseSeq32::KEY_PARAMS + 11, 0.0, 1.0, 0.0));
  1469. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(220+keyNudgeX+offsetKeyLEDx, KeyWhiteY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 11 * 2));
  1470. // ****** Right side control area ******
  1471. static const int rowRulerMK0 = 101;// Edit mode row
  1472. static const int rowRulerMK1 = rowRulerMK0 + 56; // Run row
  1473. static const int rowRulerMK2 = rowRulerMK1 + 54; // Copy-paste Tran/rot row
  1474. static const int columnRulerMK0 = 278;// Edit mode column
  1475. static const int columnRulerMK2 = columnRulerT4;// Mode/Len column
  1476. static const int columnRulerMK1 = 353;// Display column
  1477. // Edit mode switch
  1478. addParam(ParamWidget::create<CKSS>(Vec(columnRulerMK0 + 2 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, PhraseSeq32::EDIT_PARAM, 0.0f, 1.0f, PhraseSeq32::EDIT_PARAM_INIT_VALUE));
  1479. // Sequence display
  1480. SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget();
  1481. displaySequence->box.pos = Vec(columnRulerMK1-15, rowRulerMK0 + 3 + vOffsetDisplay);
  1482. displaySequence->box.size = Vec(55, 30);// 3 characters
  1483. displaySequence->module = module;
  1484. addChild(displaySequence);
  1485. // Run mode button
  1486. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK0 + 0 + offsetCKD6b), module, PhraseSeq32::RUNMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1487. // Autostep
  1488. addParam(ParamWidget::create<CKSS>(Vec(columnRulerMK0 + 2 + hOffsetCKSS, rowRulerMK1 + 7 + vOffsetCKSS), module, PhraseSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f));
  1489. // Sequence knob
  1490. addParam(createDynamicParam<IMBigKnobInf>(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, PhraseSeq32::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  1491. // Transpose/rotate button
  1492. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK1 + 4 + offsetCKD6b), module, PhraseSeq32::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1493. // Reset LED bezel and light
  1494. addParam(ParamWidget::create<LEDBezel>(Vec(columnRulerMK0 - 15 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, PhraseSeq32::RESET_PARAM, 0.0f, 1.0f, 0.0f));
  1495. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(columnRulerMK0 - 15 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq32::RESET_LIGHT));
  1496. // Run LED bezel and light
  1497. addParam(ParamWidget::create<LEDBezel>(Vec(columnRulerMK0 + 20 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, PhraseSeq32::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  1498. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(columnRulerMK0 + 20 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq32::RUN_LIGHT));
  1499. // Copy/paste buttons
  1500. addParam(ParamWidget::create<TL1105>(Vec(columnRulerMK1 - 10, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq32::COPY_PARAM, 0.0f, 1.0f, 0.0f));
  1501. addParam(ParamWidget::create<TL1105>(Vec(columnRulerMK1 + 20, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq32::PASTE_PARAM, 0.0f, 1.0f, 0.0f));
  1502. // Copy-paste mode switch (3 position)
  1503. addParam(ParamWidget::create<CKSSThreeInv>(Vec(columnRulerMK2 + hOffsetCKSS + 1, rowRulerMK2 - 3 + vOffsetCKSSThree), module, PhraseSeq32::CPMODE_PARAM, 0.0f, 2.0f, 2.0f)); // 0.0f is top position
  1504. // ****** Gate and slide section ******
  1505. static const int rowRulerMB0 = 214;
  1506. static const int columnRulerMBspacing = 70;
  1507. static const int columnRulerMB2 = 130;// Gate2
  1508. static const int columnRulerMB1 = columnRulerMB2 - columnRulerMBspacing;// Gate1
  1509. static const int columnRulerMB3 = columnRulerMB2 + columnRulerMBspacing;// Tie
  1510. static const int posLEDvsButton = + 25;
  1511. // Gate 1 light and button
  1512. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(columnRulerMB1 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::GATE1_LIGHT));
  1513. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerMB1 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::GATE1_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1514. // Gate 2 light and button
  1515. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(columnRulerMB2 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::GATE2_LIGHT));
  1516. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerMB2 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::GATE2_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1517. // Tie light and button
  1518. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(columnRulerMB3 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::TIE_LIGHT));
  1519. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerMB3 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::TIE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1520. // ****** Bottom two rows ******
  1521. static const int inputJackSpacingX = 54;
  1522. static const int outputJackSpacingX = 45;
  1523. static const int rowRulerB0 = 323;
  1524. static const int rowRulerB1 = 269;
  1525. static const int columnRulerB0 = 22;
  1526. static const int columnRulerB1 = columnRulerB0 + inputJackSpacingX;
  1527. static const int columnRulerB2 = columnRulerB1 + inputJackSpacingX;
  1528. static const int columnRulerB3 = columnRulerB2 + inputJackSpacingX;
  1529. static const int columnRulerB4 = columnRulerB3 + inputJackSpacingX;
  1530. static const int columnRulerB8 = columnRulerMK2 + 1;
  1531. static const int columnRulerB7 = columnRulerB8 - outputJackSpacingX;
  1532. static const int columnRulerB6 = columnRulerB7 - outputJackSpacingX;
  1533. static const int columnRulerB5 = columnRulerB6 - outputJackSpacingX - 4;// clock and reset
  1534. // Gate 1 probability light and button
  1535. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(columnRulerB0 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq32::GATE1_PROB_LIGHT));
  1536. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerB0 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq32::GATE1_PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1537. // Gate 1 probability knob
  1538. addParam(createDynamicParam<IMSmallKnob>(Vec(columnRulerB1 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq32::GATE1_KNOB_PARAM, 0.0f, 1.0f, 1.0f, &module->panelTheme));
  1539. // Slide light and button
  1540. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(columnRulerB2 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq32::SLIDE_LIGHT));
  1541. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerB2 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq32::SLIDE_BTN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1542. // Slide knob
  1543. addParam(createDynamicParam<IMSmallKnob>(Vec(columnRulerB3 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq32::SLIDE_KNOB_PARAM, 0.0f, 2.0f, 0.2f, &module->panelTheme));
  1544. // CV in
  1545. addInput(createDynamicPort<IMPort>(Vec(columnRulerB4, rowRulerB1), Port::INPUT, module, PhraseSeq32::CV_INPUT, &module->panelTheme));
  1546. // Clock input
  1547. addInput(createDynamicPort<IMPort>(Vec(columnRulerB5, rowRulerB1), Port::INPUT, module, PhraseSeq32::CLOCK_INPUT, &module->panelTheme));
  1548. // Channel A outputs
  1549. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB6, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::CVA_OUTPUT, &module->panelTheme));
  1550. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB7, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::GATE1A_OUTPUT, &module->panelTheme));
  1551. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB8, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::GATE2A_OUTPUT, &module->panelTheme));
  1552. // CV control Inputs
  1553. addInput(createDynamicPort<IMPort>(Vec(columnRulerB0, rowRulerB0), Port::INPUT, module, PhraseSeq32::LEFTCV_INPUT, &module->panelTheme));
  1554. addInput(createDynamicPort<IMPort>(Vec(columnRulerB1, rowRulerB0), Port::INPUT, module, PhraseSeq32::RIGHTCV_INPUT, &module->panelTheme));
  1555. addInput(createDynamicPort<IMPort>(Vec(columnRulerB2, rowRulerB0), Port::INPUT, module, PhraseSeq32::SEQCV_INPUT, &module->panelTheme));
  1556. addInput(createDynamicPort<IMPort>(Vec(columnRulerB3, rowRulerB0), Port::INPUT, module, PhraseSeq32::RUNCV_INPUT, &module->panelTheme));
  1557. addInput(createDynamicPort<IMPort>(Vec(columnRulerB4, rowRulerB0), Port::INPUT, module, PhraseSeq32::WRITE_INPUT, &module->panelTheme));
  1558. // Reset input
  1559. addInput(createDynamicPort<IMPort>(Vec(columnRulerB5, rowRulerB0), Port::INPUT, module, PhraseSeq32::RESET_INPUT, &module->panelTheme));
  1560. // Channel B outputs
  1561. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB6, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::CVB_OUTPUT, &module->panelTheme));
  1562. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB7, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::GATE1B_OUTPUT, &module->panelTheme));
  1563. addOutput(createDynamicPort<IMPort>(Vec(columnRulerB8, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::GATE2B_OUTPUT, &module->panelTheme));
  1564. // Expansion module
  1565. static const int rowRulerExpTop = 65;
  1566. static const int rowSpacingExp = 60;
  1567. static const int colRulerExp = 497;
  1568. addInput(expPorts[0] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, PhraseSeq32::GATE1CV_INPUT, &module->panelTheme));
  1569. addInput(expPorts[1] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, PhraseSeq32::GATE2CV_INPUT, &module->panelTheme));
  1570. addInput(expPorts[2] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, PhraseSeq32::TIEDCV_INPUT, &module->panelTheme));
  1571. addInput(expPorts[3] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, PhraseSeq32::SLIDECV_INPUT, &module->panelTheme));
  1572. addInput(expPorts[4] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, PhraseSeq32::MODECV_INPUT, &module->panelTheme));
  1573. }
  1574. };
  1575. } // namespace rack_plugin_ImpromptuModular
  1576. using namespace rack_plugin_ImpromptuModular;
  1577. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, PhraseSeq32) {
  1578. Model *modelPhraseSeq32 = Model::create<PhraseSeq32, PhraseSeq32Widget>("Impromptu Modular", "Phrase-Seq-32", "SEQ - Phrase-Seq-32", SEQUENCER_TAG);
  1579. return modelPhraseSeq32;
  1580. }
  1581. /*CHANGE LOG
  1582. 0.6.11:
  1583. step optimization of lights refresh
  1584. 0.6.10:
  1585. add advanced gate mode
  1586. unlock gates when tied (turn off when press tied, but allow to be turned back on)
  1587. 0.6.9:
  1588. add FW2, FW3 and FW4 run modes for sequences (but not for song)
  1589. right click on notes now does same as left click but with autostep
  1590. 0.6.8:
  1591. allow rollover when selecting sequences in a song phrase (easier access to higher numbered seqs)
  1592. 0.6.7:
  1593. allow full edit capabilities in Seq and song mode
  1594. no reset on run by default, with switch added in context menu
  1595. reset does not revert seq or song number to 1
  1596. gate 2 is off by default
  1597. fix emitted monitoring gates to depend on gate states instead of always triggering
  1598. 0.6.6:
  1599. config and knob bug fixes when loading patch
  1600. 0.6.5:
  1601. paste 4, 8 doesn't loop over to overwrite first steps
  1602. small adjustements to gates and CVs used in monitoring write operations
  1603. add GATE1, GATE2, TIED, SLIDE CV inputs
  1604. add MODE CV input (affects only selected sequence and in Seq mode)
  1605. add expansion panel option
  1606. swap MODE/LEN so that length happens first (update manual)
  1607. 0.6.4:
  1608. initial release of PS32
  1609. */