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