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.

146 lines
5.4KB

  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 trigger;
  40. static const int UPSAMPLE = 8;
  41. chowdsp::Oversampling<UPSAMPLE> oversampler;
  42. Kickall() {
  43. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  44. // TODO: review this mapping, using displayBase multiplier seems more normal
  45. configParam(TUNE_PARAM, FREQ_A0, FREQ_B2, 0.5 * (FREQ_A0 + FREQ_B2), "Tune", "Hz");
  46. configParam(TRIGG_BUTTON_PARAM, 0.f, 1.f, 0.f, "Manual trigger");
  47. configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Wave shape");
  48. configParam(DECAY_PARAM, 0.f, 1.f, 0.01f, "VCA Envelope decay time");
  49. configParam(TIME_PARAM, 0.f, 1.0f, 0.f, "Pitch envelope decay time");
  50. configParam(BEND_PARAM, 0.f, 1.f, 0.f, "Pitch envelope attenuator");
  51. volume.attackTime = 0.01;
  52. volume.attackShape = 0.5;
  53. volume.decayShape = 3.0;
  54. pitch.attackTime = 0.00165;
  55. pitch.decayShape = 3.0;
  56. // calculate up/downsampling rates
  57. onSampleRateChange();
  58. }
  59. void onSampleRateChange() override {
  60. oversampler.reset(APP->engine->getSampleRate());
  61. }
  62. void process(const ProcessArgs& args) override {
  63. // TODO: check values
  64. if (trigger.process(inputs[TRIGG_INPUT].getVoltage() / 2.0f + params[TRIGG_BUTTON_PARAM].getValue() * 10.0)) {
  65. volume.trigger();
  66. pitch.trigger();
  67. }
  68. const float vcaGain = clamp(inputs[VOLUME_INPUT].getNormalVoltage(10.f) / 10.f, 0.f, 1.0f);
  69. // pitch envelope
  70. const float bend = bendRange * std::pow(params[BEND_PARAM].getValue(), 3.0);
  71. pitch.decayTime = rescale(params[TIME_PARAM].getValue(), 0.f, 1.0f, minPitchDecay, maxPitchDecay);
  72. pitch.process(args.sampleTime);
  73. // volume envelope
  74. const float volumeDecay = minVolumeDecay * std::pow(2.f, params[DECAY_PARAM].getValue() * std::log2(maxVolumeDecay / minVolumeDecay));
  75. volume.decayTime = clamp(volumeDecay + inputs[DECAY_INPUT].getVoltage() * 0.1f, 0.01, 10.0);
  76. volume.process(args.sampleTime);
  77. float freq = params[TUNE_PARAM].getValue();
  78. freq *= std::pow(2.f, inputs[TUNE_INPUT].getVoltage());
  79. const float kickFrequency = std::max(10.0f, freq + bend * pitch.env);
  80. const float phaseInc = clamp(args.sampleTime * kickFrequency / UPSAMPLE, 1e-6, 0.35f);
  81. const float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 10.f + params[SHAPE_PARAM].getValue(), 0.0f, 1.0f) * 0.99f;
  82. const float shapeB = (1.0f - shape) / (1.0f + shape);
  83. const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape));
  84. float* inputBuf = oversampler.getOSBuffer();
  85. for (int i = 0; i < UPSAMPLE; ++i) {
  86. phase += phaseInc;
  87. phase -= std::floor(phase);
  88. inputBuf[i] = sin2pi_pade_05_5_4(phase);
  89. inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB);
  90. }
  91. const float out = volume.env * oversampler.downsample() * 5.0f * vcaGain;
  92. outputs[OUT_OUTPUT].setVoltage(out);
  93. lights[ENV_LIGHT].setBrightness(volume.env);
  94. }
  95. };
  96. struct KickallWidget : ModuleWidget {
  97. KickallWidget(Kickall* module) {
  98. setModule(module);
  99. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Kickall.svg")));
  100. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  101. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  102. addParam(createParamCentered<BefacoTinyKnobDarkGrey>(mm2px(Vec(8.472, 28.97)), module, Kickall::TUNE_PARAM));
  103. addParam(createParamCentered<BefacoPush>(mm2px(Vec(22.409, 29.159)), module, Kickall::TRIGG_BUTTON_PARAM));
  104. addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.526, 49.292)), module, Kickall::SHAPE_PARAM));
  105. addParam(createParam<BefacoSlidePot>(mm2px(Vec(19.667, 63.897)), module, Kickall::DECAY_PARAM));
  106. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 71.803)), module, Kickall::TIME_PARAM));
  107. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 93.517)), module, Kickall::BEND_PARAM));
  108. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.501, 14.508)), module, Kickall::VOLUME_INPUT));
  109. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 14.536)), module, Kickall::TRIGG_INPUT));
  110. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.525, 113.191)), module, Kickall::DECAY_INPUT));
  111. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 113.208)), module, Kickall::TUNE_INPUT));
  112. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.485, 113.208)), module, Kickall::SHAPE_INPUT));
  113. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.525, 14.52)), module, Kickall::OUT_OUTPUT));
  114. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.535, 34.943)), module, Kickall::ENV_LIGHT));
  115. }
  116. };
  117. Model* modelKickall = createModel<Kickall, KickallWidget>("Kickall");