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.

814 lines
32KB

  1. //***********************************************************************************************
  2. //Six channel 128-step 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. //Based on the BigButton sequencer by Look-Mum-No-Computer
  10. //https://www.youtube.com/watch?v=6ArDGcUqiWM
  11. //https://www.lookmumnocomputer.com/projects/#/big-button/
  12. //
  13. //***********************************************************************************************
  14. #include "ImpromptuModular.hpp"
  15. namespace rack_plugin_ImpromptuModular {
  16. struct BigButtonSeq2 : Module {
  17. enum ParamIds {
  18. CHAN_PARAM,
  19. LEN_PARAM,
  20. RND_PARAM,
  21. RESET_PARAM,
  22. CLEAR_PARAM,
  23. BANK_PARAM,
  24. DEL_PARAM,
  25. FILL_PARAM,
  26. BIG_PARAM,
  27. WRITEFILL_PARAM,
  28. QUANTIZEBIG_PARAM,
  29. SAMPLEHOLD_PARAM,
  30. CLOCK_PARAM,
  31. DISPMODE_PARAM,
  32. NUM_PARAMS
  33. };
  34. enum InputIds {
  35. CLK_INPUT,
  36. CHAN_INPUT,
  37. BIG_INPUT,
  38. LEN_INPUT,
  39. RND_INPUT,
  40. RESET_INPUT,
  41. CLEAR_INPUT,
  42. BANK_INPUT,
  43. DEL_INPUT,
  44. FILL_INPUT,
  45. CV_INPUT,
  46. NUM_INPUTS
  47. };
  48. enum OutputIds {
  49. ENUMS(CHAN_OUTPUTS, 6),
  50. ENUMS(CV_OUTPUTS, 6),
  51. NUM_OUTPUTS
  52. };
  53. enum LightIds {
  54. ENUMS(CHAN_LIGHTS, 6 * 2),// Room for GreenRed
  55. BIG_LIGHT,
  56. BIGC_LIGHT,
  57. ENUMS(METRONOME_LIGHT, 2),// Room for GreenRed
  58. WRITEFILL_LIGHT,
  59. QUANTIZEBIG_LIGHT,
  60. SAMPLEHOLD_LIGHT,
  61. NUM_LIGHTS
  62. };
  63. // Need to save
  64. int panelTheme = 0;
  65. int metronomeDiv = 4;
  66. bool writeFillsToMemory;
  67. bool quantizeBig;
  68. bool sampleAndHold;
  69. int indexStep;
  70. int bank[6];
  71. uint64_t gates[6][2][2];// channel , bank , 64x2 page for 128
  72. float cv[6][2][128];// channel , bank , indexStep
  73. // No need to save
  74. long clockIgnoreOnReset;
  75. double lastPeriod;//2.0 when not seen yet (init or stopped clock and went greater than 2s, which is max period supported for time-snap)
  76. double clockTime;//clock time counter (time since last clock)
  77. int pendingOp;// 0 means nothing pending, +1 means pending big button push, -1 means pending del
  78. float pendingCV;//
  79. bool fillPressed;
  80. unsigned int lightRefreshCounter = 0;
  81. float bigLight = 0.0f;
  82. float metronomeLightStart = 0.0f;
  83. float metronomeLightDiv = 0.0f;
  84. int channel = 0;
  85. int length = 0;
  86. float sampleHoldBuf[6] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
  87. Trigger clockTrigger;
  88. Trigger resetTrigger;
  89. Trigger bankTrigger;
  90. Trigger bigTrigger;
  91. Trigger clearTrigger;
  92. Trigger writeFillTrigger;
  93. Trigger quantizeBigTrigger;
  94. Trigger sampleHoldTrigger;
  95. Trigger internalSHTriggers[6];
  96. //PulseGenerator outPulse;
  97. PulseGenerator outLightPulse;
  98. PulseGenerator bigPulse;
  99. PulseGenerator bigLightPulse;
  100. inline bool getGate(int chan) {return !((gates[chan][bank[chan]][indexStep >> 6] & (((uint64_t)1) << (uint64_t)(indexStep & 0x3F))) == 0);}
  101. inline void setGate(int chan) {gates[chan][bank[chan]][indexStep >> 6] |= (((uint64_t)1) << (uint64_t)(indexStep & 0x3F));}
  102. inline void clearGate(int chan) {gates[chan][bank[chan]][indexStep >> 6] &= ~(((uint64_t)1) << (uint64_t)(indexStep & 0x3F));}
  103. inline void toggleGate(int chan) {gates[chan][bank[chan]][indexStep >> 6] ^= (((uint64_t)1) << (uint64_t)(indexStep & 0x3F));}
  104. inline void clearGates(int chan, int bnk) {gates[chan][bnk][0] = 0; gates[chan][bnk][1] = 0;}
  105. inline void randomizeGates(int chan, int bnk) {gates[chan][bnk][0] = randomu64(); gates[chan][bnk][1] = randomu64();}
  106. inline void writeCV(int chan, float cvValue) {cv[chan][bank[chan]][indexStep] = cvValue;}
  107. inline void writeCV(int chan, int bnk, int step, float cvValue) {cv[chan][bnk][step] = cvValue;}
  108. inline void sampleOutput(int chan) {sampleHoldBuf[chan] = cv[chan][bank[chan]][indexStep];}
  109. inline int calcChan() {
  110. float chanInputValue = inputs[CHAN_INPUT].value / 10.0f * (6.0f - 1.0f);
  111. return (int) clamp(roundf(params[CHAN_PARAM].value + chanInputValue), 0.0f, (6.0f - 1.0f));
  112. }
  113. BigButtonSeq2() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  114. onReset();
  115. }
  116. void onReset() override {
  117. writeFillsToMemory = false;
  118. quantizeBig = true;
  119. sampleAndHold = false;
  120. indexStep = 0;
  121. for (int c = 0; c < 6; c++) {
  122. bank[c] = 0;
  123. for (int b = 0; b < 2; b++) {
  124. clearGates(c, b);
  125. for (int s = 0; s < 128; s++)
  126. writeCV(c, b, s, 0.0f);
  127. }
  128. }
  129. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  130. lastPeriod = 2.0;
  131. clockTime = 0.0;
  132. pendingOp = 0;
  133. pendingCV = 0.0f;
  134. fillPressed = false;
  135. }
  136. void onRandomize() override {
  137. // indexStep = randomu32() % 128;
  138. // for (int c = 0; c < 6; c++) {
  139. // bank[c] = randomu32() % 2;
  140. // for (int b = 0; b < 2; b++) {
  141. // randomizeGates(c, b);
  142. // for (int s = 0; s < 128; s++)
  143. // writeCV(c, b, s, ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f);
  144. // }
  145. // }
  146. int chanRnd = calcChan();
  147. randomizeGates(chanRnd, bank[chanRnd]);
  148. for (int s = 0; s < 128; s++)
  149. writeCV(chanRnd, bank[chanRnd], s, ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f);
  150. }
  151. json_t *toJson() override {
  152. json_t *rootJ = json_object();
  153. // indexStep
  154. json_object_set_new(rootJ, "indexStep", json_integer(indexStep));
  155. // bank
  156. json_t *bankJ = json_array();
  157. for (int c = 0; c < 6; c++)
  158. json_array_insert_new(bankJ, c, json_integer(bank[c]));
  159. json_object_set_new(rootJ, "bank", bankJ);
  160. // gates LS64
  161. json_t *gatesLJ = json_array();
  162. for (int c = 0; c < 6; c++)
  163. for (int b = 0; b < 8; b++) {// bank to store is like uint64_t to store, so go to 8
  164. // first to get stored is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  165. unsigned int intValue = (unsigned int) ( (uint64_t)0xFFFF & (gates[c][b/4][0] >> (uint64_t)(16 * (b % 4))) );
  166. json_array_insert_new(gatesLJ, b + (c << 3) , json_integer(intValue));
  167. }
  168. json_object_set_new(rootJ, "gatesL", gatesLJ);
  169. // gates MS64
  170. json_t *gatesMJ = json_array();
  171. for (int c = 0; c < 6; c++)
  172. for (int b = 0; b < 8; b++) {// bank to store is like uint64_t to store, so go to 8
  173. // first to get stored is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  174. unsigned int intValue = (unsigned int) ( (uint64_t)0xFFFF & (gates[c][b/4][1] >> (uint64_t)(16 * (b % 4))) );
  175. json_array_insert_new(gatesMJ, b + (c << 3) , json_integer(intValue));
  176. }
  177. json_object_set_new(rootJ, "gatesM", gatesMJ);
  178. // CV bank 0
  179. json_t *cv0J = json_array();
  180. for (int c = 0; c < 6; c++) {
  181. for (int s = 0; s < 128; s++) {
  182. json_array_insert_new(cv0J, s + c * 128, json_real(cv[c][0][s]));
  183. }
  184. }
  185. json_object_set_new(rootJ, "cv0", cv0J);
  186. // CV bank 1
  187. json_t *cv1J = json_array();
  188. for (int c = 0; c < 6; c++) {
  189. for (int s = 0; s < 128; s++) {
  190. json_array_insert_new(cv1J, s + c * 128, json_real(cv[c][1][s]));
  191. }
  192. }
  193. json_object_set_new(rootJ, "cv1", cv1J);
  194. // panelTheme
  195. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  196. // metronomeDiv
  197. json_object_set_new(rootJ, "metronomeDiv", json_integer(metronomeDiv));
  198. // writeFillsToMemory
  199. json_object_set_new(rootJ, "writeFillsToMemory", json_boolean(writeFillsToMemory));
  200. // quantizeBig
  201. json_object_set_new(rootJ, "quantizeBig", json_boolean(quantizeBig));
  202. // sampleAndHold
  203. json_object_set_new(rootJ, "sampleAndHold", json_boolean(sampleAndHold));
  204. return rootJ;
  205. }
  206. void fromJson(json_t *rootJ) override {
  207. // indexStep
  208. json_t *indexStepJ = json_object_get(rootJ, "indexStep");
  209. if (indexStepJ)
  210. indexStep = json_integer_value(indexStepJ);
  211. // bank
  212. json_t *bankJ = json_object_get(rootJ, "bank");
  213. if (bankJ)
  214. for (int c = 0; c < 6; c++)
  215. {
  216. json_t *bankArrayJ = json_array_get(bankJ, c);
  217. if (bankArrayJ)
  218. bank[c] = json_integer_value(bankArrayJ);
  219. }
  220. // gates LS64
  221. json_t *gatesLJ = json_object_get(rootJ, "gatesL");
  222. uint64_t bank8intsL[8] = {0,0,0,0,0,0,0,0};
  223. if (gatesLJ) {
  224. for (int c = 0; c < 6; c++) {
  225. for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8
  226. // first to get read is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  227. json_t *gateLJ = json_array_get(gatesLJ, b + (c << 3));
  228. if (gateLJ)
  229. bank8intsL[b] = (uint64_t) json_integer_value(gateLJ);
  230. }
  231. gates[c][0][0] = bank8intsL[0] | (bank8intsL[1] << (uint64_t)16) | (bank8intsL[2] << (uint64_t)32) | (bank8intsL[3] << (uint64_t)48);
  232. gates[c][1][0] = bank8intsL[4] | (bank8intsL[5] << (uint64_t)16) | (bank8intsL[6] << (uint64_t)32) | (bank8intsL[7] << (uint64_t)48);
  233. }
  234. }
  235. // gates MS64
  236. json_t *gatesMJ = json_object_get(rootJ, "gatesM");
  237. uint64_t bank8intsM[8] = {0,0,0,0,0,0,0,0};
  238. if (gatesMJ) {
  239. for (int c = 0; c < 6; c++) {
  240. for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8
  241. // first to get read is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  242. json_t *gateMJ = json_array_get(gatesMJ, b + (c << 3));
  243. if (gateMJ)
  244. bank8intsM[b] = (uint64_t) json_integer_value(gateMJ);
  245. }
  246. gates[c][0][1] = bank8intsM[0] | (bank8intsM[1] << (uint64_t)16) | (bank8intsM[2] << (uint64_t)32) | (bank8intsM[3] << (uint64_t)48);
  247. gates[c][1][1] = bank8intsM[4] | (bank8intsM[5] << (uint64_t)16) | (bank8intsM[6] << (uint64_t)32) | (bank8intsM[7] << (uint64_t)48);
  248. }
  249. }
  250. // CV bank 0
  251. json_t *cv0J = json_object_get(rootJ, "cv0");
  252. if (cv0J) {
  253. for (int c = 0; c < 6; c++)
  254. for (int s = 0; s < 128; s++) {
  255. json_t *cv0ArrayJ = json_array_get(cv0J, s + c * 128);
  256. if (cv0ArrayJ)
  257. cv[c][0][s] = json_number_value(cv0ArrayJ);
  258. }
  259. }
  260. // CV bank 1
  261. json_t *cv1J = json_object_get(rootJ, "cv1");
  262. if (cv1J) {
  263. for (int c = 0; c < 6; c++)
  264. for (int s = 0; s < 128; s++) {
  265. json_t *cv1ArrayJ = json_array_get(cv1J, s + c * 128);
  266. if (cv1ArrayJ)
  267. cv[c][1][s] = json_number_value(cv1ArrayJ);
  268. }
  269. }
  270. // panelTheme
  271. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  272. if (panelThemeJ)
  273. panelTheme = json_integer_value(panelThemeJ);
  274. // metronomeDiv
  275. json_t *metronomeDivJ = json_object_get(rootJ, "metronomeDiv");
  276. if (metronomeDivJ)
  277. metronomeDiv = json_integer_value(metronomeDivJ);
  278. // writeFillsToMemory
  279. json_t *writeFillsToMemoryJ = json_object_get(rootJ, "writeFillsToMemory");
  280. if (writeFillsToMemoryJ)
  281. writeFillsToMemory = json_is_true(writeFillsToMemoryJ);
  282. // quantizeBig
  283. json_t *quantizeBigJ = json_object_get(rootJ, "quantizeBig");
  284. if (quantizeBigJ)
  285. quantizeBig = json_is_true(quantizeBigJ);
  286. // sampleAndHold
  287. json_t *sampleAndHoldJ = json_object_get(rootJ, "sampleAndHold");
  288. if (sampleAndHoldJ)
  289. sampleAndHold = json_is_true(sampleAndHoldJ);
  290. }
  291. void step() override {
  292. double sampleTime = 1.0 / engineGetSampleRate();
  293. static const float lightTime = 0.1f;
  294. //********** Buttons, knobs, switches and inputs **********
  295. // Length
  296. length = (int) clamp(roundf( params[LEN_PARAM].value + ( inputs[LEN_INPUT].active ? (inputs[LEN_INPUT].value / 10.0f * (128.0f - 1.0f)) : 0.0f ) ), 0.0f, (128.0f - 1.0f)) + 1;
  297. // Channel
  298. channel = calcChan();
  299. if ((lightRefreshCounter & userInputsStepSkipMask) == 0) {
  300. // Big button
  301. if (bigTrigger.process(params[BIG_PARAM].value + inputs[BIG_INPUT].value)) {
  302. bigLight = 1.0f;
  303. if (quantizeBig && (clockTime > (lastPeriod / 2.0)) && (clockTime <= (lastPeriod * 1.01))) {// allow for 1% clock jitter
  304. pendingOp = 1;
  305. pendingCV = inputs[CV_INPUT].value;
  306. }
  307. else {
  308. if (!getGate(channel)) {
  309. setGate(channel);// bank and indexStep are global
  310. bigPulse.trigger(0.001f);
  311. }
  312. writeCV(channel, inputs[CV_INPUT].value);
  313. bigLightPulse.trigger(lightTime);
  314. }
  315. }
  316. // Bank button
  317. if (bankTrigger.process(params[BANK_PARAM].value + inputs[BANK_INPUT].value))
  318. bank[channel] = 1 - bank[channel];
  319. // Clear button
  320. if (clearTrigger.process(params[CLEAR_PARAM].value + inputs[CLEAR_INPUT].value)) {
  321. clearGates(channel, bank[channel]);
  322. for (int s = 0; s < 128; s++)
  323. cv[channel][bank[channel]][s] = 0.0f;
  324. }
  325. // Write fill to memory
  326. if (writeFillTrigger.process(params[WRITEFILL_PARAM].value))
  327. writeFillsToMemory = !writeFillsToMemory;
  328. // Quantize big button (aka snap)
  329. if (quantizeBigTrigger.process(params[QUANTIZEBIG_PARAM].value))
  330. quantizeBig = !quantizeBig;
  331. // Sample and hold
  332. if (sampleHoldTrigger.process(params[SAMPLEHOLD_PARAM].value))
  333. sampleAndHold = !sampleAndHold;
  334. // Del button
  335. if (params[DEL_PARAM].value + inputs[DEL_INPUT].value > 0.5f) {
  336. if (quantizeBig && (clockTime > (lastPeriod / 2.0)) && (clockTime <= (lastPeriod * 1.01)))// allow for 1% clock jitter
  337. pendingOp = -1;// overrides the pending write if it exists
  338. else {
  339. clearGate(channel);// bank and indexStep are global
  340. cv[channel][bank[channel]][indexStep] = 0.0f;
  341. }
  342. }
  343. // Pending timeout (write/del current step)
  344. if (pendingOp != 0 && clockTime > (lastPeriod * 1.01) )
  345. performPending(channel, lightTime);
  346. }// userInputs refresh
  347. //********** Clock and reset **********
  348. // Clock
  349. if (clockIgnoreOnReset == 0l) {
  350. if (clockTrigger.process(inputs[CLK_INPUT].value + params[CLOCK_PARAM].value)) {
  351. if ((++indexStep) >= length) indexStep = 0;
  352. // Fill button
  353. fillPressed = (params[FILL_PARAM].value + inputs[FILL_INPUT].value) > 0.5f;// used in clock block and others
  354. if (fillPressed && writeFillsToMemory) {
  355. setGate(channel);// bank and indexStep are global
  356. writeCV(channel, sampleHoldBuf[channel]);
  357. }
  358. //outPulse.trigger(0.001f);
  359. outLightPulse.trigger(lightTime);
  360. if (pendingOp != 0)
  361. performPending(channel, lightTime);// Proper pending write/del to next step which is now reached
  362. if (indexStep == 0)
  363. metronomeLightStart = 1.0f;
  364. metronomeLightDiv = ((indexStep % metronomeDiv) == 0 && indexStep != 0) ? 1.0f : 0.0f;
  365. // Random (toggle gate according to probability knob)
  366. float rnd01 = params[RND_PARAM].value / 100.0f + inputs[RND_INPUT].value / 10.0f;
  367. if (rnd01 > 0.0f) {
  368. if (randomUniform() < rnd01)// randomUniform is [0.0, 1.0), see include/util/common.hpp
  369. toggleGate(channel);
  370. }
  371. lastPeriod = clockTime > 2.0 ? 2.0 : clockTime;
  372. clockTime = 0.0;
  373. }
  374. }
  375. // Reset
  376. if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) {
  377. indexStep = 0;
  378. //outPulse.trigger(0.001f);
  379. outLightPulse.trigger(0.02f);
  380. metronomeLightStart = 1.0f;
  381. metronomeLightDiv = 0.0f;
  382. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  383. clockTrigger.reset();
  384. }
  385. //********** Outputs and lights **********
  386. // Gate outputs
  387. bool bigPulseState = bigPulse.process((float)sampleTime);
  388. bool outPulseState = clockTrigger.isHigh();
  389. bool retriggingOnReset = (clockIgnoreOnReset != 0l && retrigGatesOnReset);
  390. for (int i = 0; i < 6; i++) {
  391. bool gate = getGate(i);
  392. bool outSignal = ( ((gate || (i == channel && fillPressed)) && outPulseState) || (gate && bigPulseState && i == channel) );
  393. float outGateValue = outSignal ? 10.0f : 0.0f;
  394. if (internalSHTriggers[i].process(outGateValue))
  395. sampleOutput(i);
  396. outputs[CHAN_OUTPUTS + i].value = (retriggingOnReset ? 0.0f : outGateValue);
  397. outputs[CV_OUTPUTS + i].value = sampleAndHold ? sampleHoldBuf[i] : cv[i][bank[i]][indexStep];
  398. }
  399. lightRefreshCounter++;
  400. if (lightRefreshCounter >= displayRefreshStepSkips) {
  401. lightRefreshCounter = 0;
  402. // Gate light outputs
  403. bool bigLightPulseState = bigLightPulse.process((float)sampleTime * displayRefreshStepSkips);
  404. bool outLightPulseState = outLightPulse.process((float)sampleTime * displayRefreshStepSkips);
  405. for (int i = 0; i < 6; i++) {
  406. bool gate = getGate(i);
  407. bool outLight = (((gate || (i == channel && fillPressed)) && outLightPulseState) || (gate && bigLightPulseState && i == channel));
  408. lights[(CHAN_LIGHTS + i) * 2 + 1].setBrightnessSmooth(outLight ? 1.0f : 0.0f, displayRefreshStepSkips);
  409. lights[(CHAN_LIGHTS + i) * 2 + 0].value = (i == channel ? (1.0f - lights[(CHAN_LIGHTS + i) * 2 + 1].value) / 2.0f : 0.0f);
  410. }
  411. // Big button lights
  412. lights[BIG_LIGHT].value = bank[channel] == 1 ? 1.0f : 0.0f;
  413. lights[BIGC_LIGHT].value = bigLight;
  414. // Metronome light
  415. lights[METRONOME_LIGHT + 1].value = metronomeLightStart;
  416. lights[METRONOME_LIGHT + 0].value = metronomeLightDiv;
  417. // Other push button lights
  418. lights[WRITEFILL_LIGHT].value = writeFillsToMemory ? 1.0f : 0.0f;
  419. lights[QUANTIZEBIG_LIGHT].value = quantizeBig ? 1.0f : 0.0f;
  420. lights[SAMPLEHOLD_LIGHT].value = sampleAndHold ? 1.0f : 0.0f;
  421. bigLight -= (bigLight / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  422. metronomeLightStart -= (metronomeLightStart / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  423. metronomeLightDiv -= (metronomeLightDiv / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  424. }
  425. clockTime += sampleTime;
  426. if (clockIgnoreOnReset > 0l)
  427. clockIgnoreOnReset--;
  428. }// step()
  429. inline void performPending(int chan, float lightTime) {
  430. if (pendingOp == 1) {
  431. if (!getGate(chan)) {
  432. setGate(chan);// bank and indexStep are global
  433. bigPulse.trigger(0.001f);
  434. }
  435. writeCV(chan, pendingCV);
  436. bigLightPulse.trigger(lightTime);
  437. }
  438. else {
  439. clearGate(chan);// bank and indexStep are global
  440. }
  441. pendingOp = 0;
  442. }
  443. };
  444. struct BigButtonSeq2Widget : ModuleWidget {
  445. struct ChanDisplayWidget : TransparentWidget {
  446. int *channel;
  447. std::shared_ptr<Font> font;
  448. ChanDisplayWidget() {
  449. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  450. }
  451. void draw(NVGcontext *vg) override {
  452. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  453. nvgFontFaceId(vg, font->handle);
  454. //nvgTextLetterSpacing(vg, 2.5);
  455. Vec textPos = Vec(6, 24);
  456. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  457. nvgText(vg, textPos.x, textPos.y, "~", NULL);
  458. nvgFillColor(vg, textColor);
  459. char displayStr[2];
  460. snprintf(displayStr, 2, "%1u", (unsigned) (*channel + 1) );
  461. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  462. }
  463. };
  464. struct StepsDisplayWidget : TransparentWidget {
  465. BigButtonSeq2 *module;
  466. std::shared_ptr<Font> font;
  467. StepsDisplayWidget() {
  468. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  469. }
  470. void draw(NVGcontext *vg) override {
  471. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  472. nvgFontFaceId(vg, font->handle);
  473. //nvgTextLetterSpacing(vg, 2.5);
  474. Vec textPos = Vec(6, 24);
  475. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  476. nvgText(vg, textPos.x, textPos.y, "~~~", NULL);
  477. nvgFillColor(vg, textColor);
  478. char displayStr[4];
  479. unsigned dispVal = (unsigned)(module->params[BigButtonSeq2::DISPMODE_PARAM].value < 0.5f ? module->length : module->indexStep + 1);
  480. snprintf(displayStr, 4, "%3u", dispVal);
  481. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  482. }
  483. };
  484. struct PanelThemeItem : MenuItem {
  485. BigButtonSeq2 *module;
  486. int theme;
  487. void onAction(EventAction &e) override {
  488. module->panelTheme = theme;
  489. }
  490. void step() override {
  491. rightText = (module->panelTheme == theme) ? "✔" : "";
  492. }
  493. };
  494. struct MetronomeItem : MenuItem {
  495. BigButtonSeq2 *module;
  496. int div;
  497. void onAction(EventAction &e) override {
  498. module->metronomeDiv = div;
  499. }
  500. void step() override {
  501. rightText = (module->metronomeDiv == div) ? "✔" : "";
  502. }
  503. };
  504. Menu *createContextMenu() override {
  505. Menu *menu = ModuleWidget::createContextMenu();
  506. MenuLabel *spacerLabel = new MenuLabel();
  507. menu->addChild(spacerLabel);
  508. BigButtonSeq2 *module = dynamic_cast<BigButtonSeq2*>(this->module);
  509. assert(module);
  510. MenuLabel *themeLabel = new MenuLabel();
  511. themeLabel->text = "Panel Theme";
  512. menu->addChild(themeLabel);
  513. PanelThemeItem *lightItem = new PanelThemeItem();
  514. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  515. lightItem->module = module;
  516. lightItem->theme = 0;
  517. menu->addChild(lightItem);
  518. PanelThemeItem *darkItem = new PanelThemeItem();
  519. std::string hotRodLabel = " Hot-rod";
  520. hotRodLabel.insert(0, darkPanelID);// ImpromptuModular.hpp
  521. darkItem->text = hotRodLabel;
  522. darkItem->module = module;
  523. darkItem->theme = 1;
  524. menu->addChild(darkItem);
  525. menu->addChild(new MenuLabel());// empty line
  526. MenuLabel *metronomeLabel = new MenuLabel();
  527. metronomeLabel->text = "Metronome light";
  528. menu->addChild(metronomeLabel);
  529. MetronomeItem *met1Item = MenuItem::create<MetronomeItem>("Every clock", CHECKMARK(module->metronomeDiv == 1));
  530. met1Item->module = module;
  531. met1Item->div = 1;
  532. menu->addChild(met1Item);
  533. MetronomeItem *met2Item = MenuItem::create<MetronomeItem>("/2", CHECKMARK(module->metronomeDiv == 2));
  534. met2Item->module = module;
  535. met2Item->div = 2;
  536. menu->addChild(met2Item);
  537. MetronomeItem *met4Item = MenuItem::create<MetronomeItem>("/4", CHECKMARK(module->metronomeDiv == 4));
  538. met4Item->module = module;
  539. met4Item->div = 4;
  540. menu->addChild(met4Item);
  541. MetronomeItem *met8Item = MenuItem::create<MetronomeItem>("/8", CHECKMARK(module->metronomeDiv == 8));
  542. met8Item->module = module;
  543. met8Item->div = 8;
  544. menu->addChild(met8Item);
  545. MetronomeItem *met1000Item = MenuItem::create<MetronomeItem>("Full length", CHECKMARK(module->metronomeDiv == 1000));
  546. met1000Item->module = module;
  547. met1000Item->div = 1000;
  548. menu->addChild(met1000Item);
  549. return menu;
  550. }
  551. BigButtonSeq2Widget(BigButtonSeq2 *module) : ModuleWidget(module) {
  552. // Main panel from Inkscape
  553. DynamicSVGPanel *panel = new DynamicSVGPanel();
  554. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BigButtonSeq2.svg")));
  555. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BigButtonSeq2_dark.svg")));
  556. box.size = panel->box.size;
  557. panel->mode = &module->panelTheme;
  558. addChild(panel);
  559. // Screws
  560. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  561. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme));
  562. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  563. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme));
  564. // Column rulers (horizontal positions)
  565. static const int colRulerCenter = 129;// not real center, center of big button
  566. static const int offsetChanOutX = 20;
  567. static const int rowRuler0 = 52;// chan and len knobs+displays
  568. static const int rowRuler1 = rowRuler0 + 48;// chan and len CVs, display switch
  569. static const int rowRuler2 = rowRuler1 + 40;// fillmem led button and CV in
  570. static const int rowRuler3 = rowRuler1 + 21;// bank
  571. static const int rowRuler4 = rowRuler3 + 29;// clear and del
  572. static const int rowRuler5 = rowRuler4 + 45;// reset and fill
  573. static const int colRulerT0 = colRulerCenter - offsetChanOutX * 5;
  574. static const int colRulerT1 = colRulerCenter - offsetChanOutX * 3;
  575. static const int colRulerT2 = colRulerCenter - offsetChanOutX * 1;
  576. static const int colRulerT3 = colRulerCenter + offsetChanOutX * 1;
  577. static const int colRulerT4 = colRulerCenter + offsetChanOutX * 3;
  578. static const int colRulerT5 = colRulerCenter + offsetChanOutX * 5;
  579. static const int colRulerT6 = colRulerT5 + 42;
  580. static const int clearAndDelButtonOffsetX = (colRulerCenter - colRulerT0) / 2 + 4;
  581. static const int knobCVjackOffsetY = 36;
  582. static const int lengthDisplayOffsetX = 20;
  583. // Rnd knob
  584. addParam(createDynamicParamCentered<IMSmallSnapKnob>(Vec(colRulerT0, rowRuler0), module, BigButtonSeq2::RND_PARAM, 0.0f, 100.0f, 0.0f, &module->panelTheme));
  585. // Channel knob
  586. addParam(createDynamicParamCentered<IMSixPosBigKnob>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler0), module, BigButtonSeq2::CHAN_PARAM, 0.0f, 6.0f - 1.0f, 0.0f, &module->panelTheme));
  587. // Channel display
  588. ChanDisplayWidget *displayChan = new ChanDisplayWidget();
  589. displayChan->box.pos = Vec(colRulerCenter - 12, rowRuler0 - 15);
  590. displayChan->box.size = Vec(24, 30);// 1 character
  591. displayChan->channel = &module->channel;
  592. addChild(displayChan);
  593. // Len knob
  594. addParam(createDynamicParamCentered<IMBigSnapKnob>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler0), module, BigButtonSeq2::LEN_PARAM, 0.0f, 128.0f - 1.0f, 32.0f - 1.0f, &module->panelTheme));
  595. // Length display
  596. StepsDisplayWidget *displaySteps = new StepsDisplayWidget();
  597. displaySteps->box.pos = Vec(colRulerT5 - 27 + lengthDisplayOffsetX, rowRuler0 - 15);
  598. displaySteps->box.size = Vec(55, 30);// 3 characters
  599. displaySteps->module = module;
  600. addChild(displaySteps);
  601. // Rnd jack
  602. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerT0, rowRuler1), Port::INPUT, module, BigButtonSeq2::RND_INPUT, &module->panelTheme));
  603. // Channel jack
  604. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler1), Port::INPUT, module, BigButtonSeq2::CHAN_INPUT, &module->panelTheme));
  605. // Length jack
  606. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler1), Port::INPUT, module, BigButtonSeq2::LEN_INPUT, &module->panelTheme));
  607. // Display switch
  608. addParam(createParamCentered<CKSSHNoRandom>(Vec(colRulerT5 + lengthDisplayOffsetX, rowRuler1 - 12), module, BigButtonSeq2::DISPMODE_PARAM, 0.0f, 1.0f, 0.0f));
  609. // Metronome light
  610. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT0, rowRuler2), module, BigButtonSeq2::METRONOME_LIGHT + 0));
  611. // Mem fill LED button
  612. addParam(createParamCentered<LEDButton>(Vec(colRulerT5, rowRuler2), module, BigButtonSeq2::WRITEFILL_PARAM, 0.0f, 1.0f, 0.0f));
  613. addChild(createLightCentered<MediumLight<GreenLight>>(Vec(colRulerT5, rowRuler2), module, BigButtonSeq2::WRITEFILL_LIGHT));
  614. // CV Input
  615. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerT6, rowRuler2), Port::INPUT, module, BigButtonSeq2::CV_INPUT, &module->panelTheme));
  616. // Bank button and jack
  617. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerCenter, rowRuler3), module, BigButtonSeq2::BANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  618. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter, rowRuler3 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::BANK_INPUT, &module->panelTheme));
  619. // Clock button and jack
  620. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler4), module, BigButtonSeq2::CLOCK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  621. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::CLK_INPUT, &module->panelTheme));
  622. // Del button and jack
  623. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler4), module, BigButtonSeq2::DEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  624. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::DEL_INPUT, &module->panelTheme));
  625. // Reset button and jack
  626. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerT0, rowRuler5), module, BigButtonSeq2::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  627. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerT0, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::RESET_INPUT, &module->panelTheme));
  628. // Fill button and jack
  629. addParam(createDynamicParamCentered<IMBigPushButton>(Vec(colRulerT5, rowRuler5), module, BigButtonSeq2::FILL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  630. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerT5, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::FILL_INPUT, &module->panelTheme));
  631. // And now time for... BIG BUTTON!
  632. addChild(createLightCentered<GiantLight<RedLight>>(Vec(colRulerCenter, rowRuler5 + 22), module, BigButtonSeq2::BIG_LIGHT));
  633. addParam(createParamCentered<LEDBezelBig>(Vec(colRulerCenter, rowRuler5 + 22), module, BigButtonSeq2::BIG_PARAM, 0.0f, 1.0f, 0.0f));
  634. addChild(createLightCentered<GiantLight2<RedLight>>(Vec(colRulerCenter, rowRuler5 + 22), module, BigButtonSeq2::BIGC_LIGHT));
  635. // Big CV input
  636. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::BIG_INPUT, &module->panelTheme));
  637. // Big snap
  638. addParam(createParamCentered<LEDButton>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), module, BigButtonSeq2::QUANTIZEBIG_PARAM, 0.0f, 1.0f, 0.0f));
  639. addChild(createLightCentered<MediumLight<GreenLight>>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), module, BigButtonSeq2::QUANTIZEBIG_LIGHT));
  640. // Clear button and jack
  641. addParam(createDynamicParamCentered<IMPushButton>(Vec(colRulerT6, rowRuler5), module, BigButtonSeq2::CLEAR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  642. addInput(createDynamicPortCentered<IMPort>(Vec(colRulerT6, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq2::CLEAR_INPUT, &module->panelTheme));
  643. // LEDs
  644. static const int rowRuler10 = 288;
  645. static const int ledOffsetY = -28;
  646. static const int gateOffsetY = 42;
  647. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT0, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 0));
  648. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT1, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 2));
  649. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT2, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 4));
  650. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT3, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 6));
  651. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT4, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 8));
  652. addChild(createLightCentered<MediumLight<GreenRedLight>>(Vec(colRulerT5, rowRuler10 + ledOffsetY), module, BigButtonSeq2::CHAN_LIGHTS + 10));
  653. // CV Outputs
  654. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT0, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 0, &module->panelTheme));
  655. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT1, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 1, &module->panelTheme));
  656. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT2, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 2, &module->panelTheme));
  657. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT3, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 3, &module->panelTheme));
  658. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT4, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 4, &module->panelTheme));
  659. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT5, rowRuler10), Port::OUTPUT, module, BigButtonSeq2::CV_OUTPUTS + 5, &module->panelTheme));
  660. // Gate outputs
  661. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT0, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 0, &module->panelTheme));
  662. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT1, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 1, &module->panelTheme));
  663. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT2, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 2, &module->panelTheme));
  664. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT3, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 3, &module->panelTheme));
  665. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT4, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 4, &module->panelTheme));
  666. addOutput(createDynamicPortCentered<IMPort>(Vec(colRulerT5, rowRuler10 + gateOffsetY), Port::OUTPUT, module, BigButtonSeq2::CHAN_OUTPUTS + 5, &module->panelTheme));
  667. // S&H LED button
  668. addParam(createParamCentered<LEDButton>(Vec(colRulerT6 + 2, rowRuler10 + gateOffsetY / 2), module, BigButtonSeq2::SAMPLEHOLD_PARAM, 0.0f, 1.0f, 0.0f));
  669. addChild(createLightCentered<MediumLight<GreenLight>>(Vec(colRulerT6 + 2, rowRuler10 + gateOffsetY / 2), module, BigButtonSeq2::SAMPLEHOLD_LIGHT));
  670. }
  671. };
  672. } // namespace rack_plugin_ImpromptuModular
  673. using namespace rack_plugin_ImpromptuModular;
  674. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, BigButtonSeq2) {
  675. Model *modelBigButtonSeq2 = Model::create<BigButtonSeq2, BigButtonSeq2Widget>("Impromptu Modular", "Big-Button-Seq2", "SEQ - Big-Button-Seq2", SEQUENCER_TAG);
  676. return modelBigButtonSeq2;
  677. }
  678. /*CHANGE LOG
  679. 0.6.12:
  680. input refresh optimization
  681. 0.6.11:
  682. created
  683. */