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) 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) 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) 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