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.

172 lines
4.6KB

  1. #include "plugin.hpp"
  2. struct VCA_1 : Module {
  3. enum ParamIds {
  4. LEVEL_PARAM,
  5. EXP_PARAM, // removed from panel in 2.0, still in context menu
  6. NUM_PARAMS
  7. };
  8. enum InputIds {
  9. CV_INPUT,
  10. IN_INPUT,
  11. NUM_INPUTS
  12. };
  13. enum OutputIds {
  14. OUT_OUTPUT,
  15. NUM_OUTPUTS
  16. };
  17. enum LightIds {
  18. NUM_LIGHTS
  19. };
  20. int lastChannels = 1;
  21. float lastGains[16] = {};
  22. VCA_1() {
  23. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  24. configParam(LEVEL_PARAM, 0.0, 1.0, 1.0, "Level", "%", 0, 100);
  25. configSwitch(EXP_PARAM, 0.0, 1.0, 1.0, "Response mode", {"Exponential", "Linear"});
  26. configInput(CV_INPUT, "CV");
  27. configInput(IN_INPUT, "Channel");
  28. configOutput(OUT_OUTPUT, "Channel");
  29. configBypass(IN_INPUT, OUT_OUTPUT);
  30. }
  31. void process(const ProcessArgs& args) override {
  32. int channels = std::max({1, inputs[IN_INPUT].getChannels(), inputs[CV_INPUT].getChannels()});
  33. float level = params[LEVEL_PARAM].getValue();
  34. for (int c = 0; c < channels; c++) {
  35. // Get input
  36. float in = inputs[IN_INPUT].getPolyVoltage(c);
  37. // Get gain
  38. float gain = level;
  39. if (inputs[CV_INPUT].isConnected()) {
  40. float cv = clamp(inputs[CV_INPUT].getPolyVoltage(c) / 10.f, 0.f, 1.f);
  41. if (int(params[EXP_PARAM].getValue()) == 0)
  42. cv = std::pow(cv, 4.f);
  43. gain *= cv;
  44. }
  45. // Apply gain
  46. in *= gain;
  47. lastGains[c] = gain;
  48. // Set output
  49. outputs[OUT_OUTPUT].setVoltage(in, c);
  50. }
  51. outputs[OUT_OUTPUT].setChannels(channels);
  52. lastChannels = channels;
  53. }
  54. };
  55. struct VCA_1VUKnob : SliderKnob {
  56. void drawLayer(const DrawArgs& args, int layer) override {
  57. if (layer != 1)
  58. return;
  59. VCA_1* module = dynamic_cast<VCA_1*>(this->module);
  60. Rect r = box.zeroPos();
  61. NVGcolor bgColor = nvgRGB(0x12, 0x12, 0x12);
  62. int channels = module ? module->lastChannels : 1;
  63. engine::ParamQuantity* pq = getParamQuantity();
  64. float value = pq ? pq->getValue() : 1.f;
  65. // Segment value
  66. if (value >= 0.005f) {
  67. nvgBeginPath(args.vg);
  68. nvgRect(args.vg,
  69. r.pos.x,
  70. r.pos.y + r.size.y * (1 - value),
  71. r.size.x,
  72. r.size.y * value);
  73. nvgFillColor(args.vg, color::mult(color::WHITE, 0.25));
  74. nvgFill(args.vg);
  75. }
  76. // Segment gain
  77. nvgBeginPath(args.vg);
  78. bool segmentFill = false;
  79. for (int c = 0; c < channels; c++) {
  80. float gain = module ? module->lastGains[c] : 1.f;
  81. if (gain >= 0.005f) {
  82. segmentFill = true;
  83. nvgRect(args.vg,
  84. r.pos.x + r.size.x * c / channels,
  85. r.pos.y + r.size.y * (1 - gain),
  86. r.size.x / channels,
  87. r.size.y * gain);
  88. }
  89. }
  90. nvgFillColor(args.vg, SCHEME_YELLOW);
  91. // If nvgFill() is called with 0 path elements, it can fill other undefined paths.
  92. if (segmentFill) {
  93. nvgFill(args.vg);
  94. }
  95. // Invisible separators
  96. const int segs = 25;
  97. nvgBeginPath(args.vg);
  98. for (int i = 1; i < segs; i++) {
  99. nvgRect(args.vg,
  100. r.pos.x - 1.0,
  101. r.pos.y + r.size.y * i / segs,
  102. r.size.x + 2.0,
  103. 1.0);
  104. }
  105. nvgFillColor(args.vg, bgColor);
  106. nvgFill(args.vg);
  107. }
  108. };
  109. struct VCA_1Display : LedDisplay {
  110. };
  111. struct VCA_1Widget : ModuleWidget {
  112. VCA_1Widget(VCA_1* module) {
  113. setModule(module);
  114. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCA-1.svg")));
  115. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  116. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  117. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  118. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  119. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 80.603)), module, VCA_1::CV_INPUT));
  120. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 96.859)), module, VCA_1::IN_INPUT));
  121. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.62, 113.115)), module, VCA_1::OUT_OUTPUT));
  122. VCA_1Display* display = createWidget<VCA_1Display>(mm2px(Vec(0.0, 13.039)));
  123. display->box.size = mm2px(Vec(15.263, 55.88));
  124. addChild(display);
  125. VCA_1VUKnob* knob = createParam<VCA_1VUKnob>(mm2px(Vec(2.253, 15.931)), module, VCA_1::LEVEL_PARAM);
  126. knob->box.size = mm2px(Vec(10.734, 50.253));
  127. addChild(knob);
  128. }
  129. void appendContextMenu(Menu* menu) override {
  130. VCA_1* module = dynamic_cast<VCA_1*>(this->module);
  131. assert(module);
  132. menu->addChild(new MenuSeparator);
  133. menu->addChild(createBoolMenuItem("Exponential response", "",
  134. [=]() {return module->params[VCA_1::EXP_PARAM].getValue() == 0.f;},
  135. [=](bool value) {module->params[VCA_1::EXP_PARAM].setValue(!value);}
  136. ));
  137. }
  138. };
  139. Model* modelVCA_1 = createModel<VCA_1, VCA_1Widget>("VCA-1");