Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

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