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.

794 lines
28KB

  1. //***********************************************************************************************
  2. //Three channel 32-step writable sequencer module for VCV Rack by Marc Boulé
  3. //
  4. //Based on code from the Fundamental and AudibleInstruments plugins by Andrew Belt
  5. //and graphics from the Component Library by Wes Milholen
  6. //See ./LICENSE.txt for all licenses
  7. //See ./res/fonts/ for font licenses
  8. //***********************************************************************************************
  9. #include "ImpromptuModular.hpp"
  10. #include "PhraseSeqUtil.hpp"
  11. namespace rack_plugin_ImpromptuModular {
  12. struct WriteSeq32 : Module {
  13. enum ParamIds {
  14. SHARP_PARAM,
  15. ENUMS(WINDOW_PARAM, 4),
  16. QUANTIZE_PARAM,
  17. ENUMS(GATE_PARAM, 8),
  18. CHANNEL_PARAM,
  19. COPY_PARAM,
  20. PASTE_PARAM,
  21. RUN_PARAM,
  22. WRITE_PARAM,
  23. STEPL_PARAM,
  24. MONITOR_PARAM,
  25. STEPR_PARAM,
  26. STEPS_PARAM,
  27. AUTOSTEP_PARAM,
  28. PASTESYNC_PARAM,
  29. NUM_PARAMS
  30. };
  31. enum InputIds {
  32. CHANNEL_INPUT,// no longer used
  33. CV_INPUT,
  34. GATE_INPUT,
  35. WRITE_INPUT,
  36. STEPL_INPUT,
  37. STEPR_INPUT,
  38. CLOCK_INPUT,
  39. RESET_INPUT,
  40. // -- 0.6.2
  41. RUNCV_INPUT,
  42. NUM_INPUTS
  43. };
  44. enum OutputIds {
  45. ENUMS(CV_OUTPUTS, 3),
  46. ENUMS(GATE_OUTPUTS, 3),
  47. NUM_OUTPUTS
  48. };
  49. enum LightIds {
  50. ENUMS(WINDOW_LIGHTS, 4),
  51. ENUMS(STEP_LIGHTS, 8),
  52. ENUMS(GATE_LIGHTS, 8),
  53. ENUMS(CHANNEL_LIGHTS, 4),
  54. RUN_LIGHT,
  55. ENUMS(WRITE_LIGHT, 2),// room for GreenRed
  56. PENDING_LIGHT,
  57. NUM_LIGHTS
  58. };
  59. // Need to save
  60. int panelTheme = 0;
  61. bool running;
  62. int indexStep;
  63. int indexStepStage;
  64. int indexChannel;
  65. float cv[4][32];
  66. bool gates[4][32];
  67. bool resetOnRun;
  68. // No need to save
  69. int notesPos[8]; // used for rendering notes in LCD_24, 8 gate and 8 step LEDs
  70. float cvCPbuffer[32];// copy paste buffer for CVs
  71. bool gateCPbuffer[32];// copy paste buffer for gates
  72. long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste
  73. int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits
  74. long clockIgnoreOnReset;
  75. const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays)
  76. int lightRefreshCounter;
  77. SchmittTrigger clockTrigger;
  78. SchmittTrigger resetTrigger;
  79. SchmittTrigger runningTrigger;
  80. SchmittTrigger channelTrigger;
  81. SchmittTrigger stepLTrigger;
  82. SchmittTrigger stepRTrigger;
  83. SchmittTrigger copyTrigger;
  84. SchmittTrigger pasteTrigger;
  85. SchmittTrigger writeTrigger;
  86. SchmittTrigger gateTriggers[8];
  87. SchmittTrigger windowTriggers[4];
  88. WriteSeq32() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  89. onReset();
  90. }
  91. void onReset() override {
  92. running = true;
  93. indexStep = 0;
  94. indexStepStage = 0;
  95. indexChannel = 0;
  96. for (int s = 0; s < 32; s++) {
  97. for (int c = 0; c < 4; c++) {
  98. cv[c][s] = 0.0f;
  99. gates[c][s] = true;
  100. }
  101. cvCPbuffer[s] = 0.0f;
  102. gateCPbuffer[s] = true;
  103. }
  104. infoCopyPaste = 0l;
  105. pendingPaste = 0;
  106. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  107. resetOnRun = false;
  108. lightRefreshCounter = 0;
  109. }
  110. void onRandomize() override {
  111. running = true;
  112. indexStep = 0;
  113. indexStepStage = 0;
  114. indexChannel = 0;
  115. for (int s = 0; s < 32; s++) {
  116. for (int c = 0; c < 4; c++) {
  117. cv[c][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f);
  118. gates[c][s] = (randomUniform() > 0.5f);
  119. }
  120. cvCPbuffer[s] = 0.0f;
  121. gateCPbuffer[s] = true;
  122. }
  123. infoCopyPaste = 0l;
  124. pendingPaste = 0;
  125. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  126. resetOnRun = false;
  127. }
  128. json_t *toJson() override {
  129. json_t *rootJ = json_object();
  130. // panelTheme
  131. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  132. // running
  133. json_object_set_new(rootJ, "running", json_boolean(running));
  134. // indexStep
  135. json_object_set_new(rootJ, "indexStep", json_integer(indexStep));
  136. // indexStepStage
  137. json_object_set_new(rootJ, "indexStepStage", json_integer(indexStepStage));
  138. // indexChannel
  139. json_object_set_new(rootJ, "indexChannel", json_integer(indexChannel));
  140. // CV
  141. json_t *cvJ = json_array();
  142. for (int c = 0; c < 4; c++)
  143. for (int s = 0; s < 32; s++) {
  144. json_array_insert_new(cvJ, s + (c<<5), json_real(cv[c][s]));
  145. }
  146. json_object_set_new(rootJ, "cv", cvJ);
  147. // Gates
  148. json_t *gatesJ = json_array();
  149. for (int c = 0; c < 4; c++)
  150. for (int s = 0; s < 32; s++) {
  151. json_array_insert_new(gatesJ, s + (c<<5), json_integer((int) gates[c][s]));// json_boolean wil break patches
  152. }
  153. json_object_set_new(rootJ, "gates", gatesJ);
  154. // resetOnRun
  155. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  156. return rootJ;
  157. }
  158. void fromJson(json_t *rootJ) override {
  159. // panelTheme
  160. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  161. if (panelThemeJ)
  162. panelTheme = json_integer_value(panelThemeJ);
  163. // running
  164. json_t *runningJ = json_object_get(rootJ, "running");
  165. if (runningJ)
  166. running = json_is_true(runningJ);
  167. // indexStep
  168. json_t *indexStepJ = json_object_get(rootJ, "indexStep");
  169. if (indexStepJ)
  170. indexStep = json_integer_value(indexStepJ);
  171. // indexStepStage
  172. json_t *indexStepStageJ = json_object_get(rootJ, "indexStepStage");
  173. if (indexStepStageJ)
  174. indexStepStage = json_integer_value(indexStepStageJ);
  175. // indexChannel
  176. json_t *indexChannelJ = json_object_get(rootJ, "indexChannel");
  177. if (indexChannelJ)
  178. indexChannel = json_integer_value(indexChannelJ);
  179. // CV
  180. json_t *cvJ = json_object_get(rootJ, "cv");
  181. if (cvJ) {
  182. for (int c = 0; c < 4; c++)
  183. for (int s = 0; s < 32; s++) {
  184. json_t *cvArrayJ = json_array_get(cvJ, s + (c<<5));
  185. if (cvArrayJ)
  186. cv[c][s] = json_real_value(cvArrayJ);
  187. }
  188. }
  189. // Gates
  190. json_t *gatesJ = json_object_get(rootJ, "gates");
  191. if (gatesJ) {
  192. for (int c = 0; c < 4; c++)
  193. for (int s = 0; s < 32; s++) {
  194. json_t *gateJ = json_array_get(gatesJ, s + (c<<5));
  195. if (gateJ)
  196. gates[c][s] = !!json_integer_value(gateJ);// json_is_true() will break patches
  197. }
  198. }
  199. // resetOnRun
  200. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  201. if (resetOnRunJ)
  202. resetOnRun = json_is_true(resetOnRunJ);
  203. }
  204. inline float quantize(float cv, bool enable) {
  205. return enable ? (roundf(cv * 12.0f) / 12.0f) : cv;
  206. }
  207. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  208. void step() override {
  209. static const float copyPasteInfoTime = 0.5f;// seconds
  210. //********** Buttons, knobs, switches and inputs **********
  211. int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value), 1.0f, 32.0f);
  212. // Run state button
  213. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) {
  214. running = !running;
  215. //pendingPaste = 0;// no pending pastes across run state toggles
  216. if (running && resetOnRun) {
  217. indexStep = 0;
  218. indexStepStage = 0;
  219. }
  220. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  221. }
  222. // Copy button
  223. if (copyTrigger.process(params[COPY_PARAM].value)) {
  224. infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  225. for (int s = 0; s < 32; s++) {
  226. cvCPbuffer[s] = cv[indexChannel][s];
  227. gateCPbuffer[s] = gates[indexChannel][s];
  228. }
  229. pendingPaste = 0;
  230. }
  231. // Paste button
  232. if (pasteTrigger.process(params[PASTE_PARAM].value)) {
  233. if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 3) {
  234. // Paste realtime, no pending to schedule
  235. infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  236. for (int s = 0; s < 32; s++) {
  237. cv[indexChannel][s] = cvCPbuffer[s];
  238. gates[indexChannel][s] = gateCPbuffer[s];
  239. }
  240. pendingPaste = 0;
  241. }
  242. else {
  243. pendingPaste = params[PASTESYNC_PARAM].value > 1.5f ? 2 : 1;
  244. pendingPaste |= indexChannel<<2; // add paste destination channel into pendingPaste
  245. }
  246. }
  247. // Channel selection button
  248. if (channelTrigger.process(params[CHANNEL_PARAM].value)) {
  249. indexChannel++;
  250. if (indexChannel >= 4)
  251. indexChannel = 0;
  252. }
  253. // Gate buttons
  254. for (int index8 = 0, iGate = 0; index8 < 8; index8++) {
  255. if (gateTriggers[index8].process(params[GATE_PARAM + index8].value)) {
  256. iGate = ( (indexChannel == 3 ? indexStepStage : indexStep) & 0x18) | index8;
  257. if (iGate < numSteps)// don't toggle gates beyond steps
  258. gates[indexChannel][iGate] = !gates[indexChannel][iGate];
  259. }
  260. }
  261. bool canEdit = !running || (indexChannel == 3);
  262. // Steps knob will not trigger anything in step(), and if user goes lower than current step, lower the index accordingly
  263. if (indexStep >= numSteps)
  264. indexStep = numSteps - 1;
  265. if (indexStepStage >= numSteps)
  266. indexStepStage = numSteps - 1;
  267. // Write button (must be before StepL and StepR in case route gate simultaneously to Step R and Write for example)
  268. // (write must be to correct step)
  269. if (writeTrigger.process(params[WRITE_PARAM].value + inputs[WRITE_INPUT].value)) {
  270. if (canEdit) {
  271. int index = (indexChannel == 3 ? indexStepStage : indexStep);
  272. // CV
  273. cv[indexChannel][index] = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);
  274. // Gate
  275. if (inputs[GATE_INPUT].active)
  276. gates[indexChannel][index] = (inputs[GATE_INPUT].value >= 1.0f) ? true : false;
  277. // Autostep
  278. if (params[AUTOSTEP_PARAM].value > 0.5f) {
  279. if (indexChannel == 3)
  280. indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps);
  281. else
  282. indexStep = moveIndex(indexStep, indexStep + 1, numSteps);
  283. }
  284. }
  285. }
  286. // Step L button
  287. if (stepLTrigger.process(params[STEPL_PARAM].value + inputs[STEPL_INPUT].value)) {
  288. if (canEdit) {
  289. if (indexChannel == 3)
  290. indexStepStage = moveIndex(indexStepStage, indexStepStage - 1, numSteps);
  291. else
  292. indexStep = moveIndex(indexStep, indexStep - 1, numSteps);
  293. }
  294. }
  295. // Step R button
  296. if (stepRTrigger.process(params[STEPR_PARAM].value + inputs[STEPR_INPUT].value)) {
  297. if (canEdit) {
  298. if (indexChannel == 3)
  299. indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps);
  300. else
  301. indexStep = moveIndex(indexStep, indexStep + 1, numSteps);
  302. }
  303. }
  304. // Window buttons
  305. for (int i = 0; i < 4; i++) {
  306. if (windowTriggers[i].process(params[WINDOW_PARAM+i].value)) {
  307. if (canEdit) {
  308. if (indexChannel == 3)
  309. indexStepStage = (i<<3) | (indexStepStage&0x7);
  310. else
  311. indexStep = (i<<3) | (indexStep&0x7);
  312. }
  313. }
  314. }
  315. //********** Clock and reset **********
  316. // Clock
  317. if (clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  318. if (running && clockIgnoreOnReset == 0l) {
  319. indexStep = moveIndex(indexStep, indexStep + 1, numSteps);
  320. // Pending paste on clock or end of seq
  321. if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep == 0) ) {
  322. int pasteChannel = pendingPaste>>2;
  323. infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  324. for (int s = 0; s < 32; s++) {
  325. cv[pasteChannel][s] = cvCPbuffer[s];
  326. gates[pasteChannel][s] = gateCPbuffer[s];
  327. }
  328. pendingPaste = 0;
  329. }
  330. }
  331. }
  332. // Reset
  333. if (resetTrigger.process(inputs[RESET_INPUT].value)) {
  334. indexStep = 0;
  335. indexStepStage = 0;
  336. pendingPaste = 0;
  337. //indexChannel = 0;
  338. clockTrigger.reset();
  339. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  340. }
  341. //********** Outputs and lights **********
  342. // CV and gate outputs (staging area not used)
  343. if (running) {
  344. for (int i = 0; i < 3; i++) {
  345. outputs[CV_OUTPUTS + i].value = cv[i][indexStep];
  346. outputs[GATE_OUTPUTS + i].value = (clockTrigger.isHigh() && gates[i][indexStep]) ? 10.0f : 0.0f;
  347. }
  348. }
  349. else {
  350. bool muteGate = false;// (params[WRITE_PARAM].value + params[STEPL_PARAM].value + params[STEPR_PARAM].value) > 0.5f; // set to false if don't want mute gate on button push
  351. for (int i = 0; i < 3; i++) {
  352. // CV
  353. if (params[MONITOR_PARAM].value > 0.5f)
  354. outputs[CV_OUTPUTS + i].value = cv[i][indexStep];// each CV out monitors the current step CV of that channel
  355. else
  356. outputs[CV_OUTPUTS + i].value = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);// all CV outs monitor the CV in (only current channel will have a gate though)
  357. // Gate
  358. outputs[GATE_OUTPUTS + i].value = ((i == indexChannel) && !muteGate) ? 10.0f : 0.0f;
  359. }
  360. }
  361. lightRefreshCounter++;
  362. if (lightRefreshCounter > displayRefreshStepSkips) {
  363. lightRefreshCounter = 0;
  364. int index = (indexChannel == 3 ? indexStepStage : indexStep);
  365. // Window lights
  366. for (int i = 0; i < 4; i++) {
  367. lights[WINDOW_LIGHTS + i].value = ((i == (index >> 3))?1.0f:0.0f);
  368. }
  369. // Step and gate lights
  370. for (int index8 = 0, iGate = 0; index8 < 8; index8++) {
  371. lights[STEP_LIGHTS + index8].value = (index8 == (index&0x7)) ? 1.0f : 0.0f;
  372. iGate = (index&0x18) | index8;
  373. lights[GATE_LIGHTS + index8].value = (gates[indexChannel][iGate] && iGate < numSteps) ? 1.0f : 0.0f;
  374. }
  375. // Channel lights
  376. lights[CHANNEL_LIGHTS + 0].value = (indexChannel == 0) ? 1.0f : 0.0f;// green
  377. lights[CHANNEL_LIGHTS + 1].value = (indexChannel == 1) ? 1.0f : 0.0f;// yellow
  378. lights[CHANNEL_LIGHTS + 2].value = (indexChannel == 2) ? 1.0f : 0.0f;// orange
  379. lights[CHANNEL_LIGHTS + 3].value = (indexChannel == 3) ? 1.0f : 0.0f;// blue
  380. // Run light
  381. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  382. // Write allowed light
  383. lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f;
  384. lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f;
  385. // Pending paste light
  386. lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f);
  387. if (infoCopyPaste != 0l) {
  388. if (infoCopyPaste > 0l)
  389. infoCopyPaste --;
  390. if (infoCopyPaste < 0l)
  391. infoCopyPaste ++;
  392. }
  393. }// lightRefreshCounter
  394. if (clockIgnoreOnReset > 0l)
  395. clockIgnoreOnReset--;
  396. }
  397. };
  398. struct WriteSeq32Widget : ModuleWidget {
  399. struct NotesDisplayWidget : TransparentWidget {
  400. WriteSeq32 *module;
  401. std::shared_ptr<Font> font;
  402. char text[4];
  403. NotesDisplayWidget() {
  404. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  405. }
  406. void cvToStr(int index8) {
  407. if (module->infoCopyPaste != 0l) {
  408. if (index8 == 0) {
  409. if (module->infoCopyPaste > 0l)
  410. snprintf(text, 4, "COP");
  411. else
  412. snprintf(text, 4, "PAS");
  413. }
  414. else if (index8 == 1) {
  415. if (module->infoCopyPaste > 0l)
  416. snprintf(text, 4, "Y ");
  417. else
  418. snprintf(text, 4, "TE ");
  419. }
  420. else {
  421. snprintf(text, 4, " ");
  422. }
  423. }
  424. else {
  425. int index = (module->indexChannel == 3 ? module->indexStepStage : module->indexStep);
  426. if ( ( (index&0x18) |index8) >= (int) clamp(roundf(module->params[WriteSeq32::STEPS_PARAM].value), 1.0f, 32.0f) ) {
  427. text[0] = ' ';
  428. text[1] = ' ';
  429. text[2] = ' ';
  430. }
  431. else {
  432. float cvVal = module->cv[module->indexChannel][index8|(index&0x18)];
  433. float cvValOffset = cvVal +10.0f;//to properly handle negative note voltages
  434. int indexNote = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f);
  435. bool sharp = (module->params[WriteSeq32::SHARP_PARAM].value > 0.5f) ? true : false;
  436. // note letter
  437. text[0] = sharp ? noteLettersSharp[indexNote] : noteLettersFlat[indexNote];
  438. // octave number
  439. int octave = (int) roundf(floorf(cvVal)+4.0f);
  440. if (octave < 0 || octave > 9)
  441. text[1] = (octave > 9) ? ':' : '_';
  442. else
  443. text[1] = (char) ( 0x30 + octave);
  444. // sharp/flat
  445. text[2] = ' ';
  446. if (isBlackKey[indexNote] == 1)
  447. text[2] = (sharp ? '\"' : '^' );
  448. }
  449. }
  450. // end of string
  451. text[3] = 0;
  452. }
  453. void draw(NVGcontext *vg) override {
  454. NVGcolor textColor = prepareDisplay(vg, &box);
  455. nvgFontFaceId(vg, font->handle);
  456. nvgTextLetterSpacing(vg, -1.5);
  457. for (int i = 0; i < 8; i++) {
  458. Vec textPos = Vec(module->notesPos[i], 24);
  459. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  460. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  461. nvgFillColor(vg, textColor);
  462. cvToStr(i);
  463. nvgText(vg, textPos.x, textPos.y, text, NULL);
  464. }
  465. }
  466. };
  467. struct StepsDisplayWidget : TransparentWidget {
  468. float *valueKnob;
  469. std::shared_ptr<Font> font;
  470. StepsDisplayWidget() {
  471. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  472. }
  473. void draw(NVGcontext *vg) override {
  474. NVGcolor textColor = prepareDisplay(vg, &box);
  475. nvgFontFaceId(vg, font->handle);
  476. //nvgTextLetterSpacing(vg, 2.5);
  477. Vec textPos = Vec(6, 24);
  478. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  479. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  480. nvgFillColor(vg, textColor);
  481. char displayStr[3];
  482. snprintf(displayStr, 3, "%2u", (unsigned) clamp(roundf(*valueKnob), 1.0f, 32.0f) );
  483. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  484. }
  485. };
  486. struct PanelThemeItem : MenuItem {
  487. WriteSeq32 *module;
  488. int theme;
  489. void onAction(EventAction &e) override {
  490. module->panelTheme = theme;
  491. }
  492. void step() override {
  493. rightText = (module->panelTheme == theme) ? "✔" : "";
  494. }
  495. };
  496. struct ResetOnRunItem : MenuItem {
  497. WriteSeq32 *module;
  498. void onAction(EventAction &e) override {
  499. module->resetOnRun = !module->resetOnRun;
  500. }
  501. };
  502. Menu *createContextMenu() override {
  503. Menu *menu = ModuleWidget::createContextMenu();
  504. MenuLabel *spacerLabel = new MenuLabel();
  505. menu->addChild(spacerLabel);
  506. WriteSeq32 *module = dynamic_cast<WriteSeq32*>(this->module);
  507. assert(module);
  508. MenuLabel *themeLabel = new MenuLabel();
  509. themeLabel->text = "Panel Theme";
  510. menu->addChild(themeLabel);
  511. PanelThemeItem *lightItem = new PanelThemeItem();
  512. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  513. lightItem->module = module;
  514. lightItem->theme = 0;
  515. menu->addChild(lightItem);
  516. PanelThemeItem *darkItem = new PanelThemeItem();
  517. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  518. darkItem->module = module;
  519. darkItem->theme = 1;
  520. menu->addChild(darkItem);
  521. menu->addChild(new MenuLabel());// empty line
  522. MenuLabel *settingsLabel = new MenuLabel();
  523. settingsLabel->text = "Settings";
  524. menu->addChild(settingsLabel);
  525. ResetOnRunItem *rorItem = MenuItem::create<ResetOnRunItem>("Reset on Run", CHECKMARK(module->resetOnRun));
  526. rorItem->module = module;
  527. menu->addChild(rorItem);
  528. return menu;
  529. }
  530. WriteSeq32Widget(WriteSeq32 *module) : ModuleWidget(module) {
  531. // Main panel from Inkscape
  532. DynamicSVGPanel *panel = new DynamicSVGPanel();
  533. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/WriteSeq32.svg")));
  534. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/WriteSeq32_dark.svg")));
  535. box.size = panel->box.size;
  536. panel->mode = &module->panelTheme;
  537. addChild(panel);
  538. // Screws
  539. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  540. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme));
  541. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  542. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme));
  543. // Column rulers (horizontal positions)
  544. static const int columnRuler0 = 25;
  545. static const int columnnRulerStep = 69;
  546. static const int columnRuler1 = columnRuler0 + columnnRulerStep;
  547. static const int columnRuler2 = columnRuler1 + columnnRulerStep;
  548. static const int columnRuler3 = columnRuler2 + columnnRulerStep;
  549. static const int columnRuler4 = columnRuler3 + columnnRulerStep;
  550. static const int columnRuler5 = columnRuler4 + columnnRulerStep - 15;
  551. // Row rulers (vertical positions)
  552. static const int rowRuler0 = 172;
  553. static const int rowRulerStep = 49;
  554. static const int rowRuler1 = rowRuler0 + rowRulerStep;
  555. static const int rowRuler2 = rowRuler1 + rowRulerStep;
  556. static const int rowRuler3 = rowRuler2 + rowRulerStep + 4;
  557. // ****** Top portion ******
  558. static const int yRulerTopLEDs = 42;
  559. static const int yRulerTopSwitches = yRulerTopLEDs-11;
  560. // Autostep, sharp/flat and quantize switches
  561. // Autostep
  562. addParam(ParamWidget::create<CKSS>(Vec(columnRuler0+3+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f));
  563. // Sharp/flat
  564. addParam(ParamWidget::create<CKSS>(Vec(columnRuler4+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::SHARP_PARAM, 0.0f, 1.0f, 1.0f));
  565. // Quantize
  566. addParam(ParamWidget::create<CKSS>(Vec(columnRuler5+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::QUANTIZE_PARAM, 0.0f, 1.0f, 1.0f));
  567. // Window LED buttons
  568. static const float wLightsPosX = 140.0f;
  569. static const float wLightsIntX = 35.0f;
  570. for (int i = 0; i < 4; i++) {
  571. addParam(ParamWidget::create<LEDButton>(Vec(wLightsPosX + i * wLightsIntX, yRulerTopLEDs - 4.4f), module, WriteSeq32::WINDOW_PARAM + i, 0.0f, 1.0f, 0.0f));
  572. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(wLightsPosX + 4.4f + i * wLightsIntX, yRulerTopLEDs), module, WriteSeq32::WINDOW_LIGHTS + i));
  573. }
  574. // Prepare 8 positions for step lights, gate lights and notes display
  575. module->notesPos[0] = 9;// this is also used to help line up LCD digits with LEDbuttons and avoid bad horizontal scaling with long str in display
  576. for (int i = 1; i < 8; i++) {
  577. module->notesPos[i] = module->notesPos[i-1] + 46;
  578. }
  579. // Notes display
  580. NotesDisplayWidget *displayNotes = new NotesDisplayWidget();
  581. displayNotes->box.pos = Vec(12, 76);
  582. displayNotes->box.size = Vec(381, 30);
  583. displayNotes->module = module;
  584. addChild(displayNotes);
  585. // Step LEDs (must be done after Notes display such that LED glow will overlay the notes display
  586. static const int yRulerStepLEDs = 65;
  587. for (int i = 0; i < 8; i++) {
  588. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(module->notesPos[i]+25.0f+1.5f, yRulerStepLEDs), module, WriteSeq32::STEP_LIGHTS + i));
  589. }
  590. // Gates LED buttons
  591. static const int yRulerT2 = 119.0f;
  592. for (int i = 0; i < 8; i++) {
  593. addParam(ParamWidget::create<LEDButton>(Vec(module->notesPos[i]+25.0f-4.4f, yRulerT2-4.4f), module, WriteSeq32::GATE_PARAM + i, 0.0f, 1.0f, 0.0f));
  594. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(module->notesPos[i]+25.0f, yRulerT2), module, WriteSeq32::GATE_LIGHTS + i));
  595. }
  596. // ****** Bottom portion ******
  597. // Column 0
  598. // Channel button
  599. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler0+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::CHANNEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  600. // Channel LEDS
  601. static const int chanLEDoffsetX = 25;
  602. static const int chanLEDoffsetY[4] = {-20, -8, 4, 16};
  603. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[0] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 0));
  604. addChild(ModuleLightWidget::create<MediumLight<YellowLight>>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[1] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 1));
  605. addChild(ModuleLightWidget::create<MediumLight<OrangeLight>>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[2] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 2));
  606. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[3] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 3));
  607. // Copy/paste switches
  608. addParam(ParamWidget::create<TL1105>(Vec(columnRuler0-10, rowRuler1+offsetTL1105), module, WriteSeq32::COPY_PARAM, 0.0f, 1.0f, 0.0f));
  609. addParam(ParamWidget::create<TL1105>(Vec(columnRuler0+20, rowRuler1+offsetTL1105), module, WriteSeq32::PASTE_PARAM, 0.0f, 1.0f, 0.0f));
  610. // Paste sync (and light)
  611. addParam(ParamWidget::create<CKSSThreeInv>(Vec(columnRuler0+hOffsetCKSS, rowRuler2+vOffsetCKSSThree), module, WriteSeq32::PASTESYNC_PARAM, 0.0f, 2.0f, 0.0f));
  612. addChild(ModuleLightWidget::create<SmallLight<RedLight>>(Vec(columnRuler0 + 41, rowRuler2 + 14), module, WriteSeq32::PENDING_LIGHT));
  613. // Run CV input
  614. addInput(createDynamicPort<IMPort>(Vec(columnRuler0, rowRuler3), Port::INPUT, module, WriteSeq32::RUNCV_INPUT, &module->panelTheme));
  615. // Column 1
  616. // Step L button
  617. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler1+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  618. // Run LED bezel and light
  619. addParam(ParamWidget::create<LEDBezel>(Vec(columnRuler1+offsetLEDbezel, rowRuler1+offsetLEDbezel), module, WriteSeq32::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  620. addChild(ModuleLightWidget::create<MuteLight<GreenLight>>(Vec(columnRuler1+offsetLEDbezel+offsetLEDbezelLight, rowRuler1+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq32::RUN_LIGHT));
  621. // Gate input
  622. addInput(createDynamicPort<IMPort>(Vec(columnRuler1, rowRuler2), Port::INPUT, module, WriteSeq32::GATE_INPUT, &module->panelTheme));
  623. // Step L input
  624. addInput(createDynamicPort<IMPort>(Vec(columnRuler1, rowRuler3), Port::INPUT, module, WriteSeq32::STEPL_INPUT, &module->panelTheme));
  625. // Column 2
  626. // Step R button
  627. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler2+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  628. // Write button and light
  629. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler2+offsetCKD6b, rowRuler1+offsetCKD6b), module, WriteSeq32::WRITE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  630. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(Vec(columnRuler2 -12, rowRuler1 - 12), module, WriteSeq32::WRITE_LIGHT));
  631. // CV input
  632. addInput(createDynamicPort<IMPort>(Vec(columnRuler2, rowRuler2), Port::INPUT, module, WriteSeq32::CV_INPUT, &module->panelTheme));
  633. // Step R input
  634. addInput(createDynamicPort<IMPort>(Vec(columnRuler2, rowRuler3), Port::INPUT, module, WriteSeq32::STEPR_INPUT, &module->panelTheme));
  635. // Column 3
  636. // Steps display
  637. StepsDisplayWidget *displaySteps = new StepsDisplayWidget();
  638. displaySteps->box.pos = Vec(columnRuler3-7, rowRuler0+vOffsetDisplay);
  639. displaySteps->box.size = Vec(40, 30);// 2 characters
  640. displaySteps->valueKnob = &module->params[WriteSeq32::STEPS_PARAM].value;
  641. addChild(displaySteps);
  642. // Steps knob
  643. addParam(createDynamicParam<IMBigSnapKnob>(Vec(columnRuler3+offsetIMBigKnob, rowRuler1+offsetIMBigKnob), module, WriteSeq32::STEPS_PARAM, 1.0f, 32.0f, 32.0f, &module->panelTheme));
  644. // Monitor
  645. addParam(ParamWidget::create<CKSSH>(Vec(columnRuler3+hOffsetCKSSH, rowRuler2+vOffsetCKSSH), module, WriteSeq32::MONITOR_PARAM, 0.0f, 1.0f, 0.0f));
  646. // Write input
  647. addInput(createDynamicPort<IMPort>(Vec(columnRuler3, rowRuler3), Port::INPUT, module, WriteSeq32::WRITE_INPUT, &module->panelTheme));
  648. // Column 4
  649. // Outputs
  650. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler0), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 0, &module->panelTheme));
  651. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler1), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 1, &module->panelTheme));
  652. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler2), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 2, &module->panelTheme));
  653. // Reset
  654. addInput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler3), Port::INPUT, module, WriteSeq32::RESET_INPUT, &module->panelTheme));
  655. // Column 5
  656. // Gates
  657. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler0), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 0, &module->panelTheme));
  658. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler1), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 1, &module->panelTheme));
  659. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler2), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 2, &module->panelTheme));
  660. // Clock
  661. addInput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler3), Port::INPUT, module, WriteSeq32::CLOCK_INPUT, &module->panelTheme));
  662. }
  663. };
  664. } // namespace rack_plugin_ImpromptuModular
  665. using namespace rack_plugin_ImpromptuModular;
  666. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, WriteSeq32) {
  667. Model *modelWriteSeq32 = Model::create<WriteSeq32, WriteSeq32Widget>("Impromptu Modular", "Write-Seq-32", "SEQ - Write-Seq-32", SEQUENCER_TAG);
  668. return modelWriteSeq32;
  669. }
  670. /*CHANGE LOG
  671. 0.6.7:
  672. no reset on run by default, with switch added in context menu
  673. reset does not revert chan number to 1
  674. */