├── .gitignore ├── README.md ├── Source └── Main.cpp └── WavetableCreatorConsole.jucer /.gitignore: -------------------------------------------------------------------------------- 1 | **/Builds 2 | **/JuceLibraryCode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WavetableCreatorConsole 2 | console app for creating wavetables 3 | -------------------------------------------------------------------------------- /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 | */ -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------