├── .gitignore
├── README.md
├── WavetableCreatorConsole.jucer
└── Source
└── Main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | **/Builds
2 | **/JuceLibraryCode
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WavetableCreatorConsole
2 | console app for creating wavetables
3 |
--------------------------------------------------------------------------------
/WavetableCreatorConsole.jucer:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Source/Main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #define oopsie(x) jassert(!(x))
5 |
6 | namespace wtc
7 | {
8 | static constexpr float pi = 3.14159265359f;
9 | static constexpr float tau = pi * 2.f;
10 |
11 | inline float pitchToFreq(float pitch, float basePitch = 69.f, float xen = 12.f, float masterTune = 440.f) noexcept
12 | {
13 | return std::pow(2.f, (pitch - basePitch) / xen) * masterTune;
14 | }
15 |
16 | inline float freqToPitch(float freq, float basePitch = 69.f, float xen = 12.f, float masterTune = 440.f) noexcept
17 | {
18 | return std::log2(freq / masterTune) * xen + basePitch;
19 | }
20 |
21 | inline float dbToGain(float db) noexcept
22 | {
23 | return std::pow(10.f, db * .05f);
24 | }
25 |
26 | inline float gainToDb(float gain) noexcept
27 | {
28 | return std::log10(gain) * 20.f;
29 | }
30 |
31 | inline int twoPowXInverse(int num) noexcept
32 | {
33 | auto x = 0;
34 | while (num > 1)
35 | {
36 | num >>= 1;
37 | ++x;
38 | }
39 | return x;
40 | }
41 |
42 | template
43 | inline Float modDesmos(Float a, Float b) noexcept
44 | {
45 | while (a < static_cast(0))
46 | a += b;
47 | while (a >= b)
48 | a -= b;
49 | return a;
50 | }
51 |
52 | struct BiquadFilter
53 | {
54 | enum Type { LP, HP, BP, AP, NOTCH, PEAK, LS, HS };
55 | static constexpr double DefaultCutoffFc = 1000. / 44100.;
56 | static constexpr double DefaultResonance = .1;
57 |
58 | BiquadFilter() :
59 | type(Type::BP),
60 | bCoef{ 0., 0., 0. },
61 | aCoef{ 0., 0. },
62 | w{ 0., 0. },
63 | omega(DefaultCutoffFc),
64 | cosOmega(0.), sinOmega(0.),
65 | Q(DefaultResonance), alpha(0.), A(1.),
66 | a{ 0., 0., 0. },
67 | b{ 0., 0., 0. }
68 | {
69 | }
70 |
71 | void prepare() noexcept
72 | {
73 | update();
74 | }
75 |
76 | void operator()(double* samples, int numSamples) noexcept
77 | {
78 | for (auto s = 0; s < numSamples; ++s)
79 | processSample(samples[s]);
80 | }
81 |
82 | double operator()(double sample) noexcept
83 | {
84 | return processSample(sample);
85 | }
86 |
87 | /* fc = hz / sampleRate */
88 | void setCutoffFc(double fc) noexcept
89 | {
90 | omega = static_cast(tau) * fc;
91 | }
92 |
93 | void setResonance(double q) noexcept
94 | {
95 | Q = q;
96 | }
97 |
98 | void setType(Type _type) noexcept
99 | {
100 | type = _type;
101 | }
102 |
103 | void update() noexcept
104 | {
105 | cosOmega = cos(omega);
106 | sinOmega = sin(omega);
107 |
108 | switch (type)
109 | {
110 | case LP:
111 | alpha = sinOmega / (2. * Q);
112 | b[0] = (1. - cosOmega) / 2.;
113 | b[1] = 1. - cosOmega;
114 | b[2] = b[0];
115 | a[0] = 1. + alpha;
116 | a[1] = -2. * cosOmega;
117 | a[2] = 1. - alpha;
118 | break;
119 | case HP:
120 | alpha = sinOmega / (2. * Q);
121 | b[0] = (1. + cosOmega) / 2.;
122 | b[1] = -(1. + cosOmega);
123 | b[2] = b[0];
124 | a[0] = 1. + alpha;
125 | a[1] = -2. * cosOmega;
126 | a[2] = 1. - alpha;
127 | break;
128 | case BP:
129 | alpha = sinOmega * sinh(log(2.) / 2. * Q * omega / sinOmega);
130 | b[0] = sinOmega / 2.;
131 | b[1] = 0.;
132 | b[2] = -b[0];
133 | a[0] = 1. + alpha;
134 | a[1] = -2. * cosOmega;
135 | a[2] = 1. - alpha;
136 | break;
137 | case AP:
138 | alpha = sinOmega / (2. * Q);
139 | b[0] = 1. - alpha;
140 | b[1] = -2. * cosOmega;
141 | b[2] = 1. + alpha;
142 | a[0] = b[2];
143 | a[1] = b[1];
144 | a[2] = b[0];
145 | break;
146 | case NOTCH:
147 | alpha = sinOmega * sinh(log(2.) / 2. * Q * omega / sinOmega);
148 | b[0] = 1.;
149 | b[1] = -2. * cosOmega;
150 | b[2] = 1.;
151 | a[0] = 1. + alpha;
152 | a[1] = b[1];
153 | a[2] = 1. - alpha;
154 | break;
155 | case PEAK:
156 | alpha = sinOmega * sinh(log(2.) / 2. * Q * omega / sinOmega);
157 | b[0] = 1. + (alpha * A);
158 | b[1] = -2. * cosOmega;
159 | b[2] = 1. - (alpha * A);
160 | a[0] = 1. + (alpha / A);
161 | a[1] = b[1];
162 | a[2] = 1. - (alpha / A);
163 | break;
164 | case LS:
165 | alpha = sinOmega / 2. * sqrt((A + 1. / A) * (1. / Q - 1.) + 2.);
166 | b[0] = A * ((A + 1.) - ((A - 1.) * cosOmega) + (2. * sqrt(A) * alpha));
167 | b[1] = 2. * A * ((A - 1.) - ((A + 1.) * cosOmega));
168 | b[2] = A * ((A + 1.) - ((A - 1.) * cosOmega) - (2. * sqrt(A) * alpha));
169 | a[0] = ((A + 1.) + ((A - 1.) * cosOmega) + (2. * sqrt(A) * alpha));
170 | a[1] = -2. * ((A - 1.) + ((A + 1.) * cosOmega));
171 | a[2] = ((A + 1.) + ((A - 1.) * cosOmega) - (2. * sqrt(A) * alpha));
172 | break;
173 | case HS:
174 | alpha = sinOmega / 2. * sqrt((A + 1. / A) * (1. / Q - 1.) + 2.);
175 | b[0] = A * ((A + 1.) + ((A - 1.) * cosOmega) + (2. * sqrt(A) * alpha));
176 | b[1] = -2. * A * ((A - 1.) + ((A + 1.) * cosOmega));
177 | b[2] = A * ((A + 1.) + ((A - 1.) * cosOmega) - (2. * sqrt(A) * alpha));
178 | a[0] = ((A + 1.) - ((A - 1.) * cosOmega) + (2. * sqrt(A) * alpha));
179 | a[1] = 2. * ((A - 1.) - ((A + 1.) * cosOmega));
180 | a[2] = ((A + 1.) - ((A - 1.) * cosOmega) - (2. * sqrt(A) * alpha));
181 | break;
182 | }
183 |
184 | // Normalize filter coefficients
185 | const auto factor = 1. / a[0];
186 |
187 | aCoef[0] = a[1] * factor;
188 | aCoef[1] = a[2] * factor;
189 |
190 | bCoef[0] = b[0] * factor;
191 | bCoef[1] = b[1] * factor;
192 | bCoef[2] = b[2] * factor;
193 | }
194 |
195 | void copyFrom(const BiquadFilter& other) noexcept
196 | {
197 | type = other.type;
198 | for (auto i = 0; i < bCoef.size(); ++i)
199 | bCoef[i] = other.bCoef[i];
200 | for (auto i = 0; i < aCoef.size(); ++i)
201 | aCoef[i] = other.aCoef[i];
202 | omega = other.omega;
203 | cosOmega = other.cosOmega;
204 | sinOmega = other.sinOmega;
205 | Q = other.Q;
206 | alpha = other.alpha;
207 | A = other.A;
208 | for (auto i = 0; i < a.size(); ++i)
209 | a[i] = other.a[i];
210 | for (auto i = 0; i < b.size(); ++i)
211 | b[i] = other.b[i];
212 | }
213 |
214 | Type type;
215 |
216 | std::array bCoef; // b0, b1, b2
217 | std::array aCoef; // a1, a2
218 | std::array w; // delays
219 |
220 | double omega, cosOmega, sinOmega, Q, alpha, A;
221 | std::array a;
222 | std::array b;
223 |
224 | // DF-II impl
225 | double processSample(double sample) noexcept
226 | {
227 | auto y = bCoef[0] * sample + w[0];
228 | w[0] = bCoef[1] * sample - aCoef[0] * y + w[1];
229 | w[1] = bCoef[2] * sample - aCoef[1] * y;
230 | return y;
231 | }
232 | };
233 |
234 | using Buffer = juce::AudioBuffer;
235 |
236 | struct Specs
237 | {
238 | Specs(int _tableSize = 2048, int _numTables = 256) :
239 | tableSize(_tableSize),
240 | numTables(_numTables),
241 | fftOrder(twoPowXInverse(tableSize)),
242 | fftSize(1 << fftOrder),
243 | fftSize2(fftSize * 2),
244 | tableSizeF(static_cast(tableSize)),
245 | numTablesF(static_cast(numTables)),
246 | fftSizeF(static_cast(fftSize)),
247 | fftSize2F(static_cast(fftSize2)),
248 | bins(),
249 | fifo(),
250 | fft(fftOrder),
251 | randomizer()
252 | {
253 | bins.resize(fftSize2, 0.f);
254 | fifo.resize(fftSize2, 0.f);
255 | }
256 |
257 | void clearFifo() noexcept
258 | {
259 | for (auto& f : fifo)
260 | f = std::complex(0.f, 0.f);
261 | }
262 |
263 | void setMagAndPhase(float mag, float phase, int idx) noexcept
264 | {
265 | fifo[idx] = std::complex
266 | (
267 | mag * std::cos(phase),
268 | mag * std::sin(phase)
269 | );
270 | }
271 |
272 | void addMagAndPhase(float mag, float phase, int idx) noexcept
273 | {
274 | fifo[idx] = std::complex
275 | (
276 | fifo[idx].real() + mag * std::cos(phase),
277 | fifo[idx].imag() + mag * std::sin(phase)
278 | );
279 | }
280 |
281 | void randomizePhases(float amount = 1.f) noexcept
282 | {
283 | for (auto& f : fifo)
284 | if(f.real() != 0.f)
285 | f = std::complex(f.real(), rand() * tau * amount);
286 | }
287 |
288 | void makeSomeNoise() noexcept
289 | {
290 | for (auto i = 0; i < fftSize; ++i)
291 | fifo[i] = std::complex(1.f, rand() * tau);
292 | }
293 |
294 | void applyWindowHann() noexcept
295 | {
296 | for (auto i = 0; i < fftSize; ++i)
297 | {
298 | const auto hann = .5f * (1.f - std::cos(tau * static_cast(i) / fftSizeF));
299 | fifo[i] *= hann;
300 | }
301 | }
302 |
303 | void addFunction(float x, const std::function& magFunc,
304 | const std::function& phaseFunc = [](float) { return 0.f; },
305 | const std::function& gainFunc = [](float) { return 1.f; }) noexcept
306 | {
307 | const auto pitchStart = freqToPitch(1.f);
308 | const auto pitchEnd = freqToPitch(tableSizeF * .5f);
309 | const auto pitch = pitchStart + magFunc(x) * (pitchEnd - pitchStart);
310 | const auto freq = pitchToFreq(pitch);
311 | const auto fc = freq / tableSizeF;
312 | const auto idx = static_cast(std::round(fc * fftSizeF));
313 | if(idx < 0 || idx >= fftSize)
314 | return;
315 | addMagAndPhase(gainFunc(x), phaseFunc(x), idx);
316 | }
317 |
318 | void addFuncHarmonic(float x = 0.f) noexcept
319 | {
320 | addFunction
321 | (
322 | 0.f,
323 | [x](float) { return x; }
324 | );
325 | }
326 |
327 | void normalizeFifo() noexcept
328 | {
329 | auto max = fifo[0].real();
330 | for (auto i = 1; i < fftSize2; ++i)
331 | {
332 | const auto a = std::abs(fifo[i].real());
333 | if (max < a)
334 | max = a;
335 | }
336 | if (max != 0.f)
337 | {
338 | auto gain = 1.f / max;
339 | for (auto& f : fifo)
340 | f *= gain;
341 | }
342 | }
343 |
344 | void performInverseFFT() noexcept
345 | {
346 | normalizeFifo();
347 | fft.perform(fifo.data(), bins.data(), true);
348 | }
349 |
350 | float getSpectralSound(int sampleIdx) const noexcept
351 | {
352 | return bins[sampleIdx].real();
353 | }
354 |
355 | float rand()
356 | {
357 | return randomizer.nextFloat();
358 | }
359 |
360 | int tableSize, numTables, fftOrder, fftSize, fftSize2;
361 | float tableSizeF, numTablesF, fftSizeF, fftSize2F;
362 | std::vector> bins;
363 | std::vector> fifo;
364 | private:
365 | juce::dsp::FFT fft;
366 | juce::Random randomizer;
367 | };
368 |
369 | namespace fftFX
370 | {
371 | inline void distort(Specs& specs, float gain, float distance = 2.f, bool preventAliasing = true) noexcept
372 | {
373 | for (auto i = static_cast(specs.fftSizeF / distance); i >= 0; --i)
374 | {
375 | const auto fc = static_cast(i) / specs.fftSize2F;
376 | const auto fc2 = fc * distance;
377 | if (fc2 >= 0.f && fc2 < .5f)
378 | {
379 | if (!preventAliasing || fc2 < specs.fftSizeF - 1.f)
380 | {
381 | const auto dFloor = fc2 * specs.fftSize2F;
382 | const auto d0 = static_cast(dFloor);
383 | const auto d1 = d0 + 1;
384 | const auto dX = dFloor - static_cast(d0);
385 | const auto dMag = specs.fifo[i].real() * gain;
386 | const auto dMag0 = dMag * (1.f - dX);
387 | const auto dMag1 = dMag * dX;
388 | const auto dPhs = specs.fifo[i].imag() * gain;
389 | const auto dPhs0 = dPhs * (1.f - dX);
390 | const auto dPhs1 = dPhs * dX;
391 | specs.addMagAndPhase(dMag0, dPhs0, d0);
392 | specs.addMagAndPhase(dMag1, dPhs1, d1);
393 | }
394 | }
395 | }
396 | }
397 |
398 | inline void blurBrk(Specs& specs, float fcWidth)
399 | {
400 | auto nFifo = specs.fifo;
401 |
402 | for (auto i = 0; i < specs.fftSize; ++i)
403 | {
404 | const auto mag = specs.fifo[i].real() * .5f;
405 | const auto phs = specs.fifo[i].imag() * .5f;
406 | const auto iF = static_cast(i);
407 | const auto fc = iF / specs.fftSize2F;
408 | const auto fc0 = fc - fcWidth;
409 | if (fc0 > 0.f)
410 | {
411 | const auto idx0 = static_cast(std::round(fc0 * specs.fftSize2F));
412 | nFifo[idx0] += { mag, phs };
413 | }
414 | const auto fc1 = fc + fcWidth;
415 | if (fc1 < 1.f)
416 | {
417 | const auto idx1 = static_cast(std::round(fc1 * specs.fftSize2F));
418 | nFifo[idx1] += { mag, phs };
419 | }
420 | }
421 |
422 | specs.fifo = nFifo;
423 | }
424 |
425 | inline void blur(Specs& specs,
426 | float fc, float reso, int slope) noexcept
427 | {
428 | BiquadFilter lpMag, lpPhase;
429 | lpMag.setType(BiquadFilter::Type::LP);
430 | lpMag.setCutoffFc(fc);
431 | lpMag.setResonance(reso);
432 | lpMag.update();
433 | lpPhase.setType(BiquadFilter::Type::LP);
434 | lpPhase.setCutoffFc(fc);
435 | lpPhase.setResonance(reso);
436 | lpPhase.update();
437 |
438 | auto dest = specs.fifo;
439 | auto src = specs.fifo.data();
440 |
441 | const auto lp = [&](int i)
442 | {
443 | auto mag = static_cast(src[i].real());
444 | auto phs = static_cast(src[i].imag());
445 | mag = lpMag(mag);
446 | phs = lpPhase(phs);
447 | dest[i] = { static_cast(mag), static_cast(phs) };
448 | };
449 |
450 | for (auto i = specs.fftSize - 1; i != 0; --i)
451 | lp(i);
452 | for (auto i = 0; i < specs.fftSize; ++i)
453 | lp(i);
454 |
455 | for (auto i = 1; i < slope; ++i)
456 | {
457 | for (auto j = specs.fftSize - 1; j != 0; --j)
458 | lp(i);
459 | for (auto j = 0; j < specs.fftSize; ++j)
460 | lp(i);
461 | }
462 | }
463 | }
464 |
465 | using PrepareFunc = std::function;
466 | using TableFunc = std::function;
467 |
468 | struct Creator
469 | {
470 | Creator(Specs& specs, const TableFunc& func, const PrepareFunc& prepare = [](Specs&, int){}) :
471 | tableSizeInv(1.f / static_cast(specs.tableSize)),
472 | tableSize(specs.tableSize),
473 | numTables(specs.numTables),
474 | buffer(1, tableSize * numTables)
475 | {
476 | buffer.clear();
477 | auto samples = buffer.getWritePointer(0);
478 |
479 | for (auto i = 0; i < numTables; ++i)
480 | {
481 | prepare(specs, i);
482 |
483 | const auto start = i * tableSize;
484 | const auto end = (i + 1) * tableSize;
485 | for (auto s = start; s < end; ++s)
486 | {
487 | const auto s0 = s % tableSize;
488 | const auto sF = static_cast(s0);
489 | const auto x = 2.f * sF * tableSizeInv - 1.f;
490 |
491 | auto sample = func(specs, x, s0, i);
492 |
493 | if (std::isnan(sample) || std::isinf(sample))
494 | sample = 0.f;
495 |
496 | samples[s] = sample;
497 | }
498 | }
499 | }
500 |
501 | // POST PROCESSING (SINGLE)
502 | void dcOffsetEachTable() noexcept
503 | {
504 | auto samples = buffer.getWritePointer(0);
505 |
506 | for (auto i = 0; i < numTables; ++i)
507 | {
508 | const auto start = tableSize * i;
509 | const auto end = tableSize * (i + 1);
510 |
511 | auto average = 0.f;
512 |
513 | for (auto s = start; s < end; ++s)
514 | average += samples[s];
515 |
516 | const auto offset = -average / static_cast(end - start);
517 |
518 | for (auto s = start; s < end; ++s)
519 | samples[s] += offset;
520 | }
521 | }
522 |
523 | void normalizeEachTable() noexcept
524 | {
525 | auto samples = buffer.getWritePointer(0);
526 |
527 | for (auto i = 0; i < numTables; ++i)
528 | {
529 | const auto start = tableSize * i;
530 | const auto end = tableSize * (i + 1);
531 |
532 | auto max = 0.f;
533 |
534 | for (auto s = start; s < end; ++s)
535 | {
536 | const auto a = std::abs(samples[s]);
537 | if (max < a)
538 | max = a;
539 | }
540 |
541 | const auto gain = 1.f / max;
542 |
543 | for (auto s = start; s < end; ++s)
544 | samples[s] *= gain;
545 | }
546 | }
547 |
548 | void applyHannWindowEachTable() noexcept
549 | {
550 | auto samples = buffer.getWritePointer(0);
551 |
552 | for (auto i = 0; i < numTables; ++i)
553 | {
554 | auto k = i * tableSize;
555 |
556 | for (auto j = 0; j < tableSize; ++j)
557 | {
558 | const auto hann = .5f * (1.f - std::cos(tau * static_cast(j) * tableSizeInv));
559 | samples[k + j] *= hann;
560 | }
561 | }
562 | }
563 |
564 | void xFadeTablesSingle(float lenRel)
565 | {
566 | std::vector table2;
567 | auto fadeLen = static_cast(lenRel * static_cast(tableSize));
568 | table2.resize(fadeLen);
569 |
570 | for (auto i = 0; i < numTables; ++i)
571 | {
572 | auto table = &buffer.getWritePointer(0)[i * tableSize];
573 |
574 | for (auto j = 0; j < fadeLen; ++j)
575 | table2[j] = table[fadeLen - j - 1];
576 |
577 | for (auto j = 0; j < fadeLen; ++j)
578 | {
579 | auto idx = tableSize - fadeLen + j;
580 | auto x = (float)j * tableSizeInv;
581 | table[idx] += x * (table2[j] - table[idx]);
582 | }
583 | }
584 | }
585 |
586 | // POST PROCESSING (MULTI)
587 |
588 | void xFadeTablesMulti() noexcept
589 | {
590 | for (auto i = 0; i < numTables - 1; ++i)
591 | {
592 | auto table0 = &buffer.getWritePointer(0)[i * tableSize];
593 | auto table1 = &buffer.getWritePointer(0)[(i + 1) * tableSize];
594 |
595 | for (auto j = 0; j < tableSize; ++j)
596 | {
597 | auto x = static_cast(j) * tableSizeInv;
598 | table0[j] = table0[j] + x * (table1[j] - table0[j]);
599 | }
600 | }
601 | }
602 |
603 | void upsampleNumTables(int factor)
604 | {
605 | auto newNumTables = numTables * factor;
606 | Buffer newBuffer(1, tableSize * newNumTables);
607 | auto newSamples = newBuffer.getWritePointer(0);
608 |
609 | const auto samplesOld = buffer.getReadPointer(0);
610 | auto samplesNew = newBuffer.getWritePointer(0);
611 |
612 | for (auto i = 0; i < newNumTables; ++i)
613 | {
614 | auto iNew = i * tableSize;
615 | auto iOld = iNew / factor;
616 | auto iOld0 = iOld / tableSize * tableSize;
617 | auto x = static_cast(iOld) * tableSizeInv;
618 | while (x >= 1.f)
619 | --x;
620 | for (auto j = 0; j < tableSize; ++j)
621 | {
622 | auto jNew = iNew + j;
623 | auto jOld0 = iOld0 + j;
624 | auto jOld1 = jOld0 + tableSize;
625 | if (jOld1 >= buffer.getNumSamples())
626 | jOld1 -= tableSize;
627 | samplesNew[jNew] = samplesOld[jOld0] + x * (samplesOld[jOld1] - samplesOld[jOld0]);
628 | }
629 | }
630 |
631 | numTables = newNumTables;
632 | buffer = newBuffer;
633 | }
634 |
635 | void upsampleNumTablesTo(int newNumTables)
636 | {
637 | if (newNumTables == numTables)
638 | return;
639 | const auto factor = static_cast(static_cast(newNumTables) / static_cast(numTables));
640 | upsampleNumTables(factor);
641 | }
642 |
643 | // INTERPOLATION NumTables
644 | // ...
645 |
646 | // EXPORT
647 | void makeWav(const juce::String& name)
648 | {
649 | const auto numSamples = buffer.getNumSamples();
650 | auto samples = buffer.getWritePointer(0);
651 | for (auto s = 0; s < numSamples; ++s)
652 | samples[s] = juce::jlimit(-1.f, 1.f, samples[s]);
653 |
654 | const auto path = juce::File::getSpecialLocation
655 | (
656 | juce::File::SpecialLocationType::userDesktopDirectory
657 | ).getFullPathName() + "\\WaveTableCreator\\";
658 | juce::File file(path);
659 | file.createDirectory();
660 | std::cout << "Created Folder: " << path << std::endl;
661 | const auto fullName = name + ".wav";
662 | file = juce::File(path + "\\" + fullName);
663 | if (file.existsAsFile())
664 | file.deleteFile();
665 | std::cout << "Created File: " << file.getFullPathName() << std::endl;
666 | juce::WavAudioFormat format;
667 | std::unique_ptr writer;
668 | writer.reset(format.createWriterFor(new juce::FileOutputStream(file),
669 | 48000,
670 | 1,
671 | 32,
672 | {},
673 | 0
674 | ));
675 | if (writer != nullptr)
676 | {
677 | writer->writeFromAudioSampleBuffer(buffer, 0, numSamples);
678 | std::cout << "Success :)" << std::endl;
679 |
680 | juce::File txtFile(path + "\\" + "FolderInfo.txt");
681 | if (txtFile.existsAsFile())
682 | txtFile.deleteFile();
683 | txtFile.create();
684 | txtFile.appendText("[" + juce::String(tableSize) + "]");
685 |
686 | // open folder
687 | juce::URL url(path);
688 | url.launchInDefaultBrowser();
689 | url = file.getFullPathName();
690 | url.launchInDefaultBrowser();
691 | }
692 | else
693 | {
694 | std::cout << "Failed :(" << std::endl;
695 | }
696 | }
697 |
698 | protected:
699 | float tableSizeInv;
700 | int tableSize;
701 | int numTables;
702 | Buffer buffer;
703 | };
704 | }
705 |
706 | int main(int, char*)
707 | {
708 | using namespace wtc;
709 |
710 | Specs specs(2048, 64);
711 |
712 | PrepareFunc prepare = [](Specs& specs, int tableIdx)
713 | {
714 | specs.clearFifo();
715 | const auto x = static_cast(tableIdx) / specs.numTablesF;
716 |
717 | for (auto a = 0; a < 3; ++a)
718 | {
719 | specs.addFuncHarmonic(0.f);
720 |
721 | specs.addFunction(x, [a](float x)
722 | {
723 | auto aa = (float)(1 << (a * 2));
724 | auto xx = x * aa;
725 | while (xx > 1.f)
726 | xx -= 1.f;
727 | return std::tan(xx * aa) / aa;
728 | });
729 | fftFX::blur(specs, .5f, 1.f, 3);
730 | fftFX::distort(specs, .75f, 1 << (a + 1), false);
731 | }
732 |
733 | specs.performInverseFFT();
734 | };
735 |
736 | TableFunc func = [](Specs& specs, float x, int sampleIdx, int tableIdx)
737 | {
738 | return specs.getSpectralSound(sampleIdx);
739 | };
740 |
741 | Creator creator(specs, func, prepare);
742 |
743 | creator.dcOffsetEachTable();
744 | //creator.xFadeTablesMulti();
745 | creator.normalizeEachTable();
746 | creator.upsampleNumTablesTo(256);
747 | creator.makeWav("xxx");
748 |
749 | return 0;
750 | }
751 |
752 | /*
753 |
754 | wavetable ideas and stuff:
755 |
756 | /////////////////////////////
757 |
758 | // triangle
759 | T(x,f) = 2asin(sin(fxpi))/pi
760 |
761 | //either
762 | T(x,f)sqrt(1-abs(x))
763 | //or
764 | sum(n=1,N=128,T(x,n)/n)
765 |
766 | /////////////////////////////
767 |
768 | LINEAR SWEEP FFT PREPARE:
769 |
770 | specs.clearFifo();
771 | auto norm = (float)tableIdx / (float)specs.numTables;
772 | auto idx = static_cast(norm * static_cast(specs.fftSize));
773 | specs.fifo[idx] = 1.f;
774 | specs.performInverseFFT();
775 |
776 | /////////////////////////////
777 |
778 | LOG SWEEP FFT PREPARE:
779 |
780 | specs.clearFifo();
781 | auto norm = (float)tableIdx / (float)specs.numTables;
782 | auto pitch = 48.f + norm * 64.f;
783 | auto freq = pitchToFreq(pitch);
784 | auto normFreq = freq / 44100.f;
785 | auto idx = static_cast(normFreq * static_cast(specs.fftSize));
786 | specs.fifo[idx] = 1.f;
787 | specs.performInverseFFT();
788 |
789 | /////////////////////////////
790 |
791 | NOISY LOG SWEEP FFT PREPARE:
792 |
793 | specs.clearFifo();
794 | auto norm = (float)tableIdx / (float)specs.numTables;
795 | auto pitch = 24.f + norm * 127.f;
796 | auto freq = pitchToFreq(pitch);
797 | auto normFreq = freq / 44100.f;
798 | auto idx = static_cast(normFreq * static_cast(specs.fftSize));
799 | specs.fifo[idx] = 1.f;
800 | auto diffuse = 128;
801 | for (auto i = 1; i < diffuse; ++i)
802 | {
803 | auto iF = static_cast(i);
804 | auto iX = iF / static_cast(diffuse);
805 | iX = 1.f - std::pow(iX, 1.f / 64.f);
806 | auto i0 = idx + i;
807 | auto i1 = idx - i;
808 | if(i0 < specs.fftSize)
809 | specs.fifo[i0] = iX;
810 | if(i1 >= 0)
811 | specs.fifo[i1] = iX;
812 | }
813 |
814 | specs.performInverseFFT();
815 |
816 | /////////////////////////////
817 |
818 | specs.clearFifo();
819 | auto norm = (float)tableIdx / (float)specs.numTables;
820 | auto pitch = 24.f + norm * 127.f;
821 | auto freq = pitchToFreq(pitch);
822 | auto normFreq = freq / 44100.f;
823 | auto idx = static_cast(normFreq * static_cast(specs.fftSize));
824 | specs.fifo[idx] = 1.f;
825 | auto diffuse = 8;
826 | for (auto i = 1; i < diffuse; ++i)
827 | {
828 | auto iF = static_cast(i);
829 | auto iX = iF / static_cast(diffuse);
830 | iX = 1.f - std::pow(iX, 1.f / 32.f);
831 | auto i0 = idx + i;
832 | auto i1 = idx - i;
833 | if (i0 < specs.fftSize)
834 | specs.fifo[i0] = iX;
835 | if (i1 >= 0)
836 | specs.fifo[i1] = iX;
837 | }
838 |
839 | specs.randomizePhases();
840 | specs.performInverseFFT();
841 |
842 | /////////////////////////////
843 |
844 | URANUS RINGS FFT PREPARE:
845 |
846 | specs.clearFifo();
847 | auto x = static_cast(tableIdx) / specs.numTablesF;
848 | auto pitchSweepStart = 12.f;
849 | auto pitchSweepLength = 64.f;
850 | auto numHarmonics = 32;
851 | auto pitchDistance = 5.f;
852 | for (auto i = 0; i < numHarmonics; ++i)
853 | {
854 | const auto iF = static_cast(i);
855 | const auto iPitchDistance = pitchDistance * iF;
856 | auto pitch = pitchSweepStart + x * pitchSweepLength + iPitchDistance;
857 | while(pitch >= 136.f)
858 | pitch -= 136.f;
859 | const auto freq = pitchToFreq(pitch);
860 | const auto fc = freq / 44100.f;
861 | const auto idx = static_cast(std::round(fc * specs.fftSizeF * 2.f));
862 | specs.fifo[idx] = 1.f;
863 | }
864 |
865 | specs.randomizePhases();
866 | specs.performInverseFFT();
867 |
868 | /////////////////////////////
869 |
870 | LOWEST PITCH SINE WAVE FFT PREPARE:
871 |
872 | specs.clearFifo();
873 | const auto x = static_cast(tableIdx) / specs.numTablesF;
874 | const auto lowestPitch = -36.f;
875 | const auto freq = pitchToFreq(lowestPitch);
876 | const auto fc = freq / specs.tableSizeF;
877 | const auto idx = static_cast(std::round(fc * specs.fftSize2F));
878 | specs.fifo[idx] = 1.f;
879 | specs.performInverseFFT();
880 |
881 | /////////////////////////////
882 |
883 | SAW WAVE FFT PREPARE:
884 |
885 | specs.clearFifo();
886 | const auto x = static_cast(tableIdx) / specs.numTablesF;
887 | const auto numHarmonics = specs.fftSize;
888 | for (auto i = 1; i <= numHarmonics; ++i)
889 | {
890 | const auto iF = static_cast(i);
891 | const auto freq = iF;
892 | const auto fc = freq / specs.tableSizeF;
893 | if (fc < .5f)
894 | {
895 | const auto idx = static_cast(std::round(fc * specs.fftSize2F));
896 | auto mag = 1.f / iF;
897 | auto phase = 0.f;// i % 2 == 0 ? 0 : pi;
898 | specs.setMagAndPhase(mag, phase, idx);
899 | }
900 | }
901 | specs.performInverseFFT();
902 |
903 | /////////////////////////////
904 |
905 | /////////////////////////////
906 |
907 | /////////////////////////////
908 |
909 | /////////////////////////////
910 |
911 | /////////////////////////////
912 |
913 | /////////////////////////////
914 |
915 | /////////////////////////////
916 |
917 | /////////////////////////////
918 |
919 | /////////////////////////////
920 |
921 | /////////////////////////////
922 |
923 | /////////////////////////////
924 |
925 | /////////////////////////////
926 |
927 | TODO:
928 |
929 | being able to create sub paths by typing it into the string of createWav
930 | export creates helper txt file for serum
931 |
932 | */
--------------------------------------------------------------------------------