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.

685 lines
26KB

  1. //***********************************************************************************************
  2. //Six channel 32-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 BigButtonSeq : 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. // -- 0.6.10 ^^
  28. WRITEFILL_PARAM,
  29. QUANTIZEBIG_PARAM,
  30. NUM_PARAMS
  31. };
  32. enum InputIds {
  33. CLK_INPUT,
  34. CHAN_INPUT,
  35. BIG_INPUT,
  36. LEN_INPUT,
  37. RND_INPUT,
  38. RESET_INPUT,
  39. CLEAR_INPUT,
  40. BANK_INPUT,
  41. DEL_INPUT,
  42. FILL_INPUT,
  43. NUM_INPUTS
  44. };
  45. enum OutputIds {
  46. ENUMS(CHAN_OUTPUTS, 6),
  47. NUM_OUTPUTS
  48. };
  49. enum LightIds {
  50. ENUMS(CHAN_LIGHTS, 6 * 2),// Room for GreenRed
  51. BIG_LIGHT,
  52. BIGC_LIGHT,
  53. ENUMS(METRONOME_LIGHT, 2),// Room for GreenRed
  54. // -- 0.6.10 ^^
  55. WRITEFILL_LIGHT,
  56. QUANTIZEBIG_LIGHT,
  57. NUM_LIGHTS
  58. };
  59. // Need to save
  60. int panelTheme = 0;
  61. int metronomeDiv = 4;
  62. bool writeFillsToMemory;
  63. bool quantizeBig;
  64. int indexStep;
  65. int bank[6];
  66. uint64_t gates[6][2];// chan , bank
  67. // No need to save
  68. long clockIgnoreOnReset;
  69. 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)
  70. double clockTime;//clock time counter (time since last clock)
  71. int pendingOp;// 0 means nothing pending, +1 means pending big button push, -1 means pending del
  72. bool fillPressed;
  73. unsigned int lightRefreshCounter = 0;
  74. float bigLight = 0.0f;
  75. float metronomeLightStart = 0.0f;
  76. float metronomeLightDiv = 0.0f;
  77. int chan = 0;
  78. int len = 0;
  79. Trigger clockTrigger;
  80. Trigger resetTrigger;
  81. Trigger bankTrigger;
  82. Trigger bigTrigger;
  83. Trigger writeFillTrigger;
  84. Trigger quantizeBigTrigger;
  85. PulseGenerator outPulse;
  86. PulseGenerator outLightPulse;
  87. PulseGenerator bigPulse;
  88. PulseGenerator bigLightPulse;
  89. inline void toggleGate(int chan) {gates[chan][bank[chan]] ^= (((uint64_t)1) << (uint64_t)indexStep);}
  90. inline void setGate(int chan) {gates[chan][bank[chan]] |= (((uint64_t)1) << (uint64_t)indexStep);}
  91. inline void clearGate(int chan) {gates[chan][bank[chan]] &= ~(((uint64_t)1) << (uint64_t)indexStep);}
  92. inline bool getGate(int chan) {return !((gates[chan][bank[chan]] & (((uint64_t)1) << (uint64_t)indexStep)) == 0);}
  93. inline int calcChan() {
  94. float chanInputValue = inputs[CHAN_INPUT].value / 10.0f * (6.0f - 1.0f);
  95. return (int) clamp(roundf(params[CHAN_PARAM].value + chanInputValue), 0.0f, (6.0f - 1.0f));
  96. }
  97. BigButtonSeq() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  98. onReset();
  99. }
  100. void onReset() override {
  101. writeFillsToMemory = false;
  102. quantizeBig = true;
  103. indexStep = 0;
  104. for (int c = 0; c < 6; c++) {
  105. bank[c] = 0;
  106. gates[c][0] = 0;
  107. gates[c][1] = 0;
  108. }
  109. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  110. lastPeriod = 2.0;
  111. clockTime = 0.0;
  112. pendingOp = 0;
  113. fillPressed = false;
  114. }
  115. void onRandomize() override {
  116. // indexStep = randomu32() % 64;
  117. // for (int c = 0; c < 6; c++) {
  118. // bank[c] = randomu32() % 2;
  119. // gates[c][0] = randomu64();
  120. // gates[c][1] = randomu64();
  121. // }
  122. int chanRnd = calcChan();
  123. gates[chanRnd][bank[chanRnd]] = randomu64();
  124. }
  125. json_t *toJson() override {
  126. json_t *rootJ = json_object();
  127. // indexStep
  128. json_object_set_new(rootJ, "indexStep", json_integer(indexStep));
  129. // bank
  130. json_t *bankJ = json_array();
  131. for (int c = 0; c < 6; c++)
  132. json_array_insert_new(bankJ, c, json_integer(bank[c]));
  133. json_object_set_new(rootJ, "bank", bankJ);
  134. // gates
  135. json_t *gatesJ = json_array();
  136. for (int c = 0; c < 6; c++)
  137. for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8
  138. // first to get stored is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  139. unsigned int intValue = (unsigned int) ( (uint64_t)0xFFFF & (gates[c][b/4] >> (uint64_t)(16 * (b % 4))) );
  140. json_array_insert_new(gatesJ, b + (c << 3) , json_integer(intValue));
  141. }
  142. json_object_set_new(rootJ, "gates", gatesJ);
  143. // panelTheme
  144. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  145. // metronomeDiv
  146. json_object_set_new(rootJ, "metronomeDiv", json_integer(metronomeDiv));
  147. // writeFillsToMemory
  148. json_object_set_new(rootJ, "writeFillsToMemory", json_boolean(writeFillsToMemory));
  149. // quantizeBig
  150. json_object_set_new(rootJ, "quantizeBig", json_boolean(quantizeBig));
  151. return rootJ;
  152. }
  153. void fromJson(json_t *rootJ) override {
  154. // indexStep
  155. json_t *indexStepJ = json_object_get(rootJ, "indexStep");
  156. if (indexStepJ)
  157. indexStep = json_integer_value(indexStepJ);
  158. // bank
  159. json_t *bankJ = json_object_get(rootJ, "bank");
  160. if (bankJ)
  161. for (int c = 0; c < 6; c++)
  162. {
  163. json_t *bankArrayJ = json_array_get(bankJ, c);
  164. if (bankArrayJ)
  165. bank[c] = json_integer_value(bankArrayJ);
  166. }
  167. // gates
  168. json_t *gatesJ = json_object_get(rootJ, "gates");
  169. uint64_t bank8ints[8] = {0,0,0,0,0,0,0,0};
  170. if (gatesJ) {
  171. for (int c = 0; c < 6; c++) {
  172. for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8
  173. // first to get read is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1
  174. json_t *gateJ = json_array_get(gatesJ, b + (c << 3));
  175. if (gateJ)
  176. bank8ints[b] = (uint64_t) json_integer_value(gateJ);
  177. }
  178. gates[c][0] = bank8ints[0] | (bank8ints[1] << (uint64_t)16) | (bank8ints[2] << (uint64_t)32) | (bank8ints[3] << (uint64_t)48);
  179. gates[c][1] = bank8ints[4] | (bank8ints[5] << (uint64_t)16) | (bank8ints[6] << (uint64_t)32) | (bank8ints[7] << (uint64_t)48);
  180. }
  181. }
  182. // panelTheme
  183. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  184. if (panelThemeJ)
  185. panelTheme = json_integer_value(panelThemeJ);
  186. // metronomeDiv
  187. json_t *metronomeDivJ = json_object_get(rootJ, "metronomeDiv");
  188. if (metronomeDivJ)
  189. metronomeDiv = json_integer_value(metronomeDivJ);
  190. // writeFillsToMemory
  191. json_t *writeFillsToMemoryJ = json_object_get(rootJ, "writeFillsToMemory");
  192. if (writeFillsToMemoryJ)
  193. writeFillsToMemory = json_is_true(writeFillsToMemoryJ);
  194. // quantizeBig
  195. json_t *quantizeBigJ = json_object_get(rootJ, "quantizeBig");
  196. if (quantizeBigJ)
  197. quantizeBig = json_is_true(quantizeBigJ);
  198. }
  199. void step() override {
  200. double sampleTime = 1.0 / engineGetSampleRate();
  201. static const float lightTime = 0.1f;
  202. //********** Buttons, knobs, switches and inputs **********
  203. // Length
  204. len = (int) clamp(roundf( params[LEN_PARAM].value + ( inputs[LEN_INPUT].active ? (inputs[LEN_INPUT].value / 10.0f * (64.0f - 1.0f)) : 0.0f ) ), 0.0f, (64.0f - 1.0f)) + 1;
  205. // Chan
  206. chan = calcChan();
  207. if ((lightRefreshCounter & userInputsStepSkipMask) == 0) {
  208. // Big button
  209. if (bigTrigger.process(params[BIG_PARAM].value + inputs[BIG_INPUT].value)) {
  210. bigLight = 1.0f;
  211. if (quantizeBig && (clockTime > (lastPeriod / 2.0)) && (clockTime <= (lastPeriod * 1.01))) // allow for 1% clock jitter
  212. pendingOp = 1;
  213. else {
  214. if (!getGate(chan)) {
  215. setGate(chan);// bank and indexStep are global
  216. bigPulse.trigger(0.001f);
  217. bigLightPulse.trigger(lightTime);
  218. }
  219. }
  220. }
  221. // Bank button
  222. if (bankTrigger.process(params[BANK_PARAM].value + inputs[BANK_INPUT].value))
  223. bank[chan] = 1 - bank[chan];
  224. // Clear button
  225. if (params[CLEAR_PARAM].value + inputs[CLEAR_INPUT].value > 0.5f)
  226. gates[chan][bank[chan]] = 0;
  227. // Del button
  228. if (params[DEL_PARAM].value + inputs[DEL_INPUT].value > 0.5f) {
  229. if (quantizeBig && (clockTime > (lastPeriod / 2.0)) && (clockTime <= (lastPeriod * 1.01)))// allow for 1% clock jitter
  230. pendingOp = -1;// overrides the pending write if it exists
  231. else
  232. clearGate(chan);// bank and indexStep are global
  233. }
  234. // Pending timeout (write/del current step)
  235. if (pendingOp != 0 && clockTime > (lastPeriod * 1.01) )
  236. performPending(chan, lightTime);
  237. // Write fill to memory
  238. if (writeFillTrigger.process(params[WRITEFILL_PARAM].value))
  239. writeFillsToMemory = !writeFillsToMemory;
  240. // Quantize big button (aka snap)
  241. if (quantizeBigTrigger.process(params[QUANTIZEBIG_PARAM].value))
  242. quantizeBig = !quantizeBig;
  243. }// userInputs refresh
  244. //********** Clock and reset **********
  245. // Clock
  246. if (clockIgnoreOnReset == 0l) {
  247. if (clockTrigger.process(inputs[CLK_INPUT].value)) {
  248. if ((++indexStep) >= len) indexStep = 0;
  249. // Fill button
  250. fillPressed = (params[FILL_PARAM].value + inputs[FILL_INPUT].value) > 0.5f;
  251. if (fillPressed && writeFillsToMemory)
  252. setGate(chan);// bank and indexStep are global
  253. outPulse.trigger(0.001f);
  254. outLightPulse.trigger(lightTime);
  255. if (pendingOp != 0)
  256. performPending(chan, lightTime);// Proper pending write/del to next step which is now reached
  257. if (indexStep == 0)
  258. metronomeLightStart = 1.0f;
  259. metronomeLightDiv = ((indexStep % metronomeDiv) == 0 && indexStep != 0) ? 1.0f : 0.0f;
  260. // Random (toggle gate according to probability knob)
  261. float rnd01 = params[RND_PARAM].value / 100.0f + inputs[RND_INPUT].value / 10.0f;
  262. if (rnd01 > 0.0f) {
  263. if (randomUniform() < rnd01)// randomUniform is [0.0, 1.0), see include/util/common.hpp
  264. toggleGate(chan);
  265. }
  266. lastPeriod = clockTime > 2.0 ? 2.0 : clockTime;
  267. clockTime = 0.0;
  268. }
  269. }
  270. // Reset
  271. if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) {
  272. indexStep = 0;
  273. outPulse.trigger(0.001f);
  274. outLightPulse.trigger(0.02f);
  275. metronomeLightStart = 1.0f;
  276. metronomeLightDiv = 0.0f;
  277. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  278. clockTrigger.reset();
  279. }
  280. //********** Outputs and lights **********
  281. // Gate outputs
  282. bool bigPulseState = bigPulse.process((float)sampleTime);
  283. bool outPulseState = outPulse.process((float)sampleTime);
  284. bool retriggingOnReset = (clockIgnoreOnReset != 0l && retrigGatesOnReset);
  285. for (int i = 0; i < 6; i++) {
  286. bool gate = getGate(i);
  287. bool outSignal = (((gate || (i == chan && fillPressed)) && outPulseState) || (gate && bigPulseState && i == chan));
  288. outputs[CHAN_OUTPUTS + i].value = ((outSignal && !retriggingOnReset) ? 10.0f : 0.0f);
  289. }
  290. lightRefreshCounter++;
  291. if (lightRefreshCounter >= displayRefreshStepSkips) {
  292. lightRefreshCounter = 0;
  293. // Gate light outputs
  294. bool bigLightPulseState = bigLightPulse.process((float)sampleTime * displayRefreshStepSkips);
  295. bool outLightPulseState = outLightPulse.process((float)sampleTime * displayRefreshStepSkips);
  296. for (int i = 0; i < 6; i++) {
  297. bool gate = getGate(i);
  298. bool outLight = (((gate || (i == chan && fillPressed)) && outLightPulseState) || (gate && bigLightPulseState && i == chan));
  299. lights[(CHAN_LIGHTS + i) * 2 + 1].setBrightnessSmooth(outLight ? 1.0f : 0.0f, displayRefreshStepSkips);
  300. lights[(CHAN_LIGHTS + i) * 2 + 0].value = (i == chan ? (1.0f - lights[(CHAN_LIGHTS + i) * 2 + 1].value) / 2.0f : 0.0f);
  301. }
  302. // Big button lights
  303. lights[BIG_LIGHT].value = bank[chan] == 1 ? 1.0f : 0.0f;
  304. lights[BIGC_LIGHT].value = bigLight;
  305. // Metronome light
  306. lights[METRONOME_LIGHT + 1].value = metronomeLightStart;
  307. lights[METRONOME_LIGHT + 0].value = metronomeLightDiv;
  308. // Other push button lights
  309. lights[WRITEFILL_LIGHT].value = writeFillsToMemory ? 1.0f : 0.0f;
  310. lights[QUANTIZEBIG_LIGHT].value = quantizeBig ? 1.0f : 0.0f;
  311. bigLight -= (bigLight / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  312. metronomeLightStart -= (metronomeLightStart / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  313. metronomeLightDiv -= (metronomeLightDiv / lightLambda) * (float)sampleTime * displayRefreshStepSkips;
  314. }
  315. clockTime += sampleTime;
  316. if (clockIgnoreOnReset > 0l)
  317. clockIgnoreOnReset--;
  318. }// step()
  319. inline void performPending(int chan, float lightTime) {
  320. if (pendingOp == 1) {
  321. if (!getGate(chan)) {
  322. setGate(chan);// bank and indexStep are global
  323. bigPulse.trigger(0.001f);
  324. bigLightPulse.trigger(lightTime);
  325. }
  326. }
  327. else {
  328. clearGate(chan);// bank and indexStep are global
  329. }
  330. pendingOp = 0;
  331. }
  332. };
  333. struct BigButtonSeqWidget : ModuleWidget {
  334. struct ChanDisplayWidget : TransparentWidget {
  335. int *chan;
  336. std::shared_ptr<Font> font;
  337. ChanDisplayWidget() {
  338. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  339. }
  340. void draw(NVGcontext *vg) override {
  341. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  342. nvgFontFaceId(vg, font->handle);
  343. //nvgTextLetterSpacing(vg, 2.5);
  344. Vec textPos = Vec(6, 24);
  345. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  346. nvgText(vg, textPos.x, textPos.y, "~", NULL);
  347. nvgFillColor(vg, textColor);
  348. char displayStr[2];
  349. snprintf(displayStr, 2, "%1u", (unsigned) (*chan + 1) );
  350. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  351. }
  352. };
  353. struct StepsDisplayWidget : TransparentWidget {
  354. int *len;
  355. std::shared_ptr<Font> font;
  356. StepsDisplayWidget() {
  357. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  358. }
  359. void draw(NVGcontext *vg) override {
  360. NVGcolor textColor = prepareDisplay(vg, &box, 18);
  361. nvgFontFaceId(vg, font->handle);
  362. //nvgTextLetterSpacing(vg, 2.5);
  363. Vec textPos = Vec(6, 24);
  364. nvgFillColor(vg, nvgTransRGBA(textColor, displayAlpha));
  365. nvgText(vg, textPos.x, textPos.y, "~~", NULL);
  366. nvgFillColor(vg, textColor);
  367. char displayStr[3];
  368. snprintf(displayStr, 3, "%2u", (unsigned) *len );
  369. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  370. }
  371. };
  372. struct PanelThemeItem : MenuItem {
  373. BigButtonSeq *module;
  374. int theme;
  375. void onAction(EventAction &e) override {
  376. module->panelTheme = theme;
  377. }
  378. void step() override {
  379. rightText = (module->panelTheme == theme) ? "✔" : "";
  380. }
  381. };
  382. struct MetronomeItem : MenuItem {
  383. BigButtonSeq *module;
  384. int div;
  385. void onAction(EventAction &e) override {
  386. module->metronomeDiv = div;
  387. }
  388. void step() override {
  389. rightText = (module->metronomeDiv == div) ? "✔" : "";
  390. }
  391. };
  392. Menu *createContextMenu() override {
  393. Menu *menu = ModuleWidget::createContextMenu();
  394. MenuLabel *spacerLabel = new MenuLabel();
  395. menu->addChild(spacerLabel);
  396. BigButtonSeq *module = dynamic_cast<BigButtonSeq*>(this->module);
  397. assert(module);
  398. MenuLabel *themeLabel = new MenuLabel();
  399. themeLabel->text = "Panel Theme";
  400. menu->addChild(themeLabel);
  401. PanelThemeItem *lightItem = new PanelThemeItem();
  402. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  403. lightItem->module = module;
  404. lightItem->theme = 0;
  405. menu->addChild(lightItem);
  406. PanelThemeItem *darkItem = new PanelThemeItem();
  407. std::string hotRodLabel = " Hot-rod";
  408. hotRodLabel.insert(0, darkPanelID);// ImpromptuModular.hpp
  409. darkItem->text = hotRodLabel;
  410. darkItem->module = module;
  411. darkItem->theme = 1;
  412. menu->addChild(darkItem);
  413. menu->addChild(new MenuLabel());// empty line
  414. MenuLabel *metronomeLabel = new MenuLabel();
  415. metronomeLabel->text = "Metronome light";
  416. menu->addChild(metronomeLabel);
  417. MetronomeItem *met1Item = MenuItem::create<MetronomeItem>("Every clock", CHECKMARK(module->metronomeDiv == 1));
  418. met1Item->module = module;
  419. met1Item->div = 1;
  420. menu->addChild(met1Item);
  421. MetronomeItem *met2Item = MenuItem::create<MetronomeItem>("/2", CHECKMARK(module->metronomeDiv == 2));
  422. met2Item->module = module;
  423. met2Item->div = 2;
  424. menu->addChild(met2Item);
  425. MetronomeItem *met4Item = MenuItem::create<MetronomeItem>("/4", CHECKMARK(module->metronomeDiv == 4));
  426. met4Item->module = module;
  427. met4Item->div = 4;
  428. menu->addChild(met4Item);
  429. MetronomeItem *met8Item = MenuItem::create<MetronomeItem>("/8", CHECKMARK(module->metronomeDiv == 8));
  430. met8Item->module = module;
  431. met8Item->div = 8;
  432. menu->addChild(met8Item);
  433. MetronomeItem *met1000Item = MenuItem::create<MetronomeItem>("Full length", CHECKMARK(module->metronomeDiv == 1000));
  434. met1000Item->module = module;
  435. met1000Item->div = 1000;
  436. menu->addChild(met1000Item);
  437. return menu;
  438. }
  439. BigButtonSeqWidget(BigButtonSeq *module) : ModuleWidget(module) {
  440. // Main panel from Inkscape
  441. DynamicSVGPanel *panel = new DynamicSVGPanel();
  442. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BigButtonSeq.svg")));
  443. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BigButtonSeq_dark.svg")));
  444. box.size = panel->box.size;
  445. panel->mode = &module->panelTheme;
  446. addChild(panel);
  447. // Screws
  448. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  449. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme));
  450. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  451. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme));
  452. // Column rulers (horizontal positions)
  453. static const int rowRuler0 = 49;// outputs and leds
  454. static const int colRulerCenter = 115;// not real center, but pos so that a jack would be centered
  455. static const int offsetChanOutX = 20;
  456. static const int colRulerT0 = colRulerCenter - offsetChanOutX * 5;
  457. static const int colRulerT1 = colRulerCenter - offsetChanOutX * 3;
  458. static const int colRulerT2 = colRulerCenter - offsetChanOutX * 1;
  459. static const int colRulerT3 = colRulerCenter + offsetChanOutX * 1;
  460. static const int colRulerT4 = colRulerCenter + offsetChanOutX * 3;
  461. static const int colRulerT5 = colRulerCenter + offsetChanOutX * 5;
  462. static const int ledOffsetY = 28;
  463. // Outputs
  464. addOutput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 0, &module->panelTheme));
  465. addOutput(createDynamicPort<IMPort>(Vec(colRulerT1, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 1, &module->panelTheme));
  466. addOutput(createDynamicPort<IMPort>(Vec(colRulerT2, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 2, &module->panelTheme));
  467. addOutput(createDynamicPort<IMPort>(Vec(colRulerT3, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 3, &module->panelTheme));
  468. addOutput(createDynamicPort<IMPort>(Vec(colRulerT4, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 4, &module->panelTheme));
  469. addOutput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 5, &module->panelTheme));
  470. // LEDs
  471. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT0 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 0));
  472. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT1 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 2));
  473. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT2 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 4));
  474. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT3 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 6));
  475. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT4 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 8));
  476. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT5 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 10));
  477. static const int rowRuler1 = rowRuler0 + 72;// clk, chan and big CV
  478. static const int knobCVjackOffsetX = 52;
  479. // Clock input
  480. addInput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler1), Port::INPUT, module, BigButtonSeq::CLK_INPUT, &module->panelTheme));
  481. // Chan knob and jack
  482. addParam(createDynamicParam<IMSixPosBigKnob>(Vec(colRulerCenter + offsetIMBigKnob, rowRuler1 + offsetIMBigKnob), module, BigButtonSeq::CHAN_PARAM, 0.0f, 6.0f - 1.0f, 0.0f, &module->panelTheme));
  483. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - knobCVjackOffsetX, rowRuler1), Port::INPUT, module, BigButtonSeq::CHAN_INPUT, &module->panelTheme));
  484. // Chan display
  485. ChanDisplayWidget *displayChan = new ChanDisplayWidget();
  486. displayChan->box.pos = Vec(colRulerCenter + 43, rowRuler1 + vOffsetDisplay - 1);
  487. displayChan->box.size = Vec(24, 30);// 1 character
  488. displayChan->chan = &module->chan;
  489. addChild(displayChan);
  490. // Length display
  491. StepsDisplayWidget *displaySteps = new StepsDisplayWidget();
  492. displaySteps->box.pos = Vec(colRulerT5 - 17, rowRuler1 + vOffsetDisplay - 1);
  493. displaySteps->box.size = Vec(40, 30);// 2 characters
  494. displaySteps->len = &module->len;
  495. addChild(displaySteps);
  496. static const int rowRuler2 = rowRuler1 + 50;// len and rnd
  497. static const int lenAndRndKnobOffsetX = 90;
  498. // Len knob and jack
  499. addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerCenter + lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::LEN_PARAM, 0.0f, 64.0f - 1.0f, 32.0f - 1.0f, &module->panelTheme));
  500. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter + lenAndRndKnobOffsetX - knobCVjackOffsetX, rowRuler2), Port::INPUT, module, BigButtonSeq::LEN_INPUT, &module->panelTheme));
  501. // Rnd knob and jack
  502. addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerCenter - lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::RND_PARAM, 0.0f, 100.0f, 0.0f, &module->panelTheme));
  503. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - lenAndRndKnobOffsetX + knobCVjackOffsetX, rowRuler2), Port::INPUT, module, BigButtonSeq::RND_INPUT, &module->panelTheme));
  504. static const int rowRuler3 = rowRuler2 + 35;// bank
  505. static const int rowRuler4 = rowRuler3 + 22;// clear and del
  506. static const int rowRuler5 = rowRuler4 + 52;// reset and fill
  507. static const int clearAndDelButtonOffsetX = (colRulerCenter - colRulerT0) / 2 + 8;
  508. static const int knobCVjackOffsetY = 40;
  509. // Bank button and jack
  510. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter + offsetCKD6b, rowRuler3 + offsetCKD6b), module, BigButtonSeq::BANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  511. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter, rowRuler3 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BANK_INPUT, &module->panelTheme));
  512. // Clear button and jack
  513. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter - clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::CLEAR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  514. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::CLEAR_INPUT, &module->panelTheme));
  515. // Del button and jack
  516. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::DEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  517. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::DEL_INPUT, &module->panelTheme));
  518. // Reset button and jack
  519. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerT0 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  520. addInput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::RESET_INPUT, &module->panelTheme));
  521. // Fill button and jack
  522. addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerT5 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::FILL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  523. addInput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::FILL_INPUT, &module->panelTheme));
  524. // And now time for... BIG BUTTON!
  525. addChild(createLight<GiantLight<RedLight>>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f), module, BigButtonSeq::BIG_LIGHT));
  526. addParam(createParam<LEDBezelBig>(Vec(colRulerCenter + offsetLEDbezelBig, rowRuler5 + 26 + offsetLEDbezelBig), module, BigButtonSeq::BIG_PARAM, 0.0f, 1.0f, 0.0f));
  527. addChild(createLight<GiantLight2<RedLight>>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9), module, BigButtonSeq::BIGC_LIGHT));
  528. // Big input
  529. addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BIG_INPUT, &module->panelTheme));
  530. // Big snap
  531. addParam(createParam<LEDButton>(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetLEDbutton, rowRuler5 + 1 + knobCVjackOffsetY + offsetLEDbutton), module, BigButtonSeq::QUANTIZEBIG_PARAM, 0.0f, 1.0f, 0.0f));
  532. addChild(createLight<MediumLight<GreenLight>>(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetLEDbutton + offsetLEDbuttonLight, rowRuler5 + 1 + knobCVjackOffsetY + offsetLEDbutton + offsetLEDbuttonLight), module, BigButtonSeq::QUANTIZEBIG_LIGHT));
  533. static const int rowRulerExtras = rowRuler4 + 12.0f;
  534. // Mem fill LED button
  535. addParam(createParam<LEDButton>(Vec(colRulerT5 + offsetLEDbutton, rowRulerExtras - offsetLEDbuttonLight), module, BigButtonSeq::WRITEFILL_PARAM, 0.0f, 1.0f, 0.0f));
  536. addChild(createLight<MediumLight<GreenLight>>(Vec(colRulerT5 + offsetLEDbutton + offsetLEDbuttonLight, rowRulerExtras), module, BigButtonSeq::WRITEFILL_LIGHT));
  537. // Metronome light
  538. addChild(createLight<MediumLight<GreenRedLight>>(Vec(colRulerT0 + offsetMediumLight - 1, rowRulerExtras), module, BigButtonSeq::METRONOME_LIGHT + 0));
  539. }
  540. };
  541. } // namespace rack_plugin_ImpromptuModular
  542. using namespace rack_plugin_ImpromptuModular;
  543. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, BigButtonSeq) {
  544. Model *modelBigButtonSeq = Model::create<BigButtonSeq, BigButtonSeqWidget>("Impromptu Modular", "Big-Button-Seq", "SEQ - Big-Button-Seq", SEQUENCER_TAG);
  545. return modelBigButtonSeq;
  546. }
  547. /*CHANGE LOG
  548. 0.6.10: detect BPM and snap BigButton and Del to nearest beat (with timeout if beat slows down too much or stops). TODO: update manual
  549. 0.6.8:
  550. created
  551. */