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.

1173 lines
44KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. // an implementation of a performable, 3-stage switch, i.e. where
  4. // the state triggers after being dragged a certain distance
  5. struct BefacoSwitchMomentary : SvgSwitch {
  6. BefacoSwitchMomentary() {
  7. momentary = true;
  8. addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_0.svg")));
  9. addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_1.svg")));
  10. addFrame(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoSwitch_2.svg")));
  11. }
  12. void onDragStart(const event::DragStart& e) override {
  13. latched = false;
  14. startMouseY = APP->scene->rack->getMousePos().y;
  15. ParamWidget::onDragStart(e);
  16. }
  17. void onDragMove(const event::DragMove& e) override {
  18. ParamQuantity* paramQuantity = getParamQuantity();
  19. float diff = APP->scene->rack->getMousePos().y - startMouseY;
  20. // Once the user has dragged the mouse a "threshold" distance, latch
  21. // to disallow further changes of state until the mouse is released.
  22. // We don't just setValue(1) (default/rest state) because this creates a
  23. // jarring UI experience
  24. if (diff < -10 && !latched) {
  25. paramQuantity->setValue(2);
  26. latched = true;
  27. }
  28. if (diff > 10 && !latched) {
  29. paramQuantity->setValue(0);
  30. latched = true;
  31. }
  32. ParamWidget::onDragMove(e);
  33. }
  34. void onDragEnd(const event::DragEnd& e) override {
  35. // on release, the switch resets to default/neutral/middle position
  36. getParamQuantity()->setValue(1);
  37. latched = false;
  38. ParamWidget::onDragEnd(e);
  39. }
  40. float startMouseY = 0.f;
  41. bool latched = false;
  42. };
  43. // Class which can yield a divided clock state, specifically where the
  44. // gate is generated at request time through getGate(), rather than during
  45. // process() - this means that different divisions of clock can be requested
  46. // at any point in time. In contrast, the division/multiplication setting for
  47. // ClockMultDiv cannot easily be changed _during_ a clock tick.
  48. struct MultiGateClock {
  49. float remaining = 0.f;
  50. float fullPulseLength = 0.f;
  51. /** Immediately disables the pulse */
  52. void reset(float newfullPulseLength) {
  53. fullPulseLength = newfullPulseLength;
  54. remaining = fullPulseLength;
  55. }
  56. /** Advances the state by `deltaTime`. Returns whether the pulse is in the HIGH state. */
  57. bool process(float deltaTime) {
  58. if (remaining > 0.f) {
  59. remaining -= deltaTime;
  60. return true;
  61. }
  62. return false;
  63. }
  64. bool getGate(int gateMode) {
  65. if (gateMode == 0) {
  66. // always on (special case)
  67. return true;
  68. }
  69. else if (gateMode < 0 || remaining <= 0) {
  70. // disabled (or elapsed)
  71. return false;
  72. }
  73. const float multiGateOnLength = fullPulseLength / ((gateMode > 0) ? (2.f * gateMode) : 1.0f);
  74. const bool isOddPulse = int(floor(remaining / multiGateOnLength)) % 2;
  75. return isOddPulse;
  76. }
  77. };
  78. // Class for generating a clock sequence after setting a clock multiplication or division,
  79. // given a stream of clock pulses as the "base" clock.
  80. // Implementation is heavily inspired by BogAudio RGate, with modification
  81. struct MultDivClock {
  82. // convention: negative values are used for division (1/mult), positive for multiplication (x mult)
  83. // multDiv = 0 should not be used, but if it is it will result in no modification to the clock
  84. int multDiv = 1;
  85. float secondsSinceLastClock = -1.0f;
  86. float inputClockLengthSeconds = -1.0f;
  87. // count how many divisions we've had
  88. int dividerCount = 0;
  89. float dividedProgressSeconds = 0.f;
  90. // returns the gated clock signal, returns true when high
  91. bool process(float deltaTime, bool clockPulseReceived) {
  92. if (clockPulseReceived) {
  93. // update our record of the incoming clock spacing
  94. if (secondsSinceLastClock > 0.0f) {
  95. inputClockLengthSeconds = secondsSinceLastClock;
  96. }
  97. secondsSinceLastClock = 0.0f;
  98. }
  99. bool out = false;
  100. if (secondsSinceLastClock >= 0.0f) {
  101. secondsSinceLastClock += deltaTime;
  102. // negative values are used for division (x 1/mult), positive for multiplication (x mult)
  103. const int division = std::max(-multDiv, 1);
  104. const int multiplication = std::max(multDiv, 1);
  105. if (clockPulseReceived) {
  106. if (dividerCount < 1) {
  107. dividedProgressSeconds = 0.0f;
  108. }
  109. else {
  110. dividedProgressSeconds += deltaTime;
  111. }
  112. ++dividerCount;
  113. if (dividerCount >= division) {
  114. dividerCount = 0;
  115. }
  116. }
  117. else {
  118. dividedProgressSeconds += deltaTime;
  119. }
  120. // lengths of the mult/div versions of the clock
  121. const float dividedSeconds = inputClockLengthSeconds * (float) division;
  122. const float multipliedSeconds = dividedSeconds / (float) multiplication;
  123. // length of the output gate (s)
  124. const float gateSeconds = std::max(0.001f, multipliedSeconds * 0.5f);
  125. if (dividedProgressSeconds < dividedSeconds) {
  126. float multipliedProgressSeconds = dividedProgressSeconds / multipliedSeconds;
  127. multipliedProgressSeconds -= (float)(int)multipliedProgressSeconds;
  128. multipliedProgressSeconds *= multipliedSeconds;
  129. out = (multipliedProgressSeconds <= gateSeconds);
  130. }
  131. }
  132. return out;
  133. }
  134. float getEffectiveClockLength() {
  135. // negative values are used for division (x 1/mult), positive for multiplication (x mult)
  136. const int division = std::max(-multDiv, 1);
  137. const int multiplication = std::max(multDiv, 1);
  138. // lengths of the mult/div versions of the clock
  139. const float dividedSeconds = inputClockLengthSeconds * (float) division;
  140. const float multipliedSeconds = dividedSeconds / (float) multiplication;
  141. return multipliedSeconds;
  142. }
  143. };
  144. static const std::vector<int> clockOptionsQuadratic = {-16, -8, -4, -2, 1, 2, 4, 8, 16};
  145. static const std::vector<int> clockOptionsAll = {-16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, 1,
  146. 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
  147. };
  148. inline std::string getClockOptionString(const int clockOption) {
  149. return (clockOption < 0) ? ("x 1/" + std::to_string(-clockOption)) : ("x " + std::to_string(clockOption));
  150. }
  151. struct Muxlicer : Module {
  152. enum ParamIds {
  153. PLAY_PARAM,
  154. ADDRESS_PARAM,
  155. GATE_MODE_PARAM,
  156. DIV_MULT_PARAM,
  157. ENUMS(LEVEL_PARAMS, 8),
  158. NUM_PARAMS
  159. };
  160. enum InputIds {
  161. GATE_MODE_INPUT,
  162. ADDRESS_INPUT,
  163. CLOCK_INPUT,
  164. RESET_INPUT,
  165. COM_INPUT,
  166. ENUMS(MUX_INPUTS, 8),
  167. ALL_INPUT,
  168. NUM_INPUTS
  169. };
  170. enum OutputIds {
  171. CLOCK_OUTPUT,
  172. ALL_GATES_OUTPUT,
  173. EOC_OUTPUT,
  174. ENUMS(GATE_OUTPUTS, 8),
  175. ENUMS(MUX_OUTPUTS, 8),
  176. COM_OUTPUT,
  177. ALL_OUTPUT,
  178. NUM_OUTPUTS
  179. };
  180. enum LightIds {
  181. CLOCK_LIGHT,
  182. ENUMS(GATE_LIGHTS, 8),
  183. NUM_LIGHTS
  184. };
  185. enum ModeCOMIO {
  186. COM_1_IN_8_OUT,
  187. COM_8_IN_1_OUT
  188. };
  189. enum PlayState {
  190. STATE_PLAY_ONCE,
  191. STATE_STOPPED,
  192. STATE_PLAY
  193. };
  194. /*
  195. This shows how the values of the gate mode knob + CV map onto gate triggers.
  196. See also getGateMode()
  197. value | description | quadratic only mode
  198. -1 no gate | ✔
  199. 0 gate (full timestep) | x
  200. +1 half timestep | ✔
  201. 2 two gates | ✔
  202. 3 three gates | x
  203. 4 four gates | ✔
  204. 5 five gates | x
  205. 6 six gates | x
  206. 7 seven gates | x
  207. 8 eight gates | ✔
  208. */
  209. const int possibleQuadraticGates[5] = {-1, 1, 2, 4, 8};
  210. bool quadraticGatesOnly = false;
  211. bool outputClockFollowsPlayMode = false;
  212. PlayState playState = STATE_STOPPED;
  213. dsp::BooleanTrigger playStateTrigger;
  214. uint32_t runIndex; // which step are we on (0 to 7)
  215. uint32_t addressIndex = 0;
  216. bool tapped = false;
  217. enum ResetStatus {
  218. RESET_NOT_REQUESTED,
  219. RESET_AND_PLAY_ONCE,
  220. RESET_AND_PLAY
  221. };
  222. // Used to track if a reset has been triggered. Can be from the CV input, or the momentary switch. Note
  223. // that behaviour depends on if the Muxlicer is clocked or not. If clocked, the playhead resets but waits
  224. // for the next clock tick to start. If not clocked, then the sequence will start immediately (i.e. the
  225. // internal clock will be synced at the point where `resetIsRequested` is first true.
  226. ResetStatus resetRequested = RESET_NOT_REQUESTED;
  227. // used to detect when `resetRequested` first becomes active
  228. dsp::BooleanTrigger detectResetTrigger;
  229. // used to track the clock (e.g. if external clock is not connected). NOTE: this clock
  230. // is defined _prior_ to any clock division/multiplication logic
  231. float internalClockProgress = 0.f;
  232. float internalClockLength = 0.25f;
  233. float tapTime = 99999; // used to track the time between clock pulses (or taps?)
  234. dsp::SchmittTrigger inputClockTrigger; // to detect incoming clock pulses
  235. dsp::BooleanTrigger mainClockTrigger; // to detect when divided/multiplied version of the clock signal has rising edge
  236. dsp::SchmittTrigger resetTrigger; // to detect the reset signal
  237. dsp::PulseGenerator endOfCyclePulse; // fire a signal at the end of cycle
  238. dsp::BooleanTrigger tapTempoTrigger; // to only trigger tap tempo when push is first detected
  239. MultDivClock mainClockMultDiv; // to produce a divided/multiplied version of the (internal or external) clock signal
  240. MultDivClock outputClockMultDiv; // to produce a divided/multiplied version of the output clock signal
  241. MultiGateClock multiClock; // to easily produce a divided version of the main clock (where division can be changed at any point)
  242. bool usingExternalClock = false; // is there a clock plugged into clock in (external) or not (internal)
  243. const static int SEQUENCE_LENGTH = 8;
  244. ModeCOMIO modeCOMIO = COM_8_IN_1_OUT; // are we in 1-in-8-out mode, or 8-in-1-out mode
  245. int allInNormalVoltage = 10; // what voltage is normalled into the "All In" input, selectable via context menu
  246. Module* rightModule; // for the expander
  247. // these are class variables, rather than scoped to process(...), to allow expanders to read
  248. // all gate output and clock output
  249. bool isAllGatesOutHigh = false;
  250. bool isOutputClockHigh = false;
  251. struct DivMultKnobParamQuantity : ParamQuantity {
  252. std::string getDisplayValueString() override {
  253. Muxlicer* moduleMuxlicer = reinterpret_cast<Muxlicer*>(module);
  254. if (moduleMuxlicer != nullptr) {
  255. return getClockOptionString(moduleMuxlicer->getClockOptionFromParam());
  256. }
  257. else {
  258. return "";
  259. }
  260. }
  261. };
  262. struct GateModeParamQuantity : ParamQuantity {
  263. std::string getDisplayValueString() override {
  264. Muxlicer* moduleMuxlicer = reinterpret_cast<Muxlicer*>(module);
  265. if (moduleMuxlicer != nullptr) {
  266. bool attenuatorMode = moduleMuxlicer->inputs[GATE_MODE_INPUT].isConnected();
  267. if (attenuatorMode) {
  268. return ParamQuantity::getDisplayValueString();
  269. }
  270. else {
  271. const int gate = moduleMuxlicer->getGateMode();
  272. if (gate < 0) {
  273. return "No gate";
  274. }
  275. else if (gate == 0) {
  276. return "1/2 gate";
  277. }
  278. else {
  279. return string::f("%d gate(s)", gate);
  280. }
  281. }
  282. }
  283. else {
  284. return ParamQuantity::getDisplayValueString();
  285. }
  286. }
  287. };
  288. // given param (in range 0 to 1), return the clock option from an array of choices
  289. int getClockOptionFromParam() {
  290. if (quadraticGatesOnly) {
  291. const int clockOptionIndex = round(params[Muxlicer::DIV_MULT_PARAM].getValue() * (clockOptionsQuadratic.size() - 1));
  292. return clockOptionsQuadratic[clockOptionIndex];
  293. }
  294. else {
  295. const int clockOptionIndex = round(params[Muxlicer::DIV_MULT_PARAM].getValue() * (clockOptionsAll.size() - 1));
  296. return clockOptionsAll[clockOptionIndex];
  297. }
  298. }
  299. // given a the mult/div setting for the main clock, find the index of this from an array of valid choices,
  300. // and convert to a value between 0 and 1 (update the DIV_MULT_PARAM param)
  301. void updateParamFromMainClockMultDiv() {
  302. auto const& arrayToSearch = quadraticGatesOnly ? clockOptionsQuadratic : clockOptionsAll;
  303. auto const it = std::find(arrayToSearch.begin(), arrayToSearch.end(), mainClockMultDiv.multDiv);
  304. // try to find the index in the array of valid clock mults/divs
  305. if (it != arrayToSearch.end()) {
  306. int index = it - arrayToSearch.begin();
  307. float paramIndex = (float) index / (arrayToSearch.size() - 1);
  308. params[Muxlicer::DIV_MULT_PARAM].setValue(paramIndex);
  309. }
  310. // if not, default to 0.5 (which should correspond to x1, no mult/div)
  311. else {
  312. params[Muxlicer::DIV_MULT_PARAM].setValue(0.5);
  313. }
  314. }
  315. Muxlicer() {
  316. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  317. configSwitch(Muxlicer::PLAY_PARAM, STATE_PLAY_ONCE, STATE_PLAY, STATE_STOPPED, "Play switch", {"Play Once/Reset", "", "Play/Stop"});
  318. getParamQuantity(Muxlicer::PLAY_PARAM)->randomizeEnabled = false;
  319. configParam(Muxlicer::ADDRESS_PARAM, -1.f, 7.f, -1.f, "Address");
  320. getParamQuantity(Muxlicer::ADDRESS_PARAM)->randomizeEnabled = false;
  321. configParam<GateModeParamQuantity>(Muxlicer::GATE_MODE_PARAM, -1.f, 8.f, 1.f, "Gate mode");
  322. configParam<DivMultKnobParamQuantity>(Muxlicer::DIV_MULT_PARAM, 0, 1, 0.5, "Main clock mult/div");
  323. for (int i = 0; i < SEQUENCE_LENGTH; ++i) {
  324. configParam(Muxlicer::LEVEL_PARAMS + i, 0.0, 1.0, 1.0, string::f("Gain step %d", i + 1));
  325. configInput(Muxlicer::MUX_INPUTS + i, string::f("Step %d", i + 1));
  326. configOutput(Muxlicer::GATE_OUTPUTS + i, string::f("Gate step %d", i + 1));
  327. configOutput(Muxlicer::MUX_OUTPUTS + i, string::f("Step %d", i + 1));
  328. configLight(Muxlicer::GATE_LIGHTS + i, string::f("Step %d gates", i + 1));
  329. }
  330. configOutput(Muxlicer::EOC_OUTPUT, "End of cycle trigger");
  331. configOutput(Muxlicer::CLOCK_OUTPUT, "Clock");
  332. configOutput(Muxlicer::ALL_GATES_OUTPUT, "All gates");
  333. configOutput(Muxlicer::ALL_OUTPUT, "All");
  334. configOutput(Muxlicer::COM_OUTPUT, "COM I/O");
  335. configInput(Muxlicer::GATE_MODE_INPUT, "Gate mode CV");
  336. configInput(Muxlicer::ADDRESS_INPUT, "Address CV");
  337. configInput(Muxlicer::CLOCK_INPUT, "Clock");
  338. configInput(Muxlicer::RESET_INPUT, "One shot/reset");
  339. configInput(Muxlicer::COM_INPUT, "COM I/O");
  340. configInput(Muxlicer::ALL_INPUT, "All");
  341. onReset();
  342. }
  343. void onReset() override {
  344. internalClockLength = 0.250f;
  345. internalClockProgress = 0;
  346. runIndex = 0;
  347. mainClockMultDiv.multDiv = 1;
  348. outputClockMultDiv.multDiv = 1;
  349. quadraticGatesOnly = false;
  350. playState = STATE_STOPPED;
  351. }
  352. void process(const ProcessArgs& args) override {
  353. usingExternalClock = inputs[CLOCK_INPUT].isConnected();
  354. bool externalClockPulseReceived = false;
  355. // a clock pulse does two things: 1) sets the internal clock (based on timing between two pulses), which
  356. // would continue were the clock input to be removed, and 2) synchronises/drives the clock (if clock input present)
  357. if (usingExternalClock && inputClockTrigger.process(rescale(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
  358. externalClockPulseReceived = true;
  359. }
  360. // this can also be sent by tap tempo
  361. else if (!usingExternalClock && tapTempoTrigger.process(tapped)) {
  362. externalClockPulseReceived = true;
  363. tapped = false;
  364. }
  365. mainClockMultDiv.multDiv = getClockOptionFromParam();
  366. processPlayResetLogic();
  367. const float address = params[ADDRESS_PARAM].getValue() + inputs[ADDRESS_INPUT].getVoltage();
  368. const bool isAddressInRunMode = address < 0.f;
  369. // even if we have an external clock, use its pulses to time/sync the internal clock
  370. // so that it will remain running even after CLOCK_INPUT is disconnected
  371. if (externalClockPulseReceived) {
  372. // track length between received clock pulses (using external clock) or taps
  373. // of the tap-tempo menu item (if sufficiently short)
  374. if (usingExternalClock || tapTime < 2.f) {
  375. internalClockLength = tapTime;
  376. }
  377. tapTime = 0;
  378. internalClockProgress = 0.f;
  379. }
  380. // If we get a reset signal (which can come from CV or various modes of the switch), and the clock has only
  381. // just started to tick (internalClockProgress < 1ms), we assume that the reset signal is slightly delayed
  382. // due to the 1 sample delay that Rack introduces. If this is the case, the internal clock trigger detector,
  383. // `detectResetTrigger`, which advances the sequence, will not be "primed" to detect a rising edge for another
  384. // whole clock tick, meaning the first step is repeated. See: https://github.com/VCVRack/Befaco/issues/32
  385. // Also see https://vcvrack.com/manual/VoltageStandards#Timing for 0.001 seconds justification.
  386. if (detectResetTrigger.process(resetRequested != RESET_NOT_REQUESTED) && internalClockProgress < 1e-3) {
  387. // NOTE: the sequence must also be stopped for this to come into effect. In hardware, if the Nth step Gate Out
  388. // is patched back into the reset, that step should complete before the sequence restarts.
  389. if (playState == STATE_STOPPED) {
  390. mainClockTrigger.state = false;
  391. }
  392. }
  393. tapTime += args.sampleTime;
  394. internalClockProgress += args.sampleTime;
  395. // track if the internal clock has "ticked"
  396. const bool internalClockPulseReceived = (internalClockProgress >= internalClockLength);
  397. if (internalClockPulseReceived) {
  398. internalClockProgress = 0.f;
  399. }
  400. // we can be in one of two clock modes:
  401. // * external (decided by pulses to CLOCK_INPUT)
  402. // * internal (decided by internalClockProgress exceeding the internal clock length)
  403. //
  404. // choose which clock source we are to use
  405. const bool clockPulseReceived = usingExternalClock ? externalClockPulseReceived : internalClockPulseReceived;
  406. // apply the main clock div/mult logic to whatever clock source we're using - mainClockMultDiv outputs a gate sequence
  407. // so we must use a BooleanTrigger on the divided/mult'd signal in order to detect rising edge / when to advance the sequence
  408. const bool dividedMultipliedClockPulseReceived = mainClockTrigger.process(mainClockMultDiv.process(args.sampleTime, clockPulseReceived));
  409. if (dividedMultipliedClockPulseReceived) {
  410. if (resetRequested != RESET_NOT_REQUESTED) {
  411. runIndex = 7;
  412. if (resetRequested == RESET_AND_PLAY) {
  413. playState = STATE_PLAY;
  414. }
  415. else if (resetRequested == RESET_AND_PLAY_ONCE) {
  416. playState = STATE_PLAY_ONCE;
  417. }
  418. }
  419. if (isAddressInRunMode && playState != STATE_STOPPED) {
  420. runIndex++;
  421. if (runIndex >= SEQUENCE_LENGTH) {
  422. runIndex = 0;
  423. // the sequence resets by placing the play head at the final step (so that the next clock pulse
  424. // ticks over onto the first step) - so if we are on the final step _because_ we've reset,
  425. // then don't fire EOC, just clear the reset status
  426. if (resetRequested != RESET_NOT_REQUESTED) {
  427. resetRequested = RESET_NOT_REQUESTED;
  428. }
  429. // otherwise we've naturally arrived at the last step so do fire EOC etc
  430. else {
  431. endOfCyclePulse.trigger(1e-3);
  432. // stop on this step if in one shot mode
  433. if (playState == STATE_PLAY_ONCE) {
  434. playState = STATE_STOPPED;
  435. }
  436. }
  437. }
  438. }
  439. multiClock.reset(mainClockMultDiv.getEffectiveClockLength());
  440. if (isAddressInRunMode) {
  441. addressIndex = runIndex;
  442. }
  443. else {
  444. addressIndex = clamp((int) roundf(address), 0, SEQUENCE_LENGTH - 1);
  445. }
  446. for (int i = 0; i < 8; i++) {
  447. outputs[GATE_OUTPUTS + i].setVoltage(0.f);
  448. }
  449. }
  450. // Gates
  451. for (int i = 0; i < 8; i++) {
  452. outputs[GATE_OUTPUTS + i].setVoltage(0.f);
  453. lights[GATE_LIGHTS + i].setBrightness(0.f);
  454. }
  455. outputs[ALL_GATES_OUTPUT].setVoltage(0.f);
  456. multiClock.process(args.sampleTime);
  457. const int gateMode = getGateMode();
  458. // current gate output _and_ "All Gates" output both get the gate pattern from multiClock
  459. // NOTE: isAllGatesOutHigh can also be read by expanders
  460. isAllGatesOutHigh = multiClock.getGate(gateMode) && (playState != STATE_STOPPED);
  461. outputs[GATE_OUTPUTS + addressIndex].setVoltage(isAllGatesOutHigh * 10.f);
  462. lights[GATE_LIGHTS + addressIndex].setBrightness(isAllGatesOutHigh * 1.f);
  463. outputs[ALL_GATES_OUTPUT].setVoltage(isAllGatesOutHigh * 10.f);
  464. if (modeCOMIO == COM_1_IN_8_OUT) {
  465. const int numActiveEngines = std::max(inputs[ALL_INPUT].getChannels(), inputs[COM_INPUT].getChannels());
  466. const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
  467. for (int c = 0; c < numActiveEngines; c += 4) {
  468. // Mux outputs (all zero, except active step, if playing)
  469. for (int i = 0; i < 8; i++) {
  470. outputs[MUX_OUTPUTS + i].setVoltageSimd<float_4>(0.f, c);
  471. }
  472. const float_4 com_input = inputs[COM_INPUT].getPolyVoltageSimd<float_4>(c);
  473. if (outputs[MUX_OUTPUTS + addressIndex].isConnected()) {
  474. outputs[MUX_OUTPUTS + addressIndex].setVoltageSimd(stepVolume * com_input, c);
  475. outputs[ALL_OUTPUT].setVoltageSimd<float_4>(0.f, c);
  476. }
  477. else if (outputs[ALL_OUTPUT].isConnected()) {
  478. outputs[ALL_OUTPUT].setVoltageSimd(stepVolume * com_input, c);
  479. }
  480. }
  481. for (int i = 0; i < 8; i++) {
  482. outputs[MUX_OUTPUTS + i].setChannels(numActiveEngines);
  483. }
  484. outputs[ALL_OUTPUT].setChannels(numActiveEngines);
  485. }
  486. else if (modeCOMIO == COM_8_IN_1_OUT) {
  487. // we need at least one active engine, even if nothing is connected
  488. // as we want the voltage that is normalled to All In to be processed
  489. int numActiveEngines = std::max(1, inputs[ALL_INPUT].getChannels());
  490. for (int i = 0; i < 8; i++) {
  491. numActiveEngines = std::max(numActiveEngines, inputs[MUX_INPUTS + i].getChannels());
  492. }
  493. const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
  494. for (int c = 0; c < numActiveEngines; c += 4) {
  495. const float_4 allInValue = inputs[ALL_INPUT].getNormalPolyVoltageSimd<float_4>((float_4) allInNormalVoltage, c);
  496. const float_4 stepValue = inputs[MUX_INPUTS + addressIndex].getNormalPolyVoltageSimd<float_4>(allInValue, c) * stepVolume;
  497. outputs[COM_OUTPUT].setVoltageSimd(stepValue, c);
  498. }
  499. outputs[COM_OUTPUT].setChannels(numActiveEngines);
  500. }
  501. // there is an option to stop output clock when play stops
  502. const bool playStateMask = !outputClockFollowsPlayMode || (playState != STATE_STOPPED);
  503. // NOTE: outputClockOut can also be read by expanders
  504. isOutputClockHigh = outputClockMultDiv.process(args.sampleTime, clockPulseReceived) && playStateMask;
  505. outputs[CLOCK_OUTPUT].setVoltage(isOutputClockHigh * 10.f);
  506. lights[CLOCK_LIGHT].setBrightness(isOutputClockHigh * 1.f);
  507. // end of cycle trigger trigger
  508. outputs[EOC_OUTPUT].setVoltage(endOfCyclePulse.process(args.sampleTime) ? 10.f : 0.f);
  509. }
  510. void processPlayResetLogic() {
  511. const bool resetPulseInReceived = resetTrigger.process(rescale(inputs[RESET_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f));
  512. if (resetPulseInReceived) {
  513. switch (playState) {
  514. case STATE_STOPPED: resetRequested = RESET_AND_PLAY_ONCE; break;
  515. case STATE_PLAY_ONCE: resetRequested = RESET_AND_PLAY_ONCE; break;
  516. case STATE_PLAY: resetRequested = RESET_AND_PLAY; break;
  517. }
  518. }
  519. // if the play switch has effectively been activated for the first time,
  520. // i.e. it's not just still being held
  521. const bool switchIsActive = params[PLAY_PARAM].getValue() != STATE_STOPPED;
  522. if (playStateTrigger.process(switchIsActive) && switchIsActive) {
  523. // if we were stopped, check for activation (normal, up or one-shot, down)
  524. if (playState == STATE_STOPPED) {
  525. if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
  526. resetRequested = RESET_AND_PLAY;
  527. }
  528. else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
  529. resetRequested = RESET_AND_PLAY_ONCE;
  530. }
  531. }
  532. // otherwise we are in play mode (and we've not just held onto the play switch),
  533. // so check for stop (switch up) or reset (switch down)
  534. else {
  535. // top switch will stop
  536. if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
  537. playState = STATE_STOPPED;
  538. }
  539. // bottom will reset
  540. else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
  541. resetRequested = RESET_AND_PLAY_ONCE;
  542. }
  543. }
  544. }
  545. }
  546. // determines how many gates to yield per step
  547. int getGateMode() {
  548. int gate;
  549. if (inputs[GATE_MODE_INPUT].isConnected()) {
  550. // with gate acting as attenuator, hardware reacts in 1V increments,
  551. // where x V -> (x + 1) V yields (x - 1) gates in that time
  552. float gateCV = clamp(inputs[GATE_MODE_INPUT].getVoltage(), 0.f, 10.f);
  553. float knobAttenuation = rescale(params[GATE_MODE_PARAM].getValue(), -1.f, 8.f, 0.f, 1.f);
  554. gate = int (floor(gateCV * knobAttenuation)) - 1;
  555. }
  556. else {
  557. gate = (int) roundf(params[GATE_MODE_PARAM].getValue());
  558. }
  559. // should be respected, but make sure
  560. gate = clamp(gate, -1, 8);
  561. if (quadraticGatesOnly) {
  562. int quadraticGateIndex = int(floor(rescale(gate, -1.f, 8.f, 0.f, 4.99f)));
  563. return possibleQuadraticGates[clamp(quadraticGateIndex, 0, 4)];
  564. }
  565. else {
  566. return gate;
  567. }
  568. }
  569. json_t* dataToJson() override {
  570. json_t* rootJ = json_object();
  571. json_object_set_new(rootJ, "modeCOMIO", json_integer(modeCOMIO));
  572. json_object_set_new(rootJ, "quadraticGatesOnly", json_boolean(quadraticGatesOnly));
  573. json_object_set_new(rootJ, "allInNormalVoltage", json_integer(allInNormalVoltage));
  574. json_object_set_new(rootJ, "mainClockMultDiv", json_integer(mainClockMultDiv.multDiv));
  575. json_object_set_new(rootJ, "outputClockMultDiv", json_integer(outputClockMultDiv.multDiv));
  576. json_object_set_new(rootJ, "playState", json_integer(playState));
  577. json_object_set_new(rootJ, "outputClockFollowsPlayMode", json_boolean(outputClockFollowsPlayMode));
  578. return rootJ;
  579. }
  580. void dataFromJson(json_t* rootJ) override {
  581. json_t* modeJ = json_object_get(rootJ, "modeCOMIO");
  582. if (modeJ) {
  583. modeCOMIO = (Muxlicer::ModeCOMIO) json_integer_value(modeJ);
  584. }
  585. json_t* quadraticJ = json_object_get(rootJ, "quadraticGatesOnly");
  586. if (quadraticJ) {
  587. quadraticGatesOnly = json_boolean_value(quadraticJ);
  588. }
  589. json_t* allInNormalVoltageJ = json_object_get(rootJ, "allInNormalVoltage");
  590. if (allInNormalVoltageJ) {
  591. allInNormalVoltage = json_integer_value(allInNormalVoltageJ);
  592. }
  593. json_t* mainClockMultDivJ = json_object_get(rootJ, "mainClockMultDiv");
  594. if (mainClockMultDivJ) {
  595. mainClockMultDiv.multDiv = json_integer_value(mainClockMultDivJ);
  596. }
  597. json_t* outputClockMultDivJ = json_object_get(rootJ, "outputClockMultDiv");
  598. if (outputClockMultDivJ) {
  599. outputClockMultDiv.multDiv = json_integer_value(outputClockMultDivJ);
  600. }
  601. json_t* playStateJ = json_object_get(rootJ, "playState");
  602. if (playStateJ) {
  603. playState = (PlayState) json_integer_value(playStateJ);
  604. }
  605. json_t* outputClockFollowsPlayModeJ = json_object_get(rootJ, "outputClockFollowsPlayMode");
  606. if (outputClockFollowsPlayModeJ) {
  607. outputClockFollowsPlayMode = json_boolean_value(outputClockFollowsPlayModeJ);
  608. }
  609. updateParamFromMainClockMultDiv();
  610. }
  611. };
  612. struct MuxlicerWidget : ModuleWidget {
  613. MuxlicerWidget(Muxlicer* module) {
  614. setModule(module);
  615. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/Muxlicer.svg")));
  616. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  617. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  618. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  619. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  620. addParam(createParam<BefacoSwitchMomentary>(mm2px(Vec(35.72963, 10.008)), module, Muxlicer::PLAY_PARAM));
  621. addParam(createParam<BefacoTinyKnobDarkGrey>(mm2px(Vec(3.84112, 10.90256)), module, Muxlicer::ADDRESS_PARAM));
  622. addParam(createParam<BefacoTinyKnobDarkGrey>(mm2px(Vec(67.83258, 10.86635)), module, Muxlicer::GATE_MODE_PARAM));
  623. addParam(createParam<BefacoTinyKnob>(mm2px(Vec(28.12238, 24.62151)), module, Muxlicer::DIV_MULT_PARAM));
  624. addParam(createParam<BefacoSlidePot>(mm2px(Vec(2.32728, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 0));
  625. addParam(createParam<BefacoSlidePot>(mm2px(Vec(12.45595, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 1));
  626. addParam(createParam<BefacoSlidePot>(mm2px(Vec(22.58462, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 2));
  627. addParam(createParam<BefacoSlidePot>(mm2px(Vec(32.7133, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 3));
  628. addParam(createParam<BefacoSlidePot>(mm2px(Vec(42.74195, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 4));
  629. addParam(createParam<BefacoSlidePot>(mm2px(Vec(52.97062, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 5));
  630. addParam(createParam<BefacoSlidePot>(mm2px(Vec(63.0993, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 6));
  631. addParam(createParam<BefacoSlidePot>(mm2px(Vec(73.22797, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 7));
  632. addInput(createInput<BefacoInputPort>(mm2px(Vec(51.568, 11.20189)), module, Muxlicer::GATE_MODE_INPUT));
  633. addInput(createInput<BefacoInputPort>(mm2px(Vec(21.13974, 11.23714)), module, Muxlicer::ADDRESS_INPUT));
  634. addInput(createInput<BefacoInputPort>(mm2px(Vec(44.24461, 24.93662)), module, Muxlicer::CLOCK_INPUT));
  635. addInput(createInput<BefacoInputPort>(mm2px(Vec(12.62135, 24.95776)), module, Muxlicer::RESET_INPUT));
  636. addInput(createInput<BefacoInputPort>(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_INPUT));
  637. addInput(createInput<BefacoInputPort>(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_INPUTS + 0));
  638. addInput(createInput<BefacoInputPort>(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_INPUTS + 1));
  639. addInput(createInput<BefacoInputPort>(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_INPUTS + 2));
  640. addInput(createInput<BefacoInputPort>(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_INPUTS + 3));
  641. addInput(createInput<BefacoInputPort>(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_INPUTS + 4));
  642. addInput(createInput<BefacoInputPort>(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_INPUTS + 5));
  643. addInput(createInput<BefacoInputPort>(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_INPUTS + 6));
  644. addInput(createInput<BefacoInputPort>(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_INPUTS + 7));
  645. addInput(createInput<BefacoInputPort>(mm2px(Vec(16.11766, 98.09121)), module, Muxlicer::ALL_INPUT));
  646. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(59.8492, 24.95776)), module, Muxlicer::CLOCK_OUTPUT));
  647. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(56.59663, 98.06252)), module, Muxlicer::ALL_GATES_OUTPUT));
  648. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(66.72661, 98.07008)), module, Muxlicer::EOC_OUTPUT));
  649. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(0.89595, 86.78581)), module, Muxlicer::GATE_OUTPUTS + 0));
  650. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(11.02463, 86.77068)), module, Muxlicer::GATE_OUTPUTS + 1));
  651. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(21.14758, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 2));
  652. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(31.27625, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 3));
  653. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(41.40493, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 4));
  654. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(51.56803, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 5));
  655. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(61.69671, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 6));
  656. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(71.79094, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 7));
  657. // these blocks are exclusive (for visibility / interactivity) and allows IO and OI within one module
  658. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_OUTPUTS + 0));
  659. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 1));
  660. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 2));
  661. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 3));
  662. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 4));
  663. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 5));
  664. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 6));
  665. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 7));
  666. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_OUTPUT));
  667. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(16.11766, 98.09121)), module, Muxlicer::ALL_OUTPUT));
  668. updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_OUT);
  669. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(71.28361, 28.02644)), module, Muxlicer::CLOCK_LIGHT));
  670. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(3.99336, 81.86801)), module, Muxlicer::GATE_LIGHTS + 0));
  671. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(14.09146, 81.86801)), module, Muxlicer::GATE_LIGHTS + 1));
  672. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(24.22525, 81.86801)), module, Muxlicer::GATE_LIGHTS + 2));
  673. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(34.35901, 81.86801)), module, Muxlicer::GATE_LIGHTS + 3));
  674. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(44.49277, 81.86801)), module, Muxlicer::GATE_LIGHTS + 4));
  675. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(54.62652, 81.86801)), module, Muxlicer::GATE_LIGHTS + 5));
  676. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(64.76028, 81.86801)), module, Muxlicer::GATE_LIGHTS + 6));
  677. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(74.89404, 81.86801)), module, Muxlicer::GATE_LIGHTS + 7));
  678. }
  679. void draw(const DrawArgs& args) override {
  680. Muxlicer* module = dynamic_cast<Muxlicer*>(this->module);
  681. if (module != nullptr) {
  682. updatePortVisibilityForIOMode(module->modeCOMIO);
  683. }
  684. else {
  685. // module can be null, e.g. if populating the module browser with screenshots,
  686. // in which case just assume the default (8 in, 1 out)
  687. updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_OUT);
  688. }
  689. ModuleWidget::draw(args);
  690. }
  691. struct IOMenuItem : MenuItem {
  692. Muxlicer* module;
  693. MuxlicerWidget* widget;
  694. void onAction(const event::Action& e) override {
  695. module->modeCOMIO = Muxlicer::COM_1_IN_8_OUT;
  696. widget->updatePortVisibilityForIOMode(module->modeCOMIO);
  697. widget->clearCables();
  698. }
  699. };
  700. struct OIMenuItem : MenuItem {
  701. Muxlicer* module;
  702. MuxlicerWidget* widget;
  703. void onAction(const event::Action& e) override {
  704. module->modeCOMIO = Muxlicer::COM_8_IN_1_OUT;
  705. widget->updatePortVisibilityForIOMode(module->modeCOMIO);
  706. widget->clearCables();
  707. }
  708. };
  709. struct OutputRangeChildItem : MenuItem {
  710. Muxlicer* module;
  711. int allInNormalVoltage;
  712. void onAction(const event::Action& e) override {
  713. module->allInNormalVoltage = allInNormalVoltage;
  714. }
  715. };
  716. struct OutputRangeItem : MenuItem {
  717. Muxlicer* module;
  718. Menu* createChildMenu() override {
  719. Menu* menu = new Menu;
  720. std::vector<int> voltageOptions = {1, 5, 10};
  721. for (auto voltageOption : voltageOptions) {
  722. OutputRangeChildItem* rangeItem = createMenuItem<OutputRangeChildItem>(std::to_string(voltageOption) + "V",
  723. CHECKMARK(module->allInNormalVoltage == voltageOption));
  724. rangeItem->allInNormalVoltage = voltageOption;
  725. rangeItem->module = module;
  726. menu->addChild(rangeItem);
  727. }
  728. return menu;
  729. }
  730. };
  731. struct OutputClockScalingItem : MenuItem {
  732. Muxlicer* module;
  733. struct OutputClockScalingChildItem : MenuItem {
  734. Muxlicer* module;
  735. int clockOutMulDiv;
  736. void onAction(const event::Action& e) override {
  737. module->outputClockMultDiv.multDiv = clockOutMulDiv;
  738. }
  739. };
  740. Menu* createChildMenu() override {
  741. Menu* menu = new Menu;
  742. for (int clockOption : module->quadraticGatesOnly ? clockOptionsQuadratic : clockOptionsAll) {
  743. std::string optionString = getClockOptionString(clockOption);
  744. OutputClockScalingChildItem* clockItem = createMenuItem<OutputClockScalingChildItem>(optionString,
  745. CHECKMARK(module->outputClockMultDiv.multDiv == clockOption));
  746. clockItem->clockOutMulDiv = clockOption;
  747. clockItem->module = module;
  748. menu->addChild(clockItem);
  749. }
  750. return menu;
  751. }
  752. };
  753. struct MainClockScalingItem : MenuItem {
  754. Muxlicer* module;
  755. struct MainClockScalingChildItem : MenuItem {
  756. Muxlicer* module;
  757. int mainClockMulDiv, mainClockMulDivIndex;
  758. void onAction(const event::Action& e) override {
  759. module->mainClockMultDiv.multDiv = mainClockMulDiv;
  760. module->updateParamFromMainClockMultDiv();
  761. }
  762. };
  763. Menu* createChildMenu() override {
  764. Menu* menu = new Menu;
  765. int i = 0;
  766. for (int clockOption : module->quadraticGatesOnly ? clockOptionsQuadratic : clockOptionsAll) {
  767. std::string optionString = getClockOptionString(clockOption);
  768. MainClockScalingChildItem* clockItem = createMenuItem<MainClockScalingChildItem>(optionString,
  769. CHECKMARK(module->mainClockMultDiv.multDiv == clockOption));
  770. clockItem->mainClockMulDiv = clockOption;
  771. clockItem->mainClockMulDivIndex = i;
  772. clockItem->module = module;
  773. menu->addChild(clockItem);
  774. ++i;
  775. }
  776. return menu;
  777. }
  778. };
  779. struct QuadraticGatesMenuItem : MenuItem {
  780. Muxlicer* module;
  781. void onAction(const event::Action& e) override {
  782. module->quadraticGatesOnly ^= true;
  783. module->updateParamFromMainClockMultDiv();
  784. }
  785. };
  786. struct TapTempoItem : MenuItem {
  787. Muxlicer* module;
  788. void onAction(const event::Action& e) override {
  789. e.consume(NULL);
  790. module->tapped = true;
  791. e.unconsume();
  792. }
  793. };
  794. void appendContextMenu(Menu* menu) override {
  795. Muxlicer* module = dynamic_cast<Muxlicer*>(this->module);
  796. assert(module);
  797. menu->addChild(new MenuSeparator());
  798. menu->addChild(createMenuLabel<MenuLabel>("Clock Multiplication/Division"));
  799. if (module->usingExternalClock) {
  800. menu->addChild(createMenuLabel<MenuLabel>("Using external clock"));
  801. }
  802. else {
  803. TapTempoItem* tapTempoItem = createMenuItem<TapTempoItem>("Tap to set internal tempo...");
  804. tapTempoItem->module = module;
  805. menu->addChild(tapTempoItem);
  806. }
  807. MainClockScalingItem* mainClockScaleItem = createMenuItem<MainClockScalingItem>("Input clock", "▸");
  808. mainClockScaleItem->module = module;
  809. menu->addChild(mainClockScaleItem);
  810. OutputClockScalingItem* outputClockScaleItem = createMenuItem<OutputClockScalingItem>("Output clock", "▸");
  811. outputClockScaleItem->module = module;
  812. menu->addChild(outputClockScaleItem);
  813. QuadraticGatesMenuItem* quadraticGatesItem = createMenuItem<QuadraticGatesMenuItem>("Quadratic only mode", CHECKMARK(module->quadraticGatesOnly));
  814. quadraticGatesItem->module = module;
  815. menu->addChild(quadraticGatesItem);
  816. menu->addChild(new MenuSeparator());
  817. if (module->modeCOMIO == Muxlicer::COM_8_IN_1_OUT) {
  818. OutputRangeItem* outputRangeItem = createMenuItem<OutputRangeItem>("All In Normalled Value", "▸");
  819. outputRangeItem->module = module;
  820. menu->addChild(outputRangeItem);
  821. }
  822. else {
  823. menu->addChild(createMenuLabel<MenuLabel>("All In Normalled Value (disabled)"));
  824. }
  825. menu->addChild(createBoolPtrMenuItem("Output clock follows play/stop", "", &module->outputClockFollowsPlayMode));
  826. menu->addChild(new MenuSeparator());
  827. menu->addChild(createMenuLabel<MenuLabel>("Input/Output mode"));
  828. IOMenuItem* ioItem = createMenuItem<IOMenuItem>("1 input ▸ 8 outputs",
  829. CHECKMARK(module->modeCOMIO == Muxlicer::COM_1_IN_8_OUT));
  830. ioItem->module = module;
  831. ioItem->widget = this;
  832. menu->addChild(ioItem);
  833. OIMenuItem* oiItem = createMenuItem<OIMenuItem>("8 inputs ▸ 1 output",
  834. CHECKMARK(module->modeCOMIO == Muxlicer::COM_8_IN_1_OUT));
  835. oiItem->module = module;
  836. oiItem->widget = this;
  837. menu->addChild(oiItem);
  838. }
  839. void clearCables() {
  840. for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
  841. APP->scene->rack->clearCablesOnPort(this->getOutput(i));
  842. }
  843. APP->scene->rack->clearCablesOnPort(this->getInput(Muxlicer::COM_INPUT));
  844. APP->scene->rack->clearCablesOnPort(this->getInput(Muxlicer::ALL_INPUT));
  845. for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
  846. APP->scene->rack->clearCablesOnPort(this->getInput(i));
  847. }
  848. APP->scene->rack->clearCablesOnPort(this->getOutput(Muxlicer::COM_OUTPUT));
  849. APP->scene->rack->clearCablesOnPort(this->getOutput(Muxlicer::ALL_OUTPUT));
  850. }
  851. // set ports visibility, either for 1 input -> 8 outputs or 8 inputs -> 1 output
  852. void updatePortVisibilityForIOMode(Muxlicer::ModeCOMIO mode) {
  853. bool visibleToggle = (mode == Muxlicer::COM_1_IN_8_OUT);
  854. for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
  855. this->getOutput(i)->visible = visibleToggle;
  856. }
  857. this->getInput(Muxlicer::COM_INPUT)->visible = visibleToggle;
  858. this->getOutput(Muxlicer::ALL_OUTPUT)->visible = visibleToggle;
  859. for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
  860. this->getInput(i)->visible = !visibleToggle;
  861. }
  862. this->getOutput(Muxlicer::COM_OUTPUT)->visible = !visibleToggle;
  863. this->getInput(Muxlicer::ALL_INPUT)->visible = !visibleToggle;
  864. }
  865. };
  866. Model* modelMuxlicer = createModel<Muxlicer, MuxlicerWidget>("Muxlicer");
  867. // Mex
  868. struct Mex : Module {
  869. static const int numSteps = 8;
  870. enum ParamIds {
  871. ENUMS(STEP_PARAM, numSteps),
  872. NUM_PARAMS
  873. };
  874. enum InputIds {
  875. GATE_IN_INPUT,
  876. NUM_INPUTS
  877. };
  878. enum OutputIds {
  879. OUT_OUTPUT,
  880. NUM_OUTPUTS
  881. };
  882. enum LightIds {
  883. ENUMS(LED, numSteps),
  884. NUM_LIGHTS
  885. };
  886. enum StepState {
  887. GATE_IN_MODE,
  888. MUTE_MODE,
  889. MUXLICER_MODE
  890. };
  891. dsp::SchmittTrigger gateInTrigger;
  892. Mex() {
  893. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  894. for (int i = 0; i < 8; ++i) {
  895. configSwitch(STEP_PARAM + i, 0.f, 2.f, 0.f, string::f("Step %d", i + 1), {"Gate in/Clock Out", "Muted", "All Gates"});
  896. }
  897. }
  898. Muxlicer* findHostModulePtr(Module* module) {
  899. if (module) {
  900. if (module->leftExpander.module) {
  901. // if it's Muxlicer, we're done
  902. if (module->leftExpander.module->model == modelMuxlicer) {
  903. return reinterpret_cast<Muxlicer*>(module->leftExpander.module);
  904. }
  905. // if it's Mex, keep recursing
  906. else if (module->leftExpander.module->model == modelMex) {
  907. return findHostModulePtr(module->leftExpander.module);
  908. }
  909. }
  910. }
  911. return nullptr;
  912. }
  913. void process(const ProcessArgs& args) override {
  914. for (int i = 0; i < 8; i++) {
  915. lights[i].setBrightness(0.f);
  916. }
  917. Muxlicer const* mother = findHostModulePtr(this);
  918. if (mother) {
  919. float gate = 0.f;
  920. if (mother->playState != Muxlicer::STATE_STOPPED) {
  921. const int currentStep = clamp(mother->addressIndex, 0, 7);
  922. StepState state = (StepState) params[STEP_PARAM + currentStep].getValue();
  923. if (state == MUXLICER_MODE) {
  924. gate = mother->isAllGatesOutHigh;
  925. }
  926. else if (state == GATE_IN_MODE) {
  927. // gate in will convert non-gate signals to gates (via schmitt trigger)
  928. // if input is present
  929. if (inputs[GATE_IN_INPUT].isConnected()) {
  930. gateInTrigger.process(inputs[GATE_IN_INPUT].getVoltage());
  931. gate = gateInTrigger.isHigh();
  932. }
  933. // otherwise the main Muxlicer output clock (including divisions/multiplications)
  934. // is normalled in
  935. else {
  936. gate = mother->isOutputClockHigh;
  937. }
  938. }
  939. lights[currentStep].setBrightness(gate);
  940. }
  941. outputs[OUT_OUTPUT].setVoltage(gate * 10.f);
  942. }
  943. }
  944. };
  945. struct MexWidget : ModuleWidget {
  946. MexWidget(Mex* module) {
  947. setModule(module);
  948. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/Mex.svg")));
  949. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  950. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  951. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 13.063)), module, Mex::STEP_PARAM + 0));
  952. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 25.706)), module, Mex::STEP_PARAM + 1));
  953. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 38.348)), module, Mex::STEP_PARAM + 2));
  954. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 50.990)), module, Mex::STEP_PARAM + 3));
  955. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 63.632)), module, Mex::STEP_PARAM + 4));
  956. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 76.274)), module, Mex::STEP_PARAM + 5));
  957. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 88.916)), module, Mex::STEP_PARAM + 6));
  958. addParam(createParamCentered<BefacoSwitchHorizontal>(mm2px(Vec(8.088, 101.559)), module, Mex::STEP_PARAM + 7));
  959. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(4.978, 113.445)), module, Mex::GATE_IN_INPUT));
  960. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(15.014, 113.4)), module, Mex::OUT_OUTPUT));
  961. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 13.063)), module, Mex::LED + 0));
  962. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 25.706)), module, Mex::LED + 1));
  963. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 38.348)), module, Mex::LED + 2));
  964. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 50.990)), module, Mex::LED + 3));
  965. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 63.632)), module, Mex::LED + 4));
  966. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 76.274)), module, Mex::LED + 5));
  967. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 88.916)), module, Mex::LED + 6));
  968. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(17.7, 101.558)), module, Mex::LED + 7));
  969. }
  970. };
  971. Model* modelMex = createModel<Mex, MexWidget>("Mex");