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.

220 lines
9.3KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. struct Percall : Module {
  4. enum ParamIds {
  5. ENUMS(VOL_PARAMS, 4),
  6. ENUMS(DECAY_PARAMS, 4),
  7. ENUMS(CHOKE_PARAMS, 2),
  8. NUM_PARAMS
  9. };
  10. enum InputIds {
  11. ENUMS(CH_INPUTS, 4),
  12. STRENGTH_INPUT,
  13. ENUMS(TRIG_INPUTS, 4),
  14. ENUMS(CV_INPUTS, 4),
  15. NUM_INPUTS
  16. };
  17. enum OutputIds {
  18. ENUMS(CH_OUTPUTS, 4),
  19. ENUMS(ENV_OUTPUTS, 4),
  20. NUM_OUTPUTS
  21. };
  22. enum LightIds {
  23. ENUMS(LEDS, 4),
  24. NUM_LIGHTS
  25. };
  26. ADEnvelope envs[4];
  27. float gains[4] = {};
  28. dsp::SchmittTrigger trigger[4];
  29. dsp::ClockDivider cvDivider;
  30. dsp::ClockDivider lightDivider;
  31. const int LAST_CHANNEL_ID = 3;
  32. const float attackTime = 1.5e-3;
  33. const float minDecayTime = 4.5e-3;
  34. const float maxDecayTime = 4.f;
  35. Percall() {
  36. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  37. for (int i = 0; i < 4; i++) {
  38. configParam(VOL_PARAMS + i, 0.f, 1.f, 1.f, string::f("Channel %d level", i + 1), "%", 0, 100);
  39. configParam(DECAY_PARAMS + i, 0.f, 1.f, 0.f, string::f("Channel %d decay time", i + 1));
  40. envs[i].attackTime = attackTime;
  41. envs[i].attackShape = 0.5f;
  42. envs[i].decayShape = 2.0f;
  43. }
  44. for (int i = 0; i < 2; i++) {
  45. configParam(CHOKE_PARAMS + i, 0.f, 1.f, 0.f, string::f("Choke %d to %d", 2 * i + 1, 2 * i + 2));
  46. }
  47. cvDivider.setDivision(16);
  48. lightDivider.setDivision(128);
  49. }
  50. void process(const ProcessArgs& args) override {
  51. float strength = 1.0f;
  52. if (inputs[STRENGTH_INPUT].isConnected()) {
  53. strength = std::sqrt(clamp(inputs[STRENGTH_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f));
  54. }
  55. // only calculate gains/decays every 16 samples
  56. if (cvDivider.process()) {
  57. for (int i = 0; i < 4; i++) {
  58. gains[i] = std::pow(params[VOL_PARAMS + i].getValue(), 2.f) * strength;
  59. float fallCv = inputs[CV_INPUTS + i].getVoltage() * 0.05f + params[DECAY_PARAMS + i].getValue();
  60. envs[i].decayTime = rescale(std::pow(clamp(fallCv, 0.f, 1.0f), 2.f), 0.f, 1.f, minDecayTime, maxDecayTime);
  61. }
  62. }
  63. float_4 mix[4] = {};
  64. int maxPolyphonyChannels = 1;
  65. // Mixer channels
  66. for (int i = 0; i < 4; i++) {
  67. if (trigger[i].process(rescale(inputs[TRIG_INPUTS + i].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
  68. envs[i].trigger();
  69. }
  70. // if choke is enabled, and current channel is odd and left channel is in attack
  71. if ((i % 2) && params[CHOKE_PARAMS + i / 2].getValue() && envs[i - 1].stage == ADEnvelope::STAGE_ATTACK) {
  72. // TODO: is there a more graceful way to choke, e.g. rapid envelope?
  73. // TODO: this will just silence it instantly, maybe switch to STAGE_DECAY and modify fall time
  74. envs[i].stage = ADEnvelope::STAGE_OFF;
  75. }
  76. envs[i].process(args.sampleTime);
  77. int polyphonyChannels = 1;
  78. float_4 in[4] = {};
  79. bool inputIsConnected = inputs[CH_INPUTS + i].isConnected();
  80. bool inputIsNormed = !inputIsConnected && (i % 2) && inputs[CH_INPUTS + i - 1].isConnected();
  81. if ((inputIsConnected || inputIsNormed)) {
  82. int channelToReadFrom = inputIsNormed ? CH_INPUTS + i - 1 : CH_INPUTS + i;
  83. polyphonyChannels = inputs[channelToReadFrom].getChannels();
  84. // an input only counts towards the main output polyphony count if it's not taken out of the mix
  85. // (i.e. an output is patched in). the final input should always count towards polyphony count.
  86. if (i == CH_INPUTS_LAST || !outputs[CH_OUTPUTS + i].isConnected()) {
  87. maxPolyphonyChannels = std::max(maxPolyphonyChannels, polyphonyChannels);
  88. }
  89. // only process input audio if envelope is active
  90. if (envs[i].stage != ADEnvelope::STAGE_OFF) {
  91. float gain = gains[i] * envs[i].env;
  92. for (int c = 0; c < polyphonyChannels; c += 4) {
  93. in[c / 4] = inputs[channelToReadFrom].getVoltageSimd<float_4>(c) * gain;
  94. }
  95. }
  96. }
  97. if (i != LAST_CHANNEL_ID) {
  98. // if connected, output via the jack (and don't add to mix)
  99. if (outputs[CH_OUTPUTS + i].isConnected()) {
  100. outputs[CH_OUTPUTS + i].setChannels(polyphonyChannels);
  101. for (int c = 0; c < polyphonyChannels; c += 4) {
  102. outputs[CH_OUTPUTS + i].setVoltageSimd(in[c / 4], c);
  103. }
  104. }
  105. else {
  106. // else add to mix
  107. for (int c = 0; c < polyphonyChannels; c += 4) {
  108. mix[c / 4] += in[c / 4];
  109. }
  110. }
  111. }
  112. // otherwise if it is the final channel and it's wired in
  113. else if (outputs[CH_OUTPUTS + i].isConnected()) {
  114. outputs[CH_OUTPUTS + i].setChannels(maxPolyphonyChannels);
  115. // last channel must always go into mix
  116. for (int c = 0; c < polyphonyChannels; c += 4) {
  117. mix[c / 4] += in[c / 4];
  118. }
  119. for (int c = 0; c < maxPolyphonyChannels; c += 4) {
  120. outputs[CH_OUTPUTS + i].setVoltageSimd(mix[c / 4], c);
  121. }
  122. }
  123. // set env output
  124. if (outputs[ENV_OUTPUTS + i].isConnected()) {
  125. outputs[ENV_OUTPUTS + i].setVoltage(10.f * strength * envs[i].env);
  126. }
  127. }
  128. if (lightDivider.process()) {
  129. for (int i = 0; i < 4; i++) {
  130. lights[LEDS + i].setBrightness(envs[i].env);
  131. }
  132. }
  133. }
  134. };
  135. struct PercallWidget : ModuleWidget {
  136. PercallWidget(Percall* module) {
  137. setModule(module);
  138. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Percall.svg")));
  139. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  140. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  141. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  142. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  143. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.048, 41.265)), module, Percall::VOL_PARAMS + 0));
  144. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(22.879, 41.265)), module, Percall::VOL_PARAMS + 1));
  145. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(37.709, 41.265)), module, Percall::VOL_PARAMS + 2));
  146. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(52.54, 41.265)), module, Percall::VOL_PARAMS + 3));
  147. addParam(createParam<BefacoSlidePot>(mm2px(Vec(5.385, 53.912)), module, Percall::DECAY_PARAMS + 0));
  148. addParam(createParam<BefacoSlidePot>(mm2px(Vec(20.292, 53.912)), module, Percall::DECAY_PARAMS + 1));
  149. addParam(createParam<BefacoSlidePot>(mm2px(Vec(35.173, 53.912)), module, Percall::DECAY_PARAMS + 2));
  150. addParam(createParam<BefacoSlidePot>(mm2px(Vec(49.987, 53.912)), module, Percall::DECAY_PARAMS + 3));
  151. addParam(createParam<CKSS>(mm2px(Vec(13.365, 58.672)), module, Percall::CHOKE_PARAMS + 0));
  152. addParam(createParam<CKSS>(mm2px(Vec(42.993, 58.672)), module, Percall::CHOKE_PARAMS + 1));
  153. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.15, 12.905)), module, Percall::CH_INPUTS + 0));
  154. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.298, 12.905)), module, Percall::CH_INPUTS + 1));
  155. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(40.266, 12.905)), module, Percall::CH_INPUTS + 2));
  156. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.437, 12.905)), module, Percall::CH_INPUTS + 3));
  157. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(30.282, 18.221)), module, Percall::STRENGTH_INPUT));
  158. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.15, 24.827)), module, Percall::TRIG_INPUTS + 0));
  159. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(18.488, 23.941)), module, Percall::TRIG_INPUTS + 1));
  160. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(42.171, 23.95)), module, Percall::TRIG_INPUTS + 2));
  161. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.437, 24.827)), module, Percall::TRIG_INPUTS + 3));
  162. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.037, 101.844)), module, Percall::CV_INPUTS + 0));
  163. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.159, 101.844)), module, Percall::CV_INPUTS + 1));
  164. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.28, 101.844)), module, Percall::CV_INPUTS + 2));
  165. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.402, 101.844)), module, Percall::CV_INPUTS + 3));
  166. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.524, 101.844)), module, Percall::CH_OUTPUTS + 0));
  167. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.645, 101.844)), module, Percall::CH_OUTPUTS + 1));
  168. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.524, 113.766)), module, Percall::CH_OUTPUTS + 2));
  169. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.645, 113.766)), module, Percall::CH_OUTPUTS + 3));
  170. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.037, 113.766)), module, Percall::ENV_OUTPUTS + 0));
  171. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.159, 113.766)), module, Percall::ENV_OUTPUTS + 1));
  172. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.28, 113.766)), module, Percall::ENV_OUTPUTS + 2));
  173. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.402, 113.766)), module, Percall::ENV_OUTPUTS + 3));
  174. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(8.107, 49.221)), module, Percall::LEDS + 0));
  175. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(22.934, 49.221)), module, Percall::LEDS + 1));
  176. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.762, 49.221)), module, Percall::LEDS + 2));
  177. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(52.589, 49.221)), module, Percall::LEDS + 3));
  178. }
  179. };
  180. Model* modelPercall = createModel<Percall, PercallWidget>("Percall");