1 /** 2 * DSP utility functions. 3 * 4 * Copyright: Copyright Auburn Sounds 2015-2016 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Authors: Guillaume Piolat 7 */ 8 module dplug.core.math; 9 10 import std.math; 11 12 version(LDC) 13 { 14 import ldc.intrinsics; 15 } 16 17 immutable real TAU = PI * 2; 18 19 /// Map linearly x from the range [a, b] to the range [c, d] 20 T linmap(T)(T value, T a, T b, T c, T d) pure nothrow @nogc 21 { 22 return c + (d - c) * (value - a) / (b - a); 23 } 24 25 /// map [0..1] to [min..max] logarithmically 26 /// min and max must be all > 0, t in [0..1] 27 T logmap(T)(T t, T min, T max) pure nothrow @nogc 28 { 29 assert(min < max); 30 return min * exp(t * log(max / min)); 31 } 32 33 /// Hermite interpolation. 34 T hermite(T)(T frac_pos, T xm1, T x0, T x1, T x2) pure nothrow @nogc 35 { 36 T c = (x1 - xm1) * 0.5f; 37 T v = x0 - x1; 38 T w = c + v; 39 T a = w + v + (x2 - x0) * 0.5f; 40 T b_neg = w + a; 41 return ((((a * frac_pos) - b_neg) * frac_pos + c) * frac_pos + x0); 42 } 43 44 /// Convert from dB to float. 45 T deciBelToFloat(T)(T dB) pure nothrow @nogc 46 { 47 static immutable T ln10_20 = cast(T)LN10 / 20; 48 return exp(dB * ln10_20); 49 } 50 51 /// Convert from float to dB 52 T floatToDeciBel(T)(T x) pure nothrow @nogc 53 { 54 static immutable T f20_ln10 = 20 / cast(T)LN10; 55 return log(x) * f20_ln10; 56 } 57 58 /// Is this integer odd? 59 bool isOdd(T)(T i) pure nothrow @nogc 60 { 61 return (i & 1) != 0; 62 } 63 64 /// Is this integer even? 65 bool isEven(T)(T i) pure nothrow @nogc 66 { 67 return (i & 1) == 0; 68 } 69 70 /// Returns: true of i is a power of 2. 71 bool isPowerOfTwo(int i) pure nothrow @nogc 72 { 73 assert(i >= 0); 74 return (i != 0) && ((i & (i - 1)) == 0); 75 } 76 77 /// Computes next power of 2. 78 int nextPowerOf2(int i) pure nothrow @nogc 79 { 80 int v = i - 1; 81 v |= v >> 1; 82 v |= v >> 2; 83 v |= v >> 4; 84 v |= v >> 8; 85 v |= v >> 16; 86 v++; 87 return v; 88 } 89 90 /// Computes next power of 2. 91 long nextPowerOf2(long i) pure nothrow @nogc 92 { 93 long v = i - 1; 94 v |= v >> 1; 95 v |= v >> 2; 96 v |= v >> 4; 97 v |= v >> 8; 98 v |= v >> 16; 99 v |= v >> 32; 100 v++; 101 return v; 102 } 103 104 /// Returns: x so that (1 << x) >= i 105 int iFloorLog2(int i) pure nothrow @nogc 106 { 107 assert(i >= 1); 108 int result = 0; 109 while (i > 1) 110 { 111 i = i / 2; 112 result = result + 1; 113 } 114 return result; 115 } 116 117 /// Mapping from MIDI notes to frequency. 118 float convertMIDINoteToFrequency(float note) pure nothrow @nogc 119 { 120 return 440.0f * pow(2.0, (note - 69.0f) / 12.0f); 121 } 122 123 /// Mapping from frequency to MIDI notes. 124 float convertFrequencyToMIDINote(float frequency) pure nothrow @nogc 125 { 126 return 69.0f + 12.0f * log2(frequency / 440.0f); 127 } 128 129 /// Fletcher and Munson equal-loudness curve 130 /// Reference: Xavier Serra thesis (1989). 131 T equalLoudnessCurve(T)(T frequency) pure nothrow @nogc 132 { 133 T x = cast(T)0.05 + 4000 / frequency; 134 return x * ( cast(T)10 ^^ x); 135 } 136 137 /// Cardinal sine 138 T sinc(T)(T x) pure nothrow @nogc 139 { 140 if (cast(T)(1) + x * x == cast(T)(1)) 141 return 1; 142 else 143 return sin(cast(T)PI * x) / (cast(T)PI * x); 144 } 145 146 147 /// Gets a factor for making exponential decay curves. 148 /// 149 /// Returns: Multiplier for this time constant and sampling rate. 150 /// 151 /// Params: 152 /// timeConstantInSeconds = Time after which the amplitude is only 37% of the original. 153 /// samplerate = Sampling rate. 154 double expDecayFactor(double timeConstantInSeconds, double samplerate) pure nothrow @nogc 155 { 156 // 1 - exp(-time * sampleRate) would yield innacuracies 157 return -expm1(-1.0 / (timeConstantInSeconds * samplerate)); 158 } 159 160 /// Give back a phase between -PI and PI 161 T normalizePhase(T)(T phase) nothrow @nogc 162 { 163 enum bool Assembly = D_InlineAsm_Any && !(is(Unqual!T == real)); 164 165 static if (Assembly) 166 { 167 T k_TAU = PI * 2; 168 T result = phase; 169 asm nothrow @nogc 170 { 171 fld k_TAU; // TAU 172 fld result; // phase | TAU 173 fprem1; // normalized(phase) | TAU 174 fstp result; // TAU 175 fstp ST(0); // 176 } 177 return result; 178 } 179 else 180 { 181 T res = fmod(phase, cast(T)TAU); 182 if (res > PI) 183 res -= TAU; 184 if (res < -PI) 185 res += TAU; 186 return res; 187 } 188 } 189 190 unittest 191 { 192 assert(approxEqual(normalizePhase!real(TAU), 0)); 193 194 assert(approxEqual(normalizePhase!float(0.1f), 0.1f)); 195 assert(approxEqual(normalizePhase!float(TAU + 0.1f), 0.1f)); 196 197 assert(approxEqual(normalizePhase!double(-0.1f), -0.1f)); 198 assert(approxEqual(normalizePhase!double(-TAU - 0.1f), -0.1f)); 199 200 bool approxEqual(T)(T a, T b) nothrow @nogc 201 { 202 return (a - b) < 1e-7; 203 } 204 } 205 206 /// Quick and dirty sawtooth for testing purposes. 207 T rawSawtooth(T)(T x) pure nothrow @nogc 208 { 209 return normalizePhase(x) / (cast(T)PI); 210 } 211 212 /// Quick and dirty triangle for testing purposes. 213 T rawTriangle(T)(T x) pure nothrow @nogc 214 { 215 return 1 - normalizePhase(x) / cast(T)PI_2; 216 } 217 218 /// Quick and dirty square for testing purposes. 219 T rawSquare(T)(T x) pure nothrow @nogc 220 { 221 return normalizePhase(x) > 0 ? 1 : -1; 222 } 223 224 T computeRMS(T)(T[] samples) pure nothrow @nogc 225 { 226 double sum = 0; 227 foreach(sample; samples) 228 sum += sample * sample; 229 return sqrt(sum / cast(int)samples.length); 230 } 231 232 unittest 233 { 234 double[] d = [4, 5, 6]; 235 computeRMS(d); 236 } 237 238 239 version(D_InlineAsm_X86) 240 private enum D_InlineAsm_Any = true; 241 else version(D_InlineAsm_X86_64) 242 private enum D_InlineAsm_Any = true; 243 else 244 private enum D_InlineAsm_Any = false; 245 246 // Expose LDC intrinsics, but usable with DMD too. 247 248 version(LDC) 249 { 250 // Note: function wouldn't work (depending on inlining), 251 // it's much more reliable to use alias 252 253 alias fast_exp = llvm_exp; 254 alias fast_log = llvm_log; 255 alias fast_pow = llvm_pow; 256 257 alias fast_fabs = llvm_fabs; 258 alias fast_log2 = llvm_log2; 259 alias fast_êxp2 = llvm_exp2; 260 alias fast_log10 = llvm_log10; 261 262 alias fast_floor = llvm_floor; 263 alias fast_ceil = llvm_ceil; 264 alias fast_trunc = llvm_trunc; 265 alias fast_round = llvm_round; 266 267 alias fast_sqrt = llvm_sqrt; 268 alias fast_sin = llvm_sin; 269 alias fast_cos = llvm_cos; 270 } 271 else 272 { 273 alias fast_exp = exp; 274 alias fast_log = log; 275 alias fast_pow = pow; 276 277 alias fast_fabs = fabs; 278 alias fast_log2 = log2; 279 alias fast_êxp2 = exp2; 280 alias fast_log10 = log10; 281 282 alias fast_floor = floor; 283 alias fast_ceil = ceil; 284 alias fast_trunc = trunc; 285 alias fast_round = round; 286 287 alias fast_sqrt = sqrt; // Undefined behaviour for operands below -0 288 alias fast_sin = sin; 289 alias fast_cos = cos; 290 } 291 292 /// Linear interpolation, akin to GLSL's mix. 293 S lerp(S, T)(S a, S b, T t) pure nothrow @nogc 294 if (is(typeof(t * b + (1 - t) * a) : S)) 295 { 296 return t * b + (1 - t) * a; 297 } 298 299 /// Same as GLSL smoothstep function. 300 /// See: http://en.wikipedia.org/wiki/Smoothstep 301 T smoothStep(T)(T a, T b, T t) pure nothrow @nogc 302 { 303 if (t <= a) 304 return 0; 305 else if (t >= b) 306 return 1; 307 else 308 { 309 T x = (t - a) / (b - a); 310 return x * x * (3 - 2 * x); 311 } 312 } 313 314 /// SSE approximation of reciprocal square root. 315 T inverseSqrt(T)(T x) pure nothrow @nogc if (is(T : float) || is(T: double)) 316 { 317 version(AsmX86) 318 { 319 static if (is(T == float)) 320 { 321 float result; 322 323 asm pure nothrow @nogc 324 { 325 movss XMM0, x; 326 rsqrtss XMM0, XMM0; 327 movss result, XMM0; 328 } 329 return result; 330 } 331 else 332 return 1 / sqrt(x); 333 } 334 else 335 return 1 / sqrt(x); 336 } 337 338 unittest 339 { 340 assert(abs( inverseSqrt!float(1) - 1) < 1e-3 ); 341 assert(abs( inverseSqrt!double(1) - 1) < 1e-3 ); 342 } 343 344 /// Computes a normalized frequency form a frequency. 345 float convertFrequencyToNormalizedFrequency(float frequencyHz, float samplingRate) pure nothrow @nogc 346 { 347 return frequencyHz / samplingRate; 348 } 349 350 /// Computes a frequency. 351 float convertNormalizedFreqyencyToFrequency(float frequencyCyclesPerSample, float samplingRate) pure nothrow @nogc 352 { 353 return frequencyCyclesPerSample * samplingRate; 354 }