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.

892 lines
33KB

  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->mousePos.y;
  15. ParamWidget::onDragStart(e);
  16. }
  17. void onDragMove(const event::DragMove& e) override {
  18. float diff = APP->scene->rack->mousePos.y - startMouseY;
  19. // Once the user has dragged the mouse a "threshold" distance, latch
  20. // to disallow further changes of state until the mouse is released.
  21. // We don't just setValue(1) (default/rest state) because this creates a
  22. // jarring UI experience
  23. if (diff < -10 && !latched) {
  24. paramQuantity->setValue(2);
  25. latched = true;
  26. }
  27. if (diff > 10 && !latched) {
  28. paramQuantity->setValue(0);
  29. latched = true;
  30. }
  31. ParamWidget::onDragMove(e);
  32. }
  33. void onDragEnd(const event::DragEnd& e) override {
  34. // on release, the switch resets to default/neutral/middle position
  35. paramQuantity->setValue(1);
  36. latched = false;
  37. ParamWidget::onDragEnd(e);
  38. }
  39. // do nothing
  40. void randomize() override {}
  41. float startMouseY = 0.f;
  42. bool latched = false;
  43. };
  44. // Class which can yield a divided clock state, specifically where the
  45. // gate is generated at request time through getGate(), rather than during
  46. // process() - this means that different divisions of clock can be requested
  47. // at any point in time. In contrast, the division/multiplication setting for
  48. // ClockMultDiv cannot easily be changed _during_ a clock tick.
  49. struct MultiGateClock {
  50. float remaining = 0.f;
  51. float fullPulseLength = 0.f;
  52. /** Immediately disables the pulse */
  53. void reset(float newfullPulseLength) {
  54. fullPulseLength = newfullPulseLength;
  55. remaining = fullPulseLength;
  56. }
  57. /** Advances the state by `deltaTime`. Returns whether the pulse is in the HIGH state. */
  58. bool process(float deltaTime) {
  59. if (remaining > 0.f) {
  60. remaining -= deltaTime;
  61. return true;
  62. }
  63. return false;
  64. }
  65. float getGate(int gateMode) {
  66. if (gateMode == 0) {
  67. // always on (special case)
  68. return 10.f;
  69. }
  70. else if (gateMode < 0 || remaining <= 0) {
  71. // disabled (or elapsed)
  72. return 0.f;
  73. }
  74. const float multiGateOnLength = fullPulseLength / ((gateMode > 0) ? (2.f * gateMode) : 1.0f);
  75. const bool isOddPulse = int(floor(remaining / multiGateOnLength)) % 2;
  76. return isOddPulse ? 10.f : 0.f;
  77. }
  78. };
  79. // Class for generating a clock sequence after setting a clock multiplication or division,
  80. // given a stream of clock pulses as the "base" clock.
  81. // Implementation is heavily inspired by BogAudio RGate, with modification
  82. struct MultDivClock {
  83. // convention: negative values are used for division (1/mult), positive for multiplication (x mult)
  84. // multDiv = 0 should not be used, but if it is it will result in no modification to the clock
  85. int multDiv = 1;
  86. float secondsSinceLastClock = -1.0f;
  87. float inputClockLengthSeconds = -1.0f;
  88. // count how many divisions we've had
  89. int dividerCount = 0;
  90. float dividedProgressSeconds = 0.f;
  91. // returns the gated clock signal
  92. float process(float deltaTime, bool clockPulseReceived) {
  93. if (clockPulseReceived) {
  94. // update our record of the incoming clock spacing
  95. if (secondsSinceLastClock > 0.0f) {
  96. inputClockLengthSeconds = secondsSinceLastClock;
  97. }
  98. secondsSinceLastClock = 0.0f;
  99. }
  100. float out = 0.f;
  101. if (secondsSinceLastClock >= 0.0f) {
  102. secondsSinceLastClock += deltaTime;
  103. // negative values are used for division (x 1/mult), positive for multiplication (x mult)
  104. const int division = std::max(-multDiv, 1);
  105. const int multiplication = std::max(multDiv, 1);
  106. if (clockPulseReceived) {
  107. if (dividerCount < 1) {
  108. dividedProgressSeconds = 0.0f;
  109. }
  110. else {
  111. dividedProgressSeconds += deltaTime;
  112. }
  113. ++dividerCount;
  114. if (dividerCount >= division) {
  115. dividerCount = 0;
  116. }
  117. }
  118. else {
  119. dividedProgressSeconds += deltaTime;
  120. }
  121. // lengths of the mult/div versions of the clock
  122. const float dividedSeconds = inputClockLengthSeconds * (float) division;
  123. const float multipliedSeconds = dividedSeconds / (float) multiplication;
  124. // length of the output gate (s)
  125. const float gateSeconds = std::max(0.001f, multipliedSeconds * 0.5f);
  126. if (dividedProgressSeconds < dividedSeconds) {
  127. float multipliedProgressSeconds = dividedProgressSeconds / multipliedSeconds;
  128. multipliedProgressSeconds -= (float)(int)multipliedProgressSeconds;
  129. multipliedProgressSeconds *= multipliedSeconds;
  130. out += (float)(multipliedProgressSeconds <= gateSeconds);
  131. }
  132. }
  133. return out;
  134. }
  135. float getEffectiveClockLength() {
  136. // negative values are used for division (x 1/mult), positive for multiplication (x mult)
  137. const int division = std::max(-multDiv, 1);
  138. const int multiplication = std::max(multDiv, 1);
  139. // lengths of the mult/div versions of the clock
  140. const float dividedSeconds = inputClockLengthSeconds * (float) division;
  141. const float multipliedSeconds = dividedSeconds / (float) multiplication;
  142. return multipliedSeconds;
  143. }
  144. };
  145. static const std::vector<int> clockOptions = {-16, -8, -4, -3, -2, 1, 2, 3, 4, 8, 16};
  146. inline std::string getClockOptionString(const int clockOption) {
  147. return (clockOption < 0) ? ("x 1/" + std::to_string(-clockOption)) : ("x " + std::to_string(clockOption));
  148. }
  149. struct Muxlicer : Module {
  150. enum ParamIds {
  151. PLAY_PARAM,
  152. ADDRESS_PARAM,
  153. GATE_MODE_PARAM,
  154. TAP_TEMPO_PARAM,
  155. ENUMS(LEVEL_PARAMS, 8),
  156. NUM_PARAMS
  157. };
  158. enum InputIds {
  159. GATE_MODE_INPUT,
  160. ADDRESS_INPUT,
  161. CLOCK_INPUT,
  162. RESET_INPUT,
  163. COM_INPUT,
  164. ENUMS(MUX_INPUTS, 8),
  165. ALL_INPUT,
  166. NUM_INPUTS
  167. };
  168. enum OutputIds {
  169. CLOCK_OUTPUT,
  170. ALL_GATES_OUTPUT,
  171. EOC_OUTPUT,
  172. ENUMS(GATE_OUTPUTS, 8),
  173. ENUMS(MUX_OUTPUTS, 8),
  174. COM_OUTPUT,
  175. NUM_OUTPUTS
  176. };
  177. enum LightIds {
  178. CLOCK_LIGHT,
  179. ENUMS(GATE_LIGHTS, 8),
  180. NUM_LIGHTS
  181. };
  182. enum ModeCOMIO {
  183. COM_1_IN_8_OUT,
  184. COM_8_IN_1_OUT
  185. };
  186. enum PlayState {
  187. STATE_PLAY_ONCE,
  188. STATE_STOPPED,
  189. STATE_PLAY
  190. };
  191. /*
  192. This shows how the values of the gate mode knob + CV map onto gate triggers.
  193. See also getGateMode()
  194. value | description | quadratic only mode
  195. -1 no gate | ✔
  196. 0 gate (full timestep) | x
  197. +1 half timestep | ✔
  198. 2 two gates | ✔
  199. 3 three gates | x
  200. 4 four gates | ✔
  201. 5 five gates | x
  202. 6 six gates | x
  203. 7 seven gates | x
  204. 8 eight gates | ✔
  205. */
  206. int possibleQuadraticGates[5] = {-1, 1, 2, 4, 8};
  207. bool quadraticGatesOnly = false;
  208. PlayState playState = STATE_STOPPED;
  209. dsp::BooleanTrigger playStateTrigger;
  210. uint32_t runIndex; // which step are we on (0 to 7)
  211. uint32_t addressIndex = 0;
  212. bool reset = false;
  213. bool tapped = false;
  214. // used to track the clock (e.g. if external clock is not connected). NOTE: this clock
  215. // is defined _prior_ to any clock division/multiplication logic
  216. float internalClockProgress = 0.f;
  217. float internalClockLength = 0.25f;
  218. float tapTime = 99999; // used to track the time between clock pulses (or taps?)
  219. dsp::SchmittTrigger inputClockTrigger; // to detect incoming clock pulses
  220. dsp::SchmittTrigger mainClockTrigger; // to detect rising edges from the divided/multiplied version of the clock signal
  221. dsp::SchmittTrigger resetTrigger; // to detect the reset signal
  222. dsp::PulseGenerator resetTimer; // leave a grace period before advancing the step
  223. dsp::PulseGenerator endOfCyclePulse; // fire a signal at the end of cycle
  224. dsp::BooleanTrigger tapTempoTrigger; // to only trigger tap tempo when push is first detected
  225. MultDivClock mainClockMultDiv; // to produce a divided/multiplied version of the (internal or external) clock signal
  226. MultDivClock outputClockMultDiv; // to produce a divided/multiplied version of the output clock signal
  227. MultiGateClock multiClock; // to easily produce a divided version of the main clock (where division can be changed at any point)
  228. bool usingExternalClock = false; // is there a clock plugged into clock in (external) or not (internal)
  229. const static int SEQUENCE_LENGTH = 8;
  230. ModeCOMIO modeCOMIO = COM_8_IN_1_OUT; // are we in 1-in-8-out mode, or 8-in-1-out mode
  231. int allInNormalVoltage = 10; // what voltage is normalled into the "All In" input, selectable via context menu
  232. Module* rightModule; // for the expander
  233. struct TapTempoKnobParamQuantity : ParamQuantity {
  234. std::string getDisplayValueString() override {
  235. if (module != nullptr) {
  236. const int clockOptionIndex = clamp(int(ParamQuantity::getValue()), 0, clockOptions.size());
  237. return getClockOptionString(clockOptions[clockOptionIndex]);
  238. }
  239. else {
  240. return "";
  241. }
  242. }
  243. };
  244. Muxlicer() {
  245. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  246. configParam(Muxlicer::PLAY_PARAM, STATE_PLAY_ONCE, STATE_PLAY, STATE_STOPPED, "Play switch");
  247. configParam(Muxlicer::ADDRESS_PARAM, -1.f, 7.f, -1.f, "Address");
  248. configParam(Muxlicer::GATE_MODE_PARAM, -1.f, 8.f, 0.f, "Gate mode");
  249. const int numClockOptions = clockOptions.size();
  250. configParam<TapTempoKnobParamQuantity>(Muxlicer::TAP_TEMPO_PARAM, 0, numClockOptions - 1, numClockOptions / 2, "Main clock mult/div");
  251. for (int i = 0; i < SEQUENCE_LENGTH; ++i) {
  252. configParam(Muxlicer::LEVEL_PARAMS + i, 0.0, 1.0, 1.0, string::f("Slider %d", i));
  253. }
  254. onReset();
  255. }
  256. void onReset() override {
  257. internalClockLength = 0.250f;
  258. internalClockProgress = 0;
  259. runIndex = 0;
  260. }
  261. void process(const ProcessArgs& args) override {
  262. usingExternalClock = inputs[CLOCK_INPUT].isConnected();
  263. bool externalClockPulseReceived = false;
  264. // a clock pulse does two things: 1) sets the internal clock (based on timing between two pulses), which
  265. // would continue were the clock input to be removed, and 2) synchronises/drive the clock (if clock input present)
  266. if (usingExternalClock && inputClockTrigger.process(rescale(inputs[CLOCK_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
  267. externalClockPulseReceived = true;
  268. }
  269. // this can also be sent by tap tempo
  270. else if (!usingExternalClock && tapTempoTrigger.process(tapped)) {
  271. externalClockPulseReceived = true;
  272. tapped = false;
  273. }
  274. const int clockOptionFromDial = clockOptions[int(params[TAP_TEMPO_PARAM].getValue())];
  275. mainClockMultDiv.multDiv = clockOptionFromDial;
  276. if (resetTrigger.process(rescale(inputs[RESET_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
  277. reset = true;
  278. if (playState == STATE_STOPPED) {
  279. playState = STATE_PLAY_ONCE;
  280. }
  281. }
  282. processPlayResetSwitch();
  283. const float address = params[ADDRESS_PARAM].getValue() + inputs[ADDRESS_INPUT].getVoltage();
  284. const bool isSequenceAdvancing = address < 0.f;
  285. // even if we have an external clock, use its pulses to time/sync the internal clock
  286. // so that it will remain running after CLOCK_INPUT is disconnected
  287. if (externalClockPulseReceived) {
  288. // track length between received clock pulses (using external clock) or taps
  289. // of the tap-tempo button (if sufficiently short)
  290. if (usingExternalClock || tapTime < 2.f) {
  291. internalClockLength = tapTime;
  292. }
  293. tapTime = 0;
  294. internalClockProgress = 0;
  295. }
  296. tapTime += args.sampleTime;
  297. internalClockProgress += args.sampleTime;
  298. // track if the internal clock has "ticked"
  299. const bool internalClockPulseReceived = (internalClockProgress >= internalClockLength);
  300. if (internalClockPulseReceived) {
  301. internalClockProgress = 0.f;
  302. }
  303. // we can be in one of two clock modes:
  304. // * external (decided by pulses to CLOCK_INPUT)
  305. // * internal (decided by internalClockProgress exceeding the internal clock length)
  306. //
  307. // choose which clock source we are to use
  308. const bool clockPulseReceived = usingExternalClock ? externalClockPulseReceived : internalClockPulseReceived;
  309. // apply the main clock div/mult logic to whatever clock source we're using - this outputs a gate sequence
  310. // so we must use a Schmitt Trigger on the divided/mult'd signal in order to detect when to advance the sequence
  311. const bool dividedMultipliedClockPulseReceived = mainClockTrigger.process(mainClockMultDiv.process(args.sampleTime, clockPulseReceived));
  312. // reset _doesn't_ reset/sync the clock, it just moves the sequence index marker back to the start
  313. if (reset) {
  314. runIndex = 0;
  315. reset = false;
  316. resetTimer.trigger();
  317. }
  318. // see https://vcvrack.com/manual/VoltageStandards#Timing
  319. const bool resetGracePeriodActive = resetTimer.process(args.sampleTime);
  320. if (dividedMultipliedClockPulseReceived) {
  321. if (isSequenceAdvancing && !resetGracePeriodActive) {
  322. runIndex++;
  323. if (runIndex >= 8) {
  324. // both play modes will reset to step 0 and fire an EOC trigger
  325. runIndex = 0;
  326. endOfCyclePulse.trigger(1e-3);
  327. // additionally stop if in one shot mode
  328. if (playState == STATE_PLAY_ONCE) {
  329. playState = STATE_STOPPED;
  330. }
  331. }
  332. }
  333. multiClock.reset(mainClockMultDiv.getEffectiveClockLength());
  334. for (int i = 0; i < 8; i++) {
  335. outputs[GATE_OUTPUTS + i].setVoltage(0.f);
  336. }
  337. }
  338. if (isSequenceAdvancing) {
  339. addressIndex = runIndex;
  340. }
  341. else {
  342. addressIndex = clamp((int) roundf(address), 0, 8 - 1);
  343. }
  344. // Gates
  345. for (int i = 0; i < 8; i++) {
  346. outputs[GATE_OUTPUTS + i].setVoltage(0.f);
  347. lights[GATE_LIGHTS + i].setBrightness(0.f);
  348. }
  349. outputs[ALL_GATES_OUTPUT].setVoltage(0.f);
  350. multiClock.process(args.sampleTime);
  351. const int gateMode = getGateMode();
  352. if (playState != STATE_STOPPED) {
  353. // current gate output _and_ "All Gates" output get the gate pattern from multiClock
  354. float gateValue = multiClock.getGate(gateMode);
  355. outputs[GATE_OUTPUTS + addressIndex].setVoltage(gateValue);
  356. lights[GATE_LIGHTS + addressIndex].setBrightness(gateValue / 10.f);
  357. outputs[ALL_GATES_OUTPUT].setVoltage(gateValue);
  358. }
  359. if (modeCOMIO == COM_1_IN_8_OUT) {
  360. const int numActiveEngines = std::max(inputs[ALL_INPUT].getChannels(), inputs[COM_INPUT].getChannels());
  361. const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
  362. for (int c = 0; c < numActiveEngines; c += 4) {
  363. // Mux outputs (all zero, except active step, if playing)
  364. for (int i = 0; i < 8; i++) {
  365. outputs[MUX_OUTPUTS + i].setVoltageSimd<float_4>(0.f, c);
  366. }
  367. if (playState != STATE_STOPPED) {
  368. const float_4 com_input = inputs[COM_INPUT].getPolyVoltageSimd<float_4>(c);
  369. outputs[MUX_OUTPUTS + addressIndex].setVoltageSimd(stepVolume * com_input, c);
  370. }
  371. }
  372. for (int i = 0; i < 8; i++) {
  373. outputs[MUX_OUTPUTS + i].setChannels(numActiveEngines);
  374. }
  375. }
  376. else if (modeCOMIO == COM_8_IN_1_OUT && playState != STATE_STOPPED) {
  377. // we need at least one active engine, even if nothing is connected
  378. // as we want the voltage that is normalled to All In to be processed
  379. int numActiveEngines = std::max(1, inputs[ALL_INPUT].getChannels());
  380. for (int i = 0; i < 8; i++) {
  381. numActiveEngines = std::max(numActiveEngines, inputs[MUX_INPUTS + i].getChannels());
  382. }
  383. const float stepVolume = params[LEVEL_PARAMS + addressIndex].getValue();
  384. for (int c = 0; c < numActiveEngines; c += 4) {
  385. const float_4 allInValue = inputs[ALL_INPUT].getNormalPolyVoltageSimd<float_4>((float_4) allInNormalVoltage, c);
  386. const float_4 stepValue = inputs[MUX_INPUTS + addressIndex].getNormalPolyVoltageSimd<float_4>(allInValue, c) * stepVolume;
  387. outputs[COM_OUTPUT].setVoltageSimd(stepValue, c);
  388. }
  389. outputs[COM_OUTPUT].setChannels(numActiveEngines);
  390. }
  391. const bool isOutputClockHigh = outputClockMultDiv.process(args.sampleTime, clockPulseReceived);
  392. outputs[CLOCK_OUTPUT].setVoltage(isOutputClockHigh ? 10.f : 0.f);
  393. lights[CLOCK_LIGHT].setBrightness(isOutputClockHigh ? 1.f : 0.f);
  394. // end of cycle trigger trigger
  395. outputs[EOC_OUTPUT].setVoltage(endOfCyclePulse.process(args.sampleTime) ? 10.f : 0.f);
  396. if (rightExpander.module && rightExpander.module->model == modelMex) {
  397. // Get message from right expander
  398. MexMessage* message = (MexMessage*) rightExpander.module->leftExpander.producerMessage;
  399. // Write message
  400. message->addressIndex = addressIndex;
  401. message->allGates = multiClock.getGate(gateMode);
  402. message->outputClock = isOutputClockHigh ? 10.f : 0.f;
  403. message->isPlaying = (playState != STATE_STOPPED);
  404. // Flip messages at the end of the timestep
  405. rightExpander.module->leftExpander.messageFlipRequested = true;
  406. }
  407. }
  408. void processPlayResetSwitch() {
  409. // if the play switch has effectively been activated for the first time,
  410. // i.e. it's not just still being held
  411. const bool switchIsActive = params[PLAY_PARAM].getValue() != STATE_STOPPED;
  412. if (playStateTrigger.process(switchIsActive) && switchIsActive) {
  413. // if we were stopped, check for activation (normal or one-shot)
  414. if (playState == STATE_STOPPED) {
  415. if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
  416. playState = STATE_PLAY;
  417. }
  418. else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
  419. playState = STATE_PLAY_ONCE;
  420. runIndex = 0;
  421. reset = true;
  422. }
  423. }
  424. // otherwise we are in play mode (and we've not just held onto the play switch),
  425. // so check for stop or reset
  426. else {
  427. // top switch will stop
  428. if (params[PLAY_PARAM].getValue() == STATE_PLAY) {
  429. playState = STATE_STOPPED;
  430. }
  431. // bottom will reset
  432. else if (params[PLAY_PARAM].getValue() == STATE_PLAY_ONCE) {
  433. reset = true;
  434. runIndex = 0;
  435. }
  436. }
  437. }
  438. }
  439. // determines how many gates to yield per step
  440. int getGateMode() {
  441. int gate;
  442. if (inputs[GATE_MODE_INPUT].isConnected()) {
  443. // with gate acting as attenuator, hardware reacts in 1V increments,
  444. // where x V -> (x + 1) V yields (x - 1) gates in that time
  445. float gateCV = clamp(inputs[GATE_MODE_INPUT].getVoltage(), 0.f, 10.f);
  446. float knobAttenuation = rescale(params[GATE_MODE_PARAM].getValue(), -1.f, 8.f, 0.f, 1.f);
  447. gate = int (floor(gateCV * knobAttenuation)) - 1;
  448. }
  449. else {
  450. gate = (int) roundf(params[GATE_MODE_PARAM].getValue());
  451. }
  452. // should be respected, but make sure
  453. gate = clamp(gate, -1, 8);
  454. if (quadraticGatesOnly) {
  455. int quadraticGateIndex = int(floor(rescale(gate, -1.f, 8.f, 0.f, 4.99f)));
  456. return possibleQuadraticGates[clamp(quadraticGateIndex, 0, 4)];
  457. }
  458. else {
  459. return gate;
  460. }
  461. }
  462. json_t* dataToJson() override {
  463. json_t* rootJ = json_object();
  464. json_object_set_new(rootJ, "modeCOMIO", json_integer(modeCOMIO));
  465. json_object_set_new(rootJ, "quadraticGatesOnly", json_boolean(quadraticGatesOnly));
  466. json_object_set_new(rootJ, "allInNormalVoltage", json_integer(allInNormalVoltage));
  467. json_object_set_new(rootJ, "mainClockMultDiv", json_integer(mainClockMultDiv.multDiv));
  468. json_object_set_new(rootJ, "outputClockMultDiv", json_integer(outputClockMultDiv.multDiv));
  469. json_object_set_new(rootJ, "playState", json_integer(playState));
  470. return rootJ;
  471. }
  472. void dataFromJson(json_t* rootJ) override {
  473. json_t* modeJ = json_object_get(rootJ, "modeCOMIO");
  474. modeCOMIO = (Muxlicer::ModeCOMIO) json_integer_value(modeJ);
  475. json_t* quadraticJ = json_object_get(rootJ, "quadraticGatesOnly");
  476. quadraticGatesOnly = json_boolean_value(quadraticJ);
  477. json_t* allInNormalVoltageJ = json_object_get(rootJ, "allInNormalVoltage");
  478. allInNormalVoltage = json_integer_value(allInNormalVoltageJ);
  479. json_t* mainClockMultDivJ = json_object_get(rootJ, "mainClockMultDiv");
  480. mainClockMultDiv.multDiv = json_integer_value(mainClockMultDivJ);
  481. json_t* outputClockMultDivJ = json_object_get(rootJ, "outputClockMultDiv");
  482. outputClockMultDiv.multDiv = json_integer_value(outputClockMultDivJ);
  483. json_t* playStateJ = json_object_get(rootJ, "playState");
  484. playState = (PlayState) json_integer_value(playStateJ);
  485. }
  486. };
  487. struct MuxlicerWidget : ModuleWidget {
  488. MuxlicerWidget(Muxlicer* module) {
  489. setModule(module);
  490. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Muxlicer.svg")));
  491. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  492. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  493. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  494. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  495. addParam(createParam<BefacoSwitchMomentary>(mm2px(Vec(35.72963, 10.008)), module, Muxlicer::PLAY_PARAM));
  496. addParam(createParam<BefacoTinyKnobSnap>(mm2px(Vec(3.84112, 10.90256)), module, Muxlicer::ADDRESS_PARAM));
  497. addParam(createParam<BefacoTinyKnobWhite>(mm2px(Vec(67.83258, 10.86635)), module, Muxlicer::GATE_MODE_PARAM));
  498. addParam(createParam<BefacoTinyKnobSnap>(mm2px(Vec(28.12238, 24.62151)), module, Muxlicer::TAP_TEMPO_PARAM));
  499. addParam(createParam<BefacoSlidePot>(mm2px(Vec(2.32728, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 0));
  500. addParam(createParam<BefacoSlidePot>(mm2px(Vec(12.45595, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 1));
  501. addParam(createParam<BefacoSlidePot>(mm2px(Vec(22.58462, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 2));
  502. addParam(createParam<BefacoSlidePot>(mm2px(Vec(32.7133, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 3));
  503. addParam(createParam<BefacoSlidePot>(mm2px(Vec(42.74195, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 4));
  504. addParam(createParam<BefacoSlidePot>(mm2px(Vec(52.97062, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 5));
  505. addParam(createParam<BefacoSlidePot>(mm2px(Vec(63.0993, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 6));
  506. addParam(createParam<BefacoSlidePot>(mm2px(Vec(73.22797, 40.67102)), module, Muxlicer::LEVEL_PARAMS + 7));
  507. addInput(createInput<BefacoInputPort>(mm2px(Vec(51.568, 11.20189)), module, Muxlicer::GATE_MODE_INPUT));
  508. addInput(createInput<BefacoInputPort>(mm2px(Vec(21.13974, 11.23714)), module, Muxlicer::ADDRESS_INPUT));
  509. addInput(createInput<BefacoInputPort>(mm2px(Vec(44.24461, 24.93662)), module, Muxlicer::CLOCK_INPUT));
  510. addInput(createInput<BefacoInputPort>(mm2px(Vec(12.62135, 24.95776)), module, Muxlicer::RESET_INPUT));
  511. addInput(createInput<BefacoInputPort>(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_INPUT));
  512. addInput(createInput<BefacoInputPort>(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_INPUTS + 0));
  513. addInput(createInput<BefacoInputPort>(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_INPUTS + 1));
  514. addInput(createInput<BefacoInputPort>(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_INPUTS + 2));
  515. addInput(createInput<BefacoInputPort>(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_INPUTS + 3));
  516. addInput(createInput<BefacoInputPort>(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_INPUTS + 4));
  517. addInput(createInput<BefacoInputPort>(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_INPUTS + 5));
  518. addInput(createInput<BefacoInputPort>(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_INPUTS + 6));
  519. addInput(createInput<BefacoInputPort>(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_INPUTS + 7));
  520. addInput(createInput<BefacoInputPort>(mm2px(Vec(16.11766, 98.09121)), module, Muxlicer::ALL_INPUT));
  521. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(59.8492, 24.95776)), module, Muxlicer::CLOCK_OUTPUT));
  522. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(56.59663, 98.06252)), module, Muxlicer::ALL_GATES_OUTPUT));
  523. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(66.72661, 98.07008)), module, Muxlicer::EOC_OUTPUT));
  524. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(0.89595, 86.78581)), module, Muxlicer::GATE_OUTPUTS + 0));
  525. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(11.02463, 86.77068)), module, Muxlicer::GATE_OUTPUTS + 1));
  526. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(21.14758, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 2));
  527. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(31.27625, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 3));
  528. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(41.40493, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 4));
  529. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(51.56803, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 5));
  530. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(61.69671, 86.79938)), module, Muxlicer::GATE_OUTPUTS + 6));
  531. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(71.79094, 86.77824)), module, Muxlicer::GATE_OUTPUTS + 7));
  532. // these blocks are exclusive (for visibility / interactivity) and allows IO and OI within one module
  533. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(0.895950, 109.27901)), module, Muxlicer::MUX_OUTPUTS + 0));
  534. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(11.05332, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 1));
  535. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(21.18201, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 2));
  536. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(31.27625, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 3));
  537. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(41.40493, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 4));
  538. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(51.53360, 109.27142)), module, Muxlicer::MUX_OUTPUTS + 5));
  539. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(61.69671, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 6));
  540. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(71.82537, 109.29256)), module, Muxlicer::MUX_OUTPUTS + 7));
  541. addOutput(createOutput<BefacoOutputPort>(mm2px(Vec(36.3142, 98.07911)), module, Muxlicer::COM_OUTPUT));
  542. updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_OUT);
  543. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(71.28361, 28.02644)), module, Muxlicer::CLOCK_LIGHT));
  544. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(3.99336, 81.86801)), module, Muxlicer::GATE_LIGHTS + 0));
  545. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(14.09146, 81.86801)), module, Muxlicer::GATE_LIGHTS + 1));
  546. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(24.22525, 81.86801)), module, Muxlicer::GATE_LIGHTS + 2));
  547. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(34.35901, 81.86801)), module, Muxlicer::GATE_LIGHTS + 3));
  548. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(44.49277, 81.86801)), module, Muxlicer::GATE_LIGHTS + 4));
  549. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(54.62652, 81.86801)), module, Muxlicer::GATE_LIGHTS + 5));
  550. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(64.76028, 81.86801)), module, Muxlicer::GATE_LIGHTS + 6));
  551. addChild(createLight<SmallLight<RedLight>>(mm2px(Vec(74.89404, 81.86801)), module, Muxlicer::GATE_LIGHTS + 7));
  552. }
  553. void draw(const DrawArgs& args) override {
  554. Muxlicer* module = dynamic_cast<Muxlicer*>(this->module);
  555. if (module != nullptr) {
  556. updatePortVisibilityForIOMode(module->modeCOMIO);
  557. }
  558. else {
  559. // module can be null, e.g. if populating the module browser with screenshots,
  560. // in which case just assume the default (8 in, 1 out)
  561. updatePortVisibilityForIOMode(Muxlicer::COM_8_IN_1_OUT);
  562. }
  563. ModuleWidget::draw(args);
  564. }
  565. struct IOMenuItem : MenuItem {
  566. Muxlicer* module;
  567. MuxlicerWidget* widget;
  568. void onAction(const event::Action& e) override {
  569. module->modeCOMIO = Muxlicer::COM_1_IN_8_OUT;
  570. widget->updatePortVisibilityForIOMode(module->modeCOMIO);
  571. widget->clearCables();
  572. }
  573. };
  574. struct OIMenuItem : MenuItem {
  575. Muxlicer* module;
  576. MuxlicerWidget* widget;
  577. void onAction(const event::Action& e) override {
  578. module->modeCOMIO = Muxlicer::COM_8_IN_1_OUT;
  579. widget->updatePortVisibilityForIOMode(module->modeCOMIO);
  580. widget->clearCables();
  581. }
  582. };
  583. struct OutputRangeChildItem : MenuItem {
  584. Muxlicer* module;
  585. int allInNormalVoltage;
  586. void onAction(const event::Action& e) override {
  587. module->allInNormalVoltage = allInNormalVoltage;
  588. }
  589. };
  590. struct OutputRangeItem : MenuItem {
  591. Muxlicer* module;
  592. Menu* createChildMenu() override {
  593. Menu* menu = new Menu;
  594. std::vector<int> voltageOptions = {1, 5, 10};
  595. for (auto voltageOption : voltageOptions) {
  596. OutputRangeChildItem* rangeItem = createMenuItem<OutputRangeChildItem>(std::to_string(voltageOption) + "V",
  597. CHECKMARK(module->allInNormalVoltage == voltageOption));
  598. rangeItem->allInNormalVoltage = voltageOption;
  599. rangeItem->module = module;
  600. menu->addChild(rangeItem);
  601. }
  602. return menu;
  603. }
  604. };
  605. struct OutputClockScalingItem : MenuItem {
  606. Muxlicer* module;
  607. struct OutputClockScalingChildItem : MenuItem {
  608. Muxlicer* module;
  609. int clockOutMulDiv;
  610. void onAction(const event::Action& e) override {
  611. module->outputClockMultDiv.multDiv = clockOutMulDiv;
  612. }
  613. };
  614. Menu* createChildMenu() override {
  615. Menu* menu = new Menu;
  616. for (int clockOption : clockOptions) {
  617. std::string optionString = getClockOptionString(clockOption);
  618. OutputClockScalingChildItem* clockItem = createMenuItem<OutputClockScalingChildItem>(optionString,
  619. CHECKMARK(module->outputClockMultDiv.multDiv == clockOption));
  620. clockItem->clockOutMulDiv = clockOption;
  621. clockItem->module = module;
  622. menu->addChild(clockItem);
  623. }
  624. return menu;
  625. }
  626. };
  627. struct MainClockScalingItem : MenuItem {
  628. Muxlicer* module;
  629. struct MainClockScalingChildItem : MenuItem {
  630. Muxlicer* module;
  631. int mainClockMulDiv, mainClockMulDivIndex;
  632. void onAction(const event::Action& e) override {
  633. module->mainClockMultDiv.multDiv = mainClockMulDiv;
  634. module->params[Muxlicer::TAP_TEMPO_PARAM].setValue(mainClockMulDivIndex);
  635. }
  636. };
  637. Menu* createChildMenu() override {
  638. Menu* menu = new Menu;
  639. int i = 0;
  640. for (int clockOption : clockOptions) {
  641. std::string optionString = getClockOptionString(clockOption);
  642. MainClockScalingChildItem* clockItem = createMenuItem<MainClockScalingChildItem>(optionString,
  643. CHECKMARK(module->mainClockMultDiv.multDiv == clockOption));
  644. clockItem->mainClockMulDiv = clockOption;
  645. clockItem->mainClockMulDivIndex = i;
  646. clockItem->module = module;
  647. menu->addChild(clockItem);
  648. ++i;
  649. }
  650. return menu;
  651. }
  652. };
  653. struct QuadraticGatesMenuItem : MenuItem {
  654. Muxlicer* module;
  655. void onAction(const event::Action& e) override {
  656. module->quadraticGatesOnly ^= true;
  657. }
  658. };
  659. struct TapTempoItem : MenuItem {
  660. Muxlicer* module;
  661. void onAction(const event::Action& e) override {
  662. module->tapped = true;
  663. e.consume(NULL);
  664. }
  665. };
  666. void appendContextMenu(Menu* menu) override {
  667. Muxlicer* module = dynamic_cast<Muxlicer*>(this->module);
  668. assert(module);
  669. menu->addChild(new MenuSeparator());
  670. menu->addChild(createMenuLabel<MenuLabel>("Clock Multiplication/Division"));
  671. if (module->usingExternalClock) {
  672. menu->addChild(createMenuLabel<MenuLabel>("Using external clock"));
  673. }
  674. else {
  675. TapTempoItem* tapTempoItem = createMenuItem<TapTempoItem>("Tap to set internal tempo...");
  676. tapTempoItem->module = module;
  677. menu->addChild(tapTempoItem);
  678. }
  679. MainClockScalingItem* mainClockScaleItem = createMenuItem<MainClockScalingItem>("Input clock", "▸");
  680. mainClockScaleItem->module = module;
  681. menu->addChild(mainClockScaleItem);
  682. OutputClockScalingItem* outputClockScaleItem = createMenuItem<OutputClockScalingItem>("Output clock", "▸");
  683. outputClockScaleItem->module = module;
  684. menu->addChild(outputClockScaleItem);
  685. menu->addChild(new MenuSeparator());
  686. OutputRangeItem* outputRangeItem = createMenuItem<OutputRangeItem>("All In Normalled Value", "▸");
  687. outputRangeItem->module = module;
  688. menu->addChild(outputRangeItem);
  689. QuadraticGatesMenuItem* quadraticGatesItem = createMenuItem<QuadraticGatesMenuItem>("Gate Mode: quadratic only", CHECKMARK(module->quadraticGatesOnly));
  690. quadraticGatesItem->module = module;
  691. menu->addChild(quadraticGatesItem);
  692. menu->addChild(new MenuSeparator());
  693. menu->addChild(createMenuLabel<MenuLabel>("Input/Output mode"));
  694. IOMenuItem* ioItem = createMenuItem<IOMenuItem>("1 input ▸ 8 outputs",
  695. CHECKMARK(module->modeCOMIO == Muxlicer::COM_1_IN_8_OUT));
  696. ioItem->module = module;
  697. ioItem->widget = this;
  698. menu->addChild(ioItem);
  699. OIMenuItem* oiItem = createMenuItem<OIMenuItem>("8 inputs ▸ 1 output",
  700. CHECKMARK(module->modeCOMIO == Muxlicer::COM_8_IN_1_OUT));
  701. oiItem->module = module;
  702. oiItem->widget = this;
  703. menu->addChild(oiItem);
  704. }
  705. void clearCables() {
  706. for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
  707. APP->scene->rack->clearCablesOnPort(outputs[i]);
  708. }
  709. APP->scene->rack->clearCablesOnPort(inputs[Muxlicer::COM_INPUT]);
  710. for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
  711. APP->scene->rack->clearCablesOnPort(inputs[i]);
  712. }
  713. APP->scene->rack->clearCablesOnPort(outputs[Muxlicer::COM_OUTPUT]);
  714. }
  715. // set ports visibility, either for 1 input -> 8 outputs or 8 inputs -> 1 output
  716. void updatePortVisibilityForIOMode(Muxlicer::ModeCOMIO mode) {
  717. bool visibleToggle = (mode == Muxlicer::COM_1_IN_8_OUT);
  718. for (int i = Muxlicer::MUX_OUTPUTS; i <= Muxlicer::MUX_OUTPUTS_LAST; ++i) {
  719. outputs[i]->visible = visibleToggle;
  720. }
  721. inputs[Muxlicer::COM_INPUT]->visible = visibleToggle;
  722. for (int i = Muxlicer::MUX_INPUTS; i <= Muxlicer::MUX_INPUTS_LAST; ++i) {
  723. inputs[i]->visible = !visibleToggle;
  724. }
  725. outputs[Muxlicer::COM_OUTPUT]->visible = !visibleToggle;
  726. }
  727. };
  728. Model* modelMuxlicer = createModel<Muxlicer, MuxlicerWidget>("Muxlicer");