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.

176 lines
6.9KB

  1. #include "plugin.hpp"
  2. using namespace simd;
  3. struct MuDi : Module {
  4. enum ParamId {
  5. PARAMS_LEN
  6. };
  7. enum InputId {
  8. CLOCK_INPUT,
  9. RESET_INPUT,
  10. INPUTS_LEN
  11. };
  12. enum OutputId {
  13. F_1_OUTPUT,
  14. F_2_OUTPUT,
  15. F_4_OUTPUT,
  16. F_8_OUTPUT,
  17. F_16_OUTPUT,
  18. OUTPUTS_LEN
  19. };
  20. enum LightId {
  21. ENUMS(F_1_LIGHT, 3),
  22. ENUMS(F_2_LIGHT, 3),
  23. ENUMS(F_4_LIGHT, 3),
  24. ENUMS(F_8_LIGHT, 3),
  25. ENUMS(F_16_LIGHT, 3),
  26. LIGHTS_LEN
  27. };
  28. dsp::TSchmittTrigger<float_4> clockTrigger_1[4];
  29. dsp::TSchmittTrigger<float_4> clockTrigger_2[4];
  30. dsp::TSchmittTrigger<float_4> clockTrigger_4[4];
  31. dsp::TSchmittTrigger<float_4> clockTrigger_8[4];
  32. float_4 clockState_1[4] = {};
  33. float_4 clockState_2[4] = {};
  34. float_4 clockState_4[4] = {};
  35. float_4 clockState_8[4] = {};
  36. float_4 clockState_16[4] = {};
  37. dsp::TSchmittTrigger<float_4> resetTrigger[4];
  38. dsp::ClockDivider lightDivider;
  39. bool removeClockDC = false;
  40. MuDi() {
  41. config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
  42. configInput(CLOCK_INPUT, "Clock");
  43. configInput(RESET_INPUT, "Reset");
  44. configOutput(F_1_OUTPUT, "F");
  45. configOutput(F_2_OUTPUT, "1/2 F");
  46. configOutput(F_4_OUTPUT, "1/4 F");
  47. configOutput(F_8_OUTPUT, "1/8 F");
  48. configOutput(F_16_OUTPUT, "1/16 F");
  49. lightDivider.setDivision(32);
  50. }
  51. void process(const ProcessArgs& args) override {
  52. const int numPolyphonyEngines = inputs[CLOCK_INPUT].getChannels();
  53. for (int c = 0; c < numPolyphonyEngines; c += 4) {
  54. // reset
  55. float_4 reset = resetTrigger[c / 4].process(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c));
  56. clockState_2[c / 4] = ifelse(reset, 0.f, clockState_2[c / 4]);
  57. clockState_4[c / 4] = ifelse(reset, 0.f, clockState_4[c / 4]);
  58. clockState_8[c / 4] = ifelse(reset, 0.f, clockState_8[c / 4]);
  59. clockState_16[c / 4] = ifelse(reset, 0.f, clockState_16[c / 4]);
  60. // base derived clock
  61. float_4 triggered = clockTrigger_1[c / 4].process(inputs[CLOCK_INPUT].getVoltageSimd<float_4>(c));
  62. clockState_1[c / 4] = clockTrigger_1[c / 4].isHigh();
  63. // 1/2 derived clock changes state on every rising edge of the base clock
  64. clockState_2[c / 4] = ifelse(triggered, ~clockState_2[c / 4], clockState_2[c / 4]);
  65. float_4 clockTriggered_2 = clockTrigger_2[c / 4].process(ifelse(clockState_2[c / 4], 10.f, 0.f));
  66. // 1/4 derived clock changes state on every rising edge of the 1/2 derived clock
  67. clockState_4[c / 4] = ifelse(clockTriggered_2, ~clockState_4[c / 4], clockState_4[c / 4]);
  68. float_4 clockTriggered_4 = clockTrigger_4[c / 4].process(ifelse(clockState_4[c / 4], 10.f, 0.f));
  69. // 1/8 derived clock changes state on every rising edge of the 1/4 derived clock
  70. clockState_8[c / 4] = ifelse(clockTriggered_4, ~clockState_8[c / 4], clockState_8[c / 4]);
  71. float_4 clockTriggered_8 = clockTrigger_8[c / 4].process(ifelse(clockState_8[c / 4], 10.f, 0.f));
  72. // 1/16 derived clock changes state on every rising edge of the 1/8 derived clock
  73. clockState_16[c / 4] = ifelse(clockTriggered_8, ~clockState_16[c / 4], clockState_16[c / 4]);
  74. // Set outputs
  75. outputs[F_1_OUTPUT].setVoltageSimd(ifelse(clockState_1[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c);
  76. outputs[F_2_OUTPUT].setVoltageSimd(ifelse(clockState_2[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c);
  77. outputs[F_4_OUTPUT].setVoltageSimd(ifelse(clockState_4[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c);
  78. outputs[F_8_OUTPUT].setVoltageSimd(ifelse(clockState_8[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c);
  79. outputs[F_16_OUTPUT].setVoltageSimd(ifelse(clockState_16[c / 4], 10.f, 0.f) - 5.f * removeClockDC, c);
  80. }
  81. outputs[F_1_OUTPUT].setChannels(numPolyphonyEngines);
  82. outputs[F_2_OUTPUT].setChannels(numPolyphonyEngines);
  83. outputs[F_4_OUTPUT].setChannels(numPolyphonyEngines);
  84. outputs[F_8_OUTPUT].setChannels(numPolyphonyEngines);
  85. outputs[F_16_OUTPUT].setChannels(numPolyphonyEngines);
  86. bool anyState[5] = {};
  87. for (int c = 0; c < numPolyphonyEngines; c++) {
  88. anyState[0] |= ifelse(clockState_1[c / 4], 1.f, 0.f)[c % 4] > 0.f;
  89. anyState[1] |= ifelse(clockState_2[c / 4], 1.f, 0.f)[c % 4] > 0.f;
  90. anyState[2] |= ifelse(clockState_4[c / 4], 1.f, 0.f)[c % 4] > 0.f;
  91. anyState[3] |= ifelse(clockState_8[c / 4], 1.f, 0.f)[c % 4] > 0.f;
  92. anyState[4] |= ifelse(clockState_16[c / 4], 1.f, 0.f)[c % 4] > 0.f;
  93. }
  94. // Set lights
  95. if (lightDivider.process()) {
  96. float lightTime = args.sampleTime * lightDivider.getDivision();
  97. for (int i = 0; i < 5; i++) {
  98. lights[F_1_LIGHT + 3 * i + 0].setBrightnessSmooth(anyState[i] && numPolyphonyEngines == 1, lightTime);
  99. lights[F_1_LIGHT + 3 * i + 1].setBrightness(0.f);
  100. lights[F_1_LIGHT + 3 * i + 2].setBrightnessSmooth(anyState[i] && numPolyphonyEngines > 1, lightTime);
  101. }
  102. }
  103. }
  104. void dataFromJson(json_t* rootJ) override {
  105. json_t* removeClockDCJ = json_object_get(rootJ, "removeClockDC");
  106. if (removeClockDCJ)
  107. removeClockDC = json_boolean_value(removeClockDCJ);
  108. }
  109. json_t* dataToJson() override {
  110. json_t* rootJ = json_object();
  111. json_object_set_new(rootJ, "removeClockDC", json_boolean(removeClockDC));
  112. return rootJ;
  113. }
  114. };
  115. struct MuDiWidget : ModuleWidget {
  116. MuDiWidget(MuDi* module) {
  117. setModule(module);
  118. setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MuDi.svg")));
  119. addChild(createWidget<Knurlie>(Vec(box.size.x - RACK_GRID_WIDTH, 0)));
  120. addChild(createWidget<Knurlie>(Vec(box.size.x - RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  121. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.0, 15.138)), module, MuDi::CLOCK_INPUT));
  122. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.0, 30.245)), module, MuDi::RESET_INPUT));
  123. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 56.695)), module, MuDi::F_1_OUTPUT));
  124. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 70.45)), module, MuDi::F_2_OUTPUT));
  125. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 84.204)), module, MuDi::F_4_OUTPUT));
  126. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 97.959)), module, MuDi::F_8_OUTPUT));
  127. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 111.713)), module, MuDi::F_16_OUTPUT));
  128. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(1.95, 62.74)), module, MuDi::F_1_LIGHT));
  129. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(1.95, 76.325)), module, MuDi::F_2_LIGHT));
  130. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(1.95, 90.1)), module, MuDi::F_4_LIGHT));
  131. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(1.95, 103.874)), module, MuDi::F_8_LIGHT));
  132. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(1.95, 117.648)), module, MuDi::F_16_LIGHT));
  133. }
  134. void appendContextMenu(Menu* menu) override {
  135. MuDi* module = dynamic_cast<MuDi*>(this->module);
  136. assert(module);
  137. menu->addChild(new MenuSeparator());
  138. menu->addChild(createSubmenuItem("Hardware compatibility", "",
  139. [ = ](Menu * menu) {
  140. menu->addChild(createBoolPtrMenuItem("Remove DC from clock outs", "", &module->removeClockDC));
  141. }));
  142. }
  143. };
  144. Model* modelMuDi = createModel<MuDi, MuDiWidget>("MuDi");