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.

253 lines
7.8KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. template <typename T>
  4. static T clip(T x) {
  5. // return std::tanh(x);
  6. // Pade approximant of tanh
  7. x = simd::clamp(x, -3.f, 3.f);
  8. return x * (27 + x * x) / (27 + 9 * x * x);
  9. }
  10. template <typename T>
  11. struct LadderFilter {
  12. T omega0;
  13. T resonance = 1;
  14. T state[4];
  15. T input;
  16. LadderFilter() {
  17. reset();
  18. setCutoff(0);
  19. }
  20. void reset() {
  21. for (int i = 0; i < 4; i++) {
  22. state[i] = 0;
  23. }
  24. }
  25. void setCutoff(T cutoff) {
  26. omega0 = 2 * T(M_PI) * cutoff;
  27. }
  28. void process(T input, T dt) {
  29. dsp::stepRK4(T(0), dt, state, 4, [&](T t, const T x[], T dxdt[]) {
  30. T inputt = crossfade(this->input, input, t / dt);
  31. T inputc = clip(inputt - resonance * x[3]);
  32. T yc0 = clip(x[0]);
  33. T yc1 = clip(x[1]);
  34. T yc2 = clip(x[2]);
  35. T yc3 = clip(x[3]);
  36. dxdt[0] = omega0 * (inputc - yc0);
  37. dxdt[1] = omega0 * (yc0 - yc1);
  38. dxdt[2] = omega0 * (yc1 - yc2);
  39. dxdt[3] = omega0 * (yc2 - yc3);
  40. });
  41. this->input = input;
  42. }
  43. T lowpass() {
  44. return state[3];
  45. }
  46. T highpass() {
  47. return clip((input - resonance * state[3]) - 4 * state[0] + 6 * state[1] - 4 * state[2] + state[3]);
  48. }
  49. };
  50. static const int UPSAMPLE = 2;
  51. struct VCF : Module {
  52. enum ParamIds {
  53. FREQ_PARAM,
  54. FINE_PARAM, // removed in 2.0
  55. RES_PARAM,
  56. FREQ_CV_PARAM,
  57. DRIVE_PARAM,
  58. // Added in 2.0
  59. RES_CV_PARAM,
  60. DRIVE_CV_PARAM,
  61. NUM_PARAMS
  62. };
  63. enum InputIds {
  64. FREQ_INPUT,
  65. RES_INPUT,
  66. DRIVE_INPUT,
  67. IN_INPUT,
  68. NUM_INPUTS
  69. };
  70. enum OutputIds {
  71. LPF_OUTPUT,
  72. HPF_OUTPUT,
  73. NUM_OUTPUTS
  74. };
  75. LadderFilter<float_4> filters[4];
  76. // Upsampler<UPSAMPLE, 8> inputUpsampler;
  77. // Decimator<UPSAMPLE, 8> lowpassDecimator;
  78. // Decimator<UPSAMPLE, 8> highpassDecimator;
  79. VCF() {
  80. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
  81. // To preserve backward compatibility with <2.0, FREQ_PARAM follows
  82. // freq = C4 * 2^(10 * param - 5)
  83. // or
  84. // param = (log2(freq / C4) + 5) / 10
  85. const float minFreq = (std::log2(dsp::FREQ_C4 / 8000.f) + 5) / 10;
  86. const float maxFreq = (std::log2(8000.f / dsp::FREQ_C4) + 5) / 10;
  87. const float defaultFreq = (0.f + 5) / 10;
  88. configParam(FREQ_PARAM, minFreq, maxFreq, defaultFreq, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f));
  89. configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance", "%", 0.f, 100.f);
  90. configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance CV", "%", 0.f, 100.f);
  91. configParam(FREQ_CV_PARAM, -1.f, 1.f, 0.f, "Cutoff frequency CV", "%", 0.f, 100.f);
  92. // gain(drive) = (1 + drive)^5
  93. // gain(0) = 1
  94. // gain(-1) = 0
  95. // gain(1) = 5
  96. configParam(DRIVE_PARAM, -1.f, 1.f, 0.f, "Drive", "%", 0, 100, 100);
  97. configParam(DRIVE_CV_PARAM, -1.f, 1.f, 0.f, "Drive CV", "%", 0, 100);
  98. configInput(FREQ_INPUT, "Frequency");
  99. configInput(RES_INPUT, "Resonance");
  100. configInput(DRIVE_INPUT, "Drive");
  101. configInput(IN_INPUT, "Audio");
  102. configOutput(LPF_OUTPUT, "Lowpass filter");
  103. configOutput(HPF_OUTPUT, "Highpass filter");
  104. configBypass(IN_INPUT, LPF_OUTPUT);
  105. configBypass(IN_INPUT, HPF_OUTPUT);
  106. }
  107. void onReset() override {
  108. for (int i = 0; i < 4; i++)
  109. filters[i].reset();
  110. }
  111. void process(const ProcessArgs& args) override {
  112. if (!outputs[LPF_OUTPUT].isConnected() && !outputs[HPF_OUTPUT].isConnected()) {
  113. return;
  114. }
  115. float driveParam = params[DRIVE_PARAM].getValue();
  116. float driveCvParam = params[DRIVE_CV_PARAM].getValue();
  117. float resParam = params[RES_PARAM].getValue();
  118. float resCvParam = params[RES_CV_PARAM].getValue();
  119. float freqParam = params[FREQ_PARAM].getValue();
  120. // Rescale for backward compatibility
  121. freqParam = freqParam * 10.f - 5.f;
  122. float freqCvParam = params[FREQ_CV_PARAM].getValue();
  123. int channels = std::max(1, inputs[IN_INPUT].getChannels());
  124. for (int c = 0; c < channels; c += 4) {
  125. auto& filter = filters[c / 4];
  126. float_4 input = inputs[IN_INPUT].getVoltageSimd<float_4>(c) / 5.f;
  127. // Drive gain
  128. float_4 drive = driveParam + inputs[DRIVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * driveCvParam;
  129. drive = clamp(drive, -1.f, 1.f);
  130. float_4 gain = simd::pow(1.f + drive, 5);
  131. input *= gain;
  132. // Add -120dB noise to bootstrap self-oscillation
  133. input += 1e-6f * (2.f * random::uniform() - 1.f);
  134. // Set resonance
  135. float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * resCvParam;
  136. resonance = clamp(resonance, 0.f, 1.f);
  137. filter.resonance = simd::pow(resonance, 2) * 10.f;
  138. // Get pitch
  139. float_4 pitch = freqParam + inputs[FREQ_INPUT].getPolyVoltageSimd<float_4>(c) * freqCvParam;
  140. // Set cutoff
  141. float_4 cutoff = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  142. // Without oversampling, we must limit to 8000 Hz or so @ 44100 Hz
  143. cutoff = clamp(cutoff, 1.f, args.sampleRate * 0.18f);
  144. filter.setCutoff(cutoff);
  145. // Upsample input
  146. // float dt = args.sampleTime / UPSAMPLE;
  147. // float inputBuf[UPSAMPLE];
  148. // float lowpassBuf[UPSAMPLE];
  149. // float highpassBuf[UPSAMPLE];
  150. // inputUpsampler.process(input, inputBuf);
  151. // for (int i = 0; i < UPSAMPLE; i++) {
  152. // // Step the filter
  153. // filter.process(inputBuf[i], dt);
  154. // if (outputs[LPF_OUTPUT].isConnected())
  155. // lowpassBuf[i] = filter.lowpass();
  156. // if (outputs[HPF_OUTPUT].isConnected())
  157. // highpassBuf[i] = filter.highpass();
  158. // }
  159. // // Set outputs
  160. // if (outputs[LPF_OUTPUT].isConnected()) {
  161. // outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf));
  162. // }
  163. // if (outputs[HPF_OUTPUT].isConnected()) {
  164. // outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf));
  165. // }
  166. // Set outputs
  167. filter.process(input, args.sampleTime);
  168. if (outputs[LPF_OUTPUT].isConnected()) {
  169. outputs[LPF_OUTPUT].setVoltageSimd(5.f * filter.lowpass(), c);
  170. }
  171. if (outputs[HPF_OUTPUT].isConnected()) {
  172. outputs[HPF_OUTPUT].setVoltageSimd(5.f * filter.highpass(), c);
  173. }
  174. }
  175. outputs[LPF_OUTPUT].setChannels(channels);
  176. outputs[HPF_OUTPUT].setChannels(channels);
  177. }
  178. void paramsFromJson(json_t* rootJ) override {
  179. // These attenuators didn't exist in version <2.0, so set to 1 in case they are not overwritten.
  180. params[RES_CV_PARAM].setValue(1.f);
  181. params[DRIVE_CV_PARAM].setValue(1.f);
  182. Module::paramsFromJson(rootJ);
  183. }
  184. };
  185. struct VCFWidget : ModuleWidget {
  186. VCFWidget(VCF* module) {
  187. setModule(module);
  188. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCF.svg")));
  189. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  190. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  191. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  192. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  193. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(17.587, 29.808)), module, VCF::FREQ_PARAM));
  194. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(8.895, 56.388)), module, VCF::RES_PARAM));
  195. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(26.665, 56.388)), module, VCF::DRIVE_PARAM));
  196. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.996, 80.603)), module, VCF::FREQ_CV_PARAM));
  197. addParam(createParamCentered<Trimpot>(mm2px(Vec(17.833, 80.603)), module, VCF::RES_CV_PARAM));
  198. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.67, 80.603)), module, VCF::DRIVE_CV_PARAM));
  199. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.996, 96.813)), module, VCF::FREQ_INPUT));
  200. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(17.833, 96.813)), module, VCF::RES_INPUT));
  201. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(28.67, 96.813)), module, VCF::DRIVE_INPUT));
  202. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.996, 113.115)), module, VCF::IN_INPUT));
  203. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(17.833, 113.115)), module, VCF::LPF_OUTPUT));
  204. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(28.67, 113.115)), module, VCF::HPF_OUTPUT));
  205. }
  206. };
  207. Model* modelVCF = createModel<VCF, VCFWidget>("VCF");