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.

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