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 }