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