1 /**
2 DSP utility functions. 
3 
4 Copyright: Guillaume Piolat 2015-2016.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.core.math;
8 
9 import std.math;
10 
11 version(LDC)
12 {
13     import ldc.intrinsics;
14 }
15 
16 immutable real TAU = PI * 2;
17 
18 /// Map linearly x from the range [a, b] to the range [c, d]
19 T linmap(T)(T value, T a, T b, T c, T d) pure nothrow @nogc
20 {
21     return c + (d - c) * (value - a) / (b - a);
22 }
23 
24 /// map [0..1] to [min..max] logarithmically
25 /// min and max must be all > 0, t in [0..1]
26 /// Note: you can totally have a max that is smaller than min.
27 ///       In this cases, the range mapped with more accuracy are still small values.
28 T logmap(T)(T t, T min, T max) pure nothrow @nogc
29 {
30     assert(min > 0 && max > 0);
31     T ratio = max / min;
32     return min * exp(t * log(max / min));
33 }
34 
35 /// Hermite interpolation.
36 T hermite(T)(T frac_pos, T xm1, T x0, T x1, T x2) pure nothrow @nogc
37 {
38     T c = (x1 - xm1) * 0.5f;
39     T v = x0 - x1;
40     T w = c + v;
41     T a = w + v + (x2 - x0) * 0.5f;
42     T b_neg = w + a;
43     return ((((a * frac_pos) - b_neg) * frac_pos + c) * frac_pos + x0);
44 }
45 
46 /// Converts from dB to linear gain.
47 /// Precision: This uses fast_exp which under normal conditions has a peak error under -135dB over the useful range.
48 T convertDecibelToLinearGain(T)(T dB) pure nothrow @nogc
49 {
50     static immutable T ln10_20 = cast(T)LN10 / 20;
51     return fast_exp(dB * ln10_20);
52 }
53 unittest
54 {
55     assert(convertDecibelToLinearGain!float(-float.infinity) == 0);
56     assert(convertDecibelToLinearGain!double(-double.infinity) == 0);
57 }
58 
59 /// Converts from linear gain to dB.
60 /// Precision: This uses fast_exp which under normal conditions has a peak error under -135dB over the useful range.
61 T convertLinearGainToDecibel(T)(T x) pure nothrow @nogc
62 {
63     static immutable T f20_ln10 = 20 / cast(T)LN10;
64     return fast_log(x) * f20_ln10;
65 }
66 unittest
67 {
68     assert(convertLinearGainToDecibel!float(0) == -float.infinity);
69     assert(convertLinearGainToDecibel!double(0) == -double.infinity);
70 }
71 
72 /// Converts from power to dB. 
73 /// Instantaneous power is the squared amplitude of a signal, and can be a 
74 /// nice domain to work in at times.
75 /// Precision: This uses fast_exp which under normal conditions has a peak error under -135dB over the useful range.
76 T convertPowerToDecibel(T)(T x) pure nothrow @nogc
77 {
78     // Explanation:
79     //   20.log10(amplitude) 
80     // = 20.log10(sqrt(power)) 
81     // = 20.log10( 10^(0.5 * log10(power) )
82     // = 10.log10(power)
83     static immutable T f10_ln10 = 10 / cast(T)LN10;
84     return fast_log(x) * f10_ln10;
85 }
86 
87 /// Is this integer odd?
88 bool isOdd(T)(T i) pure nothrow @nogc @safe
89 {
90     return (i & 1) != 0;
91 }
92 
93 /// Is this integer even?
94 bool isEven(T)(T i) pure nothrow @nogc @safe
95 {
96     return (i & 1) == 0;
97 }
98 
99 /// Returns: true of i is a power of 2.
100 bool isPowerOfTwo(int i) pure nothrow @nogc @safe
101 {
102     assert(i >= 0);
103     return (i != 0) && ((i & (i - 1)) == 0);
104 }
105 
106 /// Returns: x, multiple of powerOfTwo, so that x >= n.
107 size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure nothrow @nogc @safe
108 {
109     // check power-of-two
110     assert( (powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo - 1)) == 0));
111 
112     size_t mask = ~(powerOfTwo - 1);
113     return (n + powerOfTwo - 1) & mask;
114 }
115 
116 unittest
117 {
118     assert(nextMultipleOf(0, 4) == 0);
119     assert(nextMultipleOf(1, 4) == 4);
120     assert(nextMultipleOf(2, 4) == 4);
121     assert(nextMultipleOf(3, 4) == 4);
122     assert(nextMultipleOf(4, 4) == 4);
123     assert(nextMultipleOf(5, 4) == 8);
124 }
125 
126 
127 /// Computes next power of 2.
128 /// Returns: N so that N is a power of 2 and N >= i.
129 /// Note: This function is not equivalent to the builtin `std.math.nextPow2` when the input is a power of 2.
130 int nextPow2HigherOrEqual(int i) pure nothrow @nogc
131 {
132     int v = i - 1;
133     v |= v >> 1;
134     v |= v >> 2;
135     v |= v >> 4;
136     v |= v >> 8;
137     v |= v >> 16;
138     v++;
139     return v;
140 }
141 
142 ///ditto
143 long nextPow2HigherOrEqual(long i) pure nothrow @nogc
144 {
145     long v = i - 1;
146     v |= v >> 1;
147     v |= v >> 2;
148     v |= v >> 4;
149     v |= v >> 8;
150     v |= v >> 16;
151     v |= v >> 32;
152     v++;
153     return v;
154 }
155 
156 /// Returns: x so that (1 << x) >= i
157 int iFloorLog2(int i) pure nothrow @nogc
158 {
159     assert(i >= 1);
160     int result = 0;
161     while (i > 1)
162     {
163         i = i / 2;
164         result = result + 1;
165     }
166     return result;
167 }
168 
169 /// Mapping from MIDI notes to frequency.
170 float convertMIDINoteToFrequency(float note) pure nothrow @nogc
171 {
172     return 440.0f * pow(2.0, (note - 69.0f) / 12.0f);
173 }
174 
175 /// Mapping from frequency to MIDI notes.
176 float convertFrequencyToMIDINote(float frequency) pure nothrow @nogc
177 {
178     return 69.0f + 12.0f * log2(frequency / 440.0f);
179 }
180 
181 /// Fletcher and Munson equal-loudness curve
182 /// Reference: Xavier Serra thesis (1989).
183 T equalLoudnessCurve(T)(T frequency) pure nothrow @nogc
184 {
185     T x = cast(T)0.05 + 4000 / frequency;
186     return x * ( cast(T)10 ^^ x);
187 }
188 
189 /// Cardinal sine
190 T sinc(T)(T x) pure nothrow @nogc
191 {
192     if (cast(T)(1) + x * x == cast(T)(1))
193         return 1;
194     else
195         return sin(cast(T)PI * x) / (cast(T)PI * x);
196 }
197 
198 /// Gets a factor for making exponential decay curves, which are the same thing
199 /// as a 6dB/oct lowpass filter.
200 ///
201 /// Returns: Multiplier for this RC time constant and sampling rate.
202 ///
203 /// Params:
204 ///    timeConstantInSeconds = Time after which the amplitude is only 37% of the original.
205 ///    samplerate = Sampling rate.
206 ///
207 /// Note: Using `fast_exp` yield a decay-factor within -180 dB (at 384000hz) of the one obtained with `expm1`.
208 ///       The alleged "innacuracies" of plain exp just did not show up so we don't prefer `expm1` anymore.
209 ///       It doesn't change the length of an iterated envelope like `ExpSmoother`.
210 ///
211 double expDecayFactor(double timeConstantInSeconds, double samplerate) pure nothrow @nogc
212 {
213     return 1.0 - fast_exp(-1.0 / (timeConstantInSeconds * samplerate));
214 }
215 
216 /// Give back a phase between -PI and PI
217 T normalizePhase(T)(T phase) nothrow @nogc if (is(T == float) || is(T == double))
218 {
219     static if (D_InlineAsm_Any)
220     {
221         T k_TAU = PI * 2;
222         T result = phase;
223         asm nothrow @nogc
224         {
225             fld k_TAU;    // TAU
226             fld result;    // phase | TAU
227             fprem1;       // normalized(phase) | TAU
228             fstp result;   // TAU
229             fstp ST(0);   //
230         }
231         return result;
232     }
233     else
234     {
235         T res = fmod(phase, cast(T)TAU);
236         if (res > PI)
237             res -= TAU;
238         if (res < -PI)
239             res += TAU;
240         return res;
241     }
242 }
243 
244 unittest
245 {
246     assert(isClose(normalizePhase!float(0.1f), 0.1f));
247     assert(isClose(normalizePhase!float(TAU + 0.1f), 0.1f));
248 
249     assert(isClose(normalizePhase!double(-0.1f), -0.1f));
250     assert(isClose(normalizePhase!double(-TAU - 0.1f), -0.1f));
251 }
252 
253 /// Quick and dirty sawtooth for testing purposes.
254 T rawSawtooth(T)(T x) pure nothrow @nogc
255 {
256     return normalizePhase(x) / (cast(T)PI);
257 }
258 
259 /// Quick and dirty triangle for testing purposes.
260 T rawTriangle(T)(T x) pure nothrow @nogc
261 {
262     return 1 - normalizePhase(x) / cast(T)PI_2;
263 }
264 
265 /// Quick and dirty square for testing purposes.
266 T rawSquare(T)(T x) pure nothrow @nogc
267 {
268     return normalizePhase(x) > 0 ? 1 : -1;
269 }
270 
271 T computeRMS(T)(T[] samples) pure nothrow @nogc
272 {
273     double sum = 0;
274     foreach(sample; samples)
275         sum += sample * sample;
276     return sqrt(sum / cast(int)samples.length);
277 }
278 
279 unittest
280 {
281     double[] d = [4, 5, 6];
282     computeRMS(d);
283 }
284 
285 
286 version(D_InlineAsm_X86)
287     private enum D_InlineAsm_Any = true;
288 else version(D_InlineAsm_X86_64)
289     private enum D_InlineAsm_Any = true;
290 else
291     private enum D_InlineAsm_Any = false;
292 
293 // Expose LDC intrinsics, but usable with DMD too.
294 
295 version(LDC)
296 {
297     // Note: wrapper functions wouldn't work (depend on inlining), 
298     //       it's much more reliable to use alias for speed gain.
299 
300     // Gives considerable speed improvement over `std.math.exp`.
301     // Exhaustive testing for 32-bit `float` shows
302     // Relative accuracy is within < 0.0002% of std.math.exp
303     // for every possible input.
304     // So a -120 dB inaccuracy, and -140dB the vast majority of the time.
305     alias fast_exp = llvm_exp; 
306 
307 
308     alias fast_log = llvm_log;
309 
310     // Note: fast_pow with a float argument (`powf`) can be a lot faster that with a double argument.
311     alias fast_pow = llvm_pow;
312 
313     // Gives measurable speed improvement at audio-rate, without change for any input.
314     alias fast_fabs = llvm_fabs;
315 
316 
317     alias fast_log2 = llvm_log2;
318     alias fast_exp2 = llvm_exp2;
319     alias fast_log10 = llvm_log10;
320 
321     alias fast_floor = llvm_floor;
322     alias fast_ceil = llvm_ceil;
323     alias fast_trunc = llvm_trunc;
324     alias fast_round = llvm_round;
325 
326     alias fast_sqrt = llvm_sqrt;
327     alias fast_sin = llvm_sin;
328     alias fast_cos = llvm_cos; // no speed change seen when using it
329 }
330 else
331 {
332     alias fast_exp = exp;
333     alias fast_log = log;
334     alias fast_pow = pow;
335 
336     alias fast_fabs = fabs;
337     alias fast_log2 = log2;
338     alias fast_exp2 = exp2;
339     alias fast_log10 = log10;
340 
341     alias fast_floor = floor;
342     alias fast_ceil = ceil;
343     alias fast_trunc = trunc;
344     alias fast_round = round;
345 
346     alias fast_sqrt = sqrt; // Undefined behaviour for operands below -0
347     alias fast_sin = sin;
348     alias fast_cos = cos;
349 }
350 
351 unittest
352 {
353     assert(isClose(fast_exp2(0.1), std.math.exp2(0.1)));
354 }
355 
356 /// Linear interpolation, akin to GLSL's mix.
357 S lerp(S, T)(S a, S b, T t) pure nothrow @nogc 
358     if (is(typeof(t * b + (1 - t) * a) : S))
359 {
360     return t * b + (1 - t) * a;
361 }
362 
363 /// Same as GLSL smoothstep function.
364 /// See: http://en.wikipedia.org/wiki/Smoothstep
365 T smoothStep(T)(T a, T b, T t) pure nothrow @nogc 
366 {
367     if (t <= a)
368         return 0;
369     else if (t >= b)
370         return 1;
371     else
372     {
373         T x = (t - a) / (b - a);
374         return x * x * (3 - 2 * x);
375     }
376 }
377 
378 /// SSE approximation of reciprocal square root.
379 T inverseSqrt(T)(T x) pure nothrow @nogc if (is(T : float) || is(T: double))
380 {
381     version(AsmX86)
382     {
383         static if (is(T == float))
384         {
385             float result;
386 
387             asm pure nothrow @nogc 
388             {
389                 movss XMM0, x; 
390                 rsqrtss XMM0, XMM0; 
391                 movss result, XMM0; 
392             }
393             return result;
394         }
395         else
396             return 1 / sqrt(x);
397     }
398     else
399         return 1 / sqrt(x);
400 }
401 
402 unittest
403 {
404     assert(abs( inverseSqrt!float(1) - 1) < 1e-3 );
405     assert(abs( inverseSqrt!double(1) - 1) < 1e-3 );
406 }
407 
408 /// Computes a normalized frequency form a frequency.
409 float convertFrequencyToNormalizedFrequency(float frequencyHz, float samplingRate) pure nothrow @nogc
410 {
411     return frequencyHz / samplingRate;
412 }
413 
414 /// Computes a frequency.
415 float convertNormalizedFrequencyToFrequency(float frequencyCyclesPerSample, float samplingRate) pure nothrow @nogc
416 {
417     return frequencyCyclesPerSample * samplingRate;
418 }
419