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.

1565 lines
54KB

  1. //***********************************************************************************************
  2. //Gate 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 concept by Nigel Sixsmith and Marc Boulé
  10. //
  11. //Acknowledgements: please see README.md
  12. //***********************************************************************************************
  13. #include "GateSeq64Util.hpp"
  14. namespace rack_plugin_ImpromptuModular {
  15. struct GateSeq64 : Module {
  16. enum ParamIds {
  17. ENUMS(STEP_PARAMS, 64),
  18. MODES_PARAM,
  19. RUN_PARAM,
  20. CONFIG_PARAM,
  21. COPY_PARAM,
  22. PASTE_PARAM,
  23. RESET_PARAM,
  24. PROB_PARAM,
  25. EDIT_PARAM,
  26. SEQUENCE_PARAM,
  27. CPMODE_PARAM,
  28. // -- 0.6.9 ^^
  29. GMODELEFT_PARAM,// no longer used
  30. GMODERIGHT_PARAM,// no longer used
  31. // -- 0.6.11 ^^
  32. ENUMS(GMODE_PARAMS, 8),
  33. NUM_PARAMS
  34. };
  35. enum InputIds {
  36. CLOCK_INPUT,
  37. RESET_INPUT,
  38. RUNCV_INPUT,
  39. SEQCV_INPUT,
  40. // -- 0.6.7 ^^
  41. WRITE_INPUT,
  42. GATE_INPUT,
  43. PROB_INPUT,
  44. WRITE1_INPUT,
  45. WRITE0_INPUT,
  46. // -- 0.6.10 ^^
  47. STEPL_INPUT,
  48. NUM_INPUTS
  49. };
  50. enum OutputIds {
  51. ENUMS(GATE_OUTPUTS, 4),
  52. NUM_OUTPUTS
  53. };
  54. enum LightIds {
  55. ENUMS(STEP_LIGHTS, 64 * 3),// room for GreenRedWhite
  56. P_LIGHT,
  57. RUN_LIGHT,
  58. RESET_LIGHT,
  59. ENUMS(GMODE_LIGHTS, 8 * 2),// room for GreenRed
  60. NUM_LIGHTS
  61. };
  62. // Constants
  63. enum DisplayStateIds {DISP_GATE, DISP_LENGTH, DISP_MODES};
  64. static const int MAX_SEQS = 32;
  65. // 1/4 DUO D2 TR1 TR2 TR3 TR23 TRI
  66. const uint32_t advGateHitMaskGS[8] = {0x00003F, 0x03F03F, 0x03F000, 0x00000F, 0x000F00, 0x0F0000, 0x0F0F00, 0x0F0F0F};
  67. static const int blinkNumInit = 15;// init number of blink cycles for cursor
  68. static constexpr float CONFIG_PARAM_INIT_VALUE = 0.0f;// so that module constructor is coherent with widget initialization, since module created before widget
  69. // Need to save
  70. int panelTheme = 0;
  71. int expansion = 0;
  72. bool autoseq;
  73. int seqCVmethod;// 0 is 0-10V, 1 is C4-D5#, 2 is TrigIncr
  74. int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 6, 12, 24 PPS (Pulses per step)
  75. bool running;
  76. SeqAttributesGS sequences[MAX_SEQS];
  77. int runModeSong;
  78. int sequence;
  79. int phrase[64];// This is the song (series of phases; a phrase is a patten number)
  80. int phrases;// 1 to 64
  81. StepAttributesGS attributes[MAX_SEQS][64];
  82. bool resetOnRun;
  83. // No need to save
  84. int displayState;
  85. int stepIndexEdit;
  86. int stepIndexRun[4];
  87. int phraseIndexEdit;
  88. int phraseIndexRun;
  89. unsigned long stepIndexRunHistory;
  90. unsigned long phraseIndexRunHistory;
  91. StepAttributesGS attribCPbuffer[64];
  92. SeqAttributesGS seqAttribCPbuffer;
  93. bool seqCopied;
  94. int phraseCPbuffer[64];
  95. int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste)
  96. int startCP;
  97. long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste
  98. long clockIgnoreOnReset;
  99. long displayProbInfo;// downward step counter for displayProb feedback
  100. int gateCode[4];
  101. long revertDisplay;
  102. long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn
  103. int ppqnCount;
  104. long blinkCount;// positive upward counter, reset to 0 when max reached
  105. int blinkNum;// number of blink cycles to do, downward counter
  106. int stepConfig;
  107. long editingPhraseSongRunning;// downward step counter
  108. int stepConfigSync = 0;// 0 means no sync requested, 1 means soft sync (no reset lengths), 2 means hard (reset lengths)
  109. unsigned int lightRefreshCounter = 0;
  110. float resetLight = 0.0f;
  111. int sequenceKnob = 0;
  112. Trigger modesTrigger;
  113. Trigger stepTriggers[64];
  114. Trigger copyTrigger;
  115. Trigger pasteTrigger;
  116. Trigger runningTrigger;
  117. Trigger clockTrigger;
  118. Trigger resetTrigger;
  119. Trigger writeTrigger;
  120. Trigger write0Trigger;
  121. Trigger write1Trigger;
  122. Trigger stepLTrigger;
  123. Trigger gModeTriggers[8];
  124. Trigger probTrigger;
  125. Trigger seqCVTrigger;
  126. BooleanTrigger editingSequenceTrigger;
  127. HoldDetect modeHoldDetect;
  128. SeqAttributesGS seqAttribBuffer[MAX_SEQS];// buffer from Json for thread safety
  129. inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;}
  130. inline int getStepConfig(float paramValue) {// 1 = 4x16 = 0.0f, 2 = 2x32 = 1.0f, 4 = 1x64 = 2.0f
  131. if (paramValue < 0.5f) return 1;
  132. else if (paramValue < 1.5f) return 2;
  133. return 4;
  134. }
  135. inline int getAdvGateGS(int ppqnCount, int pulsesPerStep, int gateMode) {
  136. uint32_t shiftAmt = ppqnCount * (24 / pulsesPerStep);
  137. return (int)((advGateHitMaskGS[gateMode] >> shiftAmt) & (uint32_t)0x1);
  138. }
  139. inline int calcGateCode(StepAttributesGS attribute, int ppqnCount, int pulsesPerStep) {
  140. // -1 = gate off for whole step, 0 = gate off for current ppqn, 1 = gate on, 2 = clock high
  141. if (ppqnCount == 0 && attribute.getGateP() && !(randomUniform() < ((float)(attribute.getGatePVal())/100.0f)))// randomUniform is [0.0, 1.0), see include/util/common.hpp
  142. return -1;
  143. if (!attribute.getGate())
  144. return 0;
  145. if (pulsesPerStep == 1)
  146. return 2;// clock high
  147. return getAdvGateGS(ppqnCount, pulsesPerStep, attribute.getGateMode());
  148. }
  149. inline void fillStepIndexRunVector(int runMode, int len) {
  150. if (runMode != MODE_RN2) {
  151. stepIndexRun[1] = stepIndexRun[0];
  152. stepIndexRun[2] = stepIndexRun[0];
  153. stepIndexRun[3] = stepIndexRun[0];
  154. }
  155. else {
  156. stepIndexRun[1] = randomu32() % len;
  157. stepIndexRun[2] = randomu32() % len;
  158. stepIndexRun[3] = randomu32() % len;
  159. }
  160. }
  161. inline bool ppsRequirementMet(int gateButtonIndex) {
  162. return !( (pulsesPerStep < 2) || (pulsesPerStep == 4 && gateButtonIndex > 2) || (pulsesPerStep == 6 && gateButtonIndex <= 2) );
  163. }
  164. GateSeq64() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  165. for (int i = 0; i < MAX_SEQS; i++)
  166. seqAttribBuffer[i].init(16, MODE_FWD);
  167. onReset();
  168. }
  169. void onReset() override {
  170. stepConfig = getStepConfig(CONFIG_PARAM_INIT_VALUE);
  171. autoseq = false;
  172. seqCVmethod = 0;
  173. pulsesPerStep = 1;
  174. running = true;
  175. runModeSong = MODE_FWD;
  176. stepIndexEdit = 0;
  177. phraseIndexEdit = 0;
  178. sequence = 0;
  179. phrases = 4;
  180. for (int i = 0; i < MAX_SEQS; i++) {
  181. for (int s = 0; s < 64; s++) {
  182. attributes[i][s].init();
  183. }
  184. sequences[i].init(16 * stepConfig, MODE_FWD);
  185. }
  186. for (int i = 0; i < 64; i++) {
  187. phrase[i] = 0;
  188. attribCPbuffer[i].init();
  189. phraseCPbuffer[i] = 0;
  190. }
  191. initRun();
  192. seqAttribCPbuffer.init(16, MODE_FWD);
  193. seqCopied = true;
  194. countCP = 64;
  195. startCP = 0;
  196. displayState = DISP_GATE;
  197. displayProbInfo = 0l;
  198. infoCopyPaste = 0l;
  199. revertDisplay = 0l;
  200. resetOnRun = false;
  201. editingPpqn = 0l;
  202. blinkCount = 0l;
  203. blinkNum = blinkNumInit;
  204. editingPhraseSongRunning = 0l;
  205. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  206. }
  207. void onRandomize() override {
  208. // stepConfig = getStepConfig(params[CONFIG_PARAM].value);
  209. // runModeSong = randomu32() % 5;
  210. // stepIndexEdit = 0;
  211. // phraseIndexEdit = 0;
  212. // sequence = randomu32() % MAX_SEQS;
  213. // phrases = 1 + (randomu32() % 64);
  214. // for (int i = 0; i < MAX_SEQS; i++) {
  215. // for (int s = 0; s < 64; s++) {
  216. // attributes[i][s].randomize();
  217. // }
  218. // sequences[i].randomize(16 * stepConfig, NUM_MODES);
  219. // }
  220. // for (int i = 0; i < 64; i++)
  221. // phrase[i] = randomu32() % MAX_SEQS;
  222. // initRun();
  223. if (isEditingSequence()) {
  224. for (int s = 0; s < 64; s++) {
  225. attributes[sequence][s].randomize();
  226. }
  227. sequences[sequence].randomize(16 * stepConfig, NUM_MODES);// ok to use stepConfig since CONFIG_PARAM is not randomizable
  228. }
  229. }
  230. void initRun() {// run button activated or run edge in run input jack
  231. phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0);
  232. phraseIndexRunHistory = 0;
  233. int seq = (isEditingSequence() ? sequence : phrase[phraseIndexRun]);
  234. stepIndexRun[0] = (sequences[seq].getRunMode() == MODE_REV ? sequences[seq].getLength() - 1 : 0);
  235. fillStepIndexRunVector(sequences[seq].getRunMode(), sequences[seq].getLength());
  236. stepIndexRunHistory = 0;
  237. ppqnCount = 0;
  238. for (int i = 0; i < 4; i += stepConfig)
  239. gateCode[i] = calcGateCode(attributes[seq][(i * 16) + stepIndexRun[i]], 0, pulsesPerStep);
  240. }
  241. json_t *toJson() override {
  242. json_t *rootJ = json_object();
  243. // panelTheme
  244. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  245. // expansion
  246. json_object_set_new(rootJ, "expansion", json_integer(expansion));
  247. // autoseq
  248. json_object_set_new(rootJ, "autoseq", json_boolean(autoseq));
  249. // seqCVmethod
  250. json_object_set_new(rootJ, "seqCVmethod", json_integer(seqCVmethod));
  251. // pulsesPerStep
  252. json_object_set_new(rootJ, "pulsesPerStep", json_integer(pulsesPerStep));
  253. // running
  254. json_object_set_new(rootJ, "running", json_boolean(running));
  255. // runModeSong
  256. json_object_set_new(rootJ, "runModeSong3", json_integer(runModeSong));
  257. // sequence
  258. json_object_set_new(rootJ, "sequence", json_integer(sequence));
  259. // phrase
  260. json_t *phraseJ = json_array();
  261. for (int i = 0; i < 64; i++)
  262. json_array_insert_new(phraseJ, i, json_integer(phrase[i]));
  263. json_object_set_new(rootJ, "phrase2", phraseJ);// "2" appended so no break patches
  264. // phrases
  265. json_object_set_new(rootJ, "phrases", json_integer(phrases));
  266. // attributes
  267. json_t *attributesJ = json_array();
  268. for (int i = 0; i < MAX_SEQS; i++)
  269. for (int s = 0; s < 64; s++) {
  270. json_array_insert_new(attributesJ, s + (i * 64), json_integer(attributes[i][s].getAttribute()));
  271. }
  272. json_object_set_new(rootJ, "attributes2", attributesJ);// "2" appended so no break patches
  273. // resetOnRun
  274. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  275. // stepIndexEdit
  276. json_object_set_new(rootJ, "stepIndexEdit", json_integer(stepIndexEdit));
  277. // phraseIndexEdit
  278. json_object_set_new(rootJ, "phraseIndexEdit", json_integer(phraseIndexEdit));
  279. // sequences
  280. json_t *sequencesJ = json_array();
  281. for (int i = 0; i < MAX_SEQS; i++)
  282. json_array_insert_new(sequencesJ, i, json_integer(sequences[i].getSeqAttrib()));
  283. json_object_set_new(rootJ, "sequences", sequencesJ);
  284. return rootJ;
  285. }
  286. void fromJson(json_t *rootJ) override {
  287. // panelTheme
  288. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  289. if (panelThemeJ)
  290. panelTheme = json_integer_value(panelThemeJ);
  291. // expansion
  292. json_t *expansionJ = json_object_get(rootJ, "expansion");
  293. if (expansionJ)
  294. expansion = json_integer_value(expansionJ);
  295. // autoseq
  296. json_t *autoseqJ = json_object_get(rootJ, "autoseq");
  297. if (autoseqJ)
  298. autoseq = json_is_true(autoseqJ);
  299. // seqCVmethod
  300. json_t *seqCVmethodJ = json_object_get(rootJ, "seqCVmethod");
  301. if (seqCVmethodJ)
  302. seqCVmethod = json_integer_value(seqCVmethodJ);
  303. // pulsesPerStep
  304. json_t *pulsesPerStepJ = json_object_get(rootJ, "pulsesPerStep");
  305. if (pulsesPerStepJ)
  306. pulsesPerStep = json_integer_value(pulsesPerStepJ);
  307. // running
  308. json_t *runningJ = json_object_get(rootJ, "running");
  309. if (runningJ)
  310. running = json_is_true(runningJ);
  311. // sequences
  312. json_t *sequencesJ = json_object_get(rootJ, "sequences");
  313. if (sequencesJ) {
  314. for (int i = 0; i < MAX_SEQS; i++)
  315. {
  316. json_t *sequencesArrayJ = json_array_get(sequencesJ, i);
  317. if (sequencesArrayJ)
  318. seqAttribBuffer[i].setSeqAttrib(json_integer_value(sequencesArrayJ));
  319. }
  320. }
  321. else {// legacy
  322. int lengths[16];// 1 to 16
  323. int runModeSeq[16];
  324. // runModeSeq
  325. json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq3");
  326. if (runModeSeqJ) {
  327. for (int i = 0; i < 16; i++)
  328. {
  329. json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i);
  330. if (runModeSeqArrayJ)
  331. runModeSeq[i] = json_integer_value(runModeSeqArrayJ);
  332. }
  333. }
  334. else {// legacy
  335. runModeSeqJ = json_object_get(rootJ, "runModeSeq2");
  336. if (runModeSeqJ) {
  337. for (int i = 0; i < 16; i++)
  338. {
  339. json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i);
  340. if (runModeSeqArrayJ) {
  341. runModeSeq[i] = json_integer_value(runModeSeqArrayJ);
  342. if (runModeSeq[i] >= MODE_PEN)// this mode was not present in version runModeSeq2
  343. runModeSeq[i]++;
  344. }
  345. }
  346. }
  347. }
  348. // lengths
  349. json_t *lengthsJ = json_object_get(rootJ, "lengths");
  350. if (lengthsJ) {
  351. for (int i = 0; i < 16; i++)
  352. {
  353. json_t *lengthsArrayJ = json_array_get(lengthsJ, i);
  354. if (lengthsArrayJ)
  355. lengths[i] = json_integer_value(lengthsArrayJ);
  356. }
  357. }
  358. // now write into new object
  359. for (int i = 0; i < 16; i++)
  360. seqAttribBuffer[i].init(lengths[i], runModeSeq[i]);
  361. for (int i = 16; i < MAX_SEQS; i++)
  362. seqAttribBuffer[i].init(16, MODE_FWD);
  363. }
  364. // runModeSong
  365. json_t *runModeSongJ = json_object_get(rootJ, "runModeSong3");
  366. if (runModeSongJ)
  367. runModeSong = json_integer_value(runModeSongJ);
  368. else {// legacy
  369. runModeSongJ = json_object_get(rootJ, "runModeSong");
  370. if (runModeSongJ) {
  371. runModeSong = json_integer_value(runModeSongJ);
  372. if (runModeSong >= MODE_PEN)// this mode was not present in original version
  373. runModeSong++;
  374. }
  375. }
  376. // sequence
  377. json_t *sequenceJ = json_object_get(rootJ, "sequence");
  378. if (sequenceJ)
  379. sequence = json_integer_value(sequenceJ);
  380. // phrase
  381. json_t *phraseJ = json_object_get(rootJ, "phrase2");// "2" appended so no break patches
  382. if (phraseJ) {
  383. for (int i = 0; i < 64; i++)
  384. {
  385. json_t *phraseArrayJ = json_array_get(phraseJ, i);
  386. if (phraseArrayJ)
  387. phrase[i] = json_integer_value(phraseArrayJ);
  388. }
  389. }
  390. else {// legacy
  391. phraseJ = json_object_get(rootJ, "phrase");
  392. if (phraseJ) {
  393. for (int i = 0; i < 16; i++)
  394. {
  395. json_t *phraseArrayJ = json_array_get(phraseJ, i);
  396. if (phraseArrayJ)
  397. phrase[i] = json_integer_value(phraseArrayJ);
  398. }
  399. for (int i = 16; i < 64; i++)
  400. phrase[i] = 0;
  401. }
  402. }
  403. // phrases
  404. json_t *phrasesJ = json_object_get(rootJ, "phrases");
  405. if (phrasesJ)
  406. phrases = json_integer_value(phrasesJ);
  407. // attributes
  408. json_t *attributesJ = json_object_get(rootJ, "attributes2");
  409. if (attributesJ) {
  410. for (int i = 0; i < MAX_SEQS; i++)
  411. for (int s = 0; s < 64; s++) {
  412. json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 64));
  413. if (attributesArrayJ)
  414. attributes[i][s].setAttribute((unsigned short)json_integer_value(attributesArrayJ));
  415. }
  416. }
  417. else {
  418. attributesJ = json_object_get(rootJ, "attributes");
  419. if (attributesJ) {
  420. for (int i = 0; i < 16; i++) {
  421. for (int s = 0; s < 64; s++) {
  422. json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 64));
  423. if (attributesArrayJ)
  424. attributes[i][s].setAttribute((unsigned short)json_integer_value(attributesArrayJ));
  425. }
  426. }
  427. for (int i = 16; i < MAX_SEQS; i++) {
  428. for (int s = 0; s < 64; s++)
  429. attributes[i][s].init();
  430. }
  431. }
  432. }
  433. // resetOnRun
  434. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  435. if (resetOnRunJ)
  436. resetOnRun = json_is_true(resetOnRunJ);
  437. // stepIndexEdit
  438. json_t *stepIndexEditJ = json_object_get(rootJ, "stepIndexEdit");
  439. if (stepIndexEditJ)
  440. stepIndexEdit = json_integer_value(stepIndexEditJ);
  441. // phraseIndexEdit
  442. json_t *phraseIndexEditJ = json_object_get(rootJ, "phraseIndexEdit");
  443. if (phraseIndexEditJ)
  444. phraseIndexEdit = json_integer_value(phraseIndexEditJ);
  445. stepConfigSync = 1;// signal a sync from fromJson so that step will get lengths from seqAttribBuffer
  446. }
  447. void step() override {
  448. static const float displayProbInfoTime = 3.0f;// seconds
  449. static const float revertDisplayTime = 0.7f;// seconds
  450. static const float holdDetectTime = 2.0f;// seconds
  451. static const float editingPhraseSongRunningTime = 4.0f;// seconds
  452. static const float editingPpqnTime = 3.5f;// seconds
  453. float sampleRate = engineGetSampleRate();
  454. //********** Buttons, knobs, switches and inputs **********
  455. // Edit mode
  456. bool editingSequence = isEditingSequence();// true = editing sequence, false = editing song
  457. // Run state button
  458. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) {// no input refresh here, don't want to introduce startup skew
  459. running = !running;
  460. if (running) {
  461. if (resetOnRun)
  462. initRun();
  463. if (resetOnRun || clockIgnoreOnRun)
  464. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * sampleRate);
  465. }
  466. else
  467. blinkNum = blinkNumInit;
  468. displayState = DISP_GATE;
  469. }
  470. if ((lightRefreshCounter & userInputsStepSkipMask) == 0) {
  471. // Edit mode blink when change
  472. if (editingSequenceTrigger.process(editingSequence))
  473. blinkNum = blinkNumInit;
  474. // Config switch
  475. if (stepConfigSync != 0) {
  476. stepConfig = getStepConfig(params[CONFIG_PARAM].value);
  477. if (stepConfigSync == 1) {// sync from fromJson, so read lengths from seqAttribBuffer
  478. for (int i = 0; i < MAX_SEQS; i++)
  479. sequences[i].setSeqAttrib(seqAttribBuffer[i].getSeqAttrib());
  480. }
  481. else if (stepConfigSync == 2) {// sync from a real mouse drag event on the switch itself, so init lengths
  482. for (int i = 0; i < MAX_SEQS; i++)
  483. sequences[i].setLength(16 * stepConfig);
  484. }
  485. initRun();
  486. stepConfigSync = 0;
  487. }
  488. // Seq CV input
  489. if (inputs[SEQCV_INPUT].active) {
  490. if (seqCVmethod == 0) {// 0-10 V
  491. int newSeq = (int)( inputs[SEQCV_INPUT].value * (((float)MAX_SEQS) - 1.0f) / 10.0f + 0.5f );
  492. sequence = clamp(newSeq, 0, MAX_SEQS - 1);
  493. }
  494. else if (seqCVmethod == 1) {// C4-G6
  495. int newSeq = (int)( (inputs[SEQCV_INPUT].value) * 12.0f + 0.5f );
  496. sequence = clamp(newSeq, 0, MAX_SEQS - 1);
  497. }
  498. else {// TrigIncr
  499. if (seqCVTrigger.process(inputs[SEQCV_INPUT].value))
  500. sequence = clamp(sequence + 1, 0, MAX_SEQS - 1);
  501. }
  502. }
  503. // Copy button
  504. if (copyTrigger.process(params[COPY_PARAM].value)) {
  505. startCP = editingSequence ? stepIndexEdit : phraseIndexEdit;
  506. countCP = 64;
  507. if (params[CPMODE_PARAM].value > 1.5f)// ALL
  508. startCP = 0;
  509. else if (params[CPMODE_PARAM].value < 0.5f)// 4
  510. countCP = min(4, 64 - startCP);
  511. else// 8
  512. countCP = min(8, 64 - startCP);
  513. if (editingSequence) {
  514. for (int i = 0, s = startCP; i < countCP; i++, s++)
  515. attribCPbuffer[i] = attributes[sequence][s];
  516. seqAttribCPbuffer.setSeqAttrib(sequences[sequence].getSeqAttrib());
  517. seqCopied = true;
  518. }
  519. else {
  520. for (int i = 0, p = startCP; i < countCP; i++, p++)
  521. phraseCPbuffer[i] = phrase[p];
  522. seqCopied = false;// so that a cross paste can be detected
  523. }
  524. infoCopyPaste = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  525. displayState = DISP_GATE;
  526. blinkNum = blinkNumInit;
  527. }
  528. // Paste button
  529. if (pasteTrigger.process(params[PASTE_PARAM].value)) {
  530. infoCopyPaste = (long) (-1 * revertDisplayTime * sampleRate / displayRefreshStepSkips);
  531. startCP = 0;
  532. if (countCP <= 8) {
  533. startCP = editingSequence ? stepIndexEdit : phraseIndexEdit;
  534. countCP = min(countCP, 64 - startCP);
  535. }
  536. // else nothing to do for ALL
  537. if (editingSequence) {
  538. if (seqCopied) {// non-crossed paste (seq vs song)
  539. for (int i = 0, s = startCP; i < countCP; i++, s++)
  540. attributes[sequence][s] = attribCPbuffer[i];
  541. if (params[CPMODE_PARAM].value > 1.5f) {// all
  542. sequences[sequence].setSeqAttrib(seqAttribCPbuffer.getSeqAttrib());
  543. if (sequences[sequence].getLength() > 16 * stepConfig)
  544. sequences[sequence].setLength(16 * stepConfig);
  545. }
  546. }
  547. else {// crossed paste to seq (seq vs song)
  548. if (params[CPMODE_PARAM].value > 1.5f) { // ALL (init steps)
  549. for (int s = 0; s < 64; s++)
  550. attributes[sequence][s].init();
  551. }
  552. else if (params[CPMODE_PARAM].value < 0.5f) {// 4 (randomize gates)
  553. for (int s = 0; s < 64; s++)
  554. if ( (randomu32() & 0x1) != 0)
  555. attributes[sequence][s].toggleGate();
  556. }
  557. else {// 8 (randomize probs)
  558. for (int s = 0; s < 64; s++) {
  559. attributes[sequence][s].setGateP((randomu32() & 0x1) != 0);
  560. attributes[sequence][s].setGatePVal(randomu32() % 101);
  561. }
  562. }
  563. startCP = 0;
  564. countCP = 64;
  565. infoCopyPaste *= 2l;
  566. }
  567. }
  568. else {// song
  569. if (!seqCopied) {// non-crossed paste (seq vs song)
  570. for (int i = 0, p = startCP; i < countCP; i++, p++)
  571. phrase[p] = phraseCPbuffer[i] & 0xF;
  572. }
  573. else {// crossed paste to song (seq vs song)
  574. if (params[CPMODE_PARAM].value > 1.5f) { // ALL (init phrases)
  575. for (int p = 0; p < 64; p++)
  576. phrase[p] = 0;
  577. }
  578. else if (params[CPMODE_PARAM].value < 0.5f) {// 4 (phrases increase from 1 to 64)
  579. for (int p = 0; p < 64; p++)
  580. phrase[p] = p;
  581. }
  582. else {// 8 (randomize phrases)
  583. for (int p = 0; p < 64; p++)
  584. phrase[p] = randomu32() % 64;
  585. }
  586. startCP = 0;
  587. countCP = 64;
  588. infoCopyPaste *= 2l;
  589. }
  590. }
  591. displayState = DISP_GATE;
  592. blinkNum = blinkNumInit;
  593. }
  594. // Write CV inputs
  595. bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value);
  596. bool write0Trig = write0Trigger.process(inputs[WRITE0_INPUT].value);
  597. bool write1Trig = write1Trigger.process(inputs[WRITE1_INPUT].value);
  598. if (writeTrig || write0Trig || write1Trig) {
  599. if (editingSequence) {
  600. blinkNum = blinkNumInit;
  601. if (writeTrig) {// higher priority than write0 and write1
  602. if (inputs[PROB_INPUT].active) {
  603. attributes[sequence][stepIndexEdit].setGatePVal(clamp( (int)round(inputs[PROB_INPUT].value * 10.0f), 0, 100) );
  604. attributes[sequence][stepIndexEdit].setGateP(true);
  605. }
  606. else{
  607. attributes[sequence][stepIndexEdit].setGateP(false);
  608. }
  609. if (inputs[GATE_INPUT].active)
  610. attributes[sequence][stepIndexEdit].setGate(inputs[GATE_INPUT].value >= 1.0f);
  611. }
  612. else {// write1 or write0
  613. attributes[sequence][stepIndexEdit].setGate(write1Trig);
  614. }
  615. // Autostep (after grab all active inputs)
  616. stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 64);
  617. if (stepIndexEdit == 0 && autoseq && !inputs[SEQCV_INPUT].active)
  618. sequence = moveIndex(sequence, sequence + 1, MAX_SEQS);
  619. }
  620. }
  621. // Step left CV input
  622. if (stepLTrigger.process(inputs[STEPL_INPUT].value)) {
  623. if (editingSequence) {
  624. blinkNum = blinkNumInit;
  625. stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit - 1, 64);
  626. }
  627. }
  628. // Step LED button presses
  629. int stepPressed = -1;
  630. for (int i = 0; i < 64; i++) {
  631. if (stepTriggers[i].process(params[STEP_PARAMS + i].value))
  632. stepPressed = i;
  633. }
  634. if (stepPressed != -1) {
  635. if (editingSequence) {
  636. if (displayState == DISP_LENGTH) {
  637. sequences[sequence].setLength(stepPressed % (16 * stepConfig) + 1);
  638. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  639. }
  640. else if (displayState == DISP_MODES) {
  641. }
  642. else {
  643. if (params[STEP_PARAMS + stepPressed].value > 1.5f) {// right button click
  644. attributes[sequence][stepPressed].setGate(false);
  645. displayProbInfo = 0l;
  646. }
  647. else if (!attributes[sequence][stepPressed].getGate()) {// clicked inactive, so turn gate on
  648. attributes[sequence][stepPressed].setGate(true);
  649. if (attributes[sequence][stepPressed].getGateP())
  650. displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips);
  651. else
  652. displayProbInfo = 0l;
  653. }
  654. else {// clicked active
  655. if (stepIndexEdit == stepPressed && blinkNum != 0) {// only if coming from current step, turn off
  656. attributes[sequence][stepPressed].setGate(false);
  657. displayProbInfo = 0l;
  658. }
  659. else {
  660. if (attributes[sequence][stepPressed].getGateP())
  661. displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips);
  662. else
  663. displayProbInfo = 0l;
  664. }
  665. }
  666. stepIndexEdit = stepPressed;
  667. }
  668. blinkNum = blinkNumInit;
  669. }
  670. else {// editing song
  671. if (displayState == DISP_LENGTH) {
  672. phrases = stepPressed + 1;
  673. if (phrases > 64) phrases = 64;
  674. if (phrases < 1 ) phrases = 1;
  675. revertDisplay = (long) (revertDisplayTime * sampleRate / displayRefreshStepSkips);
  676. }
  677. else if (displayState == DISP_MODES) {
  678. }
  679. else {
  680. phraseIndexEdit = stepPressed;
  681. if (running)
  682. editingPhraseSongRunning = (long) (editingPhraseSongRunningTime * sampleRate / displayRefreshStepSkips);
  683. else
  684. phraseIndexRun = stepPressed;
  685. }
  686. }
  687. }
  688. // Mode/Length button
  689. if (modesTrigger.process(params[MODES_PARAM].value)) {
  690. blinkNum = blinkNumInit;
  691. if (editingPpqn != 0l)
  692. editingPpqn = 0l;
  693. if (displayState == DISP_GATE)
  694. displayState = DISP_LENGTH;
  695. else if (displayState == DISP_LENGTH)
  696. displayState = DISP_MODES;
  697. else
  698. displayState = DISP_GATE;
  699. modeHoldDetect.start((long) (holdDetectTime * sampleRate / displayRefreshStepSkips));
  700. }
  701. // Prob button
  702. if (probTrigger.process(params[PROB_PARAM].value)) {
  703. blinkNum = blinkNumInit;
  704. if (editingSequence && attributes[sequence][stepIndexEdit].getGate()) {
  705. if (attributes[sequence][stepIndexEdit].getGateP()) {
  706. displayProbInfo = 0l;
  707. attributes[sequence][stepIndexEdit].setGateP(false);
  708. }
  709. else {
  710. displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips);
  711. attributes[sequence][stepIndexEdit].setGateP(true);
  712. }
  713. }
  714. }
  715. // GateMode buttons
  716. for (int i = 0; i < 8; i++) {
  717. if (gModeTriggers[i].process(params[GMODE_PARAMS + i].value)) {
  718. blinkNum = blinkNumInit;
  719. if (editingSequence && attributes[sequence][stepIndexEdit].getGate()) {
  720. if (ppsRequirementMet(i)) {
  721. editingPpqn = 0l;
  722. attributes[sequence][stepIndexEdit].setGateMode(i);
  723. }
  724. else {
  725. editingPpqn = (long) (editingPpqnTime * sampleRate / displayRefreshStepSkips);
  726. }
  727. }
  728. }
  729. }
  730. // Sequence knob (Main knob)
  731. float seqParamValue = params[SEQUENCE_PARAM].value;
  732. int newSequenceKnob = (int)roundf(seqParamValue * 7.0f);
  733. if (seqParamValue == 0.0f)// true when constructor or fromJson() occured
  734. sequenceKnob = newSequenceKnob;
  735. int deltaKnob = newSequenceKnob - sequenceKnob;
  736. if (deltaKnob != 0) {
  737. if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example)
  738. if (displayProbInfo != 0l && editingSequence) {
  739. blinkNum = blinkNumInit;
  740. int pval = attributes[sequence][stepIndexEdit].getGatePVal();
  741. pval += deltaKnob * 2;
  742. if (pval > 100)
  743. pval = 100;
  744. if (pval < 0)
  745. pval = 0;
  746. attributes[sequence][stepIndexEdit].setGatePVal(pval);
  747. displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips);
  748. }
  749. else if (editingPpqn != 0) {
  750. pulsesPerStep = indexToPpsGS(ppsToIndexGS(pulsesPerStep) + deltaKnob);// indexToPps() does clamping
  751. editingPpqn = (long) (editingPpqnTime * sampleRate / displayRefreshStepSkips);
  752. }
  753. else if (displayState == DISP_MODES) {
  754. if (editingSequence) {
  755. sequences[sequence].setRunMode(clamp(sequences[sequence].getRunMode() + deltaKnob, 0, NUM_MODES - 1));
  756. }
  757. else {
  758. runModeSong = clamp(runModeSong + deltaKnob, 0, 6 - 1);
  759. }
  760. }
  761. else if (displayState == DISP_LENGTH) {
  762. if (editingSequence) {
  763. sequences[sequence].setLength(clamp(sequences[sequence].getLength() + deltaKnob, 1, (16 * stepConfig)));
  764. }
  765. else {
  766. phrases = clamp(phrases + deltaKnob, 1, 64);
  767. }
  768. }
  769. else {
  770. if (editingSequence) {
  771. blinkNum = blinkNumInit;
  772. if (!inputs[SEQCV_INPUT].active) {
  773. sequence = clamp(sequence + deltaKnob, 0, MAX_SEQS - 1);
  774. }
  775. }
  776. else {
  777. if (editingPhraseSongRunning > 0l || !running) {
  778. int newPhrase = phrase[phraseIndexEdit] + deltaKnob;
  779. if (newPhrase < 0)
  780. newPhrase += (1 - newPhrase / MAX_SEQS) * MAX_SEQS;// newPhrase now positive
  781. newPhrase = newPhrase % MAX_SEQS;
  782. phrase[phraseIndexEdit] = newPhrase;
  783. if (running)
  784. editingPhraseSongRunning = (long) (editingPhraseSongRunningTime * sampleRate / displayRefreshStepSkips);
  785. }
  786. }
  787. }
  788. }
  789. sequenceKnob = newSequenceKnob;
  790. }
  791. }// userInputs refresh
  792. //********** Clock and reset **********
  793. // Clock
  794. if (running && clockIgnoreOnReset == 0l) {
  795. if (clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  796. ppqnCount++;
  797. if (ppqnCount >= pulsesPerStep)
  798. ppqnCount = 0;
  799. int newSeq = sequence;// good value when editingSequence, overwrite if not editingSequence
  800. if (ppqnCount == 0) {
  801. if (editingSequence) {
  802. moveIndexRunMode(&stepIndexRun[0], sequences[sequence].getLength(), sequences[sequence].getRunMode(), &stepIndexRunHistory);
  803. }
  804. else {
  805. if (moveIndexRunMode(&stepIndexRun[0], sequences[phrase[phraseIndexRun]].getLength(), sequences[phrase[phraseIndexRun]].getRunMode(), &stepIndexRunHistory)) {
  806. moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory);
  807. stepIndexRun[0] = (sequences[phrase[phraseIndexRun]].getRunMode() == MODE_REV ? sequences[phrase[phraseIndexRun]].getLength() - 1 : 0);// must always refresh after phraseIndexRun has changed
  808. }
  809. newSeq = phrase[phraseIndexRun];
  810. }
  811. fillStepIndexRunVector(sequences[newSeq].getRunMode(), sequences[newSeq].getLength());
  812. }
  813. else {
  814. if (!editingSequence)
  815. newSeq = phrase[phraseIndexRun];
  816. }
  817. for (int i = 0; i < 4; i += stepConfig) {
  818. if (gateCode[i] != -1 || ppqnCount == 0)
  819. gateCode[i] = calcGateCode(attributes[newSeq][(i * 16) + stepIndexRun[i]], ppqnCount, pulsesPerStep);
  820. }
  821. }
  822. }
  823. // Reset
  824. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  825. initRun();// must be before SEQCV_INPUT below
  826. resetLight = 1.0f;
  827. displayState = DISP_GATE;
  828. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * sampleRate);
  829. clockTrigger.reset();
  830. if (inputs[SEQCV_INPUT].active && seqCVmethod == 2)
  831. sequence = 0;
  832. }
  833. //********** Outputs and lights **********
  834. // Gate outputs
  835. if (running) {
  836. bool retriggingOnReset = (clockIgnoreOnReset != 0l && retrigGatesOnReset);
  837. for (int i = 0; i < 4; i++)
  838. outputs[GATE_OUTPUTS + i].value = (calcGate(gateCode[i], clockTrigger) && !retriggingOnReset) ? 10.0f : 0.0f;
  839. }
  840. else {// not running (no gates, no need to hear anything)
  841. for (int i = 0; i < 4; i++)
  842. outputs[GATE_OUTPUTS + i].value = 0.0f;
  843. }
  844. lightRefreshCounter++;
  845. if (lightRefreshCounter >= displayRefreshStepSkips) {
  846. lightRefreshCounter = 0;
  847. // Step LED button lights
  848. if (infoCopyPaste != 0l) {
  849. for (int i = 0; i < 64; i++) {
  850. if (i >= startCP && i < (startCP + countCP))
  851. setGreenRed3(STEP_LIGHTS + i * 3, 0.5f, 0.0f);
  852. else
  853. setGreenRed3(STEP_LIGHTS + i * 3, 0.0f, 0.0f);
  854. }
  855. }
  856. else {
  857. int row = -1;
  858. int col = -1;
  859. for (int i = 0; i < 64; i++) {
  860. row = i >> (3 + stepConfig);//i / (16 * stepConfig);// optimized (not equivalent code, but in this case has same effect)
  861. if (stepConfig == 2 && row == 1)
  862. row++;
  863. col = (((stepConfig - 1) << 4) | 0xF) & i;//i % (16 * stepConfig);// optimized
  864. if (editingSequence) {
  865. if (displayState == DISP_LENGTH) {
  866. if (col < (sequences[sequence].getLength() - 1))
  867. setGreenRed3(STEP_LIGHTS + i * 3, 0.1f, 0.0f);
  868. else if (col == (sequences[sequence].getLength() - 1))
  869. setGreenRed3(STEP_LIGHTS + i * 3, 1.0f, 0.0f);
  870. else
  871. setGreenRed3(STEP_LIGHTS + i * 3, 0.0f, 0.0f);
  872. }
  873. else {
  874. float stepHereOffset = ((stepIndexRun[row] == col) && running) ? 0.5f : 1.0f;
  875. long blinkCountMarker = (long) (0.67f * sampleRate / displayRefreshStepSkips);
  876. if (attributes[sequence][i].getGate()) {
  877. bool blinkEnableOn = (displayState != DISP_MODES) && (blinkCount < blinkCountMarker);
  878. if (attributes[sequence][i].getGateP()) {
  879. if (i == stepIndexEdit)// more orange than yellow
  880. setGreenRed3(STEP_LIGHTS + i * 3, blinkEnableOn ? 1.0f : 0.0f, blinkEnableOn ? 1.0f : 0.0f);
  881. else// more yellow
  882. setGreenRed3(STEP_LIGHTS + i * 3, stepHereOffset, stepHereOffset);
  883. }
  884. else {
  885. if (i == stepIndexEdit)
  886. setGreenRed3(STEP_LIGHTS + i * 3, blinkEnableOn ? 1.0f : 0.0f, 0.0f);
  887. else
  888. setGreenRed3(STEP_LIGHTS + i * 3, stepHereOffset, 0.0f);
  889. }
  890. }
  891. else {
  892. if (i == stepIndexEdit && blinkCount > blinkCountMarker && displayState != DISP_MODES)
  893. setGreenRed3(STEP_LIGHTS + i * 3, 0.05f, 0.0f);
  894. else
  895. setGreenRed3(STEP_LIGHTS + i * 3, ((stepIndexRun[row] == col) && running) ? 0.1f : 0.0f, 0.0f);
  896. }
  897. }
  898. }
  899. else {// editing Song
  900. if (displayState == DISP_LENGTH) {
  901. col = i & 0xF;//i % 16;// optimized
  902. if (i < (phrases - 1))
  903. setGreenRed3(STEP_LIGHTS + i * 3, 0.1f, 0.0f);
  904. else if (i == (phrases - 1))
  905. setGreenRed3(STEP_LIGHTS + i * 3, 1.0f, 0.0f);
  906. else
  907. setGreenRed3(STEP_LIGHTS + i * 3, 0.0f, 0.0f);
  908. }
  909. else {
  910. float green = (i == (phraseIndexRun) && running) ? 1.0f : 0.0f;
  911. float red = (i == (phraseIndexEdit) && ((editingPhraseSongRunning > 0l) || !running)) ? 1.0f : 0.0f;
  912. green += ((running && (col == stepIndexRun[row]) && i != (phraseIndexEdit)) ? 0.1f : 0.0f);
  913. setGreenRed3(STEP_LIGHTS + i * 3, clamp(green, 0.0f, 1.0f), red);
  914. if (green == 0.0f && red == 0.0f && displayState != DISP_MODES){
  915. lights[STEP_LIGHTS + i * 3 + 2].value = (attributes[phrase[phraseIndexRun]][i].getGate() ? 0.2f : 0.0f);
  916. lights[STEP_LIGHTS + i * 3 + 2].value -= (attributes[phrase[phraseIndexRun]][i].getGateP() ? 0.18f : 0.0f);
  917. }
  918. }
  919. }
  920. }
  921. }
  922. // GateType lights
  923. if (pulsesPerStep != 1 && editingSequence && attributes[sequence][stepIndexEdit].getGate()) {
  924. if (editingPpqn != 0) {
  925. for (int i = 0; i < 8; i++) {
  926. if (ppsRequirementMet(i))
  927. setGreenRed(GMODE_LIGHTS + i * 2, 1.0f, 0.0f);
  928. else
  929. setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 0.0f);
  930. }
  931. }
  932. else {
  933. int gmode = attributes[sequence][stepIndexEdit].getGateMode();
  934. for (int i = 0; i < 8; i++) {
  935. if (i == gmode) {
  936. if ( (pulsesPerStep == 4 && i > 2) || (pulsesPerStep == 6 && i <= 2) ) // pps requirement not met
  937. setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 1.0f);
  938. else
  939. setGreenRed(GMODE_LIGHTS + i * 2, 1.0f, 0.0f);
  940. }
  941. else
  942. setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 0.0f);
  943. }
  944. }
  945. }
  946. else {
  947. for (int i = 0; i < 8; i++)
  948. setGreenRed(GMODE_LIGHTS + i * 2, 0.0f, 0.0f);
  949. }
  950. // Reset light
  951. lights[RESET_LIGHT].value = resetLight;
  952. resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips;
  953. // Run lights
  954. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  955. if (infoCopyPaste != 0l) {
  956. if (infoCopyPaste > 0l)
  957. infoCopyPaste --;
  958. if (infoCopyPaste < 0l)
  959. infoCopyPaste ++;
  960. }
  961. if (displayProbInfo > 0l)
  962. displayProbInfo--;
  963. if (modeHoldDetect.process(params[MODES_PARAM].value)) {
  964. displayState = DISP_GATE;
  965. editingPpqn = (long) (editingPpqnTime * sampleRate / displayRefreshStepSkips);
  966. }
  967. if (editingPpqn > 0l)
  968. editingPpqn--;
  969. if (editingPhraseSongRunning > 0l)
  970. editingPhraseSongRunning--;
  971. if (revertDisplay > 0l) {
  972. if (revertDisplay == 1)
  973. displayState = DISP_GATE;
  974. revertDisplay--;
  975. }
  976. if (blinkNum > 0) {
  977. blinkCount++;
  978. if (blinkCount >= (long) (1.0f * sampleRate / displayRefreshStepSkips)) {
  979. blinkCount = 0l;
  980. blinkNum--;
  981. }
  982. }
  983. }// lightRefreshCounter
  984. if (clockIgnoreOnReset > 0l)
  985. clockIgnoreOnReset--;
  986. }// step()
  987. inline void setGreenRed(int id, float green, float red) {
  988. lights[id + 0].value = green;
  989. lights[id + 1].value = red;
  990. }
  991. inline void setGreenRed3(int id, float green, float red) {
  992. setGreenRed(id, green, red);
  993. lights[id + 2].value = 0.0f;
  994. }
  995. };// GateSeq64 : module
  996. struct GateSeq64Widget : ModuleWidget {
  997. GateSeq64 *module;
  998. DynamicSVGPanel *panel;
  999. int oldExpansion;
  1000. int expWidth = 60;
  1001. IMPort* expPorts[6];
  1002. struct SequenceDisplayWidget : TransparentWidget {
  1003. GateSeq64 *module;
  1004. std::shared_ptr<Font> font;
  1005. char displayStr[4];
  1006. SequenceDisplayWidget() {
  1007. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  1008. }
  1009. void runModeToStr(int num) {
  1010. if (num >= 0 && num < NUM_MODES)
  1011. snprintf(displayStr, 4, "%s", modeLabels[num].c_str());
  1012. }
  1013. void draw(NVGcontext *vg) override {
  1014. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  1015. nvgFontFaceId(vg, font->handle);
  1016. bool editingSequence = module->isEditingSequence();
  1017. Vec textPos = Vec(6, 24);
  1018. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  1019. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  1020. nvgFillColor(vg, textColor);
  1021. if (module->infoCopyPaste != 0l) {
  1022. if (module->infoCopyPaste > 0l)// if copy display "CPY"
  1023. snprintf(displayStr, 4, "CPY");
  1024. else {
  1025. float cpMode = module->params[GateSeq64::CPMODE_PARAM].value;
  1026. if (editingSequence && !module->seqCopied) {// cross paste to seq
  1027. if (cpMode > 1.5f)// All = init
  1028. snprintf(displayStr, 4, "CLR");
  1029. else if (cpMode < 0.5f)// 4 = random gate
  1030. snprintf(displayStr, 4, "RGT");
  1031. else// 8 = random probs
  1032. snprintf(displayStr, 4, "RPR");
  1033. }
  1034. else if (!editingSequence && module->seqCopied) {// cross paste to song
  1035. if (cpMode > 1.5f)// All = init
  1036. snprintf(displayStr, 4, "CLR");
  1037. else if (cpMode < 0.5f)// 4 = increase by 1
  1038. snprintf(displayStr, 4, "INC");
  1039. else// 8 = random phrases
  1040. snprintf(displayStr, 4, "RPH");
  1041. }
  1042. else
  1043. snprintf(displayStr, 4, "PST");
  1044. }
  1045. }
  1046. else if (module->displayProbInfo != 0l) {
  1047. int prob = module->attributes[module->sequence][module->stepIndexEdit].getGatePVal();
  1048. if ( prob>= 100)
  1049. snprintf(displayStr, 4, "1,0");
  1050. else if (prob >= 1)
  1051. snprintf(displayStr, 4, ",%02u", (unsigned) prob);
  1052. else
  1053. snprintf(displayStr, 4, " 0");
  1054. }
  1055. else if (module->editingPpqn != 0ul) {
  1056. snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep);
  1057. }
  1058. else if (module->displayState == GateSeq64::DISP_LENGTH) {
  1059. if (editingSequence)
  1060. snprintf(displayStr, 4, "L%2u", (unsigned) module->sequences[module->sequence].getLength());
  1061. else
  1062. snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases);
  1063. }
  1064. else if (module->displayState == GateSeq64::DISP_MODES) {
  1065. if (editingSequence)
  1066. runModeToStr(module->sequences[module->sequence].getRunMode());
  1067. else
  1068. runModeToStr(module->runModeSong);
  1069. }
  1070. else {
  1071. int dispVal = 0;
  1072. char specialCode = ' ';
  1073. if (editingSequence)
  1074. dispVal = module->sequence;
  1075. else {
  1076. if (module->editingPhraseSongRunning > 0l || !module->running) {
  1077. dispVal = module->phrase[module->phraseIndexEdit];
  1078. if (module->editingPhraseSongRunning > 0l)
  1079. specialCode = '*';
  1080. }
  1081. else
  1082. dispVal = module->phrase[module->phraseIndexRun];
  1083. }
  1084. snprintf(displayStr, 4, "%c%2u", specialCode, (unsigned)(dispVal) + 1 );
  1085. }
  1086. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  1087. }
  1088. };
  1089. struct PanelThemeItem : MenuItem {
  1090. GateSeq64 *module;
  1091. int theme;
  1092. void onAction(EventAction &e) override {
  1093. module->panelTheme = theme;
  1094. }
  1095. void step() override {
  1096. rightText = (module->panelTheme == theme) ? "✔" : "";
  1097. }
  1098. };
  1099. struct ExpansionItem : MenuItem {
  1100. GateSeq64 *module;
  1101. void onAction(EventAction &e) override {
  1102. module->expansion = module->expansion == 1 ? 0 : 1;
  1103. }
  1104. };
  1105. struct ResetOnRunItem : MenuItem {
  1106. GateSeq64 *module;
  1107. void onAction(EventAction &e) override {
  1108. module->resetOnRun = !module->resetOnRun;
  1109. }
  1110. };
  1111. struct AutoseqItem : MenuItem {
  1112. GateSeq64 *module;
  1113. void onAction(EventAction &e) override {
  1114. module->autoseq = !module->autoseq;
  1115. }
  1116. };
  1117. struct SeqCVmethodItem : MenuItem {
  1118. GateSeq64 *module;
  1119. void onAction(EventAction &e) override {
  1120. module->seqCVmethod++;
  1121. if (module->seqCVmethod > 2)
  1122. module->seqCVmethod = 0;
  1123. }
  1124. void step() override {
  1125. if (module->seqCVmethod == 0)
  1126. text = "Seq CV in: <0-10V>, C4-G6, Trig-Incr";
  1127. else if (module->seqCVmethod == 1)
  1128. text = "Seq CV in: 0-10V, <C4-G6>, Trig-Incr";
  1129. else
  1130. text = "Seq CV in: 0-10V, C4-G6, <Trig-Incr>";
  1131. }
  1132. };
  1133. Menu *createContextMenu() override {
  1134. Menu *menu = ModuleWidget::createContextMenu();
  1135. MenuLabel *spacerLabel = new MenuLabel();
  1136. menu->addChild(spacerLabel);
  1137. GateSeq64 *module = dynamic_cast<GateSeq64*>(this->module);
  1138. assert(module);
  1139. MenuLabel *themeLabel = new MenuLabel();
  1140. themeLabel->text = "Panel Theme";
  1141. menu->addChild(themeLabel);
  1142. PanelThemeItem *lightItem = new PanelThemeItem();
  1143. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  1144. lightItem->module = module;
  1145. lightItem->theme = 0;
  1146. menu->addChild(lightItem);
  1147. PanelThemeItem *darkItem = new PanelThemeItem();
  1148. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  1149. darkItem->module = module;
  1150. darkItem->theme = 1;
  1151. menu->addChild(darkItem);
  1152. menu->addChild(new MenuLabel());// empty line
  1153. MenuLabel *settingsLabel = new MenuLabel();
  1154. settingsLabel->text = "Settings";
  1155. menu->addChild(settingsLabel);
  1156. ResetOnRunItem *rorItem = MenuItem::create<ResetOnRunItem>("Reset on run", CHECKMARK(module->resetOnRun));
  1157. rorItem->module = module;
  1158. menu->addChild(rorItem);
  1159. AutoseqItem *aseqItem = MenuItem::create<AutoseqItem>("AutoSeq when writing via CV inputs", CHECKMARK(module->autoseq));
  1160. aseqItem->module = module;
  1161. menu->addChild(aseqItem);
  1162. SeqCVmethodItem *seqcvItem = MenuItem::create<SeqCVmethodItem>("Seq CV in: ", "");
  1163. seqcvItem->module = module;
  1164. menu->addChild(seqcvItem);
  1165. menu->addChild(new MenuLabel());// empty line
  1166. MenuLabel *expansionLabel = new MenuLabel();
  1167. expansionLabel->text = "Expansion module";
  1168. menu->addChild(expansionLabel);
  1169. ExpansionItem *expItem = MenuItem::create<ExpansionItem>(expansionMenuLabel, CHECKMARK(module->expansion != 0));
  1170. expItem->module = module;
  1171. menu->addChild(expItem);
  1172. return menu;
  1173. }
  1174. void step() override {
  1175. if(module->expansion != oldExpansion) {
  1176. if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks
  1177. for (int i = 0; i < 6; i++)
  1178. RACK_PLUGIN_UI_RACKWIDGET->wireContainer->removeAllWires(expPorts[i]);
  1179. }
  1180. oldExpansion = module->expansion;
  1181. }
  1182. box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth;
  1183. Widget::step();
  1184. }
  1185. struct CKSSThreeInvNotify : CKSSThreeInvNoRandom {// Not randomizable
  1186. CKSSThreeInvNotify() {}
  1187. void onDragStart(EventDragStart &e) override {
  1188. ToggleSwitch::onDragStart(e);
  1189. ((GateSeq64*)(module))->stepConfigSync = 2;// signal a sync from switch so that steps get initialized
  1190. }
  1191. };
  1192. struct SequenceKnob : IMBigKnobInf {
  1193. SequenceKnob() {};
  1194. void onMouseDown(EventMouseDown &e) override {// from ParamWidget.cpp
  1195. GateSeq64* module = dynamic_cast<GateSeq64*>(this->module);
  1196. if (e.button == 1) {
  1197. // same code structure below as in sequence knob in main step()
  1198. bool editingSequence = module->isEditingSequence();
  1199. if (module->displayProbInfo != 0l && editingSequence) {
  1200. //blinkNum = blinkNumInit;
  1201. module->attributes[module->sequence][module->stepIndexEdit].setGatePVal(50);
  1202. //displayProbInfo = (long) (displayProbInfoTime * sampleRate / displayRefreshStepSkips);
  1203. }
  1204. else if (module->editingPpqn != 0) {
  1205. module->pulsesPerStep = 1;
  1206. //editingPpqn = (long) (editingPpqnTime * sampleRate / displayRefreshStepSkips);
  1207. }
  1208. else if (module->displayState == GateSeq64::DISP_MODES) {
  1209. if (editingSequence) {
  1210. module->sequences[module->sequence].setRunMode(MODE_FWD);
  1211. }
  1212. else {
  1213. module->runModeSong = MODE_FWD;
  1214. }
  1215. }
  1216. else if (module->displayState == GateSeq64::DISP_LENGTH) {
  1217. if (editingSequence) {
  1218. module->sequences[module->sequence].setLength(16 * module->stepConfig);
  1219. }
  1220. else {
  1221. module->phrases = 4;
  1222. }
  1223. }
  1224. else {
  1225. if (editingSequence) {
  1226. //blinkNum = blinkNumInit;
  1227. if (!module->inputs[GateSeq64::SEQCV_INPUT].active) {
  1228. module->sequence = 0;
  1229. }
  1230. }
  1231. else {
  1232. if (module->editingPhraseSongRunning > 0l || !module->running) {
  1233. module->phrase[module->phraseIndexEdit] = 0;
  1234. // if (running)
  1235. // editingPhraseSongRunning = (long) (editingPhraseSongRunningTime * sampleRate / displayRefreshStepSkips);
  1236. }
  1237. }
  1238. }
  1239. }
  1240. ParamWidget::onMouseDown(e);
  1241. }
  1242. };
  1243. GateSeq64Widget(GateSeq64 *module) : ModuleWidget(module) {
  1244. this->module = module;
  1245. oldExpansion = -1;
  1246. // Main panel from Inkscape
  1247. panel = new DynamicSVGPanel();
  1248. panel->mode = &module->panelTheme;
  1249. panel->expWidth = &expWidth;
  1250. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/GateSeq64.svg")));
  1251. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/GateSeq64_dark.svg")));
  1252. box.size = panel->box.size;
  1253. box.size.x = box.size.x - (1 - module->expansion) * expWidth;
  1254. addChild(panel);
  1255. // Screws
  1256. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  1257. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  1258. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 0), &module->panelTheme));
  1259. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30, 365), &module->panelTheme));
  1260. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme));
  1261. addChild(createDynamicScrew<IMScrew>(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme));
  1262. // ****** Top portion (LED button array and gate type LED buttons) ******
  1263. static const int rowRuler0 = 32;
  1264. static const int spacingRows = 32;
  1265. static const int colRulerSteps = 15;
  1266. static const int spacingSteps = 20;
  1267. static const int spacingSteps4 = 4;
  1268. // Step LED buttons and GateMode lights
  1269. for (int y = 0; y < 4; y++) {
  1270. int posX = colRulerSteps;
  1271. for (int x = 0; x < 16; x++) {
  1272. addParam(createParam<LEDButtonWithRClick>(Vec(posX, rowRuler0 + 8 + y * spacingRows - 4.4f), module, GateSeq64::STEP_PARAMS + y * 16 + x, 0.0f, 1.0f, 0.0f));
  1273. addChild(createLight<MediumLight<GreenRedWhiteLight>>(Vec(posX + 4.4f, rowRuler0 + 8 + y * spacingRows), module, GateSeq64::STEP_LIGHTS + (y * 16 + x) * 3));
  1274. posX += spacingSteps;
  1275. if ((x + 1) % 4 == 0)
  1276. posX += spacingSteps4;
  1277. }
  1278. }
  1279. // Gate type LED buttons (bottom left to top left to top right)
  1280. static const int rowRulerG0 = 166;
  1281. static const int rowSpacingG = 26;
  1282. static const int colSpacingG = 56;
  1283. static const int colRulerG0 = 15 + 28;
  1284. addParam(createParam<LEDButton>(Vec(colRulerG0, rowRulerG0 + rowSpacingG * 2 - 4.4f), module, GateSeq64::GMODE_PARAMS + 2, 0.0f, 1.0f, 0.0f));
  1285. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerG0 + 4.4f, rowRulerG0 + rowSpacingG * 2), module, GateSeq64::GMODE_LIGHTS + 2 * 2));
  1286. addParam(createParam<LEDButton>(Vec(colRulerG0, rowRulerG0 + rowSpacingG - 4.4f), module, GateSeq64::GMODE_PARAMS + 1, 0.0f, 1.0f, 0.0f));
  1287. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerG0 + 4.4f, rowRulerG0 + rowSpacingG), module, GateSeq64::GMODE_LIGHTS + 1 * 2));
  1288. addParam(createParam<LEDButton>(Vec(colRulerG0, rowRulerG0 - 4.4f), module, GateSeq64::GMODE_PARAMS + 0, 0.0f, 1.0f, 0.0f));
  1289. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerG0 + 4.4f, rowRulerG0), module, GateSeq64::GMODE_LIGHTS + 0 * 2));
  1290. for (int x = 1; x < 6; x++) {
  1291. addParam(createParam<LEDButton>(Vec(colRulerG0 + colSpacingG * x, rowRulerG0 - 4.4f), module, GateSeq64::GMODE_PARAMS + 2 + x, 0.0f, 1.0f, 0.0f));
  1292. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerG0 + colSpacingG * x + 4.4f, rowRulerG0), module, GateSeq64::GMODE_LIGHTS + (2 + x) * 2));
  1293. }
  1294. // ****** 5x3 Main bottom half Control section ******
  1295. static const int colRulerC0 = 25;
  1296. static const int colRulerC1 = 78;
  1297. static const int colRulerC2 = 126;
  1298. static const int colRulerC3 = 189;
  1299. static const int colRulerC4 = 241;
  1300. static const int rowRulerC0 = 206;
  1301. static const int rowRulerSpacing = 58;
  1302. static const int rowRulerC1 = rowRulerC0 + rowRulerSpacing;
  1303. static const int rowRulerC2 = rowRulerC1 + rowRulerSpacing;
  1304. // Clock input
  1305. addInput(createDynamicPort<IMPort>(Vec(colRulerC0, rowRulerC1), Port::INPUT, module, GateSeq64::CLOCK_INPUT, &module->panelTheme));
  1306. // Reset CV
  1307. addInput(createDynamicPort<IMPort>(Vec(colRulerC0, rowRulerC2), Port::INPUT, module, GateSeq64::RESET_INPUT, &module->panelTheme));
  1308. // Prob button
  1309. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerC1 + offsetCKD6b, rowRulerC0 + offsetCKD6b), module, GateSeq64::PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1310. // Reset LED bezel and light
  1311. addParam(createParam<LEDBezel>(Vec(colRulerC1 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f));
  1312. addChild(createLight<MuteLight<GreenLight>>(Vec(colRulerC1 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RESET_LIGHT));
  1313. // Seq CV
  1314. addInput(createDynamicPort<IMPort>(Vec(colRulerC1, rowRulerC2), Port::INPUT, module, GateSeq64::SEQCV_INPUT, &module->panelTheme));
  1315. // Sequence knob
  1316. addParam(createDynamicParam<SequenceKnob>(Vec(colRulerC2 + 1 + offsetIMBigKnob, rowRulerC0 + offsetIMBigKnob), module, GateSeq64::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  1317. // Run LED bezel and light
  1318. addParam(createParam<LEDBezel>(Vec(colRulerC2 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  1319. addChild(createLight<MuteLight<GreenLight>>(Vec(colRulerC2 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RUN_LIGHT));
  1320. // Run CV
  1321. addInput(createDynamicPort<IMPort>(Vec(colRulerC2, rowRulerC2), Port::INPUT, module, GateSeq64::RUNCV_INPUT, &module->panelTheme));
  1322. // Sequence display
  1323. SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget();
  1324. displaySequence->box.pos = Vec(colRulerC3 - 15, rowRulerC0 + vOffsetDisplay);
  1325. displaySequence->box.size = Vec(55, 30);// 3 characters
  1326. displaySequence->module = module;
  1327. addChild(displaySequence);
  1328. // Modes button
  1329. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerC3 + offsetCKD6b, rowRulerC1 + offsetCKD6b), module, GateSeq64::MODES_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1330. // Copy/paste buttons
  1331. addParam(createDynamicParam<IMPushButton>(Vec(colRulerC3 - 10, rowRulerC2 + offsetTL1105), module, GateSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1332. addParam(createDynamicParam<IMPushButton>(Vec(colRulerC3 + 20, rowRulerC2 + offsetTL1105), module, GateSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  1333. // Seq/Song selector
  1334. addParam(createParam<CKSSNoRandom>(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC0 + vOffsetCKSS), module, GateSeq64::EDIT_PARAM, 0.0f, 1.0f, 1.0f));
  1335. // Config switch (3 position)
  1336. addParam(createParam<CKSSThreeInvNotify>(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC1 - 2 + vOffsetCKSSThree), module, GateSeq64::CONFIG_PARAM, 0.0f, 2.0f, GateSeq64::CONFIG_PARAM_INIT_VALUE));// 0.0f is top position
  1337. // Copy paste mode
  1338. addParam(createParam<CKSSThreeInvNoRandom>(Vec(colRulerC4 + 2 + hOffsetCKSS, rowRulerC2 + vOffsetCKSSThree), module, GateSeq64::CPMODE_PARAM, 0.0f, 2.0f, 2.0f));
  1339. // Outputs
  1340. for (int iSides = 0; iSides < 4; iSides++)
  1341. addOutput(createDynamicPort<IMPort>(Vec(311, rowRulerC0 + iSides * 40), Port::OUTPUT, module, GateSeq64::GATE_OUTPUTS + iSides, &module->panelTheme));
  1342. // Expansion module
  1343. static const int rowRulerExpTop = 60;
  1344. static const int rowSpacingExp = 50;
  1345. static const int colRulerExp = 497 - 30 - 90;// GS64 is (2+6)HP less than PS32
  1346. addInput(expPorts[0] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, GateSeq64::WRITE_INPUT, &module->panelTheme));
  1347. addInput(expPorts[1] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, GateSeq64::GATE_INPUT, &module->panelTheme));
  1348. addInput(expPorts[2] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, GateSeq64::PROB_INPUT, &module->panelTheme));
  1349. addInput(expPorts[3] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, GateSeq64::WRITE0_INPUT, &module->panelTheme));
  1350. addInput(expPorts[4] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, GateSeq64::WRITE1_INPUT, &module->panelTheme));
  1351. addInput(expPorts[5] = createDynamicPort<IMPort>(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 5), Port::INPUT, module, GateSeq64::STEPL_INPUT, &module->panelTheme));
  1352. }
  1353. };
  1354. } // namespace rack_plugin_ImpromptuModular
  1355. using namespace rack_plugin_ImpromptuModular;
  1356. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, GateSeq64) {
  1357. Model *modelGateSeq64 = Model::create<GateSeq64, GateSeq64Widget>("Impromptu Modular", "Gate-Seq-64", "SEQ - Gate-Seq-64", SEQUENCER_TAG);
  1358. return modelGateSeq64;
  1359. }
  1360. /*CHANGE LOG
  1361. 0.6.16:
  1362. support for 32 sequences instead of 16
  1363. add step indication in song mode (white lights), and add right-click initialization on main knob
  1364. 0.6.14:
  1365. allow right click to turn steps off
  1366. 0.6.13:
  1367. fix run mode bug (history not reset when hard reset)
  1368. fix initRun() timing bug when turn off-and-then-on running button (it was resetting ppqnCount)
  1369. add two extra modes for Seq CV input (right-click menu): note-voltage-levels and trigger-increment
  1370. 0.6.12:
  1371. input refresh optimization
  1372. add separate buttons for each advanced-gate (remove left right buttons)
  1373. change behavior of write CV input in exp pannel (prob not reset when ProbIn unconnected, and gate not written when GateIn unconnected)
  1374. 0.6.11:
  1375. step optimization of lights refresh
  1376. add RN2 run mode
  1377. add step-left CV input in expansion panel
  1378. implement copy-paste in song mode and change 4/ROW/ALL to 4/8/ALL
  1379. implement cross paste trick for init and randomize seq/song
  1380. add AutoSeq option when writing via CV inputs
  1381. make song mode 64 sequences long
  1382. 0.6.10:
  1383. add advanced gate mode
  1384. 0.6.9:
  1385. add FW2, FW3 and FW4 run modes for sequences (but not for song)
  1386. 0.6.7:
  1387. add expansion panel with extra CVs for writing steps into the module
  1388. allow full edit capabilities in song mode
  1389. no reset on run by default, with switch added in context menu
  1390. reset does not revert seq or song number to 1
  1391. 0.6.6:
  1392. config and knob bug fixes when loading patch
  1393. 0.6.5:
  1394. swap MODE/LEN so that length happens first (update manual)
  1395. 0.6.4:
  1396. initial release of GS64
  1397. */