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.

788 lines
32KB

  1. //***********************************************************************************************
  2. //Atomic Duophonic Voltage Sequencer module for VCV Rack by Pierre Collard and Marc Boulé
  3. //
  4. //Based on code from the Fundamental plugins by Andrew Belt and graphics
  5. // from the Component Library by Wes Milholen.
  6. //See ./LICENSE.txt for all licenses
  7. //See ./res/fonts/ for font licenses
  8. //
  9. //***********************************************************************************************
  10. #include "Geodesics.hpp"
  11. namespace rack_plugin_Geodesics {
  12. struct Ions : Module {
  13. enum ParamIds {
  14. RUN_PARAM,
  15. RESET_PARAM,
  16. ENUMS(CV_PARAMS, 15),// 0 is center, move conter clockwise top atom, then clockwise bot atom
  17. PROB_PARAM,
  18. ENUMS(OCT_PARAMS, 2),
  19. LEAP_PARAM,
  20. ENUMS(STATE_PARAMS, 2),// 3 states : global, local, global+local
  21. PLANK_PARAM,
  22. uncertainty_PARAM,
  23. RESETONRUN_PARAM,
  24. STEPCLOCKS_PARAM,
  25. NUM_PARAMS
  26. };
  27. enum InputIds {
  28. CLK_INPUT,
  29. ENUMS(CLK_INPUTS, 2),
  30. RUN_INPUT,
  31. RESET_INPUT,
  32. PROB_INPUT,// CV_value/10 is added to PROB_PARAM, which is a 0 to 1 knob
  33. ENUMS(OCTCV_INPUTS, 2),
  34. ENUMS(STATECV_INPUTS, 2),
  35. NUM_INPUTS
  36. };
  37. enum OutputIds {
  38. ENUMS(SEQ_OUTPUTS, 2),
  39. ENUMS(JUMP_OUTPUTS, 2),
  40. NUM_OUTPUTS
  41. };
  42. enum LightIds {
  43. ENUMS(BLUE_LIGHTS, 16),
  44. ENUMS(YELLOW_LIGHTS, 16),
  45. RUN_LIGHT,
  46. RESET_LIGHT,
  47. ENUMS(GLOBAL_LIGHTS, 2),// 0 is top atom, 1 is bottom atom
  48. ENUMS(LOCAL_LIGHTS, 2),
  49. LEAP_LIGHT,
  50. ENUMS(OCTA_LIGHTS, 3),// 0 is center, 1 is inside mirrors, 2 is outside mirrors
  51. ENUMS(OCTB_LIGHTS, 3),
  52. ENUMS(PLANK_LIGHT, 3),// room for blue, yellow, white
  53. uncertainty_LIGHT,
  54. ENUMS(JUMP_LIGHTS, 2),
  55. RESETONRUN_LIGHT,
  56. STEPCLOCKS_LIGHT,
  57. NUM_LIGHTS
  58. };
  59. // Constants
  60. static constexpr float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays)
  61. const int cvMap[2][16] = {{0, 1, 2, 3, 4, 5, 6, 7, 0, 8, 9, 10, 11, 12, 13, 14},
  62. {0, 8, 9 ,10, 11, 12, 13, 14, 0, 1, 2, 3, 4, 5, 6, 7}};// map each of the 16 steps of a sequence step to a CV knob index (0-14)
  63. // Need to save, with reset
  64. bool running;
  65. bool resetOnRun;
  66. int quantize;// a.k.a. plank constant, 0 = none, 1 = blue, 2 = yellow, 3 = white (both)
  67. //bool symmetry;
  68. bool uncertainty;
  69. int stepIndexes[2];// position of electrons (sequencers)
  70. int states[2];// which clocks to use (0 = global, 1 = local, 2 = both)
  71. int ranges[2];// [0; 2], number of extra octaves to span each side of central octave (which is C4: 0 - 1V)
  72. bool leap;
  73. // Need to save, no reset
  74. int panelTheme;
  75. // No need to save, with reset
  76. long clockIgnoreOnReset;
  77. float resetLight;
  78. bool rangeInc[2];// true when 1-3-5 increasing, false when 5-3-1 decreasing
  79. // No need to save, no reset
  80. SchmittTrigger runningTrigger;
  81. SchmittTrigger clockTrigger;
  82. SchmittTrigger clocksTriggers[2];
  83. SchmittTrigger resetTrigger;
  84. SchmittTrigger stateTriggers[2];
  85. SchmittTrigger octTriggers[2];
  86. SchmittTrigger stateCVTriggers[2];
  87. SchmittTrigger leapTrigger;
  88. SchmittTrigger plankTrigger;
  89. SchmittTrigger uncertaintyTrigger;
  90. SchmittTrigger resetOnRunTrigger;
  91. SchmittTrigger stepClocksTrigger;
  92. PulseGenerator jumpPulses[2];
  93. float jumpLights[2];
  94. float stepClocksLight;
  95. inline float quantizeCV(float cv) {return roundf(cv * 12.0f) / 12.0f;}
  96. inline bool jumpRandom() {return (randomUniform() < (params[PROB_PARAM].value + inputs[PROB_INPUT].value / 10.0f));}// randomUniform is [0.0, 1.0), see include/util/common.hpp
  97. Ions() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  98. // Need to save, no reset
  99. panelTheme = 0;
  100. // No need to save, no reset
  101. runningTrigger.reset();
  102. clockTrigger.reset();
  103. for (int i = 0; i < 2; i++) {
  104. clocksTriggers[i].reset();
  105. stateTriggers[i].reset();
  106. octTriggers[i].reset();
  107. stateCVTriggers[i].reset();
  108. jumpPulses[i].reset();
  109. jumpLights[i] = 0.0f;
  110. }
  111. stepClocksLight = 0.0f;
  112. resetTrigger.reset();
  113. leapTrigger.reset();
  114. plankTrigger.reset();
  115. uncertaintyTrigger.reset();
  116. resetOnRunTrigger.reset();
  117. stepClocksTrigger.reset();
  118. onReset();
  119. }
  120. // widgets are not yet created when module is created
  121. // even if widgets not created yet, can use params[] and should handle 0.0f value since step may call
  122. // this before widget creation anyways
  123. // called from the main thread if by constructor, called by engine thread if right-click initialization
  124. // when called by constructor, module is created before the first step() is called
  125. void onReset() override {
  126. // Need to save, with reset
  127. running = false;
  128. resetOnRun = false;
  129. quantize = 3;
  130. //symmetry = false;
  131. uncertainty = false;
  132. for (int i = 0; i < 2; i++) {
  133. states[i] = 0;
  134. ranges[i] = 1;
  135. }
  136. leap = false;
  137. initRun(true, false);
  138. // No need to save, with reset
  139. for (int i = 0; i < 2; i++) {
  140. rangeInc[i] = true;
  141. }
  142. }
  143. // widgets randomized before onRandomize() is called
  144. // called by engine thread if right-click randomize
  145. void onRandomize() override {
  146. // Need to save, with reset
  147. running = false;
  148. resetOnRun = false;
  149. quantize = randomu32() % 4;
  150. //symmetry = false;
  151. uncertainty = false;
  152. for (int i = 0; i < 2; i++) {
  153. states[i] = randomu32() % 3;
  154. ranges[i] = randomu32() % 3;
  155. }
  156. leap = (randomu32() % 2) > 0;
  157. initRun(true, true);
  158. // No need to save, with reset
  159. for (int i = 0; i < 2; i++) {
  160. rangeInc[i] = true;
  161. }
  162. }
  163. void initRun(bool hard, bool randomize) {// run button activated or run edge in run input jack
  164. if (hard) {
  165. if (randomize) {
  166. stepIndexes[0] = randomu32() % 16;
  167. stepIndexes[1] = randomu32() % 16;
  168. }
  169. else {
  170. stepIndexes[0] = 0;
  171. stepIndexes[1] = 0;
  172. }
  173. }
  174. clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate());
  175. resetLight = 0.0f;
  176. }
  177. // called by main thread
  178. json_t *toJson() override {
  179. json_t *rootJ = json_object();
  180. // Need to save (reset or not)
  181. // panelTheme
  182. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  183. // resetOnRun
  184. json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun));
  185. // quantize
  186. json_object_set_new(rootJ, "quantize", json_integer(quantize));
  187. // symmetry
  188. //json_object_set_new(rootJ, "symmetry", json_boolean(symmetry));
  189. // uncertainty
  190. json_object_set_new(rootJ, "uncertainty", json_boolean(uncertainty));
  191. // running
  192. json_object_set_new(rootJ, "running", json_boolean(running));
  193. // stepIndexes
  194. json_object_set_new(rootJ, "stepIndexes0", json_integer(stepIndexes[0]));
  195. json_object_set_new(rootJ, "stepIndexes1", json_integer(stepIndexes[1]));
  196. // states
  197. json_object_set_new(rootJ, "states0", json_integer(states[0]));
  198. json_object_set_new(rootJ, "states1", json_integer(states[1]));
  199. // ranges
  200. json_object_set_new(rootJ, "ranges0", json_integer(ranges[0]));
  201. json_object_set_new(rootJ, "ranges1", json_integer(ranges[1]));
  202. // leap
  203. json_object_set_new(rootJ, "leap", json_boolean(leap));
  204. return rootJ;
  205. }
  206. // widgets have their fromJson() called before this fromJson() is called
  207. // called by main thread
  208. void fromJson(json_t *rootJ) override {
  209. // Need to save (reset or not)
  210. // panelTheme
  211. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  212. if (panelThemeJ)
  213. panelTheme = json_integer_value(panelThemeJ);
  214. // resetOnRun
  215. json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun");
  216. if (resetOnRunJ)
  217. resetOnRun = json_is_true(resetOnRunJ);
  218. // quantize
  219. json_t *quantizeJ = json_object_get(rootJ, "quantize");
  220. if (quantizeJ)
  221. quantize = json_integer_value(quantizeJ);
  222. // symmetry
  223. //json_t *symmetryJ = json_object_get(rootJ, "symmetry");
  224. //if (symmetryJ)
  225. //symmetry = json_is_true(symmetryJ);
  226. // uncertainty
  227. json_t *uncertaintyJ = json_object_get(rootJ, "uncertainty");
  228. if (uncertaintyJ)
  229. uncertainty = json_is_true(uncertaintyJ);
  230. // running
  231. json_t *runningJ = json_object_get(rootJ, "running");
  232. if (runningJ)
  233. running = json_is_true(runningJ);
  234. // stepIndexes
  235. json_t *stepIndexes0J = json_object_get(rootJ, "stepIndexes0");
  236. if (stepIndexes0J)
  237. stepIndexes[0] = json_integer_value(stepIndexes0J);
  238. json_t *stepIndexes1J = json_object_get(rootJ, "stepIndexes1");
  239. if (stepIndexes1J)
  240. stepIndexes[1] = json_integer_value(stepIndexes1J);
  241. // states
  242. json_t *states0J = json_object_get(rootJ, "states0");
  243. if (states0J)
  244. states[0] = json_integer_value(states0J);
  245. json_t *states1J = json_object_get(rootJ, "states1");
  246. if (states1J)
  247. states[1] = json_integer_value(states1J);
  248. // ranges
  249. json_t *ranges0J = json_object_get(rootJ, "ranges0");
  250. if (ranges0J)
  251. ranges[0] = json_integer_value(ranges0J);
  252. json_t *ranges1J = json_object_get(rootJ, "ranges1");
  253. if (ranges1J)
  254. ranges[1] = json_integer_value(ranges1J);
  255. // leap
  256. json_t *leapJ = json_object_get(rootJ, "leap");
  257. if (leapJ)
  258. leap = json_is_true(leapJ);
  259. // No need to save, with reset
  260. initRun(true, false);
  261. rangeInc[0] = true;
  262. rangeInc[1] = true;
  263. }
  264. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  265. void step() override {
  266. float sampleTime = engineGetSampleTime();
  267. //********** Buttons, knobs, switches and inputs **********
  268. // Run button
  269. if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUN_INPUT].value)) {
  270. running = !running;
  271. if (running)
  272. initRun(resetOnRun, false);
  273. }
  274. // Leap button
  275. if (leapTrigger.process(params[LEAP_PARAM].value)) {
  276. leap = !leap;
  277. }
  278. // Plank button (quatize)
  279. if (plankTrigger.process(params[PLANK_PARAM].value)) {
  280. quantize++;
  281. if (quantize >= 4)
  282. quantize = 0;
  283. }
  284. // uncertainty button
  285. if (uncertaintyTrigger.process(params[uncertainty_PARAM].value)) {
  286. uncertainty = !uncertainty;
  287. }
  288. // Reset on Run button
  289. if (resetOnRunTrigger.process(params[RESETONRUN_PARAM].value)) {
  290. resetOnRun = !resetOnRun;
  291. }
  292. // State buttons and CV inputs (state: 0 = global, 1 = local, 2 = both)
  293. for (int i = 0; i < 2; i++) {
  294. int stateTrig = stateTriggers[i].process(params[STATE_PARAMS + i].value);
  295. if (inputs[STATECV_INPUTS + i].active) {
  296. if (inputs[STATECV_INPUTS + i].value <= -1.0f)
  297. states[i] = 1;
  298. else if (inputs[STATECV_INPUTS + i].value < 1.0f)
  299. states[i] = 2;
  300. else
  301. states[i] = 0;
  302. }
  303. else if (stateTrig) {
  304. states[i]++;
  305. if (states[i] >= 3)
  306. states[i] = 0;
  307. }
  308. }
  309. // Range buttons and CV inputs
  310. for (int i = 0; i < 2; i++) {
  311. int rangeTrig = octTriggers[i].process(params[OCT_PARAMS + i].value);
  312. if (inputs[OCTCV_INPUTS + i].active) {
  313. if (inputs[OCTCV_INPUTS + i].value <= -1.0f)
  314. ranges[i] = 0;
  315. else if (inputs[OCTCV_INPUTS + i].value < 1.0f)
  316. ranges[i] = 1;
  317. else
  318. ranges[i] = 2;
  319. }
  320. else if (rangeTrig) {
  321. if (rangeInc[i]) {
  322. ranges[i]++;
  323. if (ranges[i] >= 3) {
  324. ranges[i] = 1;
  325. rangeInc[i] = false;
  326. }
  327. }
  328. else {
  329. ranges[i]--;
  330. if (ranges[i] < 0) {
  331. ranges[i] = 1;
  332. rangeInc[i] = true;
  333. }
  334. }
  335. }
  336. }
  337. //********** Clock and reset **********
  338. // Clocks
  339. bool globalClockTrig = clockTrigger.process(inputs[CLK_INPUT].value);
  340. bool stepClocksTrig = stepClocksTrigger.process(params[STEPCLOCKS_PARAM].value);
  341. for (int i = 0; i < 2; i++) {
  342. int jumpCount = 0;
  343. if (running && clockIgnoreOnReset == 0l) {
  344. // Local clocks and uncertainty
  345. bool localClockTrig = clocksTriggers[i].process(inputs[CLK_INPUTS + i].value);
  346. localClockTrig &= (states[i] >= 1);
  347. if (localClockTrig) {
  348. if (uncertainty) {// local clock modified by uncertainty
  349. int numSteps = 8;
  350. int prob = randomu32() % 1000;
  351. if (prob < 175)
  352. numSteps = 1;
  353. else if (prob < 330) // 175 + 155
  354. numSteps = 2;
  355. else if (prob < 475) // 175 + 155 + 145
  356. numSteps = 3;
  357. else if (prob < 610) // 175 + 155 + 145 + 135
  358. numSteps = 4;
  359. else if (prob < 725) // 175 + 155 + 145 + 135 + 115
  360. numSteps = 5;
  361. else if (prob < 830) // 175 + 155 + 145 + 135 + 115 + 105
  362. numSteps = 6;
  363. else if (prob < 925) // 175 + 155 + 145 + 135 + 115 + 105 + 95
  364. numSteps = 7;
  365. for (int n = 0; n < numSteps; n++)
  366. jumpCount += stepElectron(i, leap);
  367. }
  368. else
  369. jumpCount += stepElectron(i, leap);// normal local clock
  370. }
  371. // Global clock
  372. if (globalClockTrig && ((states[i] & 0x1) == 0) && !localClockTrig) {
  373. jumpCount += stepElectron(i, leap);
  374. }
  375. }
  376. // Magnetic clock (step clock)
  377. if (stepClocksTrig)
  378. jumpCount += stepElectron(i, leap);
  379. // Jump occurred feedback
  380. if ((jumpCount & 0x1) != 0) {
  381. jumpPulses[i].trigger(0.001f);
  382. jumpLights[i] = 1.0f;
  383. }
  384. }
  385. //if (symmetry)
  386. //stepIndexes[1] = stepIndexes[0];
  387. // Reset
  388. if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) {
  389. initRun(true, uncertainty);
  390. resetLight = 1.0f;
  391. clockTrigger.reset();
  392. }
  393. else
  394. resetLight -= (resetLight / lightLambda) * sampleTime;
  395. //********** Outputs and lights **********
  396. // Outputs
  397. for (int i = 0; i < 2; i++) {
  398. float knobVal = params[CV_PARAMS + cvMap[i][stepIndexes[i]]].value;
  399. float cv = 0.0f;
  400. int range = ranges[i];
  401. if ( (i == 0 && (quantize & 0x1) != 0) || (i == 1 && (quantize > 1)) ) {
  402. cv = (knobVal * (float)(range * 2 + 1) - (float)range);
  403. cv = quantizeCV(cv);
  404. }
  405. else {
  406. int maxCV = (range == 0 ? 1 : (range * 5));// maxCV is [1, 5, 10]
  407. cv = knobVal * (float)(maxCV * 2) - (float)maxCV;
  408. }
  409. outputs[SEQ_OUTPUTS + i].value = cv;
  410. outputs[JUMP_OUTPUTS + i].value = jumpPulses[i].process((float)sampleTime);
  411. }
  412. // Blue and Yellow lights
  413. for (int i = 0; i < 16; i++) {
  414. lights[BLUE_LIGHTS + i].value = (stepIndexes[0] == i ? 1.0f : 0.0f);
  415. lights[YELLOW_LIGHTS + i].value = (stepIndexes[1] == i ? 1.0f : 0.0f);
  416. }
  417. // Reset light
  418. lights[RESET_LIGHT].value = resetLight;
  419. // Run light
  420. lights[RUN_LIGHT].value = running ? 1.0f : 0.0f;
  421. // State lights
  422. for (int i = 0; i < 2; i++) {
  423. lights[GLOBAL_LIGHTS + i].value = (states[i] & 0x1) == 0 ? 0.5f : 0.0f;
  424. lights[LOCAL_LIGHTS + i].value = states[i] >= 1 ? 0.5f : 0.0f;
  425. }
  426. // Leap, Plank, uncertainty and ResetOnRun lights
  427. lights[LEAP_LIGHT].value = leap ? 1.0f : 0.0f;
  428. lights[PLANK_LIGHT + 0].value = (quantize == 1) ? 1.0f : 0.0f;// Blue
  429. lights[PLANK_LIGHT + 1].value = (quantize == 2) ? 1.0f : 0.0f;// Yellow
  430. lights[PLANK_LIGHT + 2].value = (quantize == 3) ? 1.0f : 0.0f;// White
  431. lights[uncertainty_LIGHT].value = uncertainty ? 1.0f : 0.0f;
  432. lights[RESETONRUN_LIGHT].value = resetOnRun ? 1.0f : 0.0f;
  433. // Range lights
  434. for (int i = 0; i < 3; i++) {
  435. lights[OCTA_LIGHTS + i].value = (i <= ranges[0] ? 1.0f : 0.0f);
  436. lights[OCTB_LIGHTS + i].value = (i <= ranges[1] ? 1.0f : 0.0f);
  437. }
  438. // Jump lights
  439. for (int i = 0; i < 2; i++) {
  440. lights[JUMP_LIGHTS + i].value = jumpLights[i];
  441. jumpLights[i] -= (jumpLights[i] / lightLambda) * sampleTime;
  442. }
  443. // Step clocks light
  444. if (stepClocksTrig)
  445. stepClocksLight = 1.0f;
  446. else
  447. stepClocksLight -= (stepClocksLight / lightLambda) * sampleTime;
  448. lights[STEPCLOCKS_LIGHT].value = stepClocksLight;
  449. if (clockIgnoreOnReset > 0l)
  450. clockIgnoreOnReset--;
  451. }// step()
  452. int stepElectron(int i, bool leap) {
  453. int jumped = 0;
  454. int base = stepIndexes[i] & 0x8;// 0 or 8
  455. int step8 = stepIndexes[i] & 0x7;// 0 to 7
  456. if ( (step8 == 7 || leap) && jumpRandom() ) {
  457. jumped = 1;
  458. base = 8 - base;// change atom
  459. }
  460. step8++;
  461. if (step8 > 7)
  462. step8 = 0;
  463. stepIndexes[i] = base | step8;
  464. return jumped;
  465. }
  466. };
  467. struct IonsWidget : ModuleWidget {
  468. struct PanelThemeItem : MenuItem {
  469. Ions *module;
  470. int theme;
  471. void onAction(EventAction &e) override {
  472. module->panelTheme = theme;
  473. }
  474. void step() override {
  475. rightText = (module->panelTheme == theme) ? "✔" : "";
  476. }
  477. };
  478. Menu *createContextMenu() override {
  479. Menu *menu = ModuleWidget::createContextMenu();
  480. MenuLabel *spacerLabel = new MenuLabel();
  481. menu->addChild(spacerLabel);
  482. Ions *module = dynamic_cast<Ions*>(this->module);
  483. assert(module);
  484. MenuLabel *themeLabel = new MenuLabel();
  485. themeLabel->text = "Panel Theme";
  486. menu->addChild(themeLabel);
  487. PanelThemeItem *lightItem = new PanelThemeItem();
  488. lightItem->text = lightPanelID;// Geodesics.hpp
  489. lightItem->module = module;
  490. lightItem->theme = 0;
  491. menu->addChild(lightItem);
  492. PanelThemeItem *darkItem = new PanelThemeItem();
  493. darkItem->text = darkPanelID;// Geodesics.hpp
  494. darkItem->module = module;
  495. darkItem->theme = 1;
  496. //menu->addChild(darkItem);
  497. return menu;
  498. }
  499. IonsWidget(Ions *module) : ModuleWidget(module) {
  500. // Main panel from Inkscape
  501. DynamicSVGPanel *panel = new DynamicSVGPanel();
  502. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/IonsBG-01.svg")));
  503. //panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/IonsBG-02.svg")));// no dark pannel for now
  504. box.size = panel->box.size;
  505. panel->mode = &module->panelTheme;
  506. addChild(panel);
  507. // Screws
  508. // part of svg panel, no code required
  509. float colRulerCenter = box.size.x / 2.0f;
  510. static constexpr float rowRulerAtomA = 125.5;
  511. static constexpr float rowRulerAtomB = 251.5f;
  512. static constexpr float radius1 = 21.0f;
  513. static constexpr float offset1 = 14.0f;
  514. static constexpr float radius2 = 35.0f;
  515. static constexpr float offset2 = 25.0f;
  516. static constexpr float radius3 = 61.0f;
  517. static constexpr float offset3 = 43.0f;
  518. // Outputs
  519. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerAtomA), Port::OUTPUT, module, Ions::SEQ_OUTPUTS + 0, &module->panelTheme));
  520. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerAtomB), Port::OUTPUT, module, Ions::SEQ_OUTPUTS + 1, &module->panelTheme));
  521. // CV knobs
  522. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomA + radius3 + 2.0f), module, Ions::CV_PARAMS + 0, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  523. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomA + offset3), module, Ions::CV_PARAMS + 1, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  524. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + radius3, rowRulerAtomA), module, Ions::CV_PARAMS + 2, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  525. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomA - offset3), module, Ions::CV_PARAMS + 3, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  526. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomA - radius3), module, Ions::CV_PARAMS + 4, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  527. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomA - offset3), module, Ions::CV_PARAMS + 5, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  528. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - radius3, rowRulerAtomA), module, Ions::CV_PARAMS + 6, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  529. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomA + offset3), module, Ions::CV_PARAMS + 7, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  530. //
  531. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomB - offset3), module, Ions::CV_PARAMS + 8, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  532. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + radius3, rowRulerAtomB), module, Ions::CV_PARAMS + 9, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  533. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomB + offset3), module, Ions::CV_PARAMS + 10, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  534. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomB + radius3), module, Ions::CV_PARAMS + 11, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  535. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomB + offset3), module, Ions::CV_PARAMS + 12, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  536. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - radius3, rowRulerAtomB), module, Ions::CV_PARAMS + 13, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  537. addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomB - offset3), module, Ions::CV_PARAMS + 14, 0.0f, 1.0f, 0.5f, &module->panelTheme));
  538. // Prob knob and CV inuput
  539. float probX = colRulerCenter + 2.0f * offset3;
  540. float probY = rowRulerAtomA + radius3 + 2.0f;
  541. addParam(createDynamicParam<GeoKnobLeft>(Vec(probX, probY), module, Ions::PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  542. addInput(createDynamicPort<GeoPort>(Vec(probX + 32.0f, probY), Port::INPUT, module, Ions::PROB_INPUT, &module->panelTheme));
  543. // Jump pulses
  544. addOutput(createDynamicPort<GeoPort>(Vec(probX + 18.0f, probY - 37.0f), Port::OUTPUT, module, Ions::JUMP_OUTPUTS + 0, &module->panelTheme));
  545. addOutput(createDynamicPort<GeoPort>(Vec(probX + 18.0f, probY + 37.0f), Port::OUTPUT, module, Ions::JUMP_OUTPUTS + 1, &module->panelTheme));
  546. // Jump lights
  547. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(probX, probY - 46.0f), module, Ions::JUMP_LIGHTS + 0));
  548. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(probX, probY + 46.0f), module, Ions::JUMP_LIGHTS + 1));
  549. // Leap light and button
  550. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - 86.5f, 62.5f), module, Ions::LEAP_LIGHT));
  551. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - 77.5f, 50.5f), module, Ions::LEAP_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  552. // Plank light and button
  553. addChild(createLightCentered<SmallLight<GeoBlueYellowWhiteLight>>(Vec(colRulerCenter + 86.5f, 62.5f), module, Ions::PLANK_LIGHT));
  554. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + 77.5f, 50.5f), module, Ions::PLANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  555. // Octave buttons and lights
  556. float octX = colRulerCenter + 107.0f;
  557. float octOffsetY = 10.0f;
  558. float octYA = rowRulerAtomA - octOffsetY;
  559. float octYB = rowRulerAtomB + octOffsetY;
  560. // top:
  561. addParam(createDynamicParam<GeoPushButton>(Vec(octX, octYA), module, Ions::OCT_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  562. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 15.0f, octYA + 2.5f), module, Ions::OCTA_LIGHTS + 0));
  563. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 12.0f, octYA - 8.0f), module, Ions::OCTA_LIGHTS + 1));
  564. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 10.0f, octYA + 11.5f), module, Ions::OCTA_LIGHTS + 1));
  565. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 3.0f, octYA - 13.5f), module, Ions::OCTA_LIGHTS + 2));
  566. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX + 0.0f, octYA + 15.0f), module, Ions::OCTA_LIGHTS + 2));
  567. // bottom:
  568. addParam(createDynamicParam<GeoPushButton>(Vec(octX, octYB), module, Ions::OCT_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  569. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 15.0f, octYB - 2.5f), module, Ions::OCTB_LIGHTS + 0));
  570. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 12.0f, octYB + 8.0f), module, Ions::OCTB_LIGHTS + 1));
  571. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 10.0f, octYB - 11.5f), module, Ions::OCTB_LIGHTS + 1));
  572. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 3.0f, octYB + 13.5f), module, Ions::OCTB_LIGHTS + 2));
  573. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX + 0.0f, octYB - 15.0f), module, Ions::OCTB_LIGHTS + 2));
  574. // Oct CV inputs
  575. addInput(createDynamicPort<GeoPort>(Vec(octX - 9.0f, octYA - 34.5), Port::INPUT, module, Ions::OCTCV_INPUTS + 0, &module->panelTheme));
  576. addInput(createDynamicPort<GeoPort>(Vec(octX - 9.0f, octYB + 34.5), Port::INPUT, module, Ions::OCTCV_INPUTS + 1, &module->panelTheme));
  577. // Blue electron lights
  578. // top blue
  579. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomA + radius2), module, Ions::BLUE_LIGHTS + 0));
  580. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset2, rowRulerAtomA + offset2), module, Ions::BLUE_LIGHTS + 1));
  581. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radius2, rowRulerAtomA), module, Ions::BLUE_LIGHTS + 2));
  582. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset2, rowRulerAtomA - offset2), module, Ions::BLUE_LIGHTS + 3));
  583. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomA - radius2), module, Ions::BLUE_LIGHTS + 4));
  584. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset2, rowRulerAtomA - offset2), module, Ions::BLUE_LIGHTS + 5));
  585. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radius2, rowRulerAtomA), module, Ions::BLUE_LIGHTS + 6));
  586. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset2, rowRulerAtomA + offset2), module, Ions::BLUE_LIGHTS + 7));
  587. // bottom blue
  588. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomB - radius1), module, Ions::BLUE_LIGHTS + 8));
  589. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset1, rowRulerAtomB - offset1), module, Ions::BLUE_LIGHTS + 9));
  590. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radius1, rowRulerAtomB), module, Ions::BLUE_LIGHTS + 10));
  591. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset1, rowRulerAtomB + offset1), module, Ions::BLUE_LIGHTS + 11));
  592. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomB + radius1), module, Ions::BLUE_LIGHTS + 12));
  593. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset1, rowRulerAtomB + offset1), module, Ions::BLUE_LIGHTS + 13));
  594. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radius1, rowRulerAtomB), module, Ions::BLUE_LIGHTS + 14));
  595. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset1, rowRulerAtomB - offset1), module, Ions::BLUE_LIGHTS + 15));
  596. // Yellow electron lights
  597. // bottom yellow
  598. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomB - radius2), module, Ions::YELLOW_LIGHTS + 0));
  599. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset2, rowRulerAtomB - offset2), module, Ions::YELLOW_LIGHTS + 1));
  600. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + radius2, rowRulerAtomB), module, Ions::YELLOW_LIGHTS + 2));
  601. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset2, rowRulerAtomB + offset2), module, Ions::YELLOW_LIGHTS + 3));
  602. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomB + radius2), module, Ions::YELLOW_LIGHTS + 4));
  603. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset2, rowRulerAtomB + offset2), module, Ions::YELLOW_LIGHTS + 5));
  604. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - radius2, rowRulerAtomB), module, Ions::YELLOW_LIGHTS + 6));
  605. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset2, rowRulerAtomB - offset2), module, Ions::YELLOW_LIGHTS + 7));
  606. // top yellow
  607. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomA + radius1), module, Ions::YELLOW_LIGHTS + 8));
  608. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset1, rowRulerAtomA + offset1), module, Ions::YELLOW_LIGHTS + 9));
  609. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + radius1, rowRulerAtomA), module, Ions::YELLOW_LIGHTS + 10));
  610. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset1, rowRulerAtomA - offset1), module, Ions::YELLOW_LIGHTS + 11));
  611. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomA - radius1), module, Ions::YELLOW_LIGHTS + 12));
  612. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset1, rowRulerAtomA - offset1), module, Ions::YELLOW_LIGHTS + 13));
  613. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - radius1, rowRulerAtomA), module, Ions::YELLOW_LIGHTS + 14));
  614. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset1, rowRulerAtomA + offset1), module, Ions::YELLOW_LIGHTS + 15));
  615. // Run jack, light and button
  616. static constexpr float rowRulerRunJack = 344.5f;
  617. static constexpr float offsetRunJackX = 119.5f;
  618. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetRunJackX, rowRulerRunJack), Port::INPUT, module, Ions::RUN_INPUT, &module->panelTheme));
  619. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRunJackX + 18.0f, rowRulerRunJack), module, Ions::RUN_LIGHT));
  620. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRunJackX + 33.0f, rowRulerRunJack), module, Ions::RUN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  621. // Reset jack, light and button
  622. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetRunJackX, rowRulerRunJack), Port::INPUT, module, Ions::RESET_INPUT, &module->panelTheme));
  623. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRunJackX - 18.0f, rowRulerRunJack), module, Ions::RESET_LIGHT));
  624. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRunJackX - 33.0f, rowRulerRunJack), module, Ions::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  625. static constexpr float offsetMagneticButton = 42.5f;
  626. // Magnetic clock (step clocks)
  627. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetMagneticButton - 15.0f, rowRulerRunJack), module, Ions::STEPCLOCKS_LIGHT));
  628. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetMagneticButton, rowRulerRunJack), module, Ions::STEPCLOCKS_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  629. // Reset on Run light and button
  630. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetMagneticButton + 15.0f, rowRulerRunJack), module, Ions::RESETONRUN_LIGHT));
  631. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetMagneticButton, rowRulerRunJack), module, Ions::RESETONRUN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  632. // Globak clock
  633. float gclkX = colRulerCenter - 2.0f * offset3;
  634. float gclkY = rowRulerAtomA + radius3 + 2.0f;
  635. addInput(createDynamicPort<GeoPort>(Vec(gclkX, gclkY), Port::INPUT, module, Ions::CLK_INPUT, &module->panelTheme));
  636. // global lights
  637. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 12.0f, gclkY - 20.0f), module, Ions::GLOBAL_LIGHTS + 0));
  638. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 12.0f, gclkY + 20.0f), module, Ions::GLOBAL_LIGHTS + 1));
  639. // state buttons
  640. addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 17.0f, gclkY - 34.0f), module, Ions::STATE_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  641. addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 17.0f, gclkY + 34.0f), module, Ions::STATE_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  642. // local lights
  643. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(gclkX - 20.0f, gclkY - 48.5f), module, Ions::LOCAL_LIGHTS + 0));
  644. addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(gclkX - 20.0f, gclkY + 48.5f), module, Ions::LOCAL_LIGHTS + 1));
  645. // local inputs
  646. addInput(createDynamicPort<GeoPort>(Vec(gclkX - 21.0f, gclkY - 72.0f), Port::INPUT, module, Ions::CLK_INPUTS + 0, &module->panelTheme));
  647. addInput(createDynamicPort<GeoPort>(Vec(gclkX - 21.0f, gclkY + 72.0f), Port::INPUT, module, Ions::CLK_INPUTS + 1, &module->panelTheme));
  648. // state inputs
  649. addInput(createDynamicPort<GeoPort>(Vec(gclkX - 11.0f, gclkY - 107.0f), Port::INPUT, module, Ions::STATECV_INPUTS + 0, &module->panelTheme));
  650. addInput(createDynamicPort<GeoPort>(Vec(gclkX - 11.0f, gclkY + 107.0f), Port::INPUT, module, Ions::STATECV_INPUTS + 1, &module->panelTheme));
  651. // uncertainty light and button
  652. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 20.0f, gclkY), module, Ions::uncertainty_LIGHT));
  653. addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 34.0f, gclkY), module, Ions::uncertainty_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  654. }
  655. };
  656. } // namespace rack_plugin_Geodesics
  657. using namespace rack_plugin_Geodesics;
  658. RACK_PLUGIN_MODEL_INIT(Geodesics, Ions) {
  659. Model *modelIons = Model::create<Ions, IonsWidget>("Geodesics", "Ions", "Ions", SEQUENCER_TAG);
  660. return modelIons;
  661. }
  662. /*CHANGE LOG
  663. 0.6.1:
  664. Ions reloaded (many changes)
  665. 0.6.0:
  666. created
  667. */