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

« « « Anklang Documentation
Loading...
Searching...
No Matches
laddervcf.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
8#include <array>
9#include <algorithm>
10#include <cassert>
11#include <cmath>
12
13namespace Ase {
14
15using PandaResampler::Resampler2;
16
18{
19public:
20 enum Mode {
21 LP1, LP2, LP3, LP4
22 };
23private:
24 struct Channel {
25 float x1, x2, x3, x4;
26 float y1, y2, y3, y4;
27
30 };
31 std::array<Channel, 2> channels_;
32 Mode mode_;
33 float rate_ = 0;
34 float freq_scale_factor_ = 0;
35 float frequency_range_min_ = 0;
36 float frequency_range_max_ = 0;
37 float clamp_freq_min_ = 0;
38 float clamp_freq_max_ = 0;
39 float freq_ = 440;
40 float reso_ = 0;
41 float drive_ = 0;
42 float global_volume_ = 1;
43 uint over_ = 0;
44 bool test_linear_ = false;
45
46 static constexpr uint MAX_BLOCK_SIZE = 1024;
47
48 struct FParams
49 {
50 float reso = 0;
51 float pre_scale = 1;
52 float post_scale = 1;
53 };
54 FParams fparams_;
55 bool fparams_valid_ = false;
56public:
57 LadderVCF (int over) :
58 over_ (over)
59 {
60 for (auto& channel : channels_)
61 {
62 channel.res_up = std::make_unique<Resampler2> (Resampler2::UP, over_, Resampler2::PREC_72DB);
63 channel.res_down = std::make_unique<Resampler2> (Resampler2::DOWN, over_, Resampler2::PREC_72DB);
64 }
65 set_mode (Mode::LP4);
66 set_rate (48000);
67 set_frequency_range (10, 24000);
68 reset();
69 }
70 void
71 set_mode (Mode new_mode)
72 {
73 mode_ = new_mode;
74 }
75 void
76 set_freq (float freq)
77 {
78 freq_ = freq;
79 }
80 void
81 set_reso (float reso)
82 {
83 reso_ = reso;
84 fparams_valid_ = false;
85 }
86 void
87 set_drive (float drive)
88 {
89 drive_ = drive;
90 fparams_valid_ = false;
91 }
92 void
93 set_global_volume (float global_volume)
94 {
95 /* every samples that is processed by the filter is
96 * - multiplied with global_volume before processing
97 * - divided by global_volume after processing
98 * which has an effect on the non-linear part of the filter (drive)
99 */
100 global_volume_ = global_volume;
101 fparams_valid_ = false;
102 }
103 void
104 set_test_linear (bool test_linear)
105 {
106 test_linear_ = test_linear;
107 fparams_valid_ = false;
108 }
109 void
110 set_rate (float r)
111 {
112 rate_ = r;
113 freq_scale_factor_ = 2 * M_PI / (rate_ * over_);
114
115 update_frequency_range();
116 }
117 void
118 set_frequency_range (float min_freq, float max_freq)
119 {
120 frequency_range_min_ = min_freq;
121 frequency_range_max_ = max_freq;
122
123 update_frequency_range();
124 }
125 void
126 reset()
127 {
128 for (auto& c : channels_)
129 {
130 c.x1 = c.x2 = c.x3 = c.x4 = 0;
131 c.y1 = c.y2 = c.y3 = c.y4 = 0;
132
133 c.res_up->reset();
134 c.res_down->reset();
135 }
136 fparams_valid_ = false;
137 }
138 double
139 delay()
140 {
141 return channels_[0].res_up->delay() / over_ + channels_[0].res_down->delay();
142 }
143private:
144 void
145 update_frequency_range()
146 {
147 /* we want to clamp to the user defined range (set_frequency_range())
148 * but also enforce that the filter is well below nyquist frequency
149 */
150 clamp_freq_min_ = frequency_range_min_;
151 clamp_freq_max_ = std::min (frequency_range_max_, rate_ * over_ * 0.49f);
152 }
153 void
154 setup_reso_drive (FParams& fparams, float reso, float drive)
155 {
156 reso = std::clamp (reso, 0.001f, 1.f);
157
158 if (test_linear_) // test filter as linear filter; don't do any resonance correction
159 {
160 const float scale = 1e-5;
161 fparams.pre_scale = scale;
162 fparams.post_scale = 1 / scale;
163 fparams.reso = reso * 4;
164
165 return;
166 }
167 const float db_x2_factor = 0.166096404744368; // 1/(20*log(2)/log(10))
168
169 // scale signal down (without normalization on output) for negative drive
170 float negative_drive_vol = 1;
171 if (drive < 0)
172 {
173 negative_drive_vol = exp2f (drive * db_x2_factor);
174 drive = 0;
175 }
176 // drive resonance boost
177 if (drive > 0)
178 reso += drive * sqrt (reso) * reso * 0.03f;
179
180 float vol = exp2f ((drive + -12 * sqrt (reso)) * db_x2_factor);
181 fparams.pre_scale = negative_drive_vol * vol * global_volume_;
182 fparams.post_scale = std::max (1 / vol, 1.0f) / global_volume_;
183 fparams.reso = sqrt (reso) * 4;
184 }
185 static float
186 tanh_approx (float x)
187 {
188 // https://www.musicdsp.org/en/latest/Other/238-rational-tanh-approximation.html
189 x = std::clamp (x, -3.0f, 3.0f);
190
191 return x * (27.0f + x * x) / (27.0f + 9.0f * x * x);
192 }
193 /*
194 * This ladder filter implementation is mainly based on
195 *
196 * Välimäki, Vesa & Huovilainen, Antti. (2006).
197 * Oscillator and Filter Algorithms for Virtual Analog Synthesis.
198 * Computer Music Journal. 30. 19-31. 10.1162/comj.2006.30.2.19.
199 */
200 template<Mode MODE, bool STEREO> inline void
201 run (float *left, float *right, float freq, uint n_samples)
202 {
203 const float fc = std::clamp (freq, clamp_freq_min_, clamp_freq_max_) * freq_scale_factor_;
204 const float g = 0.9892f * fc - 0.4342f * fc * fc + 0.1381f * fc * fc * fc - 0.0202f * fc * fc * fc * fc;
205 const float b0 = g * (1 / 1.3f);
206 const float b1 = g * (0.3f / 1.3f);
207 const float a1 = g - 1;
208
209 float res = fparams_.reso;
210 res *= 1.0029f + 0.0526f * fc - 0.0926f * fc * fc + 0.0218f * fc * fc * fc;
211
212 for (uint os = 0; os < n_samples; os++)
213 {
214 for (uint i = 0; i < (STEREO ? 2 : 1); i++)
215 {
216 float &value = i == 0 ? left[os] : right[os];
217
218 Channel& c = channels_[i];
219 const float x = value * fparams_.pre_scale;
220 const float g_comp = 0.5f; // passband gain correction
221 const float x0 = tanh_approx (x - (c.y4 - g_comp * x) * res);
222
223 c.y1 = b0 * x0 + b1 * c.x1 - a1 * c.y1;
224 c.x1 = x0;
225
226 c.y2 = b0 * c.y1 + b1 * c.x2 - a1 * c.y2;
227 c.x2 = c.y1;
228
229 c.y3 = b0 * c.y2 + b1 * c.x3 - a1 * c.y3;
230 c.x3 = c.y2;
231
232 c.y4 = b0 * c.y3 + b1 * c.x4 - a1 * c.y4;
233 c.x4 = c.y3;
234
235 switch (MODE)
236 {
237 case LP1:
238 value = c.y1 * fparams_.post_scale;
239 break;
240 case LP2:
241 value = c.y2 * fparams_.post_scale;
242 break;
243 case LP3:
244 value = c.y3 * fparams_.post_scale;
245 break;
246 case LP4:
247 value = c.y4 * fparams_.post_scale;
248 break;
249 default:
250 assert (false);
251 }
252 }
253 }
254 }
255 template<Mode MODE, bool STEREO> inline void
256 do_process_block (uint n_samples,
257 float *left,
258 float *right,
259 const float *freq_in,
260 const float *reso_in,
261 const float *drive_in)
262 {
263 float over_samples_left[over_ * n_samples];
264 float over_samples_right[over_ * n_samples];
265
266 channels_[0].res_up->process_block (left, n_samples, over_samples_left);
267 if (STEREO)
268 channels_[1].res_up->process_block (right, n_samples, over_samples_right);
269
270 if (!fparams_valid_)
271 {
272 setup_reso_drive (fparams_, reso_in ? reso_in[0] : reso_, drive_in ? drive_in[0] : drive_);
273 fparams_valid_ = true;
274 }
275
276 if (reso_in || drive_in)
277 {
278 /* for reso or drive modulation, we split the input it into small blocks
279 * and interpolate the pre_scale / post_scale / reso parameters
280 */
281 float *left_blk = over_samples_left;
282 float *right_blk = over_samples_right;
283
284 uint n_remaining_samples = n_samples;
285 while (n_remaining_samples)
286 {
287 const uint todo = std::min<uint> (n_remaining_samples, 64);
288
289 FParams fparams_end;
290 setup_reso_drive (fparams_end, reso_in ? reso_in[todo - 1] : reso_, drive_in ? drive_in[todo - 1] : drive_);
291
292 float todo_inv = 1.f / todo;
293 float delta_pre_scale = (fparams_end.pre_scale - fparams_.pre_scale) * todo_inv;
294 float delta_post_scale = (fparams_end.post_scale - fparams_.post_scale) * todo_inv;
295 float delta_reso = (fparams_end.reso - fparams_.reso) * todo_inv;
296
297 uint j = 0;
298 for (uint i = 0; i < todo * over_; i += over_)
299 {
300 fparams_.pre_scale += delta_pre_scale;
301 fparams_.post_scale += delta_post_scale;
302 fparams_.reso += delta_reso;
303
304 float freq = freq_in ? freq_in[j++] : freq_;
305
306 run<MODE, STEREO> (left_blk + i, right_blk + i, freq, over_);
307 }
308
309 n_remaining_samples -= todo;
310 left_blk += todo * over_;
311 right_blk += todo * over_;
312
313 if (freq_in)
314 freq_in += todo;
315 if (reso_in)
316 reso_in += todo;
317 if (drive_in)
318 drive_in += todo;
319 }
320 }
321 else if (freq_in)
322 {
323 uint over_pos = 0;
324
325 for (uint i = 0; i < n_samples; i++)
326 {
327 run<MODE, STEREO> (over_samples_left + over_pos, over_samples_right + over_pos, freq_in[i], over_);
328 over_pos += over_;
329 }
330 }
331 else
332 {
333 run<MODE, STEREO> (over_samples_left, over_samples_right, freq_, n_samples * over_);
334 }
335 channels_[0].res_down->process_block (over_samples_left, over_ * n_samples, left);
336 if (STEREO)
337 channels_[1].res_down->process_block (over_samples_right, over_ * n_samples, right);
338 }
339 template<Mode MODE> inline void
340 process_block_mode (uint n_samples,
341 float *left,
342 float *right,
343 const float *freq_in,
344 const float *reso_in,
345 const float *drive_in)
346 {
347 if (right) // stereo?
348 do_process_block<MODE, true> (n_samples, left, right, freq_in, reso_in, drive_in);
349 else
350 do_process_block<MODE, false> (n_samples, left, right, freq_in, reso_in, drive_in);
351 }
352public:
353 void
354 process_block (uint n_samples,
355 float *left,
356 float *right = nullptr,
357 const float *freq_in = nullptr,
358 const float *reso_in = nullptr,
359 const float *drive_in = nullptr)
360 {
361 while (n_samples)
362 {
363 const uint todo = std::min (n_samples, MAX_BLOCK_SIZE);
364
365 switch (mode_)
366 {
367 case LP4: process_block_mode<LP4> (todo, left, right, freq_in, reso_in, drive_in);
368 break;
369 case LP3: process_block_mode<LP3> (todo, left, right, freq_in, reso_in, drive_in);
370 break;
371 case LP2: process_block_mode<LP2> (todo, left, right, freq_in, reso_in, drive_in);
372 break;
373 case LP1: process_block_mode<LP1> (todo, left, right, freq_in, reso_in, drive_in);
374 break;
375 }
376
377 if (left)
378 left += todo;
379 if (right)
380 right += todo;
381 if (freq_in)
382 freq_in += todo;
383 if (reso_in)
384 reso_in += todo;
385 if (drive_in)
386 drive_in += todo;
387
388 n_samples -= todo;
389 }
390 }
391};
392
393} // SpectMorph
assert
T clamp(T... args)
exp2f
T max(T... args)
T min(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
sqrt
y1