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

« « « Anklang Documentation
Loading...
Searching...
No Matches
saturationdsp.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#include <array>
3#include <cmath>
4#include <cstdio>
5#include <algorithm>
6
7#define PANDA_RESAMPLER_HEADER_ONLY
8
9#include "pandaresampler.hh"
10
11namespace Ase {
12
14{
15 // https://www.musicdsp.org/en/latest/Other/238-rational-tanh-approximation.html
16 float
17 cheap_tanh (float x)
18 {
19 x = std::clamp (x, -3.0f, 3.0f);
20
21 return (x * (27.0f + x * x) / (27.0f + 9.0f * x * x));
22 }
23
24 /*
25 * tanh function which is restricted in the range [-4:4]
26 */
27 double
28 tanh_restricted (double x)
29 {
30 double sign = 1;
31 if (x < 0)
32 {
33 x = -x;
34 sign = -1;
35 }
36 if (x < 3)
37 return sign * tanh (x);
38 if (x > 4)
39 return sign;
40
41 // use polynomial:
42 // at (+/-)3 : match function value
43 // at (+/-)3 : identical first derivative
44 // at (+/-)4 : function value is 1.0
45 // at (+/-)4 : derivative is 0
46 double th3 = tanh (3);
47 double delta = 1 - th3;
48 double deriv = 1 - th3 * th3;
49 double b = 3 * delta - deriv;
50 double a = delta - b;
51 double x4 = 4 - x;
52 return sign * (1 - (a * x4 + b) * x4 * x4);
53 }
54
55 static constexpr int table_size = 512;
56 static constexpr int oversample = 8;
58 float current_drive = 0;
59 float dest_drive = 0;
60 float drive_max_step = 0;
61 float current_mix = 1;
62 float dest_mix = 1;
63 float mix_max_step = 0;
64
65 void
66 fill_table()
67 {
68 for (size_t x = 0; x < table_size - 2; x++)
69 {
70 double d = (x / double (table_size - 3)) * 8 - 4;
71 table[x + 1] = tanh_restricted (d);
72 }
73 table[0] = table[1];
74 table[table_size - 1] = table[table_size - 2];
75 }
80public:
81 enum class Mode {
82 TANH_TABLE,
83 TANH_TRUE,
84 TANH_CHEAP,
85 HARD_CLIP
86 };
87 Mode mode = Mode::TANH_TABLE;
89 {
90 fill_table();
91
92 using PandaResampler::Resampler2;
93 res_up_left = std::make_unique<Resampler2> (Resampler2::UP, oversample, Resampler2::PREC_72DB);
94 res_up_right = std::make_unique<Resampler2> (Resampler2::UP, oversample, Resampler2::PREC_72DB);
95 res_down_left = std::make_unique<Resampler2> (Resampler2::DOWN, oversample, Resampler2::PREC_72DB);
96 res_down_right = std::make_unique<Resampler2> (Resampler2::DOWN, oversample, Resampler2::PREC_72DB);
97 }
98 void
99 reset (unsigned int sample_rate)
100 {
101 mix_max_step = 1 / (0.050 * sample_rate * oversample); // smooth mix range over 50ms
102 drive_max_step = 6 / (0.020 * sample_rate * oversample); // smooth factor delta of 6dB over 20ms
103
104 res_up_left->reset();
105 res_up_right->reset();
106 }
107 float
108 lookup_table (float f)
109 {
110 float tbl_index = std::clamp ((f + 4) / 8 * (table_size - 3) + 1, 0.5f, table_size - 1.5f);
111 int itbl_index = tbl_index;
112 float frac = tbl_index - itbl_index;
113 return table[itbl_index] + frac * (table[itbl_index + 1] - table[itbl_index]);
114 }
115 void
116 set_drive (float d, bool now)
117 {
118 dest_drive = d;
119 if (now)
120 current_drive = dest_drive;
121 }
122 void
123 set_mix (float percent, bool now)
124 {
125 dest_mix = std::clamp (percent * 0.01, 0.0, 1.0);
126 if (now)
127 current_mix = dest_mix;
128 }
129 void
130 set_mode (Mode new_mode)
131 {
132 mode = new_mode;
133 }
134 template<bool STEREO, bool INCREMENT>
135 void
136 process_sub_block (float *left_over, float *right_over, int n_samples)
137 {
138 float mix_step = std::clamp ((dest_mix - current_mix) / (n_samples * oversample), -mix_max_step, mix_max_step);
139 float drive_step = std::clamp ((dest_drive - current_drive) / (n_samples * oversample), -drive_max_step, drive_max_step);
140
141 float current_factor = exp2f (current_drive / 6);
142 current_drive += drive_step * n_samples * oversample;
143
144 float end_factor = exp2f (current_drive / 6);
145 float factor_step = (end_factor - current_factor) / (n_samples * oversample);
146
147 if (mode == Mode::TANH_TABLE)
148 {
149 for (int i = 0; i < n_samples * oversample; i++)
150 {
151 left_over[i] = lookup_table (left_over[i] * current_factor) * current_mix + left_over[i] * (1 - current_mix);
152 if (STEREO)
153 right_over[i] = lookup_table (right_over[i] * current_factor) * current_mix + right_over[i] * (1 - current_mix);
154 if (INCREMENT)
155 {
156 current_mix += mix_step;
157 current_factor += factor_step;
158 }
159 }
160 }
161 if (mode == Mode::TANH_TRUE)
162 {
163 for (int i = 0; i < n_samples * oversample; i++)
164 {
165 left_over[i] = std::tanh (left_over[i] * current_factor) * current_mix + left_over[i] * (1 - current_mix);
166 if (STEREO)
167 right_over[i] = std::tanh (right_over[i] * current_factor) * current_mix + right_over[i] * (1 - current_mix);
168 if (INCREMENT)
169 {
170 current_mix += mix_step;
171 current_factor += factor_step;
172 }
173 }
174 }
175 if (mode == Mode::TANH_CHEAP)
176 {
177 for (int i = 0; i < n_samples * oversample; i++)
178 {
179 left_over[i] = cheap_tanh (left_over[i] * current_factor) * current_mix + left_over[i] * (1 - current_mix);
180 if (STEREO)
181 right_over[i] = cheap_tanh (right_over[i] * current_factor) * current_mix + right_over[i] * (1 - current_mix);
182 if (INCREMENT)
183 {
184 current_mix += mix_step;
185 current_factor += factor_step;
186 }
187 }
188 }
189 if (mode == Mode::HARD_CLIP)
190 {
191 for (int i = 0; i < n_samples * oversample; i++)
192 {
193 left_over[i] = std::clamp (left_over[i] * current_factor, -1.f, 1.f) * current_mix + left_over[i] * (1 - current_mix);
194 if (STEREO)
195 right_over[i] = std::clamp (right_over[i] * current_factor, -1.f, 1.f) * current_mix + right_over[i] * (1 - current_mix);
196 if (INCREMENT)
197 {
198 current_mix += mix_step;
199 current_factor += factor_step;
200 }
201 }
202 }
203 }
204 template<bool STEREO>
205 void
206 process (float *left_in, float *right_in, float *left_out, float *right_out, int n_samples)
207 {
208 float left_over[oversample * n_samples];
209 float right_over[oversample * n_samples];
210
211 res_up_left->process_block (left_in, n_samples, left_over);
212 if (STEREO)
213 res_up_right->process_block (right_in, n_samples, right_over);
214
215 int pos = 0;
216 while (pos < n_samples)
217 {
218 if (std::abs (dest_drive - current_drive) > 0.001 || std::abs (dest_mix - current_mix) > 0.001)
219 {
220 // SLOW: drive or mix change within the block
221 int todo = std::min (n_samples - pos, 64);
222 process_sub_block<STEREO, true> (left_over + pos * oversample, right_over + pos * oversample, todo);
223 pos += todo;
224 }
225 else
226 {
227 // FAST: drive and mix remain constant during the block
228 process_sub_block<STEREO, false> (left_over + pos * oversample, right_over + pos * oversample, n_samples - pos);
229 pos = n_samples;
230 }
231 }
232
233 res_down_left->process_block (left_over, oversample * n_samples, left_out);
234 if (STEREO)
235 res_down_right->process_block (right_over, oversample * n_samples, right_out);
236 }
237};
238
239
240}
T clamp(T... args)
exp2f
typedef double
T min(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
T tanh(T... args)
tanh