diff --git a/src/WTLFO.cpp b/src/WTLFO.cpp index 7a29f35..fc45f49 100644 --- a/src/WTLFO.cpp +++ b/src/WTLFO.cpp @@ -84,11 +84,10 @@ struct WTLFO : Module { configLight(PHASE_LIGHT, "Phase"); lightDivider.setDivision(16); - onReset(ResetEvent()); + onReset(); } - void onReset(const ResetEvent& e) override { - Module::onReset(e); + void onReset() override { offset = false; invert = false; wavetable.reset(); @@ -206,9 +205,9 @@ struct WTLFO : Module { size_t pos0 = std::trunc(pos[cc]); size_t pos1 = pos0 + 1; // Get waves - float out0 = crossfade(wavetable.at(i0, pos0), wavetable.at(i1, pos0), phaseF); + float out0 = crossfade(wavetable.at(pos0, i0), wavetable.at(pos0, i1), phaseF); if (posF > 0.f) { - float out1 = crossfade(wavetable.at(i0, pos1), wavetable.at(i1, pos1), phaseF); + float out1 = crossfade(wavetable.at(pos1, i0), wavetable.at(pos1, i1), phaseF); out[cc] = crossfade(out0, out1, posF); } else { diff --git a/src/WTVCO.cpp b/src/WTVCO.cpp index fd3a6e7..6b3a4fa 100644 --- a/src/WTVCO.cpp +++ b/src/WTVCO.cpp @@ -65,21 +65,19 @@ struct WTVCO : Module { configLight(PHASE_LIGHT, "Phase"); lightDivider.setDivision(16); - wavetable.quality = 4; - wavetable.reset(); + wavetable.octaves = 8; + wavetable.setQuality(4); - onReset(ResetEvent()); + onReset(); } - void onReset(const ResetEvent& e) override { - Module::onReset(e); + void onReset() override { soft = false; linear = false; wavetable.reset(); } - void onRandomize(const RandomizeEvent& e) override { - Module::onRandomize(e); + void onRandomize() override { soft = random::get(); linear = random::get(); } @@ -137,6 +135,9 @@ struct WTVCO : Module { // Limit to Nyquist frequency freq = simd::fmin(freq, args.sampleRate / 2.f); + // Number of octaves above frequency until Nyquist + float_4 octave = simd::log2(args.sampleRate / 2 / freq); + // Accumulate phase float_4 phase = phases[c / 4]; phase += freq * args.sampleTime; @@ -144,7 +145,7 @@ struct WTVCO : Module { phase -= simd::trunc(phase); phases[c / 4] = phase; // Scale phase from 0 to waveLen - phase *= wavetable.waveLen * wavetable.quality; + phase *= (wavetable.waveLen * wavetable.quality); // Get wavetable position, scaled from 0 to (waveCount - 1) float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd(c) * posCvParam / 10.f; @@ -165,9 +166,10 @@ struct WTVCO : Module { size_t pos0 = std::trunc(pos[cc]); size_t pos1 = pos0 + 1; // Get waves - float out0 = crossfade(wavetable.interpolatedAt(i0, pos0), wavetable.interpolatedAt(i1, pos0), phaseF); + int octave0 = clamp((int) octave[cc], 0, 7); + float out0 = crossfade(wavetable.interpolatedAt(octave0, pos0, i0), wavetable.interpolatedAt(octave0, pos0, i1), phaseF); if (posF > 0.f) { - float out1 = crossfade(wavetable.interpolatedAt(i0, pos1), wavetable.interpolatedAt(i1, pos1), phaseF); + float out1 = crossfade(wavetable.interpolatedAt(octave0, pos1, i0), wavetable.interpolatedAt(octave0, pos1, i1), phaseF); out[cc] = crossfade(out0, out1, posF); } else { @@ -269,4 +271,4 @@ struct WTVCOWidget : ModuleWidget { }; -Model* modelVCO2 = createModel("VCO2"); \ No newline at end of file +Model* modelVCO2 = createModel("VCO2"); diff --git a/src/Wavetable.hpp b/src/Wavetable.hpp index 4951c20..320df03 100644 --- a/src/Wavetable.hpp +++ b/src/Wavetable.hpp @@ -1,7 +1,6 @@ #pragma once #include #include -#include #include "dr_wav.h" @@ -12,31 +11,32 @@ static const char WAVETABLE_FILTERS[] = "WAV (.wav):wav,WAV"; struct Wavetable { /** Number of points in each wave */ size_t waveLen = 0; - /** All waves concatenated */ + /** All waves concatenated + (waveCount, waveLen) + */ std::vector samples; + /** Name of loaded wavetable. */ + std::string filename; + + // Interpolated wavetables /** Upsampling factor. No upsampling if 0. */ size_t quality = 0; + /** Number of filtered wavetables to precompute */ + size_t octaves = 0; + /** (octave, waveCount, waveLen * quality) */ std::vector interpolatedSamples; - /** Name of loaded wavetable. */ - std::string filename; + float sampleRate = 44100; - Wavetable() { - reset(); - } + Wavetable() {} - float &at(size_t sampleIndex, size_t waveIndex) { - return samples[sampleIndex + waveIndex * waveLen]; + float &at(size_t waveIndex, size_t sampleIndex) { + return samples[waveLen * waveIndex + sampleIndex]; } - float at(size_t sampleIndex, size_t waveIndex) const { - return samples[sampleIndex + waveIndex * waveLen]; + float at(size_t waveIndex, size_t sampleIndex) const { + return samples[waveLen * waveIndex + sampleIndex]; } - float interpolatedAt(size_t sampleIndex, size_t waveIndex) const { - return interpolatedSamples[sampleIndex + waveIndex * quality * waveLen]; - } - - /** Returns the number of waves in the wavetable. */ - size_t getWaveCount() const { - return samples.size() / waveLen; + float interpolatedAt(size_t octave, size_t waveIndex, size_t sampleIndex) const { + return interpolatedSamples[samples.size() * quality * octave + waveLen * quality * waveIndex + sampleIndex]; } void reset() { @@ -48,60 +48,80 @@ struct Wavetable { // Sine for (size_t i = 0; i < waveLen; i++) { float p = float(i) / waveLen; - at(i, 0) = std::sin(2 * float(M_PI) * p); + at(0, i) = std::sin(2 * float(M_PI) * p); } // Triangle for (size_t i = 0; i < waveLen; i++) { float p = float(i) / waveLen; - at(i, 1) = (p < 0.25f) ? 4*p : (p < 0.75f) ? 2 - 4*p : 4*p - 4; + at(1, i) = (p < 0.25f) ? 4*p : (p < 0.75f) ? 2 - 4*p : 4*p - 4; } // Sawtooth for (size_t i = 0; i < waveLen; i++) { float p = float(i) / waveLen; - at(i, 2) = (p < 0.5f) ? 2*p : 2*p - 2; + at(2, i) = (p < 0.5f) ? 2*p : 2*p - 2; } // Square for (size_t i = 0; i < waveLen; i++) { float p = float(i) / waveLen; - at(i, 3) = (p < 0.5f) ? 1 : -1; + at(3, i) = (p < 0.5f) ? 1 : -1; } interpolate(); } + void setQuality(size_t quality) { + if (quality == this->quality) + return; + this->quality = quality; + interpolate(); + } + + void setWaveLen(size_t waveLen) { + if (waveLen == this->waveLen) + return; + this->waveLen = waveLen; + interpolate(); + } + + /** Returns the number of waves in the wavetable. */ + size_t getWaveCount() const { + return samples.size() / waveLen; + } + void interpolate() { - if (quality == 0) + if (quality == 0 || octaves == 0) return; if (waveLen < 2) return; - interpolatedSamples.clear(); - interpolatedSamples.resize(samples.size() * quality); - size_t waveCount = getWaveCount(); + if (waveCount == 0) + return; - float* in = new float[3 * waveLen]; - float* out = new float[3 * waveLen * quality]; + interpolatedSamples.clear(); + interpolatedSamples.resize(octaves * samples.size() * quality); + float* in = new float[quality * waveLen](); + float* inF = new float[2 * quality * waveLen]; + dsp::RealFFT fft(quality * waveLen); for (size_t i = 0; i < waveCount; i++) { - std::memcpy(&in[0 * waveLen], &samples[i * waveLen], waveLen * sizeof(float)); - std::memcpy(&in[1 * waveLen], &samples[i * waveLen], waveLen * sizeof(float)); - std::memcpy(&in[2 * waveLen], &samples[i * waveLen], waveLen * sizeof(float)); - - SRC_DATA srcData = {}; - srcData.data_in = in; - srcData.input_frames = 3 * waveLen; - srcData.data_out = out; - srcData.output_frames = 3 * waveLen * quality; - srcData.end_of_input = true; - srcData.src_ratio = quality; - src_simple(&srcData, SRC_SINC_FASTEST, 1); - DEBUG("used %ld gen %ld", srcData.input_frames_used, srcData.output_frames_gen); - - std::memcpy(&interpolatedSamples[i * waveLen * quality], &out[1 * waveLen * quality], waveLen * quality * sizeof(float)); + // Zero-stuff interpolated wave + for (size_t j = 0; j < waveLen; j++) { + in[j * quality] = samples[i * waveLen + j] / waveLen; + } + fft.rfft(in, inF); + // Lowpass inF + for (int octave = octaves - 1; octave >= 0; octave--) { + size_t firstJ = 1 << (octave + 1); + for (size_t j = firstJ; j < waveLen * quality; j++) { + inF[2 * j + 0] = 0.f; + inF[2 * j + 1] = 0.f; + } + fft.irfft(inF, &interpolatedSamples[samples.size() * quality * octave + waveLen * quality * i]); + } } + delete[] inF; delete[] in; - delete[] out; } json_t* toJson() const { @@ -117,7 +137,7 @@ struct Wavetable { // waveLen json_t* waveLenJ = json_object_get(rootJ, "waveLen"); if (waveLenJ) - waveLen = json_integer_value(waveLenJ); + setWaveLen(json_integer_value(waveLenJ)); // filename json_t* filenameJ = json_object_get(rootJ, "filename"); if (filenameJ) @@ -125,17 +145,37 @@ struct Wavetable { } void load(std::string path) { - drwav wav; - // TODO Unicode on Windows - if (!drwav_init_file(&wav, path.c_str(), NULL)) - return; - samples.clear(); - samples.resize(wav.totalPCMFrameCount * wav.channels); - drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data()); + std::string ext = string::lowercase(system::getExtension(path)); + if (ext == ".wav") { + // Load WAV + drwav wav; +#if defined ARCH_WIN + if (!drwav_init_file_w(&wav, string::UTF8toUTF16(path).c_str(), NULL)) +#else + if (!drwav_init_file(&wav, path.c_str(), NULL)) +#endif + return; + + samples.resize(wav.totalPCMFrameCount * wav.channels); + + drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data()); + drwav_uninit(&wav); + } + else { + // Load float32 + std::vector data = system::readFile(path); + size_t len = data.size() / sizeof(float); + samples.resize(len); + std::memcpy(samples.data(), data.data(), len * sizeof(float)); + } + + // Clamp samples between -1 and 1 + for (size_t i = 0; i < samples.size(); i++) { + samples[i] = clamp(samples[i], -1.f, 1.f); + } - drwav_uninit(&wav); interpolate(); } @@ -218,7 +258,7 @@ struct Wavetable { }; -static const Wavetable defaultWavetable; +static Wavetable defaultWavetable; template @@ -227,6 +267,9 @@ struct WTDisplay : LedDisplay { void drawLayer(const DrawArgs& args, int layer) override { if (layer == 1) { + if (defaultWavetable.samples.empty()) + defaultWavetable.reset(); + // Get module data or defaults const Wavetable& wavetable = module ? module->wavetable : defaultWavetable; float lastPos = module ? module->lastPos : 0.f; @@ -261,9 +304,9 @@ struct WTDisplay : LedDisplay { for (size_t i = 0; i <= wavetable.waveLen; i += iSkip) { // Get wave value float wave; - float wave0 = wavetable.at(i % wavetable.waveLen, pos0); + float wave0 = wavetable.at(pos0, i % wavetable.waveLen); if (posF > 0.f) { - float wave1 = wavetable.at(i % wavetable.waveLen, (pos0 + 1)); + float wave1 = wavetable.at(pos0 + 1, i % wavetable.waveLen); wave = crossfade(wave0, wave1, posF); } else {