Browse Source

VCO WIP

tags/v1.0.1
Andrew Belt 4 years ago
parent
commit
1b1e855172
2 changed files with 146 additions and 125 deletions
  1. +0
    -1
      Makefile
  2. +146
    -124
      src/VCO.cpp

+ 0
- 1
Makefile View File

@@ -10,7 +10,6 @@ libsamplerate := dep/lib/libsamplerate.a
OBJECTS += $(libsamplerate)

# Dependencies
DEP_LOCAL := dep
DEPS += $(libsamplerate)

$(libsamplerate):


+ 146
- 124
src/VCO.cpp View File

@@ -4,8 +4,10 @@
using simd::float_4;


BINARY(src_sawTable_bin);
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]
@@ -31,163 +33,178 @@ struct VoltageControlledOscillator {
T lastSyncValue = 0.f;
T phase = 0.f;
T freq;
T pw = 0.5f;
T pulseWidth = 0.5f;
bool syncEnabled = false;
T syncDirection = T::zero();

dsp::Decimator<OVERSAMPLE, QUALITY, T> sinDecimator;
dsp::Decimator<OVERSAMPLE, QUALITY, T> triDecimator;
dsp::Decimator<OVERSAMPLE, QUALITY, T> sawDecimator;
dsp::Decimator<OVERSAMPLE, QUALITY, T> sqrDecimator;
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;

// For analog detuning effect
T pitchSlew = 0.f;
T pitchDrift = 0.f;
int pitchSlewIndex = 0;

T sinBuffer[OVERSAMPLE];
T triBuffer[OVERSAMPLE];
T sawBuffer[OVERSAMPLE];
T sqrBuffer[OVERSAMPLE];

void setPitch(T pitchKnob, T pitchCv) {
// Compute frequency
T pitch = pitchKnob + pitchCv;
void setPitch(T pitch) {
if (analog) {
// Apply pitch slew
const float pitchSlewAmount = 3.f;
pitch += pitchSlew * pitchSlewAmount;
// Apply pitch drift
const float pitchDriftAmount = 0.25f;
pitch += pitchDrift * pitchDriftAmount;
}
freq = dsp::FREQ_C4 * simd::pow(2.f, pitch / 12.f);
freq = dsp::FREQ_C4 * simd::pow(2.f, pitch);
}

void setPulseWidth(T pulseWidth) {
const float pwMin = 0.01f;
pw = simd::clamp(pulseWidth, pwMin, 1.f - pwMin);
this->pulseWidth = simd::clamp(pulseWidth, pwMin, 1.f - pwMin);
}

void process(float deltaTime, T syncValue) {
if (analog) {
// Adjust pitch slew
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();
}
pitchSlew += ((2.f * r - 1.f) - pitchSlew / pitchSlewTau) * deltaTime;
pitchDrift += ((2.f * r - 1.f) - pitchDrift / pitchSlewTau) * deltaTime;
pitchSlewIndex = 0;
}
}

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

// Wrap phase to [0, 1)
T phaseFloor = simd::floor(phase);
phase -= phaseFloor;

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


for (int i = 0; i < OVERSAMPLE; i++) {
// Reset if synced in this time interval
T reset = (T(i) / OVERSAMPLE <= syncCrossing) & (syncCrossing < T(i + 1) / OVERSAMPLE);
if (simd::movemask(reset)) {
if (soft) {
syncDirection = simd::ifelse(reset, ~syncDirection, syncDirection);
}
else {
phase = simd::ifelse(reset, (syncCrossing - T(i) / OVERSAMPLE) * deltaPhase, phase);
// This also works as a good approximation.
// phase = 0.f;
}
}

// Sin
if (analog) {
// Quadratic approximation of sine, slightly richer harmonics
T halfPhase = (phase < 0.5f);
T x = phase - simd::ifelse(halfPhase, 0.25f, 0.75f);
sinBuffer[i] = 1.f - 16.f * simd::pow(x, 2);
sinBuffer[i] *= simd::ifelse(halfPhase, 1.f, -1.f);
// sinBuffer[i] *= 1.08f;
T reset = (0.f <= syncCrossing) & (syncCrossing < 1.f);
int resetMask = simd::movemask(reset);
if (resetMask) {
if (soft) {
syncDirection = simd::ifelse(reset, ~syncDirection, syncDirection);
}
else {
sinBuffer[i] = sin2pi_pade_05_5_4(phase);
// sinBuffer[i] = sin2pi_pade_05_7_6(phase);
// sinBuffer[i] = simd::sin(2 * T(M_PI) * phase);
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);
// }
// }
}
}
}

// Tri
if (analog) {
const float *triTable = (const float*) BINARY_START(src_triTable_bin);
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];
}
triBuffer[i] = 1.129f * simd::crossfade(v0, v1, p);
}
else {
triBuffer[i] = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
}

// Saw
if (analog) {
const float *sawTable = (const float*) BINARY_START(src_sawTable_bin);
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];
}
sawBuffer[i] = 1.376f * simd::crossfade(v0, v1, p);
}
else {
sawBuffer[i] = simd::ifelse(phase < 0.5f, 0.f, -2.f) + 2.f * phase;
}
T sin(T phase) {
T v;
if (analog) {
// Quadratic approximation of sine, slightly richer harmonics
T halfPhase = (phase < 0.5f);
T x = phase - simd::ifelse(halfPhase, 0.25f, 0.75f);
v = 1.f - 16.f * simd::pow(x, 2);
v *= simd::ifelse(halfPhase, 1.f, -1.f);
}
else {
v = sin2pi_pade_05_5_4(phase);
// v = sin2pi_pade_05_7_6(phase);
// v = simd::sin(2 * T(M_PI) * phase);
}
return v;
}
T processSin() {
return sin(phase) + sinMinBlep.process();
}

// Sqr
sqrBuffer[i] = simd::ifelse(phase < pw, 1.f, -1.f);
if (analog) {
// Add a highpass filter here
sqrFilter.setCutoff(10.f * deltaTime);
sqrFilter.process(sqrBuffer[i]);
sqrBuffer[i] = 0.771f * sqrFilter.highpass();
T tri(T phase) {
T v;
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];
}

// Advance phase
phase += simd::ifelse(syncDirection, -1.f, 1.f) * deltaPhase / OVERSAMPLE;
// Wrap phase to [0, 1)
phase -= simd::floor(phase);
v = 1.129f * simd::crossfade(v0, v1, p);
}
else {
v = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
}
return v;
}
T processTri() {
return tri(phase) + triMinBlep.process();
}

T sin() {
return sinDecimator.process(sinBuffer);
// sinBuffer[0];
T saw(T phase) {
T v;
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);
}
else {
v = simd::ifelse(phase < 0.5f, 0.f, -2.f) + 2.f * phase;
}
return v;
}
T tri() {
return triDecimator.process(triBuffer);
// triBuffer[0];
T processSaw() {
return saw(phase) + sawMinBlep.process();
}
T saw() {
return sawDecimator.process(sawBuffer);
// sawBuffer[0];

T sqr(T phase) {
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;
}
T sqr() {
return sqrDecimator.process(sqrBuffer);
// sqrBuffer[0];
T processSqr() {
return sqr(phase) + sqrMinBlep.process();
}

T light() {
return simd::sin(2 * T(M_PI) * phase);
}
@@ -240,6 +257,10 @@ struct VCO : Module {
}

void process(const ProcessArgs &args) override {
float freqParam = params[FREQ_PARAM].getValue() / 12.f;
freqParam += dsp::quadraticBipolar(params[FINE_PARAM].getValue()) * 3.f / 12.f;
float fmParam = dsp::quadraticBipolar(params[FM_PARAM].getValue());

int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);

for (int c = 0; c < channels; c += 4) {
@@ -247,12 +268,12 @@ struct VCO : Module {
oscillator->analog = params[MODE_PARAM].getValue() > 0.f;
oscillator->soft = params[SYNC_PARAM].getValue() <= 0.f;

float pitchFine = 3.f * dsp::quadraticBipolar(params[FINE_PARAM].getValue());
float_4 pitchCv = 12.f * inputs[PITCH_INPUT].getVoltageSimd<float_4>(c);
float_4 pitch = freqParam;
pitch += inputs[PITCH_INPUT].getVoltageSimd<float_4>(c);
if (inputs[FM_INPUT].isConnected()) {
pitchCv += dsp::quadraticBipolar(params[FM_PARAM].getValue()) * 12.f * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c);
pitch += fmParam * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c);
}
oscillator->setPitch(params[FREQ_PARAM].getValue(), pitchFine + pitchCv);
oscillator->setPitch(pitch);
oscillator->setPulseWidth(params[PW_PARAM].getValue() + params[PWM_PARAM].getValue() * inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f);

oscillator->syncEnabled = inputs[SYNC_INPUT].isConnected();
@@ -260,13 +281,13 @@ struct VCO : Module {

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

outputs[SIN_OUTPUT].setChannels(channels);
@@ -367,8 +388,8 @@ struct VCO2 : Module {
}

void process(const ProcessArgs &args) override {
float freqParam = params[FREQ_PARAM].getValue();
float fmParam = params[FM_PARAM].getValue();
float freqParam = params[FREQ_PARAM].getValue() / 12.f;
float fmParam = dsp::quadraticBipolar(params[FM_PARAM].getValue());
float waveParam = params[WAVE_PARAM].getValue();

int channels = std::max(inputs[FM_INPUT].getChannels(), 1);
@@ -378,8 +399,9 @@ struct VCO2 : Module {
oscillator->analog = (params[MODE_PARAM].getValue() > 0.f);
oscillator->soft = (params[SYNC_PARAM].getValue() <= 0.f);

float_4 pitch = freqParam + dsp::quadraticBipolar(fmParam) * 12.f * inputs[FM_INPUT].getVoltageSimd<float_4>(c);
oscillator->setPitch(0.f, pitch);
float_4 pitch = freqParam;
pitch += fmParam * inputs[FM_INPUT].getVoltageSimd<float_4>(c);
oscillator->setPitch(pitch);

oscillator->syncEnabled = inputs[SYNC_INPUT].isConnected();
oscillator->process(args.sampleTime, inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c));
@@ -388,10 +410,10 @@ struct VCO2 : Module {
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 v = 0.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));
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));
outputs[OUT_OUTPUT].setVoltageSimd(5.f * v, c);
}
}


Loading…
Cancel
Save