Browse Source

Reimplement VCO as a minBLEP oscillator.

tags/v1.0.1
Andrew Belt 5 years ago
parent
commit
240643a219
4 changed files with 145 additions and 112 deletions
  1. +0
    -1
      Makefile
  2. +145
    -111
      src/VCO.cpp
  3. BIN
      src/sawTable.bin
  4. BIN
      src/triTable.bin

+ 0
- 1
Makefile View File

@@ -2,7 +2,6 @@ RACK_DIR ?= ../..


FLAGS += -Idep/include FLAGS += -Idep/include
SOURCES += $(wildcard src/*.cpp) SOURCES += $(wildcard src/*.cpp)
BINARIES += $(wildcard src/*.bin)
DISTRIBUTABLES += $(wildcard LICENSE*) res DISTRIBUTABLES += $(wildcard LICENSE*) res


# Static libs # Static libs


+ 145
- 111
src/VCO.cpp View File

@@ -4,12 +4,6 @@
using simd::float_4; using simd::float_4;




BINARY(src_triTable_bin);
const float *triTable = (const float*) BINARY_START(src_triTable_bin);
BINARY(src_sawTable_bin);
const float *sawTable = (const float*) BINARY_START(src_sawTable_bin);


// Accurate only on [0, 1] // Accurate only on [0, 1]
template <typename T> template <typename T>
T sin2pi_pade_05_7_6(T x) { T sin2pi_pade_05_7_6(T x) {
@@ -25,34 +19,39 @@ T sin2pi_pade_05_5_4(T x) {
/ (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4)); / (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4));
} }


template <typename T>
T expCurve(T x) {
return (3 + x * (-13 + 5 * x)) / (3 + 2 * x);
}



template <int OVERSAMPLE, int QUALITY, typename T> template <int OVERSAMPLE, int QUALITY, typename T>
struct VoltageControlledOscillator { struct VoltageControlledOscillator {
bool analog = false; bool analog = false;
bool soft = false; bool soft = false;
bool syncEnabled = false;
// For optimizing in serial code
int channels = 0;

T lastSyncValue = 0.f; T lastSyncValue = 0.f;
T phase = 0.f; T phase = 0.f;
T freq; T freq;
T pulseWidth = 0.5f; T pulseWidth = 0.5f;
bool syncEnabled = false;
T syncDirection = T::zero();
T syncDirection = 1.f;


dsp::TRCFilter<T> sqrFilter; dsp::TRCFilter<T> sqrFilter;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sinMinBlep;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> triMinBlep;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sawMinBlep;

dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sqrMinBlep; dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sqrMinBlep;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sawMinBlep;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> triMinBlep;
dsp::MinBlepGenerator<QUALITY, OVERSAMPLE, T> sinMinBlep;


// For analog detuning effect
T pitchDrift = 0.f;
int pitchSlewIndex = 0;
T sqrValue = 0.f;
T sawValue = 0.f;
T triValue = 0.f;
T sinValue = 0.f;


void setPitch(T pitch) { void setPitch(T pitch) {
if (analog) {
// Apply pitch drift
const float pitchDriftAmount = 0.25f;
pitch += pitchDrift * pitchDriftAmount;
}
freq = dsp::FREQ_C4 * simd::pow(2.f, pitch); freq = dsp::FREQ_C4 * simd::pow(2.f, pitch);
} }


@@ -62,67 +61,118 @@ struct VoltageControlledOscillator {
} }


void process(float deltaTime, T syncValue) { void process(float deltaTime, T syncValue) {
if (analog) {
if (++pitchSlewIndex > 32) {
// Advance pitch drift
const float pitchSlewTau = 100.f; // Time constant for leaky integrator in seconds
T r;
for (int i = 0; i < T::size; i++) {
r.s[i] = random::uniform();
}
pitchDrift += ((2.f * r - 1.f) - pitchDrift / pitchSlewTau) * deltaTime;
pitchSlewIndex = 0;
}
}

// Advance phase // Advance phase
T deltaPhase = simd::clamp(freq * deltaTime, 1e-6f, 0.5f);
T deltaPhase = simd::clamp(freq * deltaTime, 1e-6f, 0.35f);
if (soft) { if (soft) {
// Reverse direction // Reverse direction
deltaPhase = simd::ifelse(syncDirection, -deltaPhase, deltaPhase);
deltaPhase *= syncDirection;
} }
else { else {
// Reset back to forward // Reset back to forward
syncDirection = T::zero();
syncDirection = 1.f;
} }
phase += deltaPhase; phase += deltaPhase;
// Wrap phase
phase -= simd::floor(phase);

// Jump sqr when crossing 0, or 1 if backwards
T wrapPhase = (syncDirection == -1.f) & 1.f;
T wrapCrossing = (wrapPhase - (phase - deltaPhase)) / deltaPhase;
int wrapMask = simd::movemask((0 < wrapCrossing) & (wrapCrossing <= 1.f));
if (wrapMask) {
for (int i = 0; i < channels; i++) {
if (wrapMask & (1 << i)) {
T mask = simd::movemaskInverse<T>(1 << i);
float p = wrapCrossing[i] - 1.f;
T x = mask & (2.f * syncDirection);
sqrMinBlep.insertDiscontinuity(p, x);
}
}
}


// Wrap phase to [0, 1)
T phaseFloor = simd::floor(phase);
phase -= phaseFloor;
// Jump sqr when crossing `pulseWidth`
T pulseCrossing = (pulseWidth - (phase - deltaPhase)) / deltaPhase;
int pulseMask = simd::movemask((0 < pulseCrossing) & (pulseCrossing <= 1.f));
if (pulseMask) {
for (int i = 0; i < channels; i++) {
if (pulseMask & (1 << i)) {
T mask = simd::movemaskInverse<T>(1 << i);
float p = pulseCrossing[i] - 1.f;
T x = mask & (-2.f * syncDirection);
sqrMinBlep.insertDiscontinuity(p, x);
}
}
}

// Jump saw when crossing 0.5
T halfCrossing = (0.5f - (phase - deltaPhase)) / deltaPhase;
int halfMask = simd::movemask((0 < halfCrossing) & (halfCrossing <= 1.f));
if (halfMask) {
for (int i = 0; i < channels; i++) {
if (halfMask & (1 << i)) {
T mask = simd::movemaskInverse<T>(1 << i);
float p = halfCrossing[i] - 1.f;
T x = mask & (-2.f * syncDirection);
sawMinBlep.insertDiscontinuity(p, x);
}
}
}


T oldPhase = phase;
// Detect sync // Detect sync
// Might be NAN or outside of [0, 1) range // Might be NAN or outside of [0, 1) range
T syncCrossing = lastSyncValue / (lastSyncValue - syncValue);
lastSyncValue = syncValue;

T reset = (0.f <= syncCrossing) & (syncCrossing < 1.f);
int resetMask = simd::movemask(reset);
if (resetMask) {
if (soft) {
syncDirection = simd::ifelse(reset, ~syncDirection, syncDirection);
}
else {
phase = simd::ifelse(reset, syncCrossing * deltaPhase, phase);
// // Insert minBLEP for sync
// typename T::type phases[T::size];
// phase.store(phases);
// for (int i = 0; i < T::size; i++) {
// if (resetMask & (1 << i)) {
// // Inverse of movemask
// typename T::type resets1[T::size] = {};
// resets1[i] = 1.f;
// T reset1 = (T::load(resets1) != 0.f);
// // Discontinuity position
// typename T::type p = phases[i] - 1.f;
// // Discontinuity amplitude
// T x = reset1 & (sin(phase) - sin(oldPhase));
// sinMinBlep.insertDiscontinuity(p, x);
// }
// }
if (syncEnabled) {
T syncCrossing = -lastSyncValue / (syncValue - lastSyncValue);
lastSyncValue = syncValue;
T sync = (0.f < syncCrossing) & (syncCrossing <= 1.f);
int syncMask = simd::movemask(sync);
if (syncMask) {
if (soft) {
syncDirection = simd::ifelse(sync, -syncDirection, syncDirection);
}
else {
T newPhase = simd::ifelse(sync, syncCrossing * deltaPhase, phase);
// Insert minBLEP for sync
for (int i = 0; i < channels; i++) {
if (syncMask & (1 << i)) {
T mask = simd::movemaskInverse<T>(1 << i);
float p = syncCrossing[i] - 1.f;
T x;
x = mask & (sqr(newPhase) - sqr(phase));
sqrMinBlep.insertDiscontinuity(p, x);
x = mask & (saw(newPhase) - saw(phase));
sawMinBlep.insertDiscontinuity(p, x);
x = mask & (tri(newPhase) - tri(phase));
triMinBlep.insertDiscontinuity(p, x);
x = mask & (sin(newPhase) - sin(phase));
sinMinBlep.insertDiscontinuity(p, x);
}
}
phase = newPhase;
}
} }
} }

// Square
sqrValue = sqr(phase);
sqrValue += sqrMinBlep.process();

if (analog) {
sqrFilter.setCutoffFreq(20.f * deltaTime);
sqrFilter.process(sqrValue);
sqrValue = sqrFilter.highpass() * 0.95f;
}

// Saw
sawValue = saw(phase);
sawValue += sawMinBlep.process();

// Tri
triValue = tri(phase);
triValue += triMinBlep.process();

// Sin
sinValue = sin(phase);
sinValue += sinMinBlep.process();
} }


T sin(T phase) { T sin(T phase) {
@@ -141,68 +191,50 @@ struct VoltageControlledOscillator {
} }
return v; return v;
} }
T processSin() {
return sin(phase) + sinMinBlep.process();
T sin() {
return sinValue;
} }


T tri(T phase) { T tri(T phase) {
T v; T v;
if (analog) { if (analog) {
T p = phase * (BINARY_SIZE(src_triTable_bin) / sizeof(float) - 1);
simd::Vector<int32_t, T::size> index = p;
p -= index;

T v0, v1;
for (int i = 0; i < T::size; i++) {
v0.s[i] = triTable[index.s[i]];
v1.s[i] = triTable[index.s[i] + 1];
}
v = 1.129f * simd::crossfade(v0, v1, p);
T x = phase + 0.25f;
x -= simd::trunc(x);
T halfX = (x < 0.5f);
x = 2 * x - simd::ifelse(halfX, 0.f, 1.f);
v = expCurve(x) * simd::ifelse(halfX, 1.f, -1.f);
} }
else { else {
v = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f)); v = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
} }
return v; return v;
} }
T processTri() {
return tri(phase) + triMinBlep.process();
T tri() {
return triValue;
} }


T saw(T phase) { T saw(T phase) {
T v; T v;
T x = phase + 0.5f;
x -= simd::trunc(x);
if (analog) { if (analog) {
T p = phase * (BINARY_SIZE(src_sawTable_bin) / sizeof(float) - 1);
simd::Vector<int32_t, T::size> index = p;
p -= index;

T v0, v1;
for (int i = 0; i < T::size; i++) {
v0.s[i] = sawTable[index.s[i]];
v1.s[i] = sawTable[index.s[i] + 1];
}
v = 1.376f * simd::crossfade(v0, v1, p);
v = -expCurve(x);
} }
else { else {
v = simd::ifelse(phase < 0.5f, 0.f, -2.f) + 2.f * phase;
v = 2 * x - 1;
} }
return v; return v;
} }
T processSaw() {
return saw(phase) + sawMinBlep.process();
T saw() {
return sawValue;
} }


T sqr(T phase) { T sqr(T phase) {
T v = simd::ifelse(phase < pulseWidth, 1.f, -1.f); T v = simd::ifelse(phase < pulseWidth, 1.f, -1.f);
// if (analog) {
// // Add a highpass filter here
// sqrFilter.setCutoff(10.f * deltaTime);
// sqrFilter.process(v);
// v = 0.771f * sqrFilter.highpass();
// }
return v; return v;
} }
T processSqr() {
return sqr(phase) + sqrMinBlep.process();
T sqr() {
return sqrValue;
} }


T light() { T light() {
@@ -265,6 +297,7 @@ struct VCO : Module {


for (int c = 0; c < channels; c += 4) { for (int c = 0; c < channels; c += 4) {
auto *oscillator = &oscillators[c / 4]; auto *oscillator = &oscillators[c / 4];
oscillator->channels = std::min(channels - c, 4);
oscillator->analog = params[MODE_PARAM].getValue() > 0.f; oscillator->analog = params[MODE_PARAM].getValue() > 0.f;
oscillator->soft = params[SYNC_PARAM].getValue() <= 0.f; oscillator->soft = params[SYNC_PARAM].getValue() <= 0.f;


@@ -281,13 +314,13 @@ struct VCO : Module {


// Set output // Set output
if (outputs[SIN_OUTPUT].isConnected()) if (outputs[SIN_OUTPUT].isConnected())
outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator->processSin(), c);
outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator->sin(), c);
if (outputs[TRI_OUTPUT].isConnected()) if (outputs[TRI_OUTPUT].isConnected())
outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator->processTri(), c);
outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator->tri(), c);
if (outputs[SAW_OUTPUT].isConnected()) if (outputs[SAW_OUTPUT].isConnected())
outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator->processSaw(), c);
outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator->saw(), c);
if (outputs[SQR_OUTPUT].isConnected()) if (outputs[SQR_OUTPUT].isConnected())
outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator->processSqr(), c);
outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator->sqr(), c);
} }


outputs[SIN_OUTPUT].setChannels(channels); outputs[SIN_OUTPUT].setChannels(channels);
@@ -298,7 +331,7 @@ struct VCO : Module {
// Light // Light
if (lightDivider.process()) { if (lightDivider.process()) {
if (channels == 1) { if (channels == 1) {
float lightValue = oscillators[0].light().s[0];
float lightValue = oscillators[0].light()[0];
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 2].setBrightness(0.f); lights[PHASE_LIGHT + 2].setBrightness(0.f);
@@ -396,6 +429,7 @@ struct VCO2 : Module {


for (int c = 0; c < channels; c += 4) { for (int c = 0; c < channels; c += 4) {
auto *oscillator = &oscillators[c / 4]; auto *oscillator = &oscillators[c / 4];
oscillator->channels = std::min(channels - c, 4);
oscillator->analog = (params[MODE_PARAM].getValue() > 0.f); oscillator->analog = (params[MODE_PARAM].getValue() > 0.f);
oscillator->soft = (params[SYNC_PARAM].getValue() <= 0.f); oscillator->soft = (params[SYNC_PARAM].getValue() <= 0.f);


@@ -410,10 +444,10 @@ struct VCO2 : Module {
if (outputs[OUT_OUTPUT].isConnected()) { if (outputs[OUT_OUTPUT].isConnected()) {
float_4 wave = simd::clamp(waveParam + inputs[WAVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * 3.f, 0.f, 3.f); float_4 wave = simd::clamp(waveParam + inputs[WAVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * 3.f, 0.f, 3.f);
float_4 v = 0.f; float_4 v = 0.f;
v += oscillator->processSin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 0.f));
v += oscillator->processTri() * simd::fmax(0.f, 1.f - simd::fabs(wave - 1.f));
v += oscillator->processSaw() * simd::fmax(0.f, 1.f - simd::fabs(wave - 2.f));
v += oscillator->processSqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f));
v += oscillator->sin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 0.f));
v += oscillator->tri() * simd::fmax(0.f, 1.f - simd::fabs(wave - 1.f));
v += oscillator->saw() * simd::fmax(0.f, 1.f - simd::fabs(wave - 2.f));
v += oscillator->sqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f));
outputs[OUT_OUTPUT].setVoltageSimd(5.f * v, c); outputs[OUT_OUTPUT].setVoltageSimd(5.f * v, c);
} }
} }
@@ -423,7 +457,7 @@ struct VCO2 : Module {
// Light // Light
if (lightDivider.process()) { if (lightDivider.process()) {
if (channels == 1) { if (channels == 1) {
float lightValue = oscillators[0].light().s[0];
float lightValue = oscillators[0].light()[0];
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision()); lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision()); lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 2].setBrightness(0.f); lights[PHASE_LIGHT + 2].setBrightness(0.f);


BIN
src/sawTable.bin View File


BIN
src/triTable.bin View File


Loading…
Cancel
Save