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 }