SpectMorph
smskfilter.hh
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2 
3 #pragma once
4 
5 #include "smpandaresampler.hh"
6 
7 #include <algorithm>
8 
9 namespace SpectMorph {
10 
12 
13 class SKFilter
14 {
15 public:
16  enum Mode {
17  LP1, LP2, LP3, LP4, LP6, LP8,
18  BP2, BP4, BP6, BP8,
19  HP1, HP2, HP3, HP4, HP6, HP8
20  };
21 private:
22  static constexpr size_t LAST_MODE = HP8;
23  Mode mode_ = Mode::LP2;
24  float freq_ = 440;
25  float reso_ = 0;
26  float drive_ = 0;
27  float global_volume_ = 1;
28  bool test_linear_ = false;
29  int over_ = 1;
30  float freq_warp_factor_ = 0;
31  float frequency_range_min_ = 0;
32  float frequency_range_max_ = 0;
33  float clamp_freq_min_ = 0;
34  float clamp_freq_max_ = 0;
35  float rate_ = 0;
36 
37  static constexpr int MAX_STAGES = 4;
38  static constexpr uint MAX_BLOCK_SIZE = 1024;
39 
40  struct Channel
41  {
42  std::unique_ptr<Resampler2> res_up;
43  std::unique_ptr<Resampler2> res_down;
44 
45  std::array<float, MAX_STAGES> s1;
46  std::array<float, MAX_STAGES> s2;
47  };
48  struct FParams
49  {
50  std::array<float, MAX_STAGES> k;
51  float pre_scale = 1;
52  float post_scale = 1;
53  };
54  static constexpr int
55  mode2stages (Mode mode)
56  {
57  switch (mode)
58  {
59  case LP3:
60  case LP4:
61  case BP4:
62  case HP3:
63  case HP4: return 2;
64  case LP6:
65  case BP6:
66  case HP6: return 3;
67  case LP8:
68  case BP8:
69  case HP8: return 4;
70  default: return 1;
71  }
72  }
73 
74  std::array<Channel, 2> channels_;
75  FParams fparams_;
76  bool fparams_valid_ = false;
77 
78  class RTable {
79  std::vector<float> res2_k;
80  std::vector<float> res3_k;
81  std::vector<float> res4_k;
82  static constexpr int TSIZE = 16;
83  RTable()
84  {
85  for (int order = 4; order <= 8; order += 2)
86  {
87  for (int t = 0; t <= TSIZE + 1; t++)
88  {
89  double res = std::clamp (double (t) / TSIZE, 0.0, 1.0);
90 
91  // R must be in interval [0:1]
92  const double R = 1 - res;
93  const double r_alpha = std::acos (R) / (order / 2);
94 
95  std::vector<double> Rn;
96  for (int i = 0; i < order / 2; i++)
97  {
98  /* butterworth roots in s, left semi plane */
99  const double bw_s_alpha = M_PI * (4 * i + order + 2) / (2 * order);
100  /* add resonance */
101  Rn.push_back (-cos (bw_s_alpha + r_alpha));
102  }
103 
104  std::sort (Rn.begin(), Rn.end(), std::greater<double>());
105 
106  for (auto xr : Rn)
107  {
108  if (order == 4)
109  res2_k.push_back ((1 - xr) * 2);
110  if (order == 6)
111  res3_k.push_back ((1 - xr) * 2);
112  if (order == 8)
113  res4_k.push_back ((1 - xr) * 2);
114  }
115  }
116  }
117  }
118  public:
119  static const RTable&
120  the()
121  {
122  static RTable rtable;
123  return rtable;
124  }
125  void
126  interpolate_resonance (float res, int stages, float *k, const std::vector<float>& res_k) const
127  {
128  auto lerp = [] (float a, float b, float frac) {
129  return a + frac * (b - a);
130  };
131 
132  float fidx = std::clamp (res, 0.f, 1.f) * TSIZE;
133  int idx = fidx;
134  float frac = fidx - idx;
135 
136  for (int s = 0; s < stages; s++)
137  {
138  k[s] = lerp (res_k[idx * stages + s], res_k[idx * stages + stages + s], frac);
139  }
140  }
141  void
142  lookup_resonance (float res, int stages, float *k) const
143  {
144  if (stages == 2)
145  interpolate_resonance (res, stages, k, res2_k);
146 
147  if (stages == 3)
148  interpolate_resonance (res, stages, k, res3_k);
149 
150  if (stages == 4)
151  interpolate_resonance (res, stages, k, res4_k);
152 
153  if (res > 1)
154  k[stages - 1] = res * 2;
155  }
156  };
157  const RTable& rtable_;
158 public:
159  SKFilter (int over) :
160  over_ (over),
161  rtable_ (RTable::the())
162  {
163  for (auto& channel : channels_)
164  {
165  channel.res_up = std::make_unique<Resampler2> (Resampler2::UP, over_, Resampler2::PREC_72DB);
166  channel.res_down = std::make_unique<Resampler2> (Resampler2::DOWN, over_, Resampler2::PREC_72DB);
167  }
168  set_rate (48000);
169  set_frequency_range (10, 24000);
170  reset();
171  }
172 private:
173  void
174  setup_reso_drive (FParams& fparams, float reso, float drive)
175  {
176  if (test_linear_) // test filter as linear filter; don't do any resonance correction
177  {
178  const float scale = 1e-5;
179  fparams.pre_scale = scale;
180  fparams.post_scale = 1 / scale;
181  setup_k (fparams, reso);
182 
183  return;
184  }
185  const float db_x2_factor = 0.166096404744368; // 1/(20*log(2)/log(10))
186  const float sqrt2 = M_SQRT2;
187 
188  // scale signal down (without normalization on output) for negative drive
189  float negative_drive_vol = 1;
190  if (drive < 0)
191  {
192  negative_drive_vol = exp2f (drive * db_x2_factor);
193  drive = 0;
194  }
195  // drive resonance boost
196  if (drive > 0)
197  reso += drive * 0.015f;
198 
199  float vol = exp2f ((drive + -18 * reso) * db_x2_factor);
200 
201  if (reso < 0.9)
202  {
203  reso = 1 - (1-reso)*(1-reso)*(1-sqrt2/4);
204  }
205  else
206  {
207  reso = 1 - (1-0.9f)*(1-0.9f)*(1-sqrt2/4) + (reso-0.9f)*0.1f;
208  }
209 
210  fparams.pre_scale = negative_drive_vol * vol * global_volume_;
211  fparams.post_scale = std::max (1 / vol, 1.0f) / global_volume_;
212  setup_k (fparams, reso);
213  }
214  void
215  setup_k (FParams& fparams, float res)
216  {
217  if (mode2stages (mode_) == 1)
218  {
219  // just one stage
220  fparams.k[0] = res * 2;
221  }
222  else
223  {
224  rtable_.lookup_resonance (res, mode2stages (mode_), fparams.k.data());
225  }
226  }
227  void
228  zero_fill_state()
229  {
230  for (auto& channel : channels_)
231  {
232  std::fill (channel.s1.begin(), channel.s1.end(), 0.0);
233  std::fill (channel.s2.begin(), channel.s2.end(), 0.0);
234  }
235  }
236 public:
237  void
238  set_mode (Mode m)
239  {
240  if (mode_ != m)
241  {
242  mode_ = m;
243 
244  zero_fill_state();
245  fparams_valid_ = false;
246  }
247  }
248  void
249  set_freq (float freq)
250  {
251  freq_ = freq;
252  }
253  void
254  set_reso (float reso)
255  {
256  reso_ = reso;
257  fparams_valid_ = false;
258  }
259  void
260  set_drive (float drive)
261  {
262  drive_ = drive;
263  fparams_valid_ = false;
264  }
265  void
266  set_global_volume (float global_volume)
267  {
268  /* every samples that is processed by the filter is
269  * - multiplied with global_volume before processing
270  * - divided by global_volume after processing
271  * which has an effect on the non-linear part of the filter (drive)
272  */
273  global_volume_ = global_volume;
274  fparams_valid_ = false;
275  }
276  void
277  set_test_linear (bool test_linear)
278  {
279  test_linear_ = test_linear;
280  fparams_valid_ = false;
281  }
282  void
283  reset ()
284  {
285  for (auto& channel : channels_)
286  {
287  channel.res_up->reset();
288  channel.res_down->reset();
289  }
290  zero_fill_state();
291  fparams_valid_ = false;
292  }
293  void
294  set_rate (float rate)
295  {
296  freq_warp_factor_ = 4 / (rate * over_);
297  rate_ = rate;
298 
299  update_frequency_range();
300  }
301  void
302  set_frequency_range (float min_freq, float max_freq)
303  {
304  frequency_range_min_ = min_freq;
305  frequency_range_max_ = max_freq;
306 
307  update_frequency_range();
308  }
309  double
310  delay()
311  {
312  return channels_[0].res_up->delay() / over_ + channels_[0].res_down->delay();
313  }
314 private:
315  void
316  update_frequency_range()
317  {
318  /* we want to clamp to the user defined range (set_frequency_range())
319  * but also enforce that the filter is well below nyquist frequency
320  */
321  clamp_freq_min_ = frequency_range_min_;
322  clamp_freq_max_ = std::min (frequency_range_max_, rate_ * over_ * 0.49f);
323  }
324  float
325  cutoff_warp (float freq)
326  {
327  float x = freq * freq_warp_factor_;
328 
329  /* approximate tan (pi*x/4) for cutoff warping */
330  const float c1 = -3.16783027;
331  const float c2 = 0.134516124;
332  const float c3 = -4.033321984;
333 
334  float x2 = x * x;
335 
336  return x * (c1 + c2 * x2) / (c3 + x2);
337  }
338  static float
339  tanh_approx (float x)
340  {
341  // https://www.musicdsp.org/en/latest/Other/238-rational-tanh-approximation.html
342  x = std::clamp (x, -3.0f, 3.0f);
343 
344  return x * (27.0f + x * x) / (27.0f + 9.0f * x * x);
345  }
346  template<Mode MODE, bool STEREO>
347  [[gnu::flatten]]
348  void
349  process (float *left, float *right, float freq, uint n_samples)
350  {
351  float g = cutoff_warp (std::clamp (freq, clamp_freq_min_, clamp_freq_max_));
352  float G = g / (1 + g);
353 
354  for (int stage = 0; stage < mode2stages (MODE); stage++)
355  {
356  const float k = fparams_.k[stage];
357 
358  float xnorm = 1.f / (1 - k * G + k * G * G);
359  float s1feedback = -xnorm * k * (G - 1) / (1 + g);
360  float s2feedback = -xnorm * k / (1 + g);
361 
362  auto lowpass = [G] (float in, float& state)
363  {
364  float v = G * (in - state);
365  float y = v + state;
366  state = y + v;
367  return y;
368  };
369 
370  auto mode_out = [] (float y0, float y1, float y2, bool last_stage) -> float
371  {
372  float y1hp = y0 - y1;
373  float y2hp = y1 - y2;
374 
375  switch (MODE)
376  {
377  case LP2:
378  case LP4:
379  case LP6:
380  case LP8: return y2;
381  case BP2:
382  case BP4:
383  case BP6:
384  case BP8: return y2hp;
385  case HP2:
386  case HP4:
387  case HP6:
388  case HP8: return (y1hp - y2hp);
389  case LP1:
390  case LP3: return last_stage ? y1 : y2;
391  case HP1:
392  case HP3: return last_stage ? y1hp : (y1hp - y2hp);
393  }
394  };
395 
396  float s1l, s1r, s2l, s2r;
397 
398  s1l = channels_[0].s1[stage];
399  s2l = channels_[0].s2[stage];
400 
401  if (STEREO)
402  {
403  s1r = channels_[1].s1[stage];
404  s2r = channels_[1].s2[stage];
405  }
406 
407  auto tick = [&] (uint i, bool last_stage, float pre_scale, float post_scale)
408  {
409  float xl, xr, y0l, y0r, y1l, y1r, y2l, y2r;
410 
411  /*
412  * interleaving processing of both channels performs better than
413  * processing left and right channel seperately (measured on Ryzen7)
414  */
415 
416  { xl = left[i] * pre_scale; }
417  if (STEREO) { xr = right[i] * pre_scale; }
418 
419  { y0l = xl * xnorm + s1l * s1feedback + s2l * s2feedback; }
420  if (STEREO) { y0r = xr * xnorm + s1r * s1feedback + s2r * s2feedback; }
421 
422  if (last_stage)
423  {
424  { y0l = tanh_approx (y0l); }
425  if (STEREO) { y0r = tanh_approx (y0r); }
426  }
427  { y1l = lowpass (y0l, s1l); }
428  if (STEREO) { y1r = lowpass (y0r, s1r); }
429 
430  { y2l = lowpass (y1l, s2l); }
431  if (STEREO) { y2r = lowpass (y1r, s2r); }
432 
433  { left[i] = mode_out (y0l, y1l, y2l, last_stage) * post_scale; }
434  if (STEREO) { right[i] = mode_out (y0r, y1r, y2r, last_stage) * post_scale; }
435  };
436 
437  const bool last_stage = mode2stages (MODE) == (stage + 1);
438 
439  if (last_stage)
440  {
441  for (uint i = 0; i < n_samples; i++)
442  tick (i, true, fparams_.pre_scale, fparams_.post_scale);
443  }
444  else
445  {
446  for (uint i = 0; i < n_samples; i++)
447  tick (i, false, 1, 1);
448  }
449 
450  channels_[0].s1[stage] = s1l;
451  channels_[0].s2[stage] = s2l;
452 
453  if (STEREO)
454  {
455  channels_[1].s1[stage] = s1r;
456  channels_[1].s2[stage] = s2r;
457  }
458  }
459  }
460  template<Mode MODE>
461  void
462  process_block_mode (uint n_samples, float *left, float *right, const float *freq_in, const float *reso_in, const float *drive_in)
463  {
464  float over_samples_left[n_samples * over_];
465  float over_samples_right[n_samples * over_];
466 
467  /* we only support stereo (left != 0, right != 0) and mono (left != 0, right == 0) */
468  bool stereo = left && right;
469 
470  channels_[0].res_up->process_block (left, n_samples, over_samples_left);
471  if (stereo)
472  channels_[1].res_up->process_block (right, n_samples, over_samples_right);
473 
474  if (!fparams_valid_)
475  {
476  setup_reso_drive (fparams_, reso_in ? reso_in[0] : reso_, drive_in ? drive_in[0] : drive_);
477  fparams_valid_ = true;
478  }
479 
480  if (reso_in || drive_in)
481  {
482  /* for reso or drive modulation, we split the input it into small blocks
483  * and interpolate the pre_scale / post_scale / k parameters
484  */
485  float *left_blk = over_samples_left;
486  float *right_blk = over_samples_right;
487 
488  uint n_remaining_samples = n_samples;
489  while (n_remaining_samples)
490  {
491  const uint todo = std::min<uint> (n_remaining_samples, 64);
492 
493  FParams fparams_end;
494  setup_reso_drive (fparams_end, reso_in ? reso_in[todo - 1] : reso_, drive_in ? drive_in[todo - 1] : drive_);
495 
496  constexpr static int STAGES = mode2stages (MODE);
497  float todo_inv = 1.f / todo;
498  float delta_pre_scale = (fparams_end.pre_scale - fparams_.pre_scale) * todo_inv;
499  float delta_post_scale = (fparams_end.post_scale - fparams_.post_scale) * todo_inv;
500  float delta_k[STAGES];
501  for (int stage = 0; stage < STAGES; stage++)
502  delta_k[stage] = (fparams_end.k[stage] - fparams_.k[stage]) * todo_inv;
503 
504  uint j = 0;
505  for (uint i = 0; i < todo * over_; i += over_)
506  {
507  fparams_.pre_scale += delta_pre_scale;
508  fparams_.post_scale += delta_post_scale;
509 
510  for (int stage = 0; stage < STAGES; stage++)
511  fparams_.k[stage] += delta_k[stage];
512 
513  float freq = freq_in ? freq_in[j++] : freq_;
514 
515  if (stereo)
516  {
517  process<MODE, true> (left_blk + i, right_blk + i, freq, over_);
518  }
519  else
520  {
521  process<MODE, false> (left_blk + i, nullptr, freq, over_);
522  }
523  }
524 
525  n_remaining_samples -= todo;
526  left_blk += todo * over_;
527  right_blk += todo * over_;
528 
529  if (freq_in)
530  freq_in += todo;
531  if (reso_in)
532  reso_in += todo;
533  if (drive_in)
534  drive_in += todo;
535  }
536  }
537  else if (freq_in)
538  {
539  uint j = 0;
540  for (uint i = 0; i < n_samples * over_; i += over_)
541  {
542  float freq = freq_in[j++];
543 
544  if (stereo)
545  {
546  process<MODE, true> (over_samples_left + i, over_samples_right + i, freq, over_);
547  }
548  else
549  {
550  process<MODE, false> (over_samples_left + i, nullptr, freq, over_);
551  }
552  }
553  }
554  else
555  {
556  if (stereo)
557  {
558  process<MODE, true> (over_samples_left, over_samples_right, freq_, n_samples * over_);
559  }
560  else
561  {
562  process<MODE, false> (over_samples_left, nullptr, freq_, n_samples * over_);
563  }
564  }
565 
566  channels_[0].res_down->process_block (over_samples_left, n_samples * over_, left);
567  if (stereo)
568  channels_[1].res_down->process_block (over_samples_right, n_samples * over_, right);
569  }
570 
571  using ProcessBlockFunc = decltype (&SKFilter::process_block_mode<LP2>);
572 
573  template<size_t... INDICES>
574  static constexpr std::array<ProcessBlockFunc, LAST_MODE + 1>
575  make_jump_table (std::integer_sequence<size_t, INDICES...>)
576  {
577  auto mk_func = [] (auto I) { return &SKFilter::process_block_mode<Mode (I.value)>; };
578 
579  return { mk_func (std::integral_constant<int, INDICES>{})... };
580  }
581 public:
582  void
583  process_block (uint n_samples, float *left, float *right = nullptr, const float *freq_in = nullptr, const float *reso_in = nullptr, const float *drive_in = nullptr)
584  {
585  static constexpr auto jump_table { make_jump_table (std::make_index_sequence<LAST_MODE + 1>()) };
586 
587  while (n_samples)
588  {
589  const uint todo = std::min (n_samples, MAX_BLOCK_SIZE);
590 
591  (this->*jump_table[mode_]) (todo, left, right, freq_in, reso_in, drive_in);
592 
593  if (left)
594  left += todo;
595  if (right)
596  right += todo;
597  if (freq_in)
598  freq_in += todo;
599  if (reso_in)
600  reso_in += todo;
601  if (drive_in)
602  drive_in += todo;
603 
604  n_samples -= todo;
605  }
606  }
607 };
608 
609 } // SpectMorph
Definition: smpandaresampler.hh:99
Definition: smskfilter.hh:14