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.

222 lines
9.2KB

  1. #include "plugin.hpp"
  2. #include "Common.hpp"
  3. static float expDelta(float delta, float tau) {
  4. float lin = sgn(delta) * 10.f / tau;
  5. float exp = M_E * delta / tau;
  6. return crossfade(lin, exp, 0.90f);
  7. }
  8. struct Percall : Module {
  9. enum ParamIds {
  10. ENUMS(VOL_PARAMS, 4),
  11. ENUMS(DECAY_PARAMS, 4),
  12. ENUMS(CHOKE_PARAMS, 2),
  13. NUM_PARAMS
  14. };
  15. enum InputIds {
  16. ENUMS(CH_INPUTS, 4),
  17. STRENGTH_INPUT,
  18. ENUMS(TRIG_INPUTS, 4),
  19. ENUMS(CV_INPUTS, 4),
  20. NUM_INPUTS
  21. };
  22. enum OutputIds {
  23. ENUMS(CH_OUTPUTS, 4),
  24. ENUMS(ENV_OUTPUTS, 4),
  25. NUM_OUTPUTS
  26. };
  27. enum LightIds {
  28. ENUMS(LEDS, 4),
  29. NUM_LIGHTS
  30. };
  31. ADEnvelope envs[4];
  32. float gains[4] = {};
  33. float strength = 1.0f;
  34. dsp::SchmittTrigger trigger[4];
  35. dsp::ClockDivider cvDivider;
  36. dsp::ClockDivider lightDivider;
  37. const int LAST_CHANNEL_ID = 3;
  38. const float attackTime = 1.5e-3;
  39. const float minDecayTime = 4.5e-3;
  40. const float maxDecayTime = 4.f;
  41. Percall() {
  42. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  43. for (int i = 0; i < 4; i++) {
  44. configParam(VOL_PARAMS + i, 0.f, 1.f, 1.f, "Ch " + std::to_string(i + 1) + " level", "%", 0, 100);
  45. configParam(DECAY_PARAMS + i, 0.f, 1.f, 0.f, "Ch " + std::to_string(i + 1) + " decay time");
  46. envs[i].attackTime = attackTime;
  47. envs[i].attackShape = 0.5f;
  48. envs[i].decayShape = 3.0f;
  49. }
  50. for (int i = 0; i < 2; i++) {
  51. std::string description = "Choke " + std::to_string(2 * i + 1) + " to " + std::to_string(2 * i + 2);
  52. configParam(CHOKE_PARAMS + i, 0.f, 1.f, 0.f, description);
  53. }
  54. cvDivider.setDivision(16);
  55. lightDivider.setDivision(128);
  56. }
  57. void process(const ProcessArgs& args) override {
  58. strength = 1.0f;
  59. if (inputs[STRENGTH_INPUT].isConnected()) {
  60. strength = clamp(inputs[STRENGTH_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f);
  61. }
  62. // only calculate gains/decays every 16 samples
  63. if (cvDivider.process()) {
  64. for (int i = 0; i < 4; i++) {
  65. gains[i] = std::pow(params[VOL_PARAMS + i].getValue(), 2.f) * strength;
  66. float fallCv = inputs[CV_INPUTS + i].getVoltage() + params[DECAY_PARAMS + i].getValue() * 10.f;
  67. envs[i].decayTime = minDecayTime * std::pow(2.0, clamp(fallCv, 0.0f, 10.0f));
  68. }
  69. }
  70. simd::float_4 mix[4] = {};
  71. int maxChannels = 1;
  72. // Mixer channels
  73. for (int i = 0; i < 4; i++) {
  74. if (trigger[i].process(rescale(inputs[TRIG_INPUTS + i].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) {
  75. envs[i].stage = ADEnvelope::STAGE_ATTACK;
  76. }
  77. // if choke is enabled, and current channel is odd and left channel is in attack
  78. if ((i % 2) && params[CHOKE_PARAMS + i / 2].getValue() && envs[i - 1].stage == ADEnvelope::STAGE_ATTACK) {
  79. // TODO: is there a more graceful way to choke, e.g. rapid envelope?
  80. // TODO: this will just silence it instantly, maybe switch to STAGE_DECAY and modify fall time
  81. envs[i].stage = ADEnvelope::STAGE_OFF;
  82. }
  83. envs[i].process(args.sampleTime);
  84. int channels = 1;
  85. simd::float_4 in[4] = {};
  86. bool inputIsConnected = inputs[CH_INPUTS + i].isConnected();
  87. bool inputIsNormed = !inputIsConnected && (i % 2) && inputs[CH_INPUTS + i - 1].isConnected();
  88. if ((inputIsConnected || inputIsNormed)) {
  89. int channel_to_read_from = inputIsNormed ? CH_INPUTS + i - 1 : CH_INPUTS + i;
  90. channels = inputs[channel_to_read_from].getChannels();
  91. maxChannels = std::max(maxChannels, channels);
  92. // only process input audio if envelope is active
  93. if (envs[i].stage != ADEnvelope::STAGE_OFF) {
  94. float gain = gains[i] * envs[i].env;
  95. for (int c = 0; c < channels; c += 4) {
  96. in[c / 4] = simd::float_4::load(inputs[channel_to_read_from].getVoltages(c)) * gain;
  97. }
  98. }
  99. }
  100. if (i != LAST_CHANNEL_ID) {
  101. // if connected, output via the jack (and don't add to mix)
  102. if (outputs[CH_OUTPUTS + i].isConnected()) {
  103. outputs[CH_OUTPUTS + i].setChannels(channels);
  104. for (int c = 0; c < channels; c += 4) {
  105. in[c / 4].store(outputs[CH_OUTPUTS + i].getVoltages(c));
  106. }
  107. }
  108. else {
  109. // else add to mix
  110. for (int c = 0; c < channels; c += 4) {
  111. mix[c / 4] += in[c / 4];
  112. }
  113. }
  114. }
  115. // otherwise if it is the final channel and it's wired in
  116. else if (outputs[CH_OUTPUTS + i].isConnected()) {
  117. outputs[CH_OUTPUTS + i].setChannels(maxChannels);
  118. // last channel must always go into mix
  119. for (int c = 0; c < channels; c += 4) {
  120. mix[c / 4] += in[c / 4];
  121. }
  122. for (int c = 0; c < maxChannels; c += 4) {
  123. mix[c / 4].store(outputs[CH_OUTPUTS + i].getVoltages(c));
  124. }
  125. }
  126. // set env output
  127. if (outputs[ENV_OUTPUTS + i].isConnected()) {
  128. outputs[ENV_OUTPUTS + i].setVoltage(10.f * strength * envs[i].env);
  129. }
  130. }
  131. if (lightDivider.process()) {
  132. for (int i = 0; i < 4; i++) {
  133. lights[LEDS + i].setBrightness(envs[i].env);
  134. }
  135. }
  136. }
  137. };
  138. struct PercallWidget : ModuleWidget {
  139. PercallWidget(Percall* module) {
  140. setModule(module);
  141. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Percall.svg")));
  142. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  143. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  144. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  145. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  146. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.003, 41.196)), module, Percall::VOL_PARAMS + 0));
  147. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(22.829, 41.196)), module, Percall::VOL_PARAMS + 1));
  148. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(37.655, 41.196)), module, Percall::VOL_PARAMS + 2));
  149. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(52.481, 41.196)), module, Percall::VOL_PARAMS + 3));
  150. addParam(createParam<BefacoSlidePot>(mm2px(Vec(5.385, 52.476)), module, Percall::DECAY_PARAMS + 0));
  151. addParam(createParam<BefacoSlidePot>(mm2px(Vec(20.728, 52.476)), module, Percall::DECAY_PARAMS + 1));
  152. addParam(createParam<BefacoSlidePot>(mm2px(Vec(35.543, 52.476)), module, Percall::DECAY_PARAMS + 2));
  153. addParam(createParam<BefacoSlidePot>(mm2px(Vec(50.357, 52.476)), module, Percall::DECAY_PARAMS + 3));
  154. addParam(createParam<CKSS>(mm2px(Vec(13.365, 58.672)), module, Percall::CHOKE_PARAMS + 0));
  155. addParam(createParam<CKSS>(mm2px(Vec(42.993, 58.672)), module, Percall::CHOKE_PARAMS + 1));
  156. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.173, 12.894)), module, Percall::CH_INPUTS + 0));
  157. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.335, 12.894)), module, Percall::CH_INPUTS + 1));
  158. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(40.347, 12.894)), module, Percall::CH_INPUTS + 2));
  159. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.492, 12.894)), module, Percall::CH_INPUTS + 3));
  160. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(30.341, 18.236)), module, Percall::STRENGTH_INPUT));
  161. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.173, 24.834)), module, Percall::TRIG_INPUTS + 0));
  162. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(18.547, 23.904)), module, Percall::TRIG_INPUTS + 1));
  163. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(42.218, 23.904)), module, Percall::TRIG_INPUTS + 2));
  164. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.453, 24.834)), module, Percall::TRIG_INPUTS + 3));
  165. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.093, 101.799)), module, Percall::CV_INPUTS + 0));
  166. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.22, 101.799)), module, Percall::CV_INPUTS + 1));
  167. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.347, 101.799)), module, Percall::CV_INPUTS + 2));
  168. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.474, 101.799)), module, Percall::CV_INPUTS + 3));
  169. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.541, 101.699)), module, Percall::CH_OUTPUTS + 0));
  170. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.624, 101.699)), module, Percall::CH_OUTPUTS + 1));
  171. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.541, 113.696)), module, Percall::CH_OUTPUTS + 2));
  172. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.624, 113.696)), module, Percall::CH_OUTPUTS + 3));
  173. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.093, 113.74)), module, Percall::ENV_OUTPUTS + 0));
  174. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.22, 113.74)), module, Percall::ENV_OUTPUTS + 1));
  175. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.347, 113.74)), module, Percall::ENV_OUTPUTS + 2));
  176. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.474, 113.74)), module, Percall::ENV_OUTPUTS + 3));
  177. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(8.107, 49.221)), module, Percall::LEDS + 0));
  178. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(22.934, 49.221)), module, Percall::LEDS + 1));
  179. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.762, 49.221)), module, Percall::LEDS + 2));
  180. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(52.589, 49.221)), module, Percall::LEDS + 3));
  181. }
  182. };
  183. Model* modelPercall = createModel<Percall, PercallWidget>("Percall");