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.

202 lines
8.1KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. static float gainFunction(float x, float shape) {
  4. float lin = x;
  5. if (shape > 0.f) {
  6. float log = 11.f * x / (10.f * x + 1.f);
  7. return crossfade(lin, log, shape);
  8. }
  9. else {
  10. float exp = std::pow(x, 4);
  11. return crossfade(lin, exp, -shape);
  12. }
  13. }
  14. struct HexmixVCA : Module {
  15. enum ParamIds {
  16. ENUMS(SHAPE_PARAM, 6),
  17. ENUMS(VOL_PARAM, 6),
  18. NUM_PARAMS
  19. };
  20. enum InputIds {
  21. ENUMS(IN_INPUT, 6),
  22. ENUMS(CV_INPUT, 6),
  23. NUM_INPUTS
  24. };
  25. enum OutputIds {
  26. ENUMS(OUT_OUTPUT, 6),
  27. NUM_OUTPUTS
  28. };
  29. enum LightIds {
  30. NUM_LIGHTS
  31. };
  32. const static int numRows = 6;
  33. dsp::ClockDivider cvDivider;
  34. float outputLevels[numRows] = {};
  35. float shapes[numRows] = {};
  36. bool finalRowIsMix = true;
  37. HexmixVCA() {
  38. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  39. for (int i = 0; i < numRows; ++i) {
  40. configParam(SHAPE_PARAM + i, -1.f, 1.f, 0.f, string::f("Channel %d VCA response", i));
  41. configParam(VOL_PARAM + i, 0.f, 1.f, 1.f, string::f("Channel %d output level", i));
  42. }
  43. cvDivider.setDivision(16);
  44. for (int row = 0; row < numRows; ++row) {
  45. outputLevels[row] = 1.f;
  46. }
  47. }
  48. void process(const ProcessArgs& args) override {
  49. float_4 mix[4] = {};
  50. int maxChannels = 1;
  51. // only calculate gains/shapes every 16 samples
  52. if (cvDivider.process()) {
  53. for (int row = 0; row < numRows; ++row) {
  54. shapes[row] = params[SHAPE_PARAM + row].getValue();
  55. outputLevels[row] = params[VOL_PARAM + row].getValue();
  56. }
  57. }
  58. for (int row = 0; row < numRows; ++row) {
  59. bool finalRow = (row == numRows - 1);
  60. int channels = 1;
  61. float_4 in[4] = {};
  62. bool inputIsConnected = inputs[IN_INPUT + row].isConnected();
  63. if (inputIsConnected) {
  64. channels = inputs[row].getChannels();
  65. // if we're in "mixer" mode, an input only counts towards the main output polyphony count if it's
  66. // not taken out of the mix (i.e. patched in). the final row should count towards polyphony calc.
  67. if (finalRowIsMix && (finalRow || !outputs[OUT_OUTPUT + row].isConnected())) {
  68. maxChannels = std::max(maxChannels, channels);
  69. }
  70. float cvGain = clamp(inputs[CV_INPUT + row].getNormalVoltage(10.f) / 10.f, 0.f, 1.f);
  71. float gain = gainFunction(cvGain, shapes[row]) * outputLevels[row];
  72. for (int c = 0; c < channels; c += 4) {
  73. in[c / 4] = inputs[row].getVoltageSimd<float_4>(c) * gain;
  74. }
  75. }
  76. if (!finalRow) {
  77. if (outputs[OUT_OUTPUT + row].isConnected()) {
  78. // if output is connected, we don't add to mix
  79. outputs[OUT_OUTPUT + row].setChannels(channels);
  80. for (int c = 0; c < channels; c += 4) {
  81. outputs[OUT_OUTPUT + row].setVoltageSimd(in[c / 4], c);
  82. }
  83. }
  84. else if (finalRowIsMix) {
  85. // else add to mix (if setting enabled)
  86. for (int c = 0; c < channels; c += 4) {
  87. mix[c / 4] += in[c / 4];
  88. }
  89. }
  90. }
  91. // final row
  92. else {
  93. if (outputs[OUT_OUTPUT + row].isConnected()) {
  94. if (finalRowIsMix) {
  95. outputs[OUT_OUTPUT + row].setChannels(maxChannels);
  96. // last channel must always go into mix
  97. for (int c = 0; c < channels; c += 4) {
  98. mix[c / 4] += in[c / 4];
  99. }
  100. for (int c = 0; c < maxChannels; c += 4) {
  101. outputs[OUT_OUTPUT + row].setVoltageSimd(mix[c / 4], c);
  102. }
  103. }
  104. else {
  105. // same as other rows
  106. outputs[OUT_OUTPUT + row].setChannels(channels);
  107. for (int c = 0; c < channels; c += 4) {
  108. outputs[OUT_OUTPUT + row].setVoltageSimd(in[c / 4], c);
  109. }
  110. }
  111. }
  112. }
  113. }
  114. }
  115. void dataFromJson(json_t* rootJ) override {
  116. json_t* modeJ = json_object_get(rootJ, "finalRowIsMix");
  117. if (modeJ) {
  118. finalRowIsMix = json_boolean_value(modeJ);
  119. }
  120. }
  121. json_t* dataToJson() override {
  122. json_t* rootJ = json_object();
  123. json_object_set_new(rootJ, "finalRowIsMix", json_boolean(finalRowIsMix));
  124. return rootJ;
  125. }
  126. };
  127. struct HexmixVCAWidget : ModuleWidget {
  128. HexmixVCAWidget(HexmixVCA* module) {
  129. setModule(module);
  130. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/HexmixVCA.svg")));
  131. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  132. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  133. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  134. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  135. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 15.51)), module, HexmixVCA::SHAPE_PARAM + 0));
  136. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 34.115)), module, HexmixVCA::SHAPE_PARAM + 1));
  137. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 52.72)), module, HexmixVCA::SHAPE_PARAM + 2));
  138. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 71.325)), module, HexmixVCA::SHAPE_PARAM + 3));
  139. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 89.93)), module, HexmixVCA::SHAPE_PARAM + 4));
  140. addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 108.536)), module, HexmixVCA::SHAPE_PARAM + 5));
  141. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 15.51)), module, HexmixVCA::VOL_PARAM + 0));
  142. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 34.115)), module, HexmixVCA::VOL_PARAM + 1));
  143. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 52.72)), module, HexmixVCA::VOL_PARAM + 2));
  144. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 71.325)), module, HexmixVCA::VOL_PARAM + 3));
  145. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 89.93)), module, HexmixVCA::VOL_PARAM + 4));
  146. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 108.536)), module, HexmixVCA::VOL_PARAM + 5));
  147. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 15.51)), module, HexmixVCA::IN_INPUT + 0));
  148. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 34.115)), module, HexmixVCA::IN_INPUT + 1));
  149. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 52.72)), module, HexmixVCA::IN_INPUT + 2));
  150. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 71.325)), module, HexmixVCA::IN_INPUT + 3));
  151. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 89.93)), module, HexmixVCA::IN_INPUT + 4));
  152. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 108.536)), module, HexmixVCA::IN_INPUT + 5));
  153. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 15.51)), module, HexmixVCA::CV_INPUT + 0));
  154. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 34.115)), module, HexmixVCA::CV_INPUT + 1));
  155. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 52.72)), module, HexmixVCA::CV_INPUT + 2));
  156. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 71.325)), module, HexmixVCA::CV_INPUT + 3));
  157. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 89.93)), module, HexmixVCA::CV_INPUT + 4));
  158. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 108.536)), module, HexmixVCA::CV_INPUT + 5));
  159. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 15.51)), module, HexmixVCA::OUT_OUTPUT + 0));
  160. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 34.115)), module, HexmixVCA::OUT_OUTPUT + 1));
  161. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 52.72)), module, HexmixVCA::OUT_OUTPUT + 2));
  162. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 71.325)), module, HexmixVCA::OUT_OUTPUT + 3));
  163. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 89.93)), module, HexmixVCA::OUT_OUTPUT + 4));
  164. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 108.536)), module, HexmixVCA::OUT_OUTPUT + 5));
  165. }
  166. void appendContextMenu(Menu* menu) override {
  167. HexmixVCA* module = dynamic_cast<HexmixVCA*>(this->module);
  168. assert(module);
  169. menu->addChild(new MenuSeparator());
  170. menu->addChild(createBoolPtrMenuItem("Final row is mix", "", &module->finalRowIsMix));
  171. }
  172. };
  173. Model* modelHexmixVCA = createModel<HexmixVCA, HexmixVCAWidget>("HexmixVCA");