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
74KB

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