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.

861 lines
31KB

  1. //***********************************************************************************************
  2. //Four channel 64-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. namespace rack_plugin_ImpromptuModular {
  11. struct WriteSeq64 : Module {
  12. enum ParamIds {
  13. SHARP_PARAM,
  14. QUANTIZE_PARAM,
  15. GATE_PARAM,
  16. CHANNEL_PARAM,
  17. COPY_PARAM,
  18. PASTE_PARAM,
  19. RUN_PARAM,
  20. WRITE_PARAM,
  21. STEPL_PARAM,
  22. MONITOR_PARAM,
  23. STEPR_PARAM,
  24. STEPS_PARAM,
  25. STEP_PARAM,
  26. AUTOSTEP_PARAM,
  27. RESET_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. CLOCK12_INPUT,
  39. CLOCK34_INPUT,
  40. RESET_INPUT,
  41. // -- 0.6.2
  42. RUNCV_INPUT,
  43. NUM_INPUTS
  44. };
  45. enum OutputIds {
  46. ENUMS(CV_OUTPUTS, 4),
  47. ENUMS(GATE_OUTPUTS, 4),
  48. NUM_OUTPUTS
  49. };
  50. enum LightIds {
  51. ENUMS(GATE_LIGHT, 2),// room for GreenRed
  52. RUN_LIGHT,
  53. RESET_LIGHT,
  54. ENUMS(WRITE_LIGHT, 2),// room for GreenRed
  55. PENDING_LIGHT,
  56. NUM_LIGHTS
  57. };
  58. // Need to save
  59. int panelTheme = 0;
  60. bool running;
  61. //int indexChannel;
  62. int indexStep[5];// [0;63] each
  63. int indexSteps[5];// [1;64] each
  64. float cv[5][64];
  65. int gates[5][64];
  66. bool resetOnRun;
  67. // No need to save
  68. float cvCPbuffer[64];// copy paste buffer for CVs
  69. int gateCPbuffer[64];// copy paste buffer for gates
  70. int stepsCPbuffer;
  71. long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste
  72. int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits
  73. long clockIgnoreOnReset;
  74. unsigned int lightRefreshCounter = 0;
  75. int stepKnob = 0;
  76. int stepsKnob = 0;
  77. float resetLight = 0.0f;
  78. Trigger clock12Trigger;
  79. Trigger clock34Trigger;
  80. Trigger resetTrigger;
  81. Trigger runningTrigger;
  82. Trigger stepLTrigger;
  83. Trigger stepRTrigger;
  84. Trigger copyTrigger;
  85. Trigger pasteTrigger;
  86. Trigger writeTrigger;
  87. Trigger gateTrigger;
  88. inline float quantize(float cv, bool enable) {
  89. return enable ? (roundf(cv * 12.0f) / 12.0f) : cv;
  90. }
  91. inline int calcChan() {
  92. return clamp((int)(params[CHANNEL_PARAM].value + 0.5f), 0, 4);
  93. }
  94. WriteSeq64() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  95. onReset();
  96. }
  97. void onReset() override {
  98. running = true;
  99. //indexChannel = 0;
  100. for (int c = 0; c < 5; c++) {
  101. indexStep[c] = 0;
  102. indexSteps[c] = 64;
  103. for (int s = 0; s < 64; s++) {
  104. cv[c][s] = 0.0f;
  105. gates[c][s] = 1;
  106. }
  107. }
  108. for (int s = 0; s < 64; s++) {
  109. cvCPbuffer[s] = 0.0f;
  110. gateCPbuffer[s] = 1;
  111. }
  112. stepsCPbuffer = 64;
  113. infoCopyPaste = 0l;
  114. pendingPaste = 0;
  115. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  116. resetOnRun = false;
  117. }
  118. void onRandomize() override {
  119. int indexChannel = calcChan();
  120. // for (int c = 0; c < 5; c++) {
  121. // indexStep[c] = 0;
  122. // indexSteps[c] = 64;
  123. // for (int s = 0; s < 64; s++) {
  124. // cv[c][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f);
  125. // gates[c][s] = (randomUniform() > 0.5f) ? 1 : 0;
  126. // }
  127. // }
  128. // for (int s = 0; s < 64; s++) {
  129. // cvCPbuffer[s] = 0.0f;
  130. // gateCPbuffer[s] = 1;
  131. // }
  132. // stepsCPbuffer = 64;
  133. for (int s = 0; s < 64; s++) {
  134. cv[indexChannel][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f);
  135. gates[indexChannel][s] = (randomUniform() > 0.5f) ? 1 : 0;
  136. }
  137. pendingPaste = 0;
  138. }
  139. json_t *toJson() override {
  140. json_t *rootJ = json_object();
  141. // panelTheme
  142. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  143. // running
  144. json_object_set_new(rootJ, "running", json_boolean(running));
  145. // indexChannel
  146. //json_object_set_new(rootJ, "indexChannel", json_integer(indexChannel));
  147. // indexStep
  148. json_t *indexStepJ = json_array();
  149. for (int c = 0; c < 5; c++)
  150. json_array_insert_new(indexStepJ, c, json_integer(indexStep[c]));
  151. json_object_set_new(rootJ, "indexStep", indexStepJ);
  152. // indexSteps
  153. json_t *indexStepsJ = json_array();
  154. for (int c = 0; c < 5; c++)
  155. json_array_insert_new(indexStepsJ, c, json_integer(indexSteps[c]));
  156. json_object_set_new(rootJ, "indexSteps", indexStepsJ);
  157. // CV
  158. json_t *cvJ = json_array();
  159. for (int c = 0; c < 5; c++)
  160. for (int s = 0; s < 64; s++) {
  161. json_array_insert_new(cvJ, s + (c<<6), json_real(cv[c][s]));
  162. }
  163. json_object_set_new(rootJ, "cv", cvJ);
  164. // gates
  165. json_t *gatesJ = json_array();
  166. for (int c = 0; c < 5; c++)
  167. for (int s = 0; s < 64; s++) {
  168. json_array_insert_new(gatesJ, s + (c<<6), json_integer(gates[c][s]));
  169. }
  170. json_object_set_new(rootJ, "gates", gatesJ);
  171. // resetOnRun
  172. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  173. return rootJ;
  174. }
  175. void fromJson(json_t *rootJ) override {
  176. // panelTheme
  177. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  178. if (panelThemeJ)
  179. panelTheme = json_integer_value(panelThemeJ);
  180. // running
  181. json_t *runningJ = json_object_get(rootJ, "running");
  182. if (runningJ)
  183. running = json_is_true(runningJ);
  184. // indexChannel
  185. // json_t *indexChannelJ = json_object_get(rootJ, "indexChannel");
  186. // if (indexChannelJ)
  187. // indexChannel = json_integer_value(indexChannelJ);
  188. // indexStep
  189. json_t *indexStepJ = json_object_get(rootJ, "indexStep");
  190. if (indexStepJ)
  191. for (int c = 0; c < 5; c++)
  192. {
  193. json_t *indexStepArrayJ = json_array_get(indexStepJ, c);
  194. if (indexStepArrayJ)
  195. indexStep[c] = json_integer_value(indexStepArrayJ);
  196. }
  197. // indexSteps
  198. json_t *indexStepsJ = json_object_get(rootJ, "indexSteps");
  199. if (indexStepsJ)
  200. for (int c = 0; c < 5; c++)
  201. {
  202. json_t *indexStepsArrayJ = json_array_get(indexStepsJ, c);
  203. if (indexStepsArrayJ)
  204. indexSteps[c] = json_integer_value(indexStepsArrayJ);
  205. }
  206. // CV
  207. json_t *cvJ = json_object_get(rootJ, "cv");
  208. if (cvJ) {
  209. for (int c = 0; c < 5; c++)
  210. for (int i = 0; i < 64; i++) {
  211. json_t *cvArrayJ = json_array_get(cvJ, i + (c<<6));
  212. if (cvArrayJ)
  213. cv[c][i] = json_number_value(cvArrayJ);
  214. }
  215. }
  216. // gates
  217. json_t *gatesJ = json_object_get(rootJ, "gates");
  218. if (gatesJ) {
  219. for (int c = 0; c < 5; c++)
  220. for (int i = 0; i < 64; i++) {
  221. json_t *gateJ = json_array_get(gatesJ, i + (c<<6));
  222. if (gateJ)
  223. gates[c][i] = json_integer_value(gateJ);
  224. }
  225. }
  226. // resetOnRun
  227. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  228. if (resetOnRunJ)
  229. resetOnRun = json_is_true(resetOnRunJ);
  230. }
  231. void step() override {
  232. static const float copyPasteInfoTime = 0.7f;// seconds
  233. //********** Buttons, knobs, switches and inputs **********
  234. int indexChannel = calcChan();
  235. bool canEdit = !running || (indexChannel == 4);
  236. // Run state button
  237. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) {// no input refresh here, don't want to introduce startup skew
  238. running = !running;
  239. //pendingPaste = 0;// no pending pastes across run state toggles
  240. if (running) {
  241. if (resetOnRun) {
  242. for (int c = 0; c < 5; c++)
  243. indexStep[c] = 0;
  244. }
  245. if (resetOnRun || clockIgnoreOnRun)
  246. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  247. }
  248. }
  249. if ((lightRefreshCounter & userInputsStepSkipMask) == 0) {
  250. // Copy button
  251. if (copyTrigger.process(params[COPY_PARAM].value)) {
  252. infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  253. for (int s = 0; s < 64; s++) {
  254. cvCPbuffer[s] = cv[indexChannel][s];
  255. gateCPbuffer[s] = gates[indexChannel][s];
  256. }
  257. stepsCPbuffer = indexSteps[indexChannel];
  258. pendingPaste = 0;
  259. }
  260. // Paste button
  261. if (pasteTrigger.process(params[PASTE_PARAM].value)) {
  262. if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 4) {
  263. // Paste realtime, no pending to schedule
  264. infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  265. for (int s = 0; s < 64; s++) {
  266. cv[indexChannel][s] = cvCPbuffer[s];
  267. gates[indexChannel][s] = gateCPbuffer[s];
  268. }
  269. indexSteps[indexChannel] = stepsCPbuffer;
  270. if (indexStep[indexChannel] >= stepsCPbuffer)
  271. indexStep[indexChannel] = stepsCPbuffer - 1;
  272. pendingPaste = 0;
  273. }
  274. else {
  275. pendingPaste = params[PASTESYNC_PARAM].value > 1.5f ? 2 : 1;
  276. pendingPaste |= indexChannel<<2; // add paste destination channel into pendingPaste
  277. }
  278. }
  279. // Gate button
  280. if (gateTrigger.process(params[GATE_PARAM].value)) {
  281. if (params[GATE_PARAM].value > 1.5f) {// right button click
  282. gates[indexChannel][indexStep[indexChannel]] = 0;
  283. }
  284. else {
  285. gates[indexChannel][indexStep[indexChannel]]++;
  286. if (gates[indexChannel][indexStep[indexChannel]] > 2)
  287. gates[indexChannel][indexStep[indexChannel]] = 0;
  288. }
  289. }
  290. // Steps knob
  291. float stepsParamValue = params[STEPS_PARAM].value;
  292. int newStepsKnob = (int)roundf(stepsParamValue * 10.0f);
  293. if (stepsParamValue == 0.0f)// true when constructor or fromJson() occured
  294. stepsKnob = newStepsKnob;
  295. if (newStepsKnob != stepsKnob) {
  296. if (abs(newStepsKnob - stepsKnob) <= 3) // avoid discontinuous step (initialize for example)
  297. indexSteps[indexChannel] = clamp( indexSteps[indexChannel] + newStepsKnob - stepsKnob, 1, 64);
  298. stepsKnob = newStepsKnob;
  299. }
  300. // Step knob
  301. float stepParamValue = params[STEP_PARAM].value;
  302. int newStepKnob = (int)roundf(stepParamValue * 10.0f);
  303. if (stepParamValue == 0.0f)// true when constructor or fromJson() occured
  304. stepKnob = newStepKnob;
  305. if (newStepKnob != stepKnob) {
  306. if (canEdit && (abs(newStepKnob - stepKnob) <= 3) ) // avoid discontinuous step (initialize for example)
  307. indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + newStepKnob - stepKnob, indexSteps[indexChannel]);
  308. stepKnob = newStepKnob;// must do this step whether running or not
  309. }
  310. // If steps knob goes down past step, step knob will not get triggered above, so reduce accordingly
  311. for (int c = 0; c < 5; c++)
  312. if (indexStep[c] >= indexSteps[c])
  313. indexStep[c] = indexSteps[c] - 1;
  314. // Write button and input (must be before StepL and StepR in case route gate simultaneously to Step R and Write for example)
  315. // (write must be to correct step)
  316. if (writeTrigger.process(params[WRITE_PARAM].value + inputs[WRITE_INPUT].value)) {
  317. if (canEdit) {
  318. // CV
  319. cv[indexChannel][indexStep[indexChannel]] = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);
  320. // Gate
  321. if (inputs[GATE_INPUT].active)
  322. gates[indexChannel][indexStep[indexChannel]] = (inputs[GATE_INPUT].value >= 1.0f) ? 1 : 0;
  323. // Autostep
  324. if (params[AUTOSTEP_PARAM].value > 0.5f)
  325. indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + 1, indexSteps[indexChannel]);
  326. }
  327. }
  328. // Step L button
  329. if (stepLTrigger.process(params[STEPL_PARAM].value + inputs[STEPL_INPUT].value)) {
  330. if (canEdit) {
  331. indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] - 1, indexSteps[indexChannel]);
  332. }
  333. }
  334. // Step R button
  335. if (stepRTrigger.process(params[STEPR_PARAM].value + inputs[STEPR_INPUT].value)) {
  336. if (canEdit) {
  337. indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + 1, indexSteps[indexChannel]);
  338. }
  339. }
  340. }// userInputs refresh
  341. //********** Clock and reset **********
  342. // Clock
  343. if (running && clockIgnoreOnReset == 0l) {
  344. bool clk12step = clock12Trigger.process(inputs[CLOCK12_INPUT].value);
  345. bool clk34step = ((!inputs[CLOCK34_INPUT].active) && clk12step) ||
  346. clock34Trigger.process(inputs[CLOCK34_INPUT].value);
  347. if (clk12step) {
  348. indexStep[0] = moveIndex(indexStep[0], indexStep[0] + 1, indexSteps[0]);
  349. indexStep[1] = moveIndex(indexStep[1], indexStep[1] + 1, indexSteps[1]);
  350. }
  351. if (clk34step) {
  352. indexStep[2] = moveIndex(indexStep[2], indexStep[2] + 1, indexSteps[2]);
  353. indexStep[3] = moveIndex(indexStep[3], indexStep[3] + 1, indexSteps[3]);
  354. }
  355. // Pending paste on clock or end of seq
  356. if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep[indexChannel] == 0) ) {
  357. if ( (clk12step && (indexChannel == 0 || indexChannel == 1)) ||
  358. (clk34step && (indexChannel == 2 || indexChannel == 3)) ) {
  359. infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips);
  360. int pasteChannel = pendingPaste>>2;
  361. for (int s = 0; s < 64; s++) {
  362. cv[pasteChannel][s] = cvCPbuffer[s];
  363. gates[pasteChannel][s] = gateCPbuffer[s];
  364. }
  365. indexSteps[pasteChannel] = stepsCPbuffer;
  366. if (indexStep[pasteChannel] >= stepsCPbuffer)
  367. indexStep[pasteChannel] = stepsCPbuffer - 1;
  368. pendingPaste = 0;
  369. }
  370. }
  371. }
  372. // Reset
  373. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  374. for (int t = 0; t < 5; t++)
  375. indexStep[t] = 0;
  376. resetLight = 1.0f;
  377. pendingPaste = 0;
  378. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  379. clock12Trigger.reset();
  380. clock34Trigger.reset();
  381. }
  382. //********** Outputs and lights **********
  383. // CV and gate outputs (staging area not used)
  384. if (running) {
  385. bool clockHigh = false;
  386. bool retriggingOnReset = (clockIgnoreOnReset != 0l && retrigGatesOnReset);
  387. for (int i = 0; i < 4; i++) {
  388. outputs[CV_OUTPUTS + i].value = cv[i][indexStep[i]];
  389. clockHigh = i < 2 ? clock12Trigger.isHigh() : clock34Trigger.isHigh();
  390. outputs[GATE_OUTPUTS + i].value = ( (((gates[i][indexStep[i]] == 1) && clockHigh) || gates[i][indexStep[i]] == 2) && !retriggingOnReset ) ? 10.0f : 0.0f;
  391. }
  392. }
  393. else {
  394. 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
  395. for (int i = 0; i < 4; i++) {
  396. // CV
  397. if (params[MONITOR_PARAM].value > 0.5f)
  398. outputs[CV_OUTPUTS + i].value = cv[i][indexStep[i]];// each CV out monitors the current step CV of that channel
  399. else
  400. 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)
  401. // Gate
  402. outputs[GATE_OUTPUTS + i].value = ((i == indexChannel) && !muteGate) ? 10.0f : 0.0f;
  403. }
  404. }
  405. lightRefreshCounter++;
  406. if (lightRefreshCounter >= displayRefreshStepSkips) {
  407. lightRefreshCounter = 0;
  408. // Gate light
  409. float green = 0.0f;
  410. float red = 0.0f;
  411. if (gates[indexChannel][indexStep[indexChannel]] != 0) {
  412. if (gates[indexChannel][indexStep[indexChannel]] == 1) green = 1.0f;
  413. else { green = 0.2f; red = 1.0f;}
  414. }
  415. lights[GATE_LIGHT + 0].value = green;
  416. lights[GATE_LIGHT + 1].value = red;
  417. // Reset light
  418. lights[RESET_LIGHT].value = resetLight;
  419. resetLight -= (resetLight / lightLambda) * engineGetSampleTime() * displayRefreshStepSkips;
  420. // Run light
  421. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  422. // Write allowed light
  423. lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f;
  424. lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f;
  425. // Pending paste light
  426. lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f);
  427. if (infoCopyPaste != 0l) {
  428. if (infoCopyPaste > 0l)
  429. infoCopyPaste --;
  430. if (infoCopyPaste < 0l)
  431. infoCopyPaste ++;
  432. }
  433. }// lightRefreshCounter
  434. if (clockIgnoreOnReset > 0l)
  435. clockIgnoreOnReset--;
  436. }
  437. };
  438. struct WriteSeq64Widget : ModuleWidget {
  439. struct NoteDisplayWidget : TransparentWidget {
  440. WriteSeq64 *module;
  441. std::shared_ptr<Font> font;
  442. char text[7];
  443. NoteDisplayWidget() {
  444. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  445. }
  446. void cvToStr(void) {
  447. int indexChannel = module->calcChan();
  448. float cvVal = module->cv[indexChannel][module->indexStep[indexChannel]];
  449. if (module->infoCopyPaste != 0l) {
  450. if (module->infoCopyPaste > 0l) {// if copy then display "Copy"
  451. snprintf(text, 7, "COPY");
  452. }
  453. else {// paste then display "Paste"
  454. snprintf(text, 7, "PASTE");
  455. }
  456. }
  457. else {
  458. if (module->params[WriteSeq64::SHARP_PARAM].value > 0.5f) {// show notes
  459. text[0] = ' ';
  460. printNote(cvVal, &text[1], module->params[WriteSeq64::SHARP_PARAM].value < 1.5f);
  461. }
  462. else {// show volts
  463. float cvValPrint = fabsf(cvVal);
  464. cvValPrint = (cvValPrint > 9.999f) ? 9.999f : cvValPrint;
  465. snprintf(text, 7, " %4.3f", cvValPrint);// Four-wide, three positions after the decimal, left-justified
  466. text[0] = (cvVal<0.0f) ? '-' : ' ';
  467. text[2] = ',';
  468. }
  469. }
  470. }
  471. void draw(NVGcontext *vg) override {
  472. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  473. nvgFontFaceId(vg, font->handle);
  474. nvgTextLetterSpacing(vg, -1.5);
  475. Vec textPos = Vec(6, 24);
  476. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  477. nvgText(vg, textPos.x, textPos.y, "~~~~~~", NULL);
  478. nvgFillColor(vg, textColor);
  479. cvToStr();
  480. nvgText(vg, textPos.x, textPos.y, text, NULL);
  481. }
  482. };
  483. struct StepsDisplayWidget : TransparentWidget {
  484. WriteSeq64 *module;
  485. std::shared_ptr<Font> font;
  486. StepsDisplayWidget() {
  487. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  488. }
  489. void draw(NVGcontext *vg) override {
  490. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  491. nvgFontFaceId(vg, font->handle);
  492. //nvgTextLetterSpacing(vg, 2.5);
  493. Vec textPos = Vec(6, 24);
  494. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  495. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  496. nvgFillColor(vg, textColor);
  497. char displayStr[3];
  498. snprintf(displayStr, 3, "%2u", (unsigned) module->indexSteps[module->calcChan()]);
  499. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  500. }
  501. };
  502. struct StepDisplayWidget : TransparentWidget {
  503. WriteSeq64 *module;
  504. std::shared_ptr<Font> font;
  505. StepDisplayWidget() {
  506. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  507. }
  508. void draw(NVGcontext *vg) override {
  509. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  510. nvgFontFaceId(vg, font->handle);
  511. //nvgTextLetterSpacing(vg, 2.5);
  512. Vec textPos = Vec(6, 24);
  513. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  514. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  515. nvgFillColor(vg, textColor);
  516. char displayStr[3];
  517. snprintf(displayStr, 3, "%2u", (unsigned) module->indexStep[module->calcChan()] + 1);
  518. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  519. }
  520. };
  521. struct ChannelDisplayWidget : TransparentWidget {
  522. WriteSeq64 *module;
  523. //int *indexTrack;
  524. std::shared_ptr<Font> font;
  525. ChannelDisplayWidget() {
  526. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  527. }
  528. void draw(NVGcontext *vg) override {
  529. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  530. nvgFontFaceId(vg, font->handle);
  531. //nvgTextLetterSpacing(vg, 2.5);
  532. Vec textPos = Vec(6, 24);
  533. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  534. nvgText(vg, textPos.x, textPos.y, "~", NULL);
  535. nvgFillColor(vg, textColor);
  536. char displayStr[2];
  537. displayStr[0] = 0x30 + (char) (module->calcChan() + 1);
  538. displayStr[1] = 0;
  539. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  540. }
  541. };
  542. struct PanelThemeItem : MenuItem {
  543. WriteSeq64 *module;
  544. int theme;
  545. void onAction(EventAction &e) override {
  546. module->panelTheme = theme;
  547. }
  548. void step() override {
  549. rightText = (module->panelTheme == theme) ? "✔" : "";
  550. }
  551. };
  552. struct ResetOnRunItem : MenuItem {
  553. WriteSeq64 *module;
  554. void onAction(EventAction &e) override {
  555. module->resetOnRun = !module->resetOnRun;
  556. }
  557. };
  558. Menu *createContextMenu() override {
  559. Menu *menu = ModuleWidget::createContextMenu();
  560. MenuLabel *spacerLabel = new MenuLabel();
  561. menu->addChild(spacerLabel);
  562. WriteSeq64 *module = dynamic_cast<WriteSeq64*>(this->module);
  563. assert(module);
  564. MenuLabel *themeLabel = new MenuLabel();
  565. themeLabel->text = "Panel Theme";
  566. menu->addChild(themeLabel);
  567. PanelThemeItem *lightItem = new PanelThemeItem();
  568. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  569. lightItem->module = module;
  570. lightItem->theme = 0;
  571. menu->addChild(lightItem);
  572. PanelThemeItem *darkItem = new PanelThemeItem();
  573. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  574. darkItem->module = module;
  575. darkItem->theme = 1;
  576. menu->addChild(darkItem);
  577. menu->addChild(new MenuLabel());// empty line
  578. MenuLabel *settingsLabel = new MenuLabel();
  579. settingsLabel->text = "Settings";
  580. menu->addChild(settingsLabel);
  581. ResetOnRunItem *rorItem = MenuItem::create<ResetOnRunItem>("Reset on run", CHECKMARK(module->resetOnRun));
  582. rorItem->module = module;
  583. menu->addChild(rorItem);
  584. return menu;
  585. }
  586. WriteSeq64Widget(WriteSeq64 *module) : ModuleWidget(module) {
  587. // Main panel from Inkscape
  588. DynamicSVGPanel *panel = new DynamicSVGPanel();
  589. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/WriteSeq64.svg")));
  590. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/WriteSeq64_dark.svg")));
  591. box.size = panel->box.size;
  592. panel->mode = &module->panelTheme;
  593. addChild(panel);
  594. // Screws
  595. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  596. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme));
  597. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  598. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme));
  599. // ****** Top portion ******
  600. static const int rowRulerT0 = 56;
  601. static const int columnRulerT0 = 22;
  602. static const int columnRulerT1 = columnRulerT0 + 58;
  603. static const int columnRulerT2 = columnRulerT1 + 50;
  604. static const int columnRulerT3 = columnRulerT2 + 43;
  605. static const int columnRulerT4 = columnRulerT3 + 175;
  606. // Channel display
  607. ChannelDisplayWidget *channelTrack = new ChannelDisplayWidget();
  608. channelTrack->box.pos = Vec(columnRulerT0+1, rowRulerT0+vOffsetDisplay);
  609. channelTrack->box.size = Vec(24, 30);// 1 character
  610. channelTrack->module = module;
  611. addChild(channelTrack);
  612. // Step display
  613. StepDisplayWidget *displayStep = new StepDisplayWidget();
  614. displayStep->box.pos = Vec(columnRulerT1-8, rowRulerT0+vOffsetDisplay);
  615. displayStep->box.size = Vec(40, 30);// 2 characters
  616. displayStep->module = module;
  617. addChild(displayStep);
  618. // Gate LED
  619. addChild(createLight<MediumLight<GreenRedLight>>(Vec(columnRulerT2+offsetLEDbutton+offsetLEDbuttonLight, rowRulerT0+offsetLEDbutton+offsetLEDbuttonLight), module, WriteSeq64::GATE_LIGHT));
  620. // Note display
  621. NoteDisplayWidget *displayNote = new NoteDisplayWidget();
  622. displayNote->box.pos = Vec(columnRulerT3, rowRulerT0+vOffsetDisplay);
  623. displayNote->box.size = Vec(98, 30);// 6 characters (ex.: "-1,234")
  624. displayNote->module = module;
  625. addChild(displayNote);
  626. // Volt/sharp/flat switch
  627. addParam(createParam<CKSSThreeInvNoRandom>(Vec(columnRulerT3+114+hOffsetCKSS, rowRulerT0+vOffsetCKSSThree), module, WriteSeq64::SHARP_PARAM, 0.0f, 2.0f, 1.0f));
  628. // Steps display
  629. StepsDisplayWidget *displaySteps = new StepsDisplayWidget();
  630. displaySteps->box.pos = Vec(columnRulerT4-7, rowRulerT0+vOffsetDisplay);
  631. displaySteps->box.size = Vec(40, 30);// 2 characters
  632. displaySteps->module = module;
  633. addChild(displaySteps);
  634. static const int rowRulerT1 = 105;
  635. // Channel knob
  636. addParam(createDynamicParam<IMFivePosSmallKnob>(Vec(columnRulerT0+offsetCKD6b+1, rowRulerT1+offsetCKD6b+1), module, WriteSeq64::CHANNEL_PARAM, 0.0f, 4.0f, 0.0f, &module->panelTheme));
  637. // Step knob
  638. addParam(createDynamicParam<IMBigKnobInf>(Vec(columnRulerT1+offsetIMBigKnob, rowRulerT1+offsetIMBigKnob), module, WriteSeq64::STEP_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  639. // Gate button
  640. addParam(createDynamicParam<IMBigPushButtonWithRClick>(Vec(columnRulerT2-1+offsetCKD6b, rowRulerT1+offsetCKD6b), module, WriteSeq64::GATE_PARAM , 0.0f, 1.0f, 0.0f, &module->panelTheme));
  641. // Autostep
  642. addParam(createParam<CKSSNoRandom>(Vec(columnRulerT2+53+hOffsetCKSS, rowRulerT1+6+vOffsetCKSS), module, WriteSeq64::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f));
  643. // Quantize switch
  644. addParam(createParam<CKSSNoRandom>(Vec(columnRulerT2+110+hOffsetCKSS, rowRulerT1+6+vOffsetCKSS), module, WriteSeq64::QUANTIZE_PARAM, 0.0f, 1.0f, 1.0f));
  645. // Reset LED bezel and light
  646. addParam(createParam<LEDBezel>(Vec(columnRulerT2+164+offsetLEDbezel, rowRulerT1+6+offsetLEDbezel), module, WriteSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f));
  647. addChild(createLight<MuteLight<GreenLight>>(Vec(columnRulerT2+164+offsetLEDbezel+offsetLEDbezelLight, rowRulerT1+6+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq64::RESET_LIGHT));
  648. // Steps knob
  649. addParam(createDynamicParam<IMBigKnobInf>(Vec(columnRulerT4+offsetIMBigKnob, rowRulerT1+offsetIMBigKnob), module, WriteSeq64::STEPS_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme));
  650. // ****** Bottom portion ******
  651. // Column rulers (horizontal positions)
  652. static const int columnRuler0 = 25;
  653. static const int columnnRulerStep = 69;
  654. static const int columnRuler1 = columnRuler0 + columnnRulerStep;
  655. static const int columnRuler2 = columnRuler1 + columnnRulerStep;
  656. static const int columnRuler3 = columnRuler2 + columnnRulerStep;
  657. static const int columnRuler4 = columnRuler3 + columnnRulerStep;
  658. static const int columnRuler5 = columnRuler4 + columnnRulerStep - 15;
  659. // Row rulers (vertical positions)
  660. static const int rowRuler0 = 172;
  661. static const int rowRulerStep = 49;
  662. static const int rowRuler1 = rowRuler0 + rowRulerStep;
  663. static const int rowRuler2 = rowRuler1 + rowRulerStep;
  664. static const int rowRuler3 = rowRuler2 + rowRulerStep;
  665. // Column 0
  666. // Copy/paste switches
  667. addParam(createDynamicParam<IMPushButton>(Vec(columnRuler0-10, rowRuler0+offsetTL1105), module, WriteSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  668. addParam(createDynamicParam<IMPushButton>(Vec(columnRuler0+20, rowRuler0+offsetTL1105), module, WriteSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  669. // Paste sync (and light)
  670. addParam(createParam<CKSSThreeInvNoRandom>(Vec(columnRuler0+hOffsetCKSS, rowRuler1+vOffsetCKSSThree), module, WriteSeq64::PASTESYNC_PARAM, 0.0f, 2.0f, 0.0f));
  671. addChild(createLight<SmallLight<RedLight>>(Vec(columnRuler0 + 41, rowRuler1 + 14), module, WriteSeq64::PENDING_LIGHT));
  672. // Gate input
  673. addInput(createDynamicPort<IMPort>(Vec(columnRuler0, rowRuler2), Port::INPUT, module, WriteSeq64::GATE_INPUT, &module->panelTheme));
  674. // Run CV input
  675. addInput(createDynamicPort<IMPort>(Vec(columnRuler0, rowRuler3), Port::INPUT, module, WriteSeq64::RUNCV_INPUT, &module->panelTheme));
  676. // Column 1
  677. // Step L button
  678. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler1+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq64::STEPL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  679. // Run LED bezel and light
  680. addParam(createParam<LEDBezel>(Vec(columnRuler1+offsetLEDbezel, rowRuler1+offsetLEDbezel), module, WriteSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f));
  681. addChild(createLight<MuteLight<GreenLight>>(Vec(columnRuler1+offsetLEDbezel+offsetLEDbezelLight, rowRuler1+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq64::RUN_LIGHT));
  682. // CV input
  683. addInput(createDynamicPort<IMPort>(Vec(columnRuler1, rowRuler2), Port::INPUT, module, WriteSeq64::CV_INPUT, &module->panelTheme));
  684. // Step L input
  685. addInput(createDynamicPort<IMPort>(Vec(columnRuler1, rowRuler3), Port::INPUT, module, WriteSeq64::STEPL_INPUT, &module->panelTheme));
  686. // Column 2
  687. // Step R button
  688. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler2+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq64::STEPR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  689. // Write button and light
  690. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRuler2+offsetCKD6b, rowRuler1+offsetCKD6b), module, WriteSeq64::WRITE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  691. addChild(createLight<SmallLight<GreenRedLight>>(Vec(columnRuler2 -12, rowRuler1 - 12), module, WriteSeq64::WRITE_LIGHT));
  692. // Monitor
  693. addParam(createParam<CKSSHNoRandom>(Vec(columnRuler2+hOffsetCKSSH, rowRuler2+vOffsetCKSSH), module, WriteSeq64::MONITOR_PARAM, 0.0f, 1.0f, 0.0f));
  694. // Step R input
  695. addInput(createDynamicPort<IMPort>(Vec(columnRuler2, rowRuler3), Port::INPUT, module, WriteSeq64::STEPR_INPUT, &module->panelTheme));
  696. // Column 3
  697. // Clocks
  698. addInput(createDynamicPort<IMPort>(Vec(columnRuler3, rowRuler0), Port::INPUT, module, WriteSeq64::CLOCK12_INPUT, &module->panelTheme));
  699. addInput(createDynamicPort<IMPort>(Vec(columnRuler3, rowRuler1), Port::INPUT, module, WriteSeq64::CLOCK34_INPUT, &module->panelTheme));
  700. // Reset
  701. addInput(createDynamicPort<IMPort>(Vec(columnRuler3, rowRuler2), Port::INPUT, module, WriteSeq64::RESET_INPUT, &module->panelTheme));
  702. // Write input
  703. addInput(createDynamicPort<IMPort>(Vec(columnRuler3, rowRuler3), Port::INPUT, module, WriteSeq64::WRITE_INPUT, &module->panelTheme));
  704. // Column 4 (CVs)
  705. // Outputs
  706. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler0), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 0, &module->panelTheme));
  707. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler1), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 1, &module->panelTheme));
  708. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler2), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 2, &module->panelTheme));
  709. addOutput(createDynamicPort<IMPort>(Vec(columnRuler4, rowRuler3), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 3, &module->panelTheme));
  710. // Column 5 (Gates)
  711. // Gates
  712. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler0), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 0, &module->panelTheme));
  713. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler1), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 1, &module->panelTheme));
  714. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler2), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 2, &module->panelTheme));
  715. addOutput(createDynamicPort<IMPort>(Vec(columnRuler5, rowRuler3), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 3, &module->panelTheme));
  716. }
  717. };
  718. } // namespace rack_plugin_ImpromptuModular
  719. using namespace rack_plugin_ImpromptuModular;
  720. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, WriteSeq64) {
  721. Model *modelWriteSeq64 = Model::create<WriteSeq64, WriteSeq64Widget>("Impromptu Modular", "Write-Seq-64", "SEQ - Write-Seq-64", SEQUENCER_TAG);
  722. return modelWriteSeq64;
  723. }
  724. /*CHANGE LOG
  725. 0.6.16:
  726. add 2nd gate mode for held gates (with right click to turn off)
  727. 0.6.12:
  728. input refresh optimization
  729. 0.6.7:
  730. no reset on run by default, with switch added in context menu
  731. reset does not revert chan number to 1
  732. */