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.

180 lines
5.1KB

  1. #include "plugin.hpp"
  2. static float clip(float x) {
  3. return std::tanh(x);
  4. }
  5. struct LadderFilter {
  6. float omega0;
  7. float resonance = 1.f;
  8. float state[4];
  9. float input;
  10. float lowpass;
  11. float highpass;
  12. LadderFilter() {
  13. reset();
  14. setCutoff(0.f);
  15. }
  16. void reset() {
  17. for (int i = 0; i < 4; i++) {
  18. state[i] = 0.f;
  19. }
  20. }
  21. void setCutoff(float cutoff) {
  22. omega0 = 2.f*M_PI * cutoff;
  23. }
  24. void process(float input, float dt) {
  25. dsp::stepRK4(0.f, dt, state, 4, [&](float t, const float x[], float dxdt[]) {
  26. float inputc = clip(input - resonance * x[3]);
  27. float yc0 = clip(x[0]);
  28. float yc1 = clip(x[1]);
  29. float yc2 = clip(x[2]);
  30. float yc3 = clip(x[3]);
  31. dxdt[0] = omega0 * (inputc - yc0);
  32. dxdt[1] = omega0 * (yc0 - yc1);
  33. dxdt[2] = omega0 * (yc1 - yc2);
  34. dxdt[3] = omega0 * (yc2 - yc3);
  35. });
  36. lowpass = state[3];
  37. // TODO This is incorrect when `resonance > 0`. Is the math wrong?
  38. highpass = clip((input - resonance*state[3]) - 4 * state[0] + 6*state[1] - 4*state[2] + state[3]);
  39. }
  40. };
  41. static const int UPSAMPLE = 2;
  42. struct VCF : Module {
  43. enum ParamIds {
  44. FREQ_PARAM,
  45. FINE_PARAM,
  46. RES_PARAM,
  47. FREQ_CV_PARAM,
  48. DRIVE_PARAM,
  49. NUM_PARAMS
  50. };
  51. enum InputIds {
  52. FREQ_INPUT,
  53. RES_INPUT,
  54. DRIVE_INPUT,
  55. IN_INPUT,
  56. NUM_INPUTS
  57. };
  58. enum OutputIds {
  59. LPF_OUTPUT,
  60. HPF_OUTPUT,
  61. NUM_OUTPUTS
  62. };
  63. LadderFilter filter;
  64. // Upsampler<UPSAMPLE, 8> inputUpsampler;
  65. // Decimator<UPSAMPLE, 8> lowpassDecimator;
  66. // Decimator<UPSAMPLE, 8> highpassDecimator;
  67. VCF() {
  68. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
  69. params[FREQ_PARAM].config(0.f, 1.f, 0.5f, "Frequency");
  70. params[FINE_PARAM].config(0.f, 1.f, 0.5f, "Fine frequency");
  71. params[RES_PARAM].config(0.f, 1.f, 0.f, "Resonance");
  72. params[FREQ_CV_PARAM].config(-1.f, 1.f, 0.f, "Frequency modulation");
  73. params[DRIVE_PARAM].config(0.f, 1.f, 0.f, "Drive");
  74. }
  75. void onReset() override {
  76. filter.reset();
  77. }
  78. void process(const ProcessArgs &args) override {
  79. if (!outputs[LPF_OUTPUT].isConnected() && !outputs[HPF_OUTPUT].isConnected()) {
  80. outputs[LPF_OUTPUT].setVoltage(0.f);
  81. outputs[HPF_OUTPUT].setVoltage(0.f);
  82. return;
  83. }
  84. float input = inputs[IN_INPUT].getVoltage() / 5.f;
  85. float drive = clamp(params[DRIVE_PARAM].getValue() + inputs[DRIVE_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  86. float gain = std::pow(1.f + drive, 5);
  87. input *= gain;
  88. // Add -60dB noise to bootstrap self-oscillation
  89. input += 1e-6f * (2.f * random::uniform() - 1.f);
  90. // Set resonance
  91. float res = clamp(params[RES_PARAM].getValue() + inputs[RES_INPUT].getVoltage() / 10.f, 0.f, 1.f);
  92. filter.resonance = std::pow(res, 2) * 10.f;
  93. // Set cutoff frequency
  94. float pitch = 0.f;
  95. if (inputs[FREQ_INPUT].isConnected())
  96. pitch += inputs[FREQ_INPUT].getVoltage() * dsp::quadraticBipolar(params[FREQ_CV_PARAM].getValue());
  97. pitch += params[FREQ_PARAM].getValue() * 10.f - 5.f;
  98. pitch += dsp::quadraticBipolar(params[FINE_PARAM].getValue() * 2.f - 1.f) * 7.f / 12.f;
  99. float cutoff = 261.626f * std::pow(2.f, pitch);
  100. cutoff = clamp(cutoff, 1.f, 8000.f);
  101. filter.setCutoff(cutoff);
  102. /*
  103. // Process sample
  104. float dt = args.sampleTime / UPSAMPLE;
  105. float inputBuf[UPSAMPLE];
  106. float lowpassBuf[UPSAMPLE];
  107. float highpassBuf[UPSAMPLE];
  108. inputUpsampler.process(input, inputBuf);
  109. for (int i = 0; i < UPSAMPLE; i++) {
  110. // Step the filter
  111. filter.process(inputBuf[i], dt);
  112. lowpassBuf[i] = filter.lowpass;
  113. highpassBuf[i] = filter.highpass;
  114. }
  115. // Set outputs
  116. if (outputs[LPF_OUTPUT].isConnected()) {
  117. outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf));
  118. }
  119. if (outputs[HPF_OUTPUT].isConnected()) {
  120. outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf));
  121. }
  122. */
  123. filter.process(input, args.sampleTime);
  124. outputs[LPF_OUTPUT].setVoltage(5.f * filter.lowpass);
  125. outputs[HPF_OUTPUT].setVoltage(5.f * filter.highpass);
  126. }
  127. };
  128. struct VCFWidget : ModuleWidget {
  129. VCFWidget(VCF *module) {
  130. setModule(module);
  131. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/VCF.svg")));
  132. addChild(createWidget<ScrewSilver>(Vec(15, 0)));
  133. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0)));
  134. addChild(createWidget<ScrewSilver>(Vec(15, 365)));
  135. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365)));
  136. addParam(createParam<RoundHugeBlackKnob>(Vec(33, 61), module, VCF::FREQ_PARAM));
  137. addParam(createParam<RoundLargeBlackKnob>(Vec(12, 143), module, VCF::FINE_PARAM));
  138. addParam(createParam<RoundLargeBlackKnob>(Vec(71, 143), module, VCF::RES_PARAM));
  139. addParam(createParam<RoundLargeBlackKnob>(Vec(12, 208), module, VCF::FREQ_CV_PARAM));
  140. addParam(createParam<RoundLargeBlackKnob>(Vec(71, 208), module, VCF::DRIVE_PARAM));
  141. addInput(createInput<PJ301MPort>(Vec(10, 276), module, VCF::FREQ_INPUT));
  142. addInput(createInput<PJ301MPort>(Vec(48, 276), module, VCF::RES_INPUT));
  143. addInput(createInput<PJ301MPort>(Vec(85, 276), module, VCF::DRIVE_INPUT));
  144. addInput(createInput<PJ301MPort>(Vec(10, 320), module, VCF::IN_INPUT));
  145. addOutput(createOutput<PJ301MPort>(Vec(48, 320), module, VCF::LPF_OUTPUT));
  146. addOutput(createOutput<PJ301MPort>(Vec(85, 320), module, VCF::HPF_OUTPUT));
  147. }
  148. };
  149. Model *modelVCF = createModel<VCF, VCFWidget>("VCF");