Browse Source

WIP experiment for antialiasing WTVCO.

tags/v2.0.1
Andrew Belt 3 years ago
parent
commit
fd2dc19605
2 changed files with 92 additions and 72 deletions
  1. +28
    -62
      src/WTVCO.cpp
  2. +64
    -10
      src/Wavetable.hpp

+ 28
- 62
src/WTVCO.cpp View File

@@ -1,6 +1,5 @@
#include "plugin.hpp" #include "plugin.hpp"
#include "Wavetable.hpp" #include "Wavetable.hpp"
#include <samplerate.h>




using simd::float_4; using simd::float_4;
@@ -41,14 +40,8 @@ struct WTVCO : Module {
bool soft = false; bool soft = false;
bool linear = false; bool linear = false;


float_4 phases[4] = {};
float lastPos = 0.f; 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::ClockDivider lightDivider;
dsp::BooleanTrigger softTrigger; dsp::BooleanTrigger softTrigger;
@@ -72,21 +65,12 @@ struct WTVCO : Module {
configLight(PHASE_LIGHT, "Phase"); configLight(PHASE_LIGHT, "Phase");


lightDivider.setDivision(16); 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()); onReset(ResetEvent());
} }


~WTVCO() {
for (int c = 0; c < 16; c++) {
src_delete(src[c]);
}
}

void onReset(const ResetEvent& e) override { void onReset(const ResetEvent& e) override {
Module::onReset(e); Module::onReset(e);
soft = false; soft = false;
@@ -121,38 +105,6 @@ struct WTVCO : Module {
lights[PHASE_LIGHT + 2].setBrightness(0.f); 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 { void process(const ProcessArgs& args) override {
if (linearTrigger.process(params[LINEAR_PARAM].getValue() > 0.f)) if (linearTrigger.process(params[LINEAR_PARAM].getValue() > 0.f))
linear ^= true; linear ^= true;
@@ -185,7 +137,14 @@ struct WTVCO : Module {
// Limit to Nyquist frequency // Limit to Nyquist frequency
freq = simd::fmin(freq, args.sampleRate / 2.f); 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) // Get wavetable position, scaled from 0 to (waveCount - 1)
float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd<float_4>(c) * posCvParam / 10.f; float_4 pos = posParam + inputs[POS_INPUT].getPolyVoltageSimd<float_4>(c) * posCvParam / 10.f;
@@ -196,17 +155,24 @@ struct WTVCO : Module {
lastPos = pos[0]; lastPos = pos[0];


float_4 out = 0.f; float_4 out = 0.f;

for (int cc = 0; cc < 4 && c + cc < channels; cc++) { 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); outputs[WAVE_OUTPUT].setVoltageSimd(out * 5.f, c);


+ 64
- 10
src/Wavetable.hpp View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <rack.hpp> #include <rack.hpp>
#include <osdialog.h> #include <osdialog.h>
#include <samplerate.h>
#include "dr_wav.h" #include "dr_wav.h"




@@ -9,10 +10,13 @@ static const char WAVETABLE_FILTERS[] = "WAV (.wav):wav,WAV";


/** Loads and stores wavetable samples and metadata */ /** Loads and stores wavetable samples and metadata */
struct Wavetable { struct Wavetable {
/** All waves concatenated */
std::vector<float> samples;
/** Number of points in each wave */ /** Number of points in each wave */
size_t waveLen = 0; size_t waveLen = 0;
/** All waves concatenated */
std::vector<float> samples;
/** Upsampling factor. No upsampling if 0. */
size_t quality = 0;
std::vector<float> interpolatedSamples;
/** Name of loaded wavetable. */ /** Name of loaded wavetable. */
std::string filename; std::string filename;


@@ -26,6 +30,9 @@ struct Wavetable {
float at(size_t sampleIndex, size_t waveIndex) const { float at(size_t sampleIndex, size_t waveIndex) const {
return samples[sampleIndex + waveIndex * waveLen]; 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. */ /** Returns the number of waves in the wavetable. */
size_t getWaveCount() const { size_t getWaveCount() const {
@@ -38,17 +45,63 @@ struct Wavetable {
samples.clear(); samples.clear();
samples.resize(waveLen * 4); 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++) { for (size_t i = 0; i < waveLen; i++) {
float p = float(i) / waveLen; 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 { json_t* toJson() const {
@@ -83,6 +136,7 @@ struct Wavetable {
drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data()); drwav_read_pcm_frames_f32(&wav, wav.totalPCMFrameCount, samples.data());


drwav_uninit(&wav); drwav_uninit(&wav);
interpolate();
} }


void loadDialog() { void loadDialog() {


Loading…
Cancel
Save