1 /**
2     DSP utility functions.
3     They are a range of math function usual in DSP.
4 
5     Copyright: Guillaume Piolat 2015-2024.
6     License:   http://www.boost.org/LICENSE_1_0.txt
7 */
8 module dplug.core.math;
9 
10 import std.math;
11 version(LDC) import ldc.intrinsics;
12 
13 nothrow @nogc:
14 
15 
16 /**
17     TAU is two times PI.
18 */
19 immutable real TAU = PI * 2;
20 
21 
22 /**
23     Map linearly x from the range [a, b] to the range [c, d].
24 */
25 T linmap(T)(T value, T a, T b, T c, T d) pure
26 {
27     return c + (d - c) * (value - a) / (b - a);
28 }
29 unittest
30 {
31     double f = linmap(0.5f, 0.0, 2.0, 4.0, 5.0);
32     assert( f == 4.25 );
33 }
34 
35 
36 /**
37     Map the `[0..1]` range to `[min..max]` logarithmically.
38 
39     Params:
40         t   Interpolating value from [0 to 1]. UB if out of range.
41         min Value corresponding to t = 0. Must be > 0.
42         max Value corresponding to t = 1. Must be > 0.
43 
44     Note: You can totally have a max that is smaller than min.
45           In this case, the range mapped with more accuracy will be
46           small values (around `max` not `min`).
47 */
48 T logmap(T)(T t, T min, T max) pure
49 {
50     assert(min > 0 && max > 0);
51     return min * exp(t * log(max / min));
52 }
53 unittest
54 {
55     assert(isClose(logmap!float(0.5f, 2.0f, 200.0f), 20.0f));
56 }
57 
58 
59 /**
60     Gets a factor for making exponential decay curves, which are also
61     the same thing as a 6dB/oct lowpass filter.
62 
63     Params:
64         timeConstantSecs Time after which the amplitude is only 37%
65                          of the original.
66         samplerate       Sampling rate.
67 
68     Returns:
69         Multiplier for this RC time constant and sampling rate.
70         Use it like this: `smoothed += (v - smoothed) * result;`
71 
72     Note:
73         Using `fast_exp` yield a decay-factor within -180 dB
74         (at 384000hz) of the one obtained with `expm1`.
75         The alleged inaccuracies of plain exp just did not show up so
76         we don't prefer `expm1` anymore. This doesn't change the
77         length of an iterated envelope like `ExpSmoother`.
78         Actually, small variations of a decay factor often results in
79         a misleadingly large RMS difference, that doesn't actually
80         change the sound quality.
81 */
82 double expDecayFactor(double timeConstantSecs,
83                       double samplerate) pure
84 {
85     return 1.0 - fast_exp(-1.0 / (timeConstantSecs * samplerate));
86 }
87 
88 
89 /**
90     Map from MIDI notes to frequency (Hz).
91 */
92 float convertMIDINoteToFrequency(float note) pure
93 {
94     return 440.0f * pow(2.0, (note - 69.0f) / 12.0f);
95 }
96 
97 
98 /**
99     Map from frequency (Hz) to MIDI notes.
100 */
101 float convertFrequencyToMIDINote(float frequency) pure
102 {
103     return 69.0f + 12.0f * log2(frequency / 440.0f);
104 }
105 
106 
107 /**
108     Converts from decibels (dB) to linear gain (aka. voltage).
109 
110     Params:
111         x Value in decibels. Can be -infinity.
112 
113     Returns:
114         A voltage value, linear gain.
115 
116     Note:    0 dB is 1.0 in linear gain.
117             -6 dB is 0.5 in linear gain.
118           -inf dB is 0.0 in linear gain.
119 
120     Precision: This uses fast_exp which under normal conditions has a
121                peak error under -135dB over the useful range.
122                However, keep in mind that different exp function sound
123                differently when modulated.
124 */
125 float convertDecibelToLinearGain(float dB) pure @safe
126 {
127     static immutable float ln10_20 = cast(float)LN10 / 20;
128     return fast_exp(dB * ln10_20);
129 }
130 ///ditto
131 double convertDecibelToLinearGain(double dB) pure @safe
132 {
133     static immutable double ln10_20 = cast(double)LN10 / 20;
134     return fast_exp(dB * ln10_20);
135 }
136 unittest
137 {
138     assert(convertDecibelToLinearGain(-float.infinity) == 0);
139     assert(convertDecibelToLinearGain(-double.infinity) == 0);
140 }
141 
142 
143 /**
144     Converts from linear gain (voltage) to decibels (dB).
145 
146     Params:
147         x Linear gain. Must be >= 0.
148 
149     Returns:
150         A decibel value.
151 
152     Note: 1.0 linear gain is    0 dB in decibels.
153           0.5 linear gain is   -6 dB in decibels.
154           0.0 linear gain is -inf dB in decibels.
155 
156     Precision: This uses `fast_exp` which under normal conditions has
157                a peak error under -135dB over the useful range.
158 */
159 float convertLinearGainToDecibel(float x) pure @safe
160 {
161     static immutable float f20_ln10 = 20 / cast(float)LN10;
162     return fast_log(x) * f20_ln10;
163 }
164 ///ditto
165 double convertLinearGainToDecibel(double x) pure @safe
166 {
167     static immutable double f20_ln10 = 20 / cast(double)LN10;
168     return fast_log(x) * f20_ln10;
169 }
170 unittest
171 {
172     assert(convertLinearGainToDecibel(0.0f) == -float.infinity);
173     assert(convertLinearGainToDecibel(0.0) == -double.infinity);
174 }
175 
176 /**
177     Converts a power value to decibels (dB).
178 
179     Instantaneous power is the squared amplitude of a signal, and can
180     be a nice domain to work in at times.
181 
182     Precision: This uses `fast_exp` which under normal conditions has
183                a peak error under -135 dB over the useful range.
184 */
185 float convertPowerToDecibel(float x) pure @safe
186 {
187     // Explanation:
188     //   20.log10(amplitude)
189     // = 20.log10(sqrt(power))
190     // = 20.log10( 10^(0.5 * log10(power) )
191     // = 10.log10(power)
192     static immutable float f10_ln10 = 10 / cast(float)LN10;
193     return fast_log(x) * f10_ln10;
194 }
195 ///ditto
196 double convertPowerToDecibel(double x) pure @safe
197 {
198     static immutable double f10_ln10 = 10 / cast(double)LN10;
199     return fast_log(x) * f10_ln10;
200 }
201 
202 
203 /// Linear interpolation, akin to GLSL's mix.
204 S lerp(S, T)(S a, S b, T t) pure
205     if (is(typeof(t * b + (1 - t) * a) : S))
206 {
207     return t * b + (1 - t) * a;
208 }
209 
210 /// Same as GLSL smoothstep function.
211 /// See: http://en.wikipedia.org/wiki/Smoothstep
212 T smoothStep(T)(T a, T b, T t) pure
213 {
214     if (t <= a)
215         return 0;
216     else if (t >= b)
217         return 1;
218     else
219     {
220         T x = (t - a) / (b - a);
221         return x * x * (3 - 2 * x);
222     }
223 }
224 
225 
226 /**
227     Normalized sinc function.
228     Returns: `sin(PI*x)/(PI*x)`.
229 */
230 T sinc(T)(T x) pure
231 {
232     if (cast(T)(1) + x * x == cast(T)(1))
233         return 1;
234     else
235         return sin(cast(T)PI * x) / (cast(T)PI * x);
236 }
237 unittest
238 {
239     assert(sinc(0.0) == 1.0);
240 }
241 
242 
243 /// Give back a phase between -PI and PI
244 T normalizePhase(T)(T phase) if (is(T == float) || is(T == double))
245 {
246     static if (D_InlineAsm_Any)
247     {
248         T k_TAU = PI * 2;
249         T result = phase;
250         asm nothrow @nogc
251         {
252             fld k_TAU;    // TAU
253             fld result;   // phase | TAU
254             fprem1;       // normalized(phase) | TAU
255             fstp result;  // TAU
256             fstp ST(0);   //
257         }
258         return result;
259     }
260     else
261     {
262         T res = fmod(phase, cast(T)TAU);
263         if (res > PI)
264             res -= TAU;
265         if (res < -PI)
266             res += TAU;
267         return res;
268     }
269 }
270 unittest
271 {
272     assert(isClose(normalizePhase!float(0.1f), 0.1f));
273     assert(isClose(normalizePhase!float(TAU + 0.1f), 0.1f));
274     assert(isClose(normalizePhase!double(-0.1f), -0.1f));
275     assert(isClose(normalizePhase!double(-TAU - 0.1f), -0.1f));
276 }
277 
278 
279 /**
280     Hermite interpolation.
281 
282     Params:
283         f_pos Position of interpolation between (0 to 1).
284               0 means at position x0, 1 at position x1.
285         xm1   Value of f(x-1)
286         x0    Value of f(x)
287         x1    Value of f(x+1)
288         x2    Value of f(x+2)
289 
290     Returns:
291         An interpolated value corresponding to `f(x0 + f_pos)`.
292 */
293 T hermiteInterp(T)(T f_pos, T xm1, T x0, T x1, T x2) pure
294 {
295     T c = (x1 - xm1) * 0.5f;
296     T v = x0 - x1;
297     T w = c + v;
298     T a = w + v + (x2 - x0) * 0.5f;
299     T b_neg = w + a;
300     return ((((a * f_pos) - b_neg) * f_pos + c) * f_pos + x0);
301 }
302 deprecated("Renamed to hermiteInterp") alias hermite = hermiteInterp;
303 
304 
305 version(D_InlineAsm_X86)
306     private enum D_InlineAsm_Any = true;
307 else version(D_InlineAsm_X86_64)
308     private enum D_InlineAsm_Any = true;
309 else
310     private enum D_InlineAsm_Any = false;
311 
312 // Expose LDC intrinsics, but usable with DMD too.
313 
314 version(LDC)
315 {
316     // Note: wrapper functions wouldn't work (depend on inlining),
317     //       it's much more reliable to use alias for speed gain.
318 
319     // Gives considerable speed improvement over `std.math.exp`.
320     // Exhaustive testing for 32-bit `float` shows
321     // Relative accuracy is within < 0.0002% of std.math.exp
322     // for every possible input.
323     // So a -120 dB inaccuracy, and -140dB the vast majority of the
324     // 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
331     // faster that with a double argument.
332     alias fast_pow = llvm_pow;
333 
334     // Gives measurable speed improvement at audio-rate, without
335     // change for any input.
336     alias fast_fabs = llvm_fabs;
337 
338 
339     alias fast_log2 = llvm_log2;
340     alias fast_exp2 = llvm_exp2;
341     alias fast_log10 = llvm_log10;
342 
343     alias fast_floor = llvm_floor;
344     alias fast_ceil = llvm_ceil;
345     alias fast_trunc = llvm_trunc;
346     alias fast_round = llvm_round;
347 
348     alias fast_sqrt = llvm_sqrt;
349     alias fast_sin = llvm_sin;
350     alias fast_cos = llvm_cos; // no speed change seen when using it
351 }
352 else
353 {
354     alias fast_exp = exp;
355     alias fast_log = log;
356     alias fast_pow = pow;
357 
358     alias fast_fabs = fabs;
359     alias fast_log2 = log2;
360     alias fast_exp2 = exp2;
361     alias fast_log10 = log10;
362 
363     alias fast_floor = floor;
364     alias fast_ceil = ceil;
365     alias fast_trunc = trunc;
366     alias fast_round = round;
367 
368     alias fast_sqrt = sqrt; // UB for operands below -0
369     alias fast_sin = sin;
370     alias fast_cos = cos;
371 }
372 
373 
374 /**
375     Compute the next higher multiple of a pow^2 number.
376 
377     Returns:
378         `x`, multiple of `powerOfTwo`, so that `x >= n`.
379 */
380 size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure @safe
381 {
382     // FUTURE: why is it here in dplug:core?
383 
384     // check power-of-two
385     assert((powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo-1)) == 0));
386 
387     size_t mask = ~(powerOfTwo - 1);
388     return (n + powerOfTwo - 1) & mask;
389 }
390 unittest
391 {
392     assert(nextMultipleOf(0, 4) == 0);
393     assert(nextMultipleOf(1, 4) == 4);
394     assert(nextMultipleOf(2, 4) == 4);
395     assert(nextMultipleOf(3, 4) == 4);
396     assert(nextMultipleOf(4, 4) == 4);
397     assert(nextMultipleOf(5, 4) == 8);
398 }
399 
400 
401 /**
402     Computes next power of 2.
403 
404     Returns:
405         `N` so that `N` is a power of 2 and `N >= i`.
406 
407     Note: This function is NOT equivalent to the builtin
408           `std.math.nextPow2` when the input is a power of 2.
409 */
410 int nextPow2HigherOrEqual(int i) pure
411 {
412     int v = i - 1;
413     v |= v >> 1;
414     v |= v >> 2;
415     v |= v >> 4;
416     v |= v >> 8;
417     v |= v >> 16;
418     v++;
419     return v;
420 }
421 ///ditto
422 long nextPow2HigherOrEqual(long i) pure
423 {
424     long v = i - 1;
425     v |= v >> 1;
426     v |= v >> 2;
427     v |= v >> 4;
428     v |= v >> 8;
429     v |= v >> 16;
430     v |= v >> 32;
431     v++;
432     return v;
433 }
434 unittest
435 {
436     assert(nextPow2HigherOrEqual(0) == 0);
437     assert(nextPow2HigherOrEqual(64) == 64);
438     assert(nextPow2HigherOrEqual(65L) == 128);
439 }
440 
441 
442 /// Returns: true of i is a power of 2.
443 bool isPowerOfTwo(int i) pure @safe
444 {
445     assert(i >= 0);
446     return (i != 0) && ((i & (i - 1)) == 0);
447 }
448 
449 
450 
451 // -------- LINE OF DEPRECATION AREA -----------
452 
453 /**
454     Integer log, rounds towards -inf.
455 
456     Returns: x so that (1 << x) >= i
457 
458     FUTURE: Why is that in dplug:core?
459 */
460 deprecated("Will be removed in Dplug v16") 
461 int iFloorLog2(int i) pure @safe
462 {
463     assert(i >= 1);
464     int result = 0;
465     while (i > 1)
466     {
467         i = i / 2;
468         result = result + 1;
469     }
470     return result;
471 }
472 
473 /// Fletcher and Munson equal-loudness curve
474 /// Reference: Xavier Serra thesis (1989).
475 deprecated("equalLoudnessCurve will be removed in Dplug v16")
476 T equalLoudnessCurve(T)(T frequency) pure
477 {
478     T x = cast(T)0.05 + 4000 / frequency;
479     return x * ( cast(T)10 ^^ x);
480 }
481 
482 /// Is this integer odd?
483 deprecated bool isOdd(T)(T i) pure @safe
484 {
485     return (i & 1) != 0;
486 }
487 
488 /// Is this integer even?
489 deprecated bool isEven(T)(T i) pure @safe
490 {
491     return (i & 1) == 0;
492 }
493 
494 /// SSE approximation of reciprocal square root.
495 deprecated("Use _mm_rcp_ss or 1/sqrt(x) instead")
496 T inverseSqrt(T)(T x) @nogc if (is(T : float) || is(T: double))
497 {
498     version(AsmX86)
499     {
500         static if (is(T == float))
501         {
502             float result;
503 
504             asm pure nothrow @nogc
505             {
506                 movss XMM0, x;
507                 rsqrtss XMM0, XMM0;
508                 movss result, XMM0;
509             }
510             return result;
511         }
512         else
513             return 1 / sqrt(x);
514     }
515     else
516         return 1 / sqrt(x);
517 }
518 
519 deprecated("use frequencyHz * samplingRate instead")
520 float convertFrequencyToNormalizedFrequency(float frequencyHz,
521                                             float samplingRate) pure
522 {
523     return frequencyHz / samplingRate;
524 }
525 
526 deprecated("use freqCyclesPerSample * samplingRate instead")
527 float convertNormalizedFrequencyToFrequency(float freqCyclesPerSample,
528                                             float samplingRate) pure
529 {
530     return freqCyclesPerSample * samplingRate;
531 }
532 
533 /// Quick and dirty sawtooth for testing purposes.
534 deprecated("rawSawtooth will be removed in Dplug v16")
535 T rawSawtooth(T)(T x)
536 {
537     return normalizePhase(x) / (cast(T)PI);
538 }
539 
540 /// Quick and dirty triangle for testing purposes.
541 deprecated("rawTriangle will be removed in Dplug v16")
542 T rawTriangle(T)(T x)
543 {
544     return 1 - normalizePhase(x) / cast(T)PI_2;
545 }
546 
547 /// Quick and dirty square for testing purposes.
548 deprecated("rawSquare will be removed in Dplug v16")
549 T rawSquare(T)(T x)
550 {
551     return normalizePhase(x) > 0 ? 1 : -1;
552 }
553 
554 deprecated("computeRMS will be removed in Dplug v16")
555 T computeRMS(T)(T[] samples) pure
556 {
557     double sum = 0;
558     foreach(sample; samples)
559         sum += sample * sample;
560     return sqrt(sum / cast(int)samples.length);
561 }