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.

570 lines
27KB

  1. //***********************************************************************************************
  2. //Neutron Powered Rotating Crossfader 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 Pulsars : Module {
  13. enum ParamIds {
  14. ENUMS(VOID_PARAMS, 2),// push-button
  15. ENUMS(REV_PARAMS, 2),// push-button
  16. ENUMS(RND_PARAMS, 2),// push-button
  17. ENUMS(CVLEVEL_PARAMS, 2),// push-button
  18. NUM_PARAMS
  19. };
  20. enum InputIds {
  21. ENUMS(INA_INPUTS, 8),
  22. INB_INPUT,
  23. ENUMS(LFO_INPUTS, 2),
  24. ENUMS(VOID_INPUTS, 2),
  25. ENUMS(REV_INPUTS, 2),
  26. NUM_INPUTS
  27. };
  28. enum OutputIds {
  29. OUTA_OUTPUT,
  30. ENUMS(OUTB_OUTPUTS, 8),
  31. NUM_OUTPUTS
  32. };
  33. enum LightIds {
  34. ENUMS(LFO_LIGHTS, 2),
  35. ENUMS(MIXA_LIGHTS, 8),
  36. ENUMS(MIXB_LIGHTS, 8),
  37. ENUMS(VOID_LIGHTS, 2),
  38. ENUMS(REV_LIGHTS, 2),
  39. ENUMS(RND_LIGHTS, 2),
  40. ENUMS(CVALEVEL_LIGHTS, 2),// White, but two lights (light 0 is cvMode bit = 0, light 1 is cvMode bit = 1)
  41. ENUMS(CVBLEVEL_LIGHTS, 2),// White, but two lights
  42. NUM_LIGHTS
  43. };
  44. // Constants
  45. static constexpr float epsilon = 0.0001f;// pulsar crossovers at epsilon and 1-epsilon in 0.0f to 1.0f space
  46. // Need to save, with reset
  47. bool isVoid[2];
  48. bool isReverse[2];
  49. bool isRandom[2];
  50. int cvMode;// 0 is -5v to 5v, 1 is -10v to 10v; bit 0 is upper Pulsar, bit 1 is lower Pulsar
  51. // Need to save, no reset
  52. int panelTheme;
  53. // No need to save, with reset
  54. int posA;// always between 0 and 7
  55. int posB;// always between 0 and 7
  56. int posAnext;// always between 0 and 7
  57. int posBnext;// always between 0 and 7
  58. bool topCross[2];
  59. // No need to save, no reset
  60. SchmittTrigger voidTriggers[2];
  61. SchmittTrigger revTriggers[2];
  62. SchmittTrigger rndTriggers[2];
  63. SchmittTrigger cvLevelTriggers[2];
  64. float lfoLights[2];
  65. Pulsars() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  66. // Need to save, no reset
  67. panelTheme = 0;
  68. // No need to save, no reset
  69. for (int i = 0; i < 2; i++) {
  70. lfoLights[i] = 0.0f;
  71. voidTriggers[i].reset();
  72. revTriggers[i].reset();
  73. rndTriggers[i].reset();
  74. }
  75. onReset();
  76. }
  77. // widgets are not yet created when module is created
  78. // even if widgets not created yet, can use params[] and should handle 0.0f value since step may call
  79. // this before widget creation anyways
  80. // called from the main thread if by constructor, called by engine thread if right-click initialization
  81. // when called by constructor, module is created before the first step() is called
  82. void onReset() override {
  83. // Need to save, with reset
  84. cvMode = 0;
  85. for (int i = 0; i < 2; i++) {
  86. topCross[i] = false;
  87. isVoid[i] = false;
  88. isReverse[i] = false;
  89. isRandom[i] = false;
  90. }
  91. // No need to save, with reset
  92. posA = 0;// no need to check isVoid here, will be checked in step()
  93. posB = 0;// no need to check isVoid here, will be checked in step()
  94. posAnext = 1;// no need to check isVoid here, will be checked in step()
  95. posBnext = 1;// no need to check isVoid here, will be checked in step()
  96. }
  97. // widgets randomized before onRandomize() is called
  98. // called by engine thread if right-click randomize
  99. void onRandomize() override {
  100. // Need to save, with reset
  101. for (int i = 0; i < 2; i++) {
  102. isVoid[i] = (randomu32() % 2) > 0;
  103. isReverse[i] = (randomu32() % 2) > 0;
  104. isRandom[i] = (randomu32() % 2) > 0;
  105. }
  106. // No need to save, with reset
  107. posA = randomu32() % 8;// no need to check isVoid here, will be checked in step()
  108. posB = randomu32() % 8;// no need to check isVoid here, will be checked in step()
  109. posAnext = (posA + (isReverse[0] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step()
  110. posBnext = (posB + (isReverse[1] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step()
  111. }
  112. // called by main thread
  113. json_t *toJson() override {
  114. json_t *rootJ = json_object();
  115. // Need to save (reset or not)
  116. // isVoid
  117. json_object_set_new(rootJ, "isVoid0", json_real(isVoid[0]));
  118. json_object_set_new(rootJ, "isVoid1", json_real(isVoid[1]));
  119. // isReverse
  120. json_object_set_new(rootJ, "isReverse0", json_real(isReverse[0]));
  121. json_object_set_new(rootJ, "isReverse1", json_real(isReverse[1]));
  122. // isRandom
  123. json_object_set_new(rootJ, "isRandom0", json_real(isRandom[0]));
  124. json_object_set_new(rootJ, "isRandom1", json_real(isRandom[1]));
  125. // panelTheme
  126. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  127. // cvMode
  128. json_object_set_new(rootJ, "cvMode", json_integer(cvMode));
  129. return rootJ;
  130. }
  131. // widgets have their fromJson() called before this fromJson() is called
  132. // called by main thread
  133. void fromJson(json_t *rootJ) override {
  134. // Need to save (reset or not)
  135. // isVoid
  136. json_t *isVoid0J = json_object_get(rootJ, "isVoid0");
  137. if (isVoid0J)
  138. isVoid[0] = json_real_value(isVoid0J);
  139. json_t *isVoid1J = json_object_get(rootJ, "isVoid1");
  140. if (isVoid1J)
  141. isVoid[1] = json_real_value(isVoid1J);
  142. // isReverse
  143. json_t *isReverse0J = json_object_get(rootJ, "isReverse0");
  144. if (isReverse0J)
  145. isReverse[0] = json_real_value(isReverse0J);
  146. json_t *isReverse1J = json_object_get(rootJ, "isReverse1");
  147. if (isReverse1J)
  148. isReverse[1] = json_real_value(isReverse1J);
  149. // isRandom
  150. json_t *isRandom0J = json_object_get(rootJ, "isRandom0");
  151. if (isRandom0J)
  152. isRandom[0] = json_real_value(isRandom0J);
  153. json_t *isRandom1J = json_object_get(rootJ, "isRandom1");
  154. if (isRandom1J)
  155. isRandom[1] = json_real_value(isRandom1J);
  156. // panelTheme
  157. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  158. if (panelThemeJ)
  159. panelTheme = json_integer_value(panelThemeJ);
  160. // cvMode
  161. json_t *cvModeJ = json_object_get(rootJ, "cvMode");
  162. if (cvModeJ)
  163. cvMode = json_integer_value(cvModeJ);
  164. // No need to save, with reset
  165. posA = 0;// no need to check isVoid here, will be checked in step()
  166. posB = 0;// no need to check isVoid here, will be checked in step()
  167. posAnext = (posA + (isReverse[0] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step()
  168. posBnext = (posB + (isReverse[1] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step()
  169. }
  170. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  171. void step() override {
  172. // Void, Reverse and Random buttons
  173. for (int i = 0; i < 2; i++) {
  174. if (voidTriggers[i].process(params[VOID_PARAMS + i].value + inputs[VOID_INPUTS + i].value)) {
  175. isVoid[i] = !isVoid[i];
  176. }
  177. if (revTriggers[i].process(params[REV_PARAMS + i].value + inputs[REV_INPUTS + i].value)) {
  178. isReverse[i] = !isReverse[i];
  179. }
  180. if (rndTriggers[i].process(params[RND_PARAMS + i].value)) {// + inputs[RND_INPUTS + i].value)) {
  181. isRandom[i] = !isRandom[i];
  182. }
  183. }
  184. // CV Level buttons
  185. for (int i = 0; i < 2; i++) {
  186. if (cvLevelTriggers[i].process(params[CVLEVEL_PARAMS + i].value))
  187. cvMode ^= (0x1 << i);
  188. }
  189. // LFO values (normalized to 0.0f to 1.0f space, clamped and offset adjusted depending cvMode)
  190. float lfoVal[2];
  191. lfoVal[0] = inputs[LFO_INPUTS + 0].value;
  192. lfoVal[1] = inputs[LFO_INPUTS + 1].active ? inputs[LFO_INPUTS + 1].value : lfoVal[0];
  193. for (int i = 0; i < 2; i++)
  194. lfoVal[i] = clamp( (lfoVal[i] + ((cvMode & (0x1 << i)) == 0 ? 5.0f : 0.0f)) / 10.0f , 0.0f , 1.0f);
  195. // Pulsar A
  196. bool active8[8];
  197. bool atLeastOneActive = false;
  198. for (int i = 0; i < 8; i++) {
  199. active8[i] = inputs[INA_INPUTS + i].active;
  200. if (active8[i])
  201. atLeastOneActive = true;
  202. }
  203. if (atLeastOneActive) {
  204. if (!isVoid[0]) {
  205. if (!active8[posA])// ensure start on valid input when no void
  206. posA = getNextClosestActive(posA, active8, false, false, false);
  207. if (!active8[posAnext])
  208. posAnext = getNextClosestActive(posA, active8, false, isReverse[0], isRandom[0]);
  209. }
  210. float posPercent = topCross[0] ? (1.0f - lfoVal[0]) : lfoVal[0];
  211. float nextPosPercent = 1.0f - posPercent;
  212. outputs[OUTA_OUTPUT].value = posPercent * inputs[INA_INPUTS + posA].value + nextPosPercent * inputs[INA_INPUTS + posAnext].value;
  213. for (int i = 0; i < 8; i++)
  214. lights[MIXA_LIGHTS + i].setBrightness(0.0f + ((i == posA) ? posPercent : 0.0f) + ((i == posAnext) ? nextPosPercent : 0.0f));
  215. // PulsarA crossover (LFO detection)
  216. if ( (topCross[0] && lfoVal[0] > (1.0f - epsilon)) || (!topCross[0] && lfoVal[0] < epsilon) ) {
  217. topCross[0] = !topCross[0];// switch to opposite detection
  218. posA = posAnext;
  219. posAnext = getNextClosestActive(posA, active8, isVoid[0], isReverse[0], isRandom[0]);
  220. lfoLights[0] = 1.0f;
  221. }
  222. }
  223. else {
  224. outputs[OUTA_OUTPUT].value = 0.0f;
  225. for (int i = 0; i < 8; i++)
  226. lights[MIXA_LIGHTS + i].value = 0.0f;
  227. }
  228. // Pulsar B
  229. atLeastOneActive = false;
  230. for (int i = 0; i < 8; i++) {
  231. active8[i] = outputs[OUTB_OUTPUTS + i].active;
  232. if (active8[i])
  233. atLeastOneActive = true;
  234. }
  235. if (atLeastOneActive) {
  236. if (!isVoid[1]) {
  237. if (!active8[posB])// ensure start on valid output when no void
  238. posB = getNextClosestActive(posB, active8, false, false, false);
  239. if (!active8[posBnext])
  240. posBnext = getNextClosestActive(posB, active8, false, isReverse[1], isRandom[1]);
  241. }
  242. float posPercent = topCross[1] ? (1.0f - lfoVal[1]) : lfoVal[1];
  243. float nextPosPercent = 1.0f - posPercent;
  244. for (int i = 0; i < 8; i++) {
  245. if (inputs[INB_INPUT].active)
  246. outputs[OUTB_OUTPUTS + i].value = 0.0f + ((i == posB) ? (posPercent * inputs[INB_INPUT].value) : 0.0f) + ((i == posBnext) ? (nextPosPercent * inputs[INB_INPUT].value) : 0.0f);
  247. else// mutidimentional trick
  248. outputs[OUTB_OUTPUTS + i].value = 0.0f + ((i == posB) ? (posPercent * inputs[INA_INPUTS + i].value) : 0.0f) + ((i == posBnext) ? (nextPosPercent * inputs[INA_INPUTS + i].value) : 0.0f);
  249. lights[MIXB_LIGHTS + i].setBrightness(0.0f + ((i == posB) ? posPercent : 0.0f) + ((i == posBnext) ? nextPosPercent : 0.0f));
  250. }
  251. // PulsarB crossover (LFO detection)
  252. if ( (topCross[1] && lfoVal[1] > (1.0f - epsilon)) || (!topCross[1] && lfoVal[1] < epsilon) ) {
  253. topCross[1] = !topCross[1];// switch to opposite detection
  254. posB = posBnext;
  255. posBnext = getNextClosestActive(posB, active8, isVoid[1], isReverse[1], isRandom[1]);
  256. lfoLights[1] = 1.0f;
  257. }
  258. }
  259. else {
  260. for (int i = 0; i < 8; i++) {
  261. outputs[OUTB_OUTPUTS + i].value = 0.0f;
  262. lights[MIXB_LIGHTS + i].value = 0.0f;
  263. }
  264. }
  265. // Void, Reverse and Random lights
  266. for (int i = 0; i < 2; i++) {
  267. lights[VOID_LIGHTS + i].value = isVoid[i] ? 1.0f : 0.0f;
  268. lights[REV_LIGHTS + i].value = isReverse[i] ? 1.0f : 0.0f;
  269. lights[RND_LIGHTS + i].value = isRandom[i] ? 1.0f : 0.0f;
  270. }
  271. // CV Level lights
  272. lights[CVALEVEL_LIGHTS + 0].value = (cvMode & 0x1) == 0 ? 1.0f : 0.0f;
  273. lights[CVALEVEL_LIGHTS + 1].value = 1.0f - lights[CVALEVEL_LIGHTS + 0].value;
  274. lights[CVBLEVEL_LIGHTS + 0].value = (cvMode & 0x2) == 0 ? 1.0f : 0.0f;
  275. lights[CVBLEVEL_LIGHTS + 1].value = 1.0f - lights[CVBLEVEL_LIGHTS + 0].value;
  276. // LFO lights
  277. for (int i = 0; i < 2; i++) {
  278. lights[LFO_LIGHTS + i].value = lfoLights[i];
  279. lfoLights[i] -= (lfoLights[i] / lightLambda) * (float)engineGetSampleTime();
  280. }
  281. }// step()
  282. int getNextClosestActive(int pos, bool* active8, bool voidd, bool reverse, bool random) {
  283. // finds the next closest active position (excluding current if active)
  284. // assumes at least one active, but may not be given pos; will always return an active pos
  285. // scans all 8 positions
  286. int posNext = -1;// should never be returned
  287. if (random) {
  288. if (voidd)
  289. posNext = (pos + 1 + randomu32() % 7) % 8;
  290. else {
  291. posNext = pos;
  292. int activeIndexes[8];// room for all indexes of active positions except current if active(max size is guaranteed to be < 8)
  293. int activeIndexesI = 0;
  294. for (int i = 0; i < 8; i++) {
  295. if (active8[i] && i != pos) {
  296. activeIndexes[activeIndexesI] = i;
  297. activeIndexesI++;
  298. }
  299. }
  300. if (activeIndexesI > 0)
  301. posNext = activeIndexes[randomu32()%activeIndexesI];
  302. }
  303. }
  304. else {
  305. posNext = (pos + (reverse ? 7 : 1)) % 8;// void approach by default (choose slot whether active of not)
  306. if (!voidd) {
  307. if (reverse) {
  308. for (int i = posNext + 8; i > posNext; i--) {
  309. if (active8[i % 8]) {
  310. posNext = i % 8;
  311. break;
  312. }
  313. }
  314. }
  315. else {
  316. for (int i = posNext; i < posNext + 8; i++) {
  317. if (active8[i % 8]) {
  318. posNext = i % 8;
  319. break;
  320. }
  321. }
  322. }
  323. }
  324. }
  325. return posNext;
  326. }
  327. };
  328. struct PulsarsWidget : ModuleWidget {
  329. struct PanelThemeItem : MenuItem {
  330. Pulsars *module;
  331. int theme;
  332. void onAction(EventAction &e) override {
  333. module->panelTheme = theme;
  334. }
  335. void step() override {
  336. rightText = (module->panelTheme == theme) ? "✔" : "";
  337. }
  338. };
  339. Menu *createContextMenu() override {
  340. Menu *menu = ModuleWidget::createContextMenu();
  341. MenuLabel *spacerLabel = new MenuLabel();
  342. menu->addChild(spacerLabel);
  343. Pulsars *module = dynamic_cast<Pulsars*>(this->module);
  344. assert(module);
  345. MenuLabel *themeLabel = new MenuLabel();
  346. themeLabel->text = "Panel Theme";
  347. menu->addChild(themeLabel);
  348. PanelThemeItem *lightItem = new PanelThemeItem();
  349. lightItem->text = lightPanelID;// Geodesics.hpp
  350. lightItem->module = module;
  351. lightItem->theme = 0;
  352. menu->addChild(lightItem);
  353. PanelThemeItem *darkItem = new PanelThemeItem();
  354. darkItem->text = darkPanelID;// Geodesics.hpp
  355. darkItem->module = module;
  356. darkItem->theme = 1;
  357. //menu->addChild(darkItem);
  358. return menu;
  359. }
  360. PulsarsWidget(Pulsars *module) : ModuleWidget(module) {
  361. // Main panel from Inkscape
  362. DynamicSVGPanel *panel = new DynamicSVGPanel();
  363. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PulsarsBG-01.svg")));
  364. //panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PulsarsBG-02.svg")));// no dark pannel for now
  365. box.size = panel->box.size;
  366. panel->mode = &module->panelTheme;
  367. addChild(panel);
  368. // Screws
  369. // part of svg panel, no code required
  370. float colRulerCenter = box.size.x / 2.0f;
  371. static constexpr float rowRulerPulsarA = 127.5;
  372. static constexpr float rowRulerPulsarB = 261.5f;
  373. float rowRulerLFOlights = (rowRulerPulsarA + rowRulerPulsarB) / 2.0f;
  374. static constexpr float offsetLFOlightsX = 25.0f;
  375. static constexpr float radiusLeds = 23.0f;
  376. static constexpr float radiusJacks = 46.0f;
  377. static constexpr float offsetLeds = 17.0f;
  378. static constexpr float offsetJacks = 33.0f;
  379. static constexpr float offsetLFO = 24.0f;// used also for void and reverse CV input jacks
  380. static constexpr float offsetLedX = 13.0f;// adds/subs to offsetLFO for void and reverse lights
  381. static constexpr float offsetButtonX = 26.0f;// adds/subs to offsetLFO for void and reverse buttons
  382. static constexpr float offsetLedY = 11.0f;// adds/subs to offsetLFO for void and reverse lights
  383. static constexpr float offsetButtonY = 18.0f;// adds/subs to offsetLFO for void and reverse buttons
  384. static constexpr float offsetRndButtonX = 58.0f;// from center of pulsar
  385. static constexpr float offsetRndButtonY = 24.0f;// from center of pulsar
  386. static constexpr float offsetRndLedX = 63.0f;// from center of pulsar
  387. static constexpr float offsetRndLedY = 11.0f;// from center of pulsar
  388. static constexpr float offsetLedVsButBX = 8.0f;// BI
  389. static constexpr float offsetLedVsButBY = 10.0f;
  390. static constexpr float offsetLedVsButUX = 13.0f;// UNI
  391. static constexpr float offsetLedVsButUY = 1.0f;
  392. // PulsarA center output
  393. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA), Port::OUTPUT, module, Pulsars::OUTA_OUTPUT, &module->panelTheme));
  394. // PulsarA inputs
  395. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA - radiusJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 0, &module->panelTheme));
  396. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarA - offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 1, &module->panelTheme));
  397. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusJacks, rowRulerPulsarA), Port::INPUT, module, Pulsars::INA_INPUTS + 2, &module->panelTheme));
  398. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarA + offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 3, &module->panelTheme));
  399. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA + radiusJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 4, &module->panelTheme));
  400. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarA + offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 5, &module->panelTheme));
  401. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusJacks, rowRulerPulsarA), Port::INPUT, module, Pulsars::INA_INPUTS + 6, &module->panelTheme));
  402. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarA - offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 7, &module->panelTheme));
  403. // PulsarA lights
  404. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarA - radiusLeds), module, Pulsars::MIXA_LIGHTS + 0));
  405. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarA - offsetLeds), module, Pulsars::MIXA_LIGHTS + 1));
  406. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radiusLeds, rowRulerPulsarA), module, Pulsars::MIXA_LIGHTS + 2));
  407. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarA + offsetLeds), module, Pulsars::MIXA_LIGHTS + 3));
  408. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarA + radiusLeds), module, Pulsars::MIXA_LIGHTS + 4));
  409. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarA + offsetLeds), module, Pulsars::MIXA_LIGHTS + 5));
  410. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radiusLeds, rowRulerPulsarA), module, Pulsars::MIXA_LIGHTS + 6));
  411. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarA - offsetLeds), module, Pulsars::MIXA_LIGHTS + 7));
  412. // PulsarA void (jack, light and button)
  413. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarA - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::VOID_INPUTS + 0, &module->panelTheme));
  414. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetLedX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetLedY), module, Pulsars::VOID_LIGHTS + 0));
  415. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetButtonX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetButtonY), module, Pulsars::VOID_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  416. // PulsarA reverse (jack, light and button)
  417. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarA - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::REV_INPUTS + 0, &module->panelTheme));
  418. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetLedX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetLedY), module, Pulsars::REV_LIGHTS + 0));
  419. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetButtonX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetButtonY), module, Pulsars::REV_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  420. // PulsarA random (light and button)
  421. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndLedX, rowRulerPulsarA + offsetRndLedY), module, Pulsars::RND_LIGHTS + 0));
  422. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRndButtonX, rowRulerPulsarA + offsetRndButtonY), module, Pulsars::RND_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  423. // PulsarA CV level (lights and button)
  424. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRndButtonX, rowRulerPulsarA + offsetRndButtonY), module, Pulsars::CVLEVEL_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  425. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndButtonX - offsetLedVsButBX, rowRulerPulsarA + offsetRndButtonY + offsetLedVsButBY), module, Pulsars::CVALEVEL_LIGHTS + 0));
  426. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndButtonX - offsetLedVsButUX, rowRulerPulsarA + offsetRndButtonY - offsetLedVsButUY), module, Pulsars::CVALEVEL_LIGHTS + 1));
  427. // PulsarA LFO input and light
  428. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarA + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::LFO_INPUTS + 0, &module->panelTheme));
  429. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetLFOlightsX, rowRulerLFOlights), module, Pulsars::LFO_LIGHTS + 0));
  430. // PulsarB center input
  431. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB), Port::INPUT, module, Pulsars::INB_INPUT, &module->panelTheme));
  432. // PulsarB outputs
  433. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB - radiusJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 0, &module->panelTheme));
  434. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarB - offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 1, &module->panelTheme));
  435. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusJacks, rowRulerPulsarB), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 2, &module->panelTheme));
  436. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarB + offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 3, &module->panelTheme));
  437. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB + radiusJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 4, &module->panelTheme));
  438. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarB + offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 5, &module->panelTheme));
  439. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusJacks, rowRulerPulsarB), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 6, &module->panelTheme));
  440. addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarB - offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 7, &module->panelTheme));
  441. // PulsarB lights
  442. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarB - radiusLeds), module, Pulsars::MIXB_LIGHTS + 0));
  443. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarB - offsetLeds), module, Pulsars::MIXB_LIGHTS + 1));
  444. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radiusLeds, rowRulerPulsarB), module, Pulsars::MIXB_LIGHTS + 2));
  445. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarB + offsetLeds), module, Pulsars::MIXB_LIGHTS + 3));
  446. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarB + radiusLeds), module, Pulsars::MIXB_LIGHTS + 4));
  447. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarB + offsetLeds), module, Pulsars::MIXB_LIGHTS + 5));
  448. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radiusLeds, rowRulerPulsarB), module, Pulsars::MIXB_LIGHTS + 6));
  449. addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarB - offsetLeds), module, Pulsars::MIXB_LIGHTS + 7));
  450. // PulsarB void (jack, light and button)
  451. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarB + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::VOID_INPUTS + 1, &module->panelTheme));
  452. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetLedX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetLedY), module, Pulsars::VOID_LIGHTS + 1));
  453. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetButtonX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetButtonY), module, Pulsars::VOID_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  454. // PulsarB reverse (jack, light and button)
  455. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarB + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::REV_INPUTS + 1, &module->panelTheme));
  456. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetLedX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetLedY), module, Pulsars::REV_LIGHTS + 1));
  457. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetButtonX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetButtonY), module, Pulsars::REV_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  458. // PulsarB random (light and button)
  459. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndLedX, rowRulerPulsarB - offsetRndLedY), module, Pulsars::RND_LIGHTS + 1));
  460. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRndButtonX, rowRulerPulsarB - offsetRndButtonY), module, Pulsars::RND_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  461. // PulsarB CV level (lights and button)
  462. addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRndButtonX, rowRulerPulsarB - offsetRndButtonY), module, Pulsars::CVLEVEL_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  463. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndButtonX + offsetLedVsButBX, rowRulerPulsarB - offsetRndButtonY - offsetLedVsButBY), module, Pulsars::CVBLEVEL_LIGHTS + 0));
  464. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndButtonX + offsetLedVsButUX, rowRulerPulsarB - offsetRndButtonY + offsetLedVsButUY), module, Pulsars::CVBLEVEL_LIGHTS + 1));
  465. // PulsarA LFO input and light
  466. addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarB - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::LFO_INPUTS + 1, &module->panelTheme));
  467. addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetLFOlightsX, rowRulerLFOlights), module, Pulsars::LFO_LIGHTS + 1));
  468. }
  469. };
  470. } // namespace rack_plugin_Geodesics
  471. using namespace rack_plugin_Geodesics;
  472. RACK_PLUGIN_MODEL_INIT(Geodesics, Pulsars) {
  473. Model *modelPulsars = Model::create<Pulsars, PulsarsWidget>("Geodesics", "Pulsars", "Pulsars", MIXER_TAG);
  474. return modelPulsars;
  475. }
  476. /*CHANGE LOG
  477. 0.6.1:
  478. add CV level modes buttons and lights, remove from right-click menu
  479. 0.6.0:
  480. created
  481. */