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.

313 lines
9.1KB

  1. #include "plugin.hpp"
  2. /** Approximates 1/x, using the rcp() instruction and 1 Newton-Raphson refinement */
  3. template <typename T>
  4. inline T rcp_newton1(T x) {
  5. T r = simd::rcp(x);
  6. return r * (T(2) - x * r);
  7. }
  8. /** Approximates 1/sqrt(x), using the rsqrt() instruction and 1 Newton-Raphson refinement */
  9. template <typename T>
  10. inline T rsqrt_newton1(T x) {
  11. T y = simd::rsqrt(x);
  12. return y * (T(3) - x * y * y) * T(0.5);
  13. }
  14. /** Approximates tan(x) for x in [0, pi*0.5).
  15. Optimized coefficients for max relative error: 2.78e-05.
  16. */
  17. template <typename T>
  18. inline T tan_1_2(T x) {
  19. T x2 = x * x;
  20. T num = T(1) + x2 * T(-0.09776575533683811);
  21. T den = T(1) + x2 * (T(-0.43119539396382) + x2 * T(0.0105011966117302));
  22. return x * num / den;
  23. }
  24. /** 1st-order ADAA for softclip: f(x) = x/sqrt(x²+1), F(x) = sqrt(x²+1). */
  25. template <typename T>
  26. struct SoftclipADAA1 {
  27. T xPrev = T(0);
  28. T fPrev = T(0); // f(0) = 0
  29. T FPrev = T(1); // F(0) = sqrt(0+1) = 1
  30. void reset() {
  31. xPrev = T(0);
  32. fPrev = T(0);
  33. FPrev = T(1);
  34. }
  35. T process(T x) {
  36. const T eps = T(1e-5);
  37. T diff = x - xPrev;
  38. // f = x/sqrt(x^2+1), F = sqrt(x^2+1)
  39. T x2p1 = x * x + T(1);
  40. T r = rsqrt_newton1(x2p1);
  41. T f = x * r;
  42. T F = x2p1 * r;
  43. T adaaResult = (F - FPrev) * rcp_newton1(diff);
  44. T fallbackResult = (f + fPrev) * T(0.5);
  45. T y = simd::ifelse(simd::abs(diff) > eps, adaaResult, fallbackResult);
  46. xPrev = x;
  47. fPrev = f;
  48. FPrev = F;
  49. return y;
  50. }
  51. };
  52. /** 4-pole transistor ladder filter using TPT (Topology-Preserving Transform).
  53. Feedback uses previous sample (i.e. 1 sample delayed) to avoid iterative solving.
  54. For the equivalent state / trapezoidal integrator approach:
  55. Zavalishin, V. "The Art of VA Filter Design". 2018
  56. https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.2.pdf
  57. For Cytomic's formulation:
  58. https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
  59. For antiderivative anti-aliasing (ADAA):
  60. Parker, J., Zavalishin, V., Le Bihan, E. "Reducing the Aliasing of Nonlinear Waveshaping Using Continuous-Time Convolution". DAFx 2016
  61. https://dafx.de/paper-archive/2016/dafxpapers/20-DAFx-16_paper_41-PN.pdf
  62. */
  63. template <typename T>
  64. struct LadderFilter {
  65. T state[4];
  66. SoftclipADAA1<T> adaa[5];
  67. struct Frame {
  68. T input;
  69. /** Normalized cutoff frequency fc/fs in [0, 0.5) */
  70. T cutoff;
  71. T resonance;
  72. /** 4-pole lowpass output. */
  73. T lowpass4;
  74. /** 4-pole highpass output. */
  75. T highpass4;
  76. };
  77. LadderFilter() {
  78. reset();
  79. }
  80. void reset() {
  81. for (int i = 0; i < 4; i++) {
  82. state[i] = T(0);
  83. }
  84. for (int i = 0; i < 5; i++) {
  85. adaa[i].reset();
  86. }
  87. }
  88. void process(Frame& frame) {
  89. // Pre-warped frequency
  90. T g = tan_1_2(T(M_PI) * frame.cutoff);
  91. // Integrator gain
  92. T G = g / (T(1) + g);
  93. // Feedback path
  94. T feedback = adaa[4].process(frame.resonance * state[3]);
  95. T u = frame.input - feedback;
  96. // Stage 0
  97. T sat0 = adaa[0].process(u);
  98. T v0 = G * (sat0 - state[0]);
  99. T y0 = v0 + state[0];
  100. state[0] = y0 + v0;
  101. // Stage 1
  102. T sat1 = adaa[1].process(y0);
  103. T v1 = G * (sat1 - state[1]);
  104. T y1 = v1 + state[1];
  105. state[1] = y1 + v1;
  106. // Stage 2
  107. T sat2 = adaa[2].process(y1);
  108. T v2 = G * (sat2 - state[2]);
  109. T y2 = v2 + state[2];
  110. state[2] = y2 + v2;
  111. // Stage 3
  112. T sat3 = adaa[3].process(y2);
  113. T v3 = G * (sat3 - state[3]);
  114. T y3 = v3 + state[3];
  115. state[3] = y3 + v3;
  116. frame.lowpass4 = y3;
  117. frame.highpass4 = sat0 - T(4) * y0 + T(6) * y1 - T(4) * y2 + y3;
  118. }
  119. };
  120. using simd::float_4;
  121. struct VCF : Module {
  122. enum ParamIds {
  123. FREQ_PARAM,
  124. FINE_PARAM, // removed in 2.0
  125. RES_PARAM,
  126. FREQ_CV_PARAM,
  127. DRIVE_PARAM,
  128. // Added in 2.0
  129. RES_CV_PARAM,
  130. DRIVE_CV_PARAM,
  131. NUM_PARAMS
  132. };
  133. enum InputIds {
  134. FREQ_INPUT,
  135. RES_INPUT,
  136. DRIVE_INPUT,
  137. IN_INPUT,
  138. NUM_INPUTS
  139. };
  140. enum OutputIds {
  141. LPF_OUTPUT,
  142. HPF_OUTPUT,
  143. NUM_OUTPUTS
  144. };
  145. LadderFilter<float_4> filters[4];
  146. VCF() {
  147. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
  148. // To preserve backward compatibility with <2.0, FREQ_PARAM follows
  149. // freq = C4 * 2^(10 * param - 5)
  150. // or
  151. // param = (log2(freq / C4) + 5) / 10
  152. const float minFreq = (std::log2(8.f / dsp::FREQ_C4) + 5) / 10;
  153. const float maxFreq = (std::log2(22000.f / dsp::FREQ_C4) + 5) / 10;
  154. const float defaultFreq = (minFreq + maxFreq) / 2.f;
  155. configParam(FREQ_PARAM, minFreq, maxFreq, defaultFreq, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f));
  156. configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance", "%", 0.f, 100.f);
  157. configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance CV", "%", 0.f, 100.f);
  158. configParam(FREQ_CV_PARAM, -1.f, 1.f, 0.f, "Cutoff frequency CV", "%", 0.f, 100.f);
  159. // gain(drive) = (1 + drive)^5
  160. // gain(-1) = 0
  161. // gain(0) = 1
  162. // gain(1) = 32
  163. configParam(DRIVE_PARAM, -1.f, 1.f, 0.f, "Drive", "%", 0, 100, 100);
  164. configParam(DRIVE_CV_PARAM, -1.f, 1.f, 0.f, "Drive CV", "%", 0, 100);
  165. configInput(FREQ_INPUT, "Frequency");
  166. configInput(RES_INPUT, "Resonance");
  167. configInput(DRIVE_INPUT, "Drive");
  168. configInput(IN_INPUT, "Audio");
  169. configOutput(LPF_OUTPUT, "Lowpass filter");
  170. configOutput(HPF_OUTPUT, "Highpass filter");
  171. configBypass(IN_INPUT, LPF_OUTPUT);
  172. configBypass(IN_INPUT, HPF_OUTPUT);
  173. }
  174. void onReset() override {
  175. for (int i = 0; i < 4; i++) {
  176. filters[i].reset();
  177. }
  178. }
  179. void process(const ProcessArgs& args) override {
  180. if (!outputs[LPF_OUTPUT].isConnected() && !outputs[HPF_OUTPUT].isConnected()) {
  181. return;
  182. }
  183. float driveParam = params[DRIVE_PARAM].getValue();
  184. float driveCvParam = params[DRIVE_CV_PARAM].getValue();
  185. float resParam = params[RES_PARAM].getValue();
  186. float resCvParam = params[RES_CV_PARAM].getValue();
  187. float freqParam = params[FREQ_PARAM].getValue();
  188. // Rescale for backward compatibility
  189. freqParam = freqParam * 10.f - 5.f;
  190. float freqCvParam = params[FREQ_CV_PARAM].getValue();
  191. int channels = std::max(1, inputs[IN_INPUT].getChannels());
  192. for (int c = 0; c < channels; c += 4) {
  193. LadderFilter<float_4>::Frame frame;
  194. // Input
  195. float_4 input = inputs[IN_INPUT].getVoltageSimd<float_4>(c) / 5.f;
  196. // Drive
  197. float_4 drive = driveParam + inputs[DRIVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * driveCvParam;
  198. drive = simd::clamp(drive, -1.f, 1.f);
  199. float_4 gain = simd::pow(1.f + drive, 5);
  200. input *= gain;
  201. // Add -120dB noise to bootstrap self-oscillation
  202. input += 1e-6f * (2.f * random::uniform() - 1.f);
  203. frame.input = input;
  204. // Resonance
  205. float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * resCvParam;
  206. resonance = simd::clamp(resonance, 0.f, 1.f);
  207. frame.resonance = simd::pow(resonance, 2) * 10.f;
  208. // Cutoff frequency
  209. float_4 pitch = freqParam + inputs[FREQ_INPUT].getPolyVoltageSimd<float_4>(c) * freqCvParam;
  210. float_4 cutoff = dsp::FREQ_C4 * dsp::exp2_taylor5(pitch);
  211. frame.cutoff = simd::clamp(cutoff * args.sampleTime, 0.f, 0.499f);
  212. // Process
  213. filters[c / 4].process(frame);
  214. // Outputs
  215. outputs[LPF_OUTPUT].setVoltageSimd(frame.lowpass4 * 5.f, c);
  216. outputs[HPF_OUTPUT].setVoltageSimd(frame.highpass4 * 5.f, c);
  217. }
  218. outputs[LPF_OUTPUT].setChannels(channels);
  219. outputs[HPF_OUTPUT].setChannels(channels);
  220. }
  221. void paramsFromJson(json_t* rootJ) override {
  222. // These attenuators didn't exist in version <2, so set to 1 in case they are not overwritten.
  223. params[RES_CV_PARAM].setValue(1.f);
  224. params[DRIVE_CV_PARAM].setValue(1.f);
  225. Module::paramsFromJson(rootJ);
  226. }
  227. };
  228. struct VCFWidget : ModuleWidget {
  229. VCFWidget(VCF* module) {
  230. setModule(module);
  231. setPanel(createPanel(asset::plugin(pluginInstance, "res/VCF.svg"), asset::plugin(pluginInstance, "res/VCF-dark.svg")));
  232. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  233. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  234. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  235. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  236. addParam(createParamCentered<RoundHugeBlackKnob>(mm2px(Vec(17.587, 29.808)), module, VCF::FREQ_PARAM));
  237. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(8.895, 56.388)), module, VCF::RES_PARAM));
  238. addParam(createParamCentered<RoundLargeBlackKnob>(mm2px(Vec(26.665, 56.388)), module, VCF::DRIVE_PARAM));
  239. addParam(createParamCentered<Trimpot>(mm2px(Vec(6.996, 80.603)), module, VCF::FREQ_CV_PARAM));
  240. addParam(createParamCentered<Trimpot>(mm2px(Vec(17.833, 80.603)), module, VCF::RES_CV_PARAM));
  241. addParam(createParamCentered<Trimpot>(mm2px(Vec(28.67, 80.603)), module, VCF::DRIVE_CV_PARAM));
  242. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.996, 96.813)), module, VCF::FREQ_INPUT));
  243. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(17.833, 96.813)), module, VCF::RES_INPUT));
  244. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(28.67, 96.813)), module, VCF::DRIVE_INPUT));
  245. addInput(createInputCentered<ThemedPJ301MPort>(mm2px(Vec(6.996, 113.115)), module, VCF::IN_INPUT));
  246. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(17.833, 113.115)), module, VCF::LPF_OUTPUT));
  247. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(28.67, 113.115)), module, VCF::HPF_OUTPUT));
  248. }
  249. };
  250. Model* modelVCF = createModel<VCF, VCFWidget>("VCF");