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.

1786 lines
73KB

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