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