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.

159 lines
5.9KB

  1. #include "plugin.hpp"
  2. #include "ChowDSP.hpp"
  3. struct Kickall : Module {
  4. enum ParamIds {
  5. TUNE_PARAM,
  6. TRIGG_BUTTON_PARAM,
  7. SHAPE_PARAM,
  8. DECAY_PARAM,
  9. TIME_PARAM,
  10. BEND_PARAM,
  11. NUM_PARAMS
  12. };
  13. enum InputIds {
  14. TRIGG_INPUT,
  15. VOLUME_INPUT,
  16. TUNE_INPUT,
  17. SHAPE_INPUT,
  18. DECAY_INPUT,
  19. NUM_INPUTS
  20. };
  21. enum OutputIds {
  22. OUT_OUTPUT,
  23. NUM_OUTPUTS
  24. };
  25. enum LightIds {
  26. ENV_LIGHT,
  27. NUM_LIGHTS
  28. };
  29. static constexpr float FREQ_A0 = 27.5f;
  30. static constexpr float FREQ_B2 = 123.471f;
  31. static constexpr float minVolumeDecay = 0.075f;
  32. static constexpr float maxVolumeDecay = 4.f;
  33. static constexpr float minPitchDecay = 0.0075f;
  34. static constexpr float maxPitchDecay = 1.f;
  35. static constexpr float bendRange = 10000;
  36. float phase = 0.f;
  37. ADEnvelope volume;
  38. ADEnvelope pitch;
  39. dsp::SchmittTrigger gateTrigger;
  40. dsp::BooleanTrigger buttonTrigger;
  41. static const int UPSAMPLE = 8;
  42. chowdsp::Oversampling<UPSAMPLE> oversampler;
  43. Kickall() {
  44. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  45. // TODO: review this mapping, using displayBase multiplier seems more normal
  46. configParam(TUNE_PARAM, FREQ_A0, FREQ_B2, 0.5 * (FREQ_A0 + FREQ_B2), "Tune", "Hz");
  47. configButton(TRIGG_BUTTON_PARAM, "Manual trigger");
  48. configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Wave shape");
  49. configParam(DECAY_PARAM, 0.f, 1.f, 0.01f, "VCA Envelope decay time");
  50. configParam(TIME_PARAM, 0.f, 1.0f, 0.f, "Pitch envelope decay time");
  51. configParam(BEND_PARAM, 0.f, 1.f, 0.f, "Pitch envelope attenuator");
  52. volume.attackTime = 0.01;
  53. volume.attackShape = 0.5;
  54. volume.decayShape = 3.0;
  55. pitch.attackTime = 0.00165;
  56. pitch.decayShape = 3.0;
  57. configInput(TRIGG_INPUT, "Trigger");
  58. configInput(VOLUME_INPUT, "Gain");
  59. configInput(TUNE_INPUT, "Tune (V/Oct)");
  60. configInput(SHAPE_INPUT, "Shape CV");
  61. configInput(DECAY_INPUT, "Decay CV");
  62. configOutput(OUT_OUTPUT, "Kick");
  63. configLight(ENV_LIGHT, "Volume envelope");
  64. // calculate up/downsampling rates
  65. onSampleRateChange();
  66. }
  67. void onSampleRateChange() override {
  68. oversampler.reset(APP->engine->getSampleRate());
  69. }
  70. void process(const ProcessArgs& args) override {
  71. // TODO: check values
  72. const bool risingEdgeGate = gateTrigger.process(inputs[TRIGG_INPUT].getVoltage() / 2.0f);
  73. const bool buttonTriggered = buttonTrigger.process(params[TRIGG_BUTTON_PARAM].getValue());
  74. // can be triggered by either rising edge on trigger in, or a button press
  75. if (risingEdgeGate || buttonTriggered) {
  76. volume.trigger();
  77. pitch.trigger();
  78. }
  79. const float vcaGain = clamp(inputs[VOLUME_INPUT].getNormalVoltage(10.f) / 10.f, 0.f, 1.0f);
  80. // pitch envelope
  81. const float bend = bendRange * std::pow(params[BEND_PARAM].getValue(), 3.0);
  82. pitch.decayTime = rescale(params[TIME_PARAM].getValue(), 0.f, 1.0f, minPitchDecay, maxPitchDecay);
  83. pitch.process(args.sampleTime);
  84. // volume envelope
  85. const float volumeDecay = minVolumeDecay * std::pow(2.f, params[DECAY_PARAM].getValue() * std::log2(maxVolumeDecay / minVolumeDecay));
  86. volume.decayTime = clamp(volumeDecay + inputs[DECAY_INPUT].getVoltage() * 0.1f, 0.01, 10.0);
  87. volume.process(args.sampleTime);
  88. float freq = params[TUNE_PARAM].getValue();
  89. freq *= std::pow(2.f, inputs[TUNE_INPUT].getVoltage());
  90. const float kickFrequency = std::max(10.0f, freq + bend * pitch.env);
  91. const float phaseInc = clamp(args.sampleTime * kickFrequency / UPSAMPLE, 1e-6, 0.35f);
  92. const float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 10.f + params[SHAPE_PARAM].getValue(), 0.0f, 1.0f) * 0.99f;
  93. const float shapeB = (1.0f - shape) / (1.0f + shape);
  94. const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape));
  95. float* inputBuf = oversampler.getOSBuffer();
  96. for (int i = 0; i < UPSAMPLE; ++i) {
  97. phase += phaseInc;
  98. phase -= std::floor(phase);
  99. inputBuf[i] = sin2pi_pade_05_5_4(phase);
  100. inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB);
  101. }
  102. const float out = volume.env * oversampler.downsample() * 5.0f * vcaGain;
  103. outputs[OUT_OUTPUT].setVoltage(out);
  104. lights[ENV_LIGHT].setBrightness(volume.env);
  105. }
  106. };
  107. struct KickallWidget : ModuleWidget {
  108. KickallWidget(Kickall* module) {
  109. setModule(module);
  110. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/Kickall.svg")));
  111. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  112. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  113. addParam(createParamCentered<BefacoTinyKnobDarkGrey>(mm2px(Vec(8.472, 28.97)), module, Kickall::TUNE_PARAM));
  114. addParam(createParamCentered<BefacoPush>(mm2px(Vec(22.409, 29.159)), module, Kickall::TRIGG_BUTTON_PARAM));
  115. addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.526, 49.292)), module, Kickall::SHAPE_PARAM));
  116. addParam(createParam<BefacoSlidePot>(mm2px(Vec(19.667, 63.897)), module, Kickall::DECAY_PARAM));
  117. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 71.803)), module, Kickall::TIME_PARAM));
  118. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 93.517)), module, Kickall::BEND_PARAM));
  119. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.501, 14.508)), module, Kickall::VOLUME_INPUT));
  120. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 14.536)), module, Kickall::TRIGG_INPUT));
  121. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.525, 113.191)), module, Kickall::DECAY_INPUT));
  122. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 113.208)), module, Kickall::TUNE_INPUT));
  123. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.485, 113.208)), module, Kickall::SHAPE_INPUT));
  124. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.525, 14.52)), module, Kickall::OUT_OUTPUT));
  125. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.535, 34.943)), module, Kickall::ENV_LIGHT));
  126. }
  127. };
  128. Model* modelKickall = createModel<Kickall, KickallWidget>("Kickall");