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.

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