1 /** 2 * Naive mipmapped wave-table oscillators. 3 * 4 * Copyright: Copyright Auburn Sounds 2015 and later. 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Authors: Guillaume Piolat 7 */ 8 module dplug.dsp.wavetable; 9 10 import std.math; 11 12 //import dplug.core.nogc; 13 import dplug.core.math; 14 import dplug.core.vec; 15 16 /// Generate a sine. 17 /// It turns out it's very stable, stable enough for table generation at least. 18 /// FUTURE: resync method 19 struct SineGenerator(T) 20 { 21 void initialize(T initPhase, T frequency, T samplerate) nothrow @nogc 22 { 23 T w = frequency * 2 * PI / samplerate; 24 _b1 = 2 * cos(w); 25 _y1 = sin(initPhase - w); 26 _y2 = sin(initPhase - 2 * w); 27 } 28 29 T next() nothrow @nogc 30 { 31 T y0 = _b1 * _y1 - _y2; 32 _y2 = _y1; 33 _y1 = y0; 34 return y0; 35 } 36 37 T _y1, _y2; 38 T _b1; 39 } 40 41 enum WaveformType 42 { 43 SINE, 44 SAWTOOTH, 45 SQUARE, 46 TRIANGLE 47 } 48 49 // wavetable with mip-maps 50 51 /// Generates anti-aliased waveform generation through 52 /// procedurally generated mipmapped tables. 53 /// FUTURE: only integer phase 54 struct Wavetable 55 { 56 nothrow: 57 @nogc: 58 59 void initialize(int largestSize, WaveformType waveform) 60 { 61 resize(largestSize); 62 generate(waveform); // regenerate tables 63 } 64 65 ~this() 66 { 67 _mipmapData.reallocBuffer(0); 68 _wholeBuffer.reallocBuffer(0); 69 } 70 71 @disable this(this); 72 73 float lookupLinear(uint phaseIntPart, float phaseFractional, int level) 74 { 75 float* mipmap0 = mipmapData(level); 76 int mask = sizeOfMipmap(level) - 1; 77 float a = mipmap0[ phaseIntPart & mask ]; 78 float b = mipmap0[ ( 1 + phaseIntPart ) & mask ]; 79 return a * (1 - phaseFractional) + phaseFractional * b; 80 } 81 82 float lookupCatmullRom(uint phaseIntPart, float phaseFractional, int level) 83 { 84 float* mipmap0 = mipmapData(level); 85 int mask = sizeOfMipmap(level) - 1; 86 float p0 = mipmap0[ (phaseIntPart - 1) & mask ]; 87 float p1 = mipmap0[ phaseIntPart & mask ]; 88 float p2 = mipmap0[ ( 1 + phaseIntPart ) & mask ]; 89 float p3 = mipmap0[ ( 2 + phaseIntPart ) & mask ]; 90 float t = phaseFractional; 91 92 return 0.5f * ((2 * p1) 93 + (-p0 + p2) * t 94 + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t 95 + (-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t); 96 } 97 98 float lookupCatmullRomMipmap(uint phaseIntPart, float phaseFractional, float phaseIncrementSamples) 99 { 100 101 float level = cast(float)log2(phaseIncrementSamples); 102 int level0 = cast(int)floor(level); 103 int level1 = level0 + 1; 104 if (level0 < 0) 105 level0 = 0; 106 if (level1 < 0) 107 level1 = 0; 108 int maxLevel = cast(int)_numTables - 1; 109 if (level0 > maxLevel) 110 level0 = maxLevel; 111 if (level1 > maxLevel) 112 level1 = maxLevel; 113 114 if (level1 == 0) 115 { 116 return lookupCatmullRom(phaseIntPart, phaseFractional, 0); 117 } 118 else 119 { 120 float fractionalLevel = level - level0; 121 122 float phaseL0 = (phaseIntPart + phaseFractional) / cast(float)(1 << level0); 123 int iPart0 = cast(int)(phaseL0); 124 float fractional0 = phaseL0 - iPart0; 125 126 float phaseL1 = (phaseIntPart + phaseFractional) / cast(float)(1 << level1); 127 int iPart1 = cast(int)(phaseL1); 128 float fractional1 = phaseL1 - iPart1; 129 130 float L0 = lookupCatmullRom(iPart0, fractional0, level0); 131 float L1 = lookupCatmullRom(iPart1, fractional1, level1); 132 return lerp(L0, L1, fractionalLevel); 133 } 134 } 135 136 // mimaps levels range from 0 to numMipmaps() - 1 137 int numMipmaps() const 138 { 139 return _numTables; 140 } 141 142 int sizeOfMipmap(int level) const 143 { 144 return _largestSize >> level; 145 } 146 147 float* mipmapData(int level) 148 { 149 return _mipmapData[level]; 150 } 151 152 private: 153 154 int _largestSize; 155 int _mast; 156 int _numTables; 157 158 float*[] _mipmapData; 159 float[] _wholeBuffer; 160 161 /// Defines the harmonic rolloff, critical to avoid aliasing around the last mipmap 162 /// This is very arbitrary and ultimately power of two mipmaps are maybe not sufficient 163 /// to have less aliasing. 164 static double rolloffHarmonic(double normalizedFrequency) pure nothrow @nogc // between 0 and 1 165 { 166 double cosF0 = cos(normalizedFrequency * PI); 167 return cosF0 * cosF0; 168 } 169 170 void resize(int largestSize) nothrow @nogc 171 { 172 assert(isPowerOfTwo(largestSize)); 173 174 _largestSize = largestSize; 175 // compute size for all mipmaps 176 int sizeNeeded = 0; 177 _numTables = 0; 178 int sizeOfTable = largestSize; 179 while (sizeOfTable > 0) 180 { 181 sizeNeeded += sizeOfTable; 182 sizeOfTable /= 2; 183 _numTables += 1; 184 } 185 186 _wholeBuffer.reallocBuffer(sizeNeeded); 187 188 // fill table pointers 189 { 190 _mipmapData.reallocBuffer(_numTables); 191 int cumulated = 0; 192 for (int level = 0; level < _numTables; ++level) 193 { 194 _mipmapData[level] = &_wholeBuffer[cumulated]; 195 cumulated += _largestSize >> level; 196 } 197 } 198 } 199 200 // fill all table with waveform 201 void generate(WaveformType waveform) nothrow @nogc 202 { 203 for (int level = 0; level < _numTables; ++level) 204 { 205 int size = sizeOfMipmap(level); 206 float* data = mipmapData(level); 207 208 for (int t = 0; t < size; ++t) 209 { 210 data[t] = 0; 211 } 212 213 int numHarmonics = size / 2; 214 215 if (size > 2) 216 { 217 for (int h = 0; h < numHarmonics; ++h) 218 { 219 double normalizedFrequency = (1 + h) / cast(double)(numHarmonics - 1); 220 double amplitude = getWaveHarmonicAmplitude(waveform, h + 1) * rolloffHarmonic(normalizedFrequency); 221 222 for (int t = 0; t < size; ++t) 223 { 224 double x = sin( cast(double)t * (2 * PI) * (h + 1) / cast(double)size ) * amplitude; 225 data[t] += cast(float)x; 226 } 227 } 228 } 229 230 for (int t = 0; t < size; ++t) 231 { 232 assert(isFinite(data[t])); 233 } 234 } 235 } 236 } 237 238 239 struct WavetableOsc 240 { 241 public: 242 void initialize(Wavetable* wavetable, float sampleRate) nothrow @nogc 243 { 244 _wavetable = wavetable; 245 _phaseIntPart = 0; 246 _phaseFractional = 0; 247 _phaseFactor = _wavetable.sizeOfMipmap(0) / sampleRate; 248 } 249 250 /// Allows dirty resync 251 void resetPhase() nothrow @nogc 252 { 253 _phaseIntPart = 0; 254 _phaseFractional = 0; 255 } 256 257 deprecated alias next = nextSample; 258 259 /// Get next sample. 260 float nextSample(float frequencyHz) nothrow @nogc 261 { 262 float phaseIncrementSamples = frequencyHz * _phaseFactor; 263 assert(phaseIncrementSamples >= 0); 264 int iPart = cast(int)(phaseIncrementSamples); 265 _phaseIntPart += iPart; 266 267 _phaseFractional += (phaseIncrementSamples - iPart); 268 if (_phaseFractional >= 1) 269 { 270 _phaseFractional -= 1.0; 271 _phaseIntPart += 1; 272 273 // in case the assert above would fail (which happen) 274 if (_phaseFractional >= 1) 275 { 276 _phaseFractional -= 1.0; 277 _phaseIntPart += 1; 278 } 279 } 280 281 assert(_phaseFractional >= 0 && _phaseFractional <= 1); 282 283 return _wavetable.lookupCatmullRomMipmap(_phaseIntPart, cast(float)_phaseFractional, phaseIncrementSamples); 284 // return _wavetable->lookupLinear(_phaseIntPart, _phaseFractional, 1); 285 } 286 287 private: 288 uint _phaseIntPart; 289 float _phaseFractional; 290 Wavetable* _wavetable; 291 float _phaseFactor; 292 } 293 294 295 private: 296 297 double getWaveHarmonicAmplitude(WaveformType waveform, int n) nothrow @nogc 298 { 299 assert(n > 0); 300 final switch(waveform) 301 { 302 case WaveformType.SINE: 303 { 304 if (n == 1) 305 return 1; 306 else 307 return 0; 308 } 309 310 case WaveformType.SAWTOOTH: 311 { 312 return 1 / cast(double)n; 313 } 314 315 case WaveformType.SQUARE: 316 { 317 if (n % 2 == 0) 318 return 0; 319 else 320 return 1 / cast(double)n; 321 } 322 323 case WaveformType.TRIANGLE: 324 { 325 if (n % 2 == 0) 326 return 0; 327 else if (n % 4 == 1) 328 return 1 / cast(double)(n*n); 329 else 330 { 331 assert(n % 4 == 3); 332 return -1 / cast(double)(n*n); 333 } 334 } 335 } 336 }