From fd2dc19605be60bcc04085db9f60ace736199bab Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 22 Oct 2021 10:42:07 -0400 Subject: [PATCH] WIP experiment for antialiasing WTVCO. --- src/WTVCO.cpp | 90 +++++++++++++++-------------------------------- src/Wavetable.hpp | 74 ++++++++++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 72 deletions(-) diff --git a/src/WTVCO.cpp b/src/WTVCO.cpp index edce617..25db867 100644 --- a/src/WTVCO.cpp +++ b/src/WTVCO.cpp @@ -1,6 +1,5 @@ #include "plugin.hpp" #include "Wavetable.hpp" -#include using simd::float_4; @@ -41,14 +40,8 @@ struct WTVCO : Module { bool soft = false; bool linear = false; + float_4 phases[4] = {}; float lastPos = 0.f; - SRC_STATE* src[16]; - // callback state - uint8_t callbackChannel = 0; - float callbackPos = 0.f; - uint32_t callbackIndexes[16] = {}; - static constexpr int CALLBACK_BUFFER_LEN = 16; - float callbackBuffers[16][CALLBACK_BUFFER_LEN] = {}; dsp::ClockDivider lightDivider; dsp::BooleanTrigger softTrigger; @@ -72,21 +65,12 @@ struct WTVCO : Module { configLight(PHASE_LIGHT, "Phase"); lightDivider.setDivision(16); - - for (int c = 0; c < 16; c++) { - src[c] = src_callback_new(srcCallback, SRC_SINC_FASTEST, 1, NULL, this); - assert(src[c]); - } + wavetable.quality = 4; + wavetable.reset(); onReset(ResetEvent()); } - ~WTVCO() { - for (int c = 0; c < 16; c++) { - src_delete(src[c]); - } - } - void onReset(const ResetEvent& e) override { Module::onReset(e); soft = false; @@ -121,38 +105,6 @@ struct WTVCO : Module { lights[PHASE_LIGHT + 2].setBrightness(0.f); } - static long srcCallback(void* cbData, float** data) { - WTVCO* that = (WTVCO*) cbData; - int c = that->callbackChannel; - // Get pos - float posF = that->callbackPos - std::trunc(that->callbackPos); - size_t pos0 = std::trunc(that->callbackPos); - size_t pos1 = pos0 + 1; - // Fill callbackBuffer - for (int i = 0; i < CALLBACK_BUFFER_LEN; i++) { - size_t index = (that->callbackIndexes[c] + i) % that->wavetable.waveLen; - // Get waves - float out; - float out0 = that->wavetable.at(index, pos0); - if (posF > 0.f) { - float out1 = that->wavetable.at(index, pos1); - out = crossfade(out0, out1, posF); - } - else { - out = out0; - } - - that->callbackBuffers[c][i] = out; - } - - that->callbackIndexes[c] += CALLBACK_BUFFER_LEN; - data[0] = that->callbackBuffers[c]; - return CALLBACK_BUFFER_LEN; - } - - void fillInputBuffer(int c) { - } - void process(const ProcessArgs& args) override { if (linearTrigger.process(params[LINEAR_PARAM].getValue() > 0.f)) linear ^= true; @@ -185,7 +137,14 @@ struct WTVCO : Module { // Limit to Nyquist frequency freq = simd::fmin(freq, args.sampleRate / 2.f); - float_4 ratio = args.sampleRate / freq / wavetable.waveLen; + // Accumulate phase + float_4 phase = phases[c / 4]; + phase += freq * args.sampleTime; + // Wrap phase + phase -= simd::trunc(phase); + phases[c / 4] = phase; + // Scale phase from 0 to waveLen + 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; @@ -196,17 +155,24 @@ struct WTVCO : Module { lastPos = pos[0]; float_4 out = 0.f; - for (int cc = 0; cc < 4 && c + cc < channels; cc++) { - callbackChannel = c + cc; - callbackPos = pos[cc]; - // Not sure why this call is needed since we set ratio in src_callback_read(). Perhaps a SRC bug? - src_set_ratio(src[c + cc], ratio[cc]); - float outBuf[1]; - long ret = src_callback_read(src[c + cc], ratio[cc], 1, outBuf); - // DEBUG("ret %ld ratio %f", ret, ratio[cc]); - if (ret > 0) - out[cc] = outBuf[0]; + // Get wave indexes + float phaseF = phase[cc] - std::trunc(phase[cc]); + size_t i0 = std::trunc(phase[cc]); + size_t i1 = (i0 + 1) % (wavetable.waveLen * wavetable.quality); + // Get pos indexes + float posF = pos[cc] - std::trunc(pos[cc]); + 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); + if (posF > 0.f) { + float out1 = crossfade(wavetable.interpolatedAt(i0, pos1), wavetable.interpolatedAt(i1, pos1), phaseF); + out[cc] = crossfade(out0, out1, posF); + } + else { + out[cc] = out0; + } } outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c); diff --git a/src/Wavetable.hpp b/src/Wavetable.hpp index 6761ef1..4951c20 100644 --- a/src/Wavetable.hpp +++ b/src/Wavetable.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "dr_wav.h" @@ -9,10 +10,13 @@ static const char WAVETABLE_FILTERS[] = "WAV (.wav):wav,WAV"; /** Loads and stores wavetable samples and metadata */ struct Wavetable { - /** All waves concatenated */ - std::vector samples; /** Number of points in each wave */ size_t waveLen = 0; + /** All waves concatenated */ + std::vector samples; + /** Upsampling factor. No upsampling if 0. */ + size_t quality = 0; + std::vector interpolatedSamples; /** Name of loaded wavetable. */ std::string filename; @@ -26,6 +30,9 @@ struct Wavetable { float at(size_t sampleIndex, size_t waveIndex) const { return samples[sampleIndex + waveIndex * waveLen]; } + 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 { @@ -38,17 +45,63 @@ struct Wavetable { samples.clear(); samples.resize(waveLen * 4); + // Sine + for (size_t i = 0; i < waveLen; i++) { + float p = float(i) / waveLen; + at(i, 0) = 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; + } + // 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; + } + // Square for (size_t i = 0; i < waveLen; i++) { float p = float(i) / waveLen; - float sin = std::sin(2 * float(M_PI) * p); - at(i, 0) = sin; - float tri = (p < 0.25f) ? 4*p : (p < 0.75f) ? 2 - 4*p : 4*p - 4; - at(i, 1) = tri; - float saw = (p < 0.5f) ? 2*p : 2*p - 2; - at(i, 2) = saw; - float sqr = (p < 0.5f) ? 1 : -1; - at(i, 3) = sqr; + at(i, 3) = (p < 0.5f) ? 1 : -1; } + interpolate(); + } + + void interpolate() { + if (quality == 0) + return; + if (waveLen < 2) + return; + + interpolatedSamples.clear(); + interpolatedSamples.resize(samples.size() * quality); + + size_t waveCount = getWaveCount(); + + float* in = new float[3 * waveLen]; + float* out = new float[3 * waveLen * quality]; + + 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)); + } + + delete[] in; + delete[] out; } json_t* toJson() const { @@ -83,6 +136,7 @@ struct Wavetable { drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data()); drwav_uninit(&wav); + interpolate(); } void loadDialog() {