Browse Source

Rewrote VCO engine with SIMD. Added polyphony. Going for correctness first, not performance.

tags/v1.0.1
Andrew Belt 5 years ago
parent
commit
abb87f0ec4
2 changed files with 252 additions and 192 deletions
  1. +31
    -55
      src/LFO.cpp
  2. +221
    -137
      src/VCO.cpp

+ 31
- 55
src/LFO.cpp View File

@@ -66,7 +66,7 @@ struct LowFrequencyOscillator {
return v; return v;
} }
T light() { T light() {
return 1.f - 2.f * phase;
return simd::sin(2 * T(M_PI) * phase);
} }
}; };


@@ -127,53 +127,39 @@ struct LFO : 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->invert = (params[INVERT_PARAM].getValue() == 0.f);
oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f);

float_4 pitch = freqParam; float_4 pitch = freqParam;
// FM1, polyphonic // FM1, polyphonic
pitch += float_4::load(inputs[FM1_INPUT].getVoltages(c)) * fm1Param;
pitch += inputs[FM1_INPUT].getVoltageSimd<float_4>(c) * fm1Param;
// FM2, polyphonic or monophonic // FM2, polyphonic or monophonic
if (inputs[FM2_INPUT].isPolyphonic())
pitch += float_4::load(inputs[FM2_INPUT].getVoltages(c)) * fm2Param;
else
pitch += inputs[FM2_INPUT].getVoltage() * fm2Param;
pitch += inputs[FM2_INPUT].getPolyVoltageSimd<float_4>(c) * fm2Param;
oscillator->setPitch(pitch); oscillator->setPitch(pitch);


// Pulse width // Pulse width
float_4 pw = pwParam;
if (inputs[PW_INPUT].isPolyphonic())
pw += float_4::load(inputs[PW_INPUT].getVoltages(c)) / 10.f * pwmParam;
else
pw += inputs[PW_INPUT].getVoltage() / 10.f * pwmParam;
float_4 pw = pwParam + inputs[PW_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * pwmParam;
oscillator->setPulseWidth(pw); oscillator->setPulseWidth(pw);


// Settings
oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f);
oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f);
oscillator->step(args.sampleTime); oscillator->step(args.sampleTime);
oscillator->setReset(inputs[RESET_INPUT].getVoltage());
oscillator->setReset(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c));


// Outputs // Outputs
if (outputs[SIN_OUTPUT].isConnected()) {
outputs[SIN_OUTPUT].setChannels(channels);
float_4 v = 5.f * oscillator->sin();
v.store(outputs[SIN_OUTPUT].getVoltages(c));
}
if (outputs[TRI_OUTPUT].isConnected()) {
outputs[TRI_OUTPUT].setChannels(channels);
float_4 v = 5.f * oscillator->tri();
v.store(outputs[TRI_OUTPUT].getVoltages(c));
}
if (outputs[SAW_OUTPUT].isConnected()) {
outputs[SAW_OUTPUT].setChannels(channels);
float_4 v = 5.f * oscillator->saw();
v.store(outputs[SAW_OUTPUT].getVoltages(c));
}
if (outputs[SQR_OUTPUT].isConnected()) {
outputs[SQR_OUTPUT].setChannels(channels);
float_4 v = 5.f * oscillator->sqr();
v.store(outputs[SQR_OUTPUT].getVoltages(c));
}
if (outputs[SIN_OUTPUT].isConnected())
outputs[SIN_OUTPUT].setVoltageSimd(5.f * oscillator->sin(), c);
if (outputs[TRI_OUTPUT].isConnected())
outputs[TRI_OUTPUT].setVoltageSimd(5.f * oscillator->tri(), c);
if (outputs[SAW_OUTPUT].isConnected())
outputs[SAW_OUTPUT].setVoltageSimd(5.f * oscillator->saw(), c);
if (outputs[SQR_OUTPUT].isConnected())
outputs[SQR_OUTPUT].setVoltageSimd(5.f * oscillator->sqr(), c);
} }


outputs[SIN_OUTPUT].setChannels(channels);
outputs[TRI_OUTPUT].setChannels(channels);
outputs[SAW_OUTPUT].setChannels(channels);
outputs[SQR_OUTPUT].setChannels(channels);

// Light // Light
if (lightDivider.process()) { if (lightDivider.process()) {
if (channels == 1) { if (channels == 1) {
@@ -269,46 +255,36 @@ struct LFO2 : Module {


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


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


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];
float_4 pitch = freqParam;
// FM, polyphonic
pitch += float_4::load(inputs[FM_INPUT].getVoltages(c)) * fmParam;
oscillator->setPitch(pitch);

// Wave
float_4 wave = waveParam;
inputs[WAVE_INPUT].getVoltage();
if (inputs[WAVE_INPUT].isPolyphonic())
wave += float_4::load(inputs[WAVE_INPUT].getVoltages(c)) / 10.f * 3.f;
else
wave += inputs[WAVE_INPUT].getVoltage() / 10.f * 3.f;
wave = clamp(wave, 0.f, 3.f);

// Settings
oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f); oscillator->invert = (params[INVERT_PARAM].getValue() == 0.f);
oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f); oscillator->bipolar = (params[OFFSET_PARAM].getValue() == 0.f);

float_4 pitch = freqParam + inputs[FM_INPUT].getVoltageSimd<float_4>(c) * fmParam;
oscillator->setPitch(pitch);

oscillator->step(args.sampleTime); oscillator->step(args.sampleTime);
oscillator->setReset(inputs[RESET_INPUT].getVoltage());
oscillator->setReset(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c));


// Outputs // Outputs
if (outputs[INTERP_OUTPUT].isConnected()) { if (outputs[INTERP_OUTPUT].isConnected()) {
outputs[INTERP_OUTPUT].setChannels(channels);
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->sin() * simd::fmax(0.f, 1.f - simd::fabs(wave - 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->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->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->sqr() * simd::fmax(0.f, 1.f - simd::fabs(wave - 3.f));
v *= 5.f;
v.store(outputs[INTERP_OUTPUT].getVoltages(c));
outputs[INTERP_OUTPUT].setVoltageSimd(5.f * v, c);
} }
} }


outputs[INTERP_OUTPUT].setChannels(channels);

// Light // Light
if (lightDivider.process()) { if (lightDivider.process()) {
if (channels == 1) { if (channels == 1) {


+ 221
- 137
src/VCO.cpp View File

@@ -1,157 +1,195 @@
#include "plugin.hpp" #include "plugin.hpp"




using simd::float_4;


BINARY(src_sawTable_bin); BINARY(src_sawTable_bin);
BINARY(src_triTable_bin); BINARY(src_triTable_bin);




template <int OVERSAMPLE, int QUALITY>
// Accurate only on [0, 1]
template <typename T>
T sin2pi_pade_05_7_6(T x) {
x -= 0.5f;
return (T(-6.28319) * x + T(35.353) * simd::pow(x, 3) - T(44.9043) * simd::pow(x, 5) + T(16.0951) * simd::pow(x, 7))
/ (1 + T(0.953136) * simd::pow(x, 2) + T(0.430238) * simd::pow(x, 4) + T(0.0981408) * simd::pow(x, 6));
}

template <typename T>
T sin2pi_pade_05_5_4(T x) {
x -= 0.5f;
return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5))
/ (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4));
}


template <int OVERSAMPLE, int QUALITY, typename T>
struct VoltageControlledOscillator { struct VoltageControlledOscillator {
bool analog = false; bool analog = false;
bool soft = false; bool soft = false;
float lastSyncValue = 0.f;
float phase = 0.f;
float freq;
float pw = 0.5f;
float pitch;
T lastSyncValue = 0.f;
T phase = 0.f;
T freq;
T pw = 0.5f;
bool syncEnabled = false; bool syncEnabled = false;
bool syncDirection = false;
T syncDirection = T::zero();


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


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


float sinBuffer[OVERSAMPLE] = {};
float triBuffer[OVERSAMPLE] = {};
float sawBuffer[OVERSAMPLE] = {};
float sqrBuffer[OVERSAMPLE] = {};
T sinBuffer[OVERSAMPLE];
T triBuffer[OVERSAMPLE];
T sawBuffer[OVERSAMPLE];
T sqrBuffer[OVERSAMPLE];


void setPitch(float pitchKnob, float pitchCv) {
void setPitch(T pitchKnob, T pitchCv) {
// Compute frequency // Compute frequency
pitch = pitchKnob;
T pitch = pitchKnob + pitchCv;
if (analog) { if (analog) {
// Apply pitch slew // Apply pitch slew
const float pitchSlewAmount = 3.f; const float pitchSlewAmount = 3.f;
pitch += pitchSlew * pitchSlewAmount; pitch += pitchSlew * pitchSlewAmount;
} }
pitch += pitchCv;
// Note C4
freq = dsp::FREQ_C4 * std::pow(2.f, pitch / 12.f);
freq = dsp::FREQ_C4 * simd::pow(2.f, pitch / 12.f);
} }
void setPulseWidth(float pulseWidth) {
void setPulseWidth(T pulseWidth) {
const float pwMin = 0.01f; const float pwMin = 0.01f;
pw = clamp(pulseWidth, pwMin, 1.f - pwMin);
pw = simd::clamp(pulseWidth, pwMin, 1.f - pwMin);
} }


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


// Advance phase // Advance phase
float deltaPhase = clamp(freq * deltaTime, 1e-6f, 0.5f);

// Detect sync
int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE)
float syncCrossing = 0.f; // Offset that sync occurs [0.f, 1.f)
if (syncEnabled) {
syncValue -= 0.01f;
if (syncValue > 0.f && lastSyncValue <= 0.f) {
float deltaSync = syncValue - lastSyncValue;
syncCrossing = 1.f - syncValue / deltaSync;
syncCrossing *= OVERSAMPLE;
syncIndex = (int)syncCrossing;
syncCrossing -= syncIndex;
}
lastSyncValue = syncValue;
T deltaPhase = simd::clamp(freq * deltaTime, 1e-6f, 0.5f);
if (!soft) {
syncDirection = T::zero();
} }


if (syncDirection)
deltaPhase *= -1.f;
// Detect sync
// Might be NAN or outside of [0, 1) range
T syncCrossing = lastSyncValue / (lastSyncValue - syncValue);
lastSyncValue = syncValue;


sqrFilter.setCutoff(40.f * deltaTime);


for (int i = 0; i < OVERSAMPLE; i++) { for (int i = 0; i < OVERSAMPLE; i++) {
if (syncIndex == 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) { if (soft) {
syncDirection = !syncDirection;
deltaPhase *= -1.f;
syncDirection = simd::ifelse(reset, ~syncDirection, syncDirection);
} }
else { else {
// phase = syncCrossing * deltaPhase / OVERSAMPLE;
phase = 0.f;
phase = simd::ifelse(reset, (syncCrossing - T(i) / OVERSAMPLE) * deltaPhase, phase);
// This also works as a good approximation.
// phase = 0.f;
} }
} }


// Sin
if (analog) { if (analog) {
// Quadratic approximation of sine, slightly richer harmonics // Quadratic approximation of sine, slightly richer harmonics
if (phase < 0.5f)
sinBuffer[i] = 1.f - 16.f * std::pow(phase - 0.25f, 2);
else
sinBuffer[i] = -1.f + 16.f * std::pow(phase - 0.75f, 2);
sinBuffer[i] *= 1.08f;
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;
} }
else { else {
sinBuffer[i] = std::sin(2.f*M_PI * phase);
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);
} }

// Tri
if (analog) { if (analog) {
triBuffer[i] = 1.25f * interpolateLinear((const float*) BINARY_START(src_triTable_bin), phase * 2047.f);
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 { else {
if (phase < 0.25f)
triBuffer[i] = 4.f * phase;
else if (phase < 0.75f)
triBuffer[i] = 2.f - 4.f * phase;
else
triBuffer[i] = -4.f + 4.f * phase;
triBuffer[i] = 1 - 4 * simd::fmin(simd::fabs(phase - 0.25f), simd::fabs(phase - 1.25f));
} }

// Saw
if (analog) { if (analog) {
sawBuffer[i] = 1.66f * interpolateLinear((const float*) BINARY_START(src_sawTable_bin), phase * 2047.f);
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 { else {
if (phase < 0.5f)
sawBuffer[i] = 2.f * phase;
else
sawBuffer[i] = -2.f + 2.f * phase;
sawBuffer[i] = simd::ifelse(phase < 0.5f, 0.f, -2.f) + 2.f * phase;
} }
sqrBuffer[i] = (phase < pw) ? 1.f : -1.f;

// Sqr
sqrBuffer[i] = simd::ifelse(phase < pw, 1.f, -1.f);
if (analog) { if (analog) {
// Simply filter here
// Add a highpass filter here
sqrFilter.setCutoff(10.f * deltaTime);
sqrFilter.process(sqrBuffer[i]); sqrFilter.process(sqrBuffer[i]);
sqrBuffer[i] = 0.71f * sqrFilter.highpass();
sqrBuffer[i] = 0.771f * sqrFilter.highpass();
} }


// Advance phase // Advance phase
phase += deltaPhase / OVERSAMPLE;
phase = eucMod(phase, 1.f);
phase += simd::ifelse(syncDirection, -1.f, 1.f) * deltaPhase / OVERSAMPLE;
// Wrap phase to [0, 1)
phase -= simd::floor(phase);
} }
} }


float sin() {
T sin() {
return sinDecimator.process(sinBuffer); return sinDecimator.process(sinBuffer);
// sinBuffer[0];
} }
float tri() {
T tri() {
return triDecimator.process(triBuffer); return triDecimator.process(triBuffer);
// triBuffer[0];
} }
float saw() {
T saw() {
return sawDecimator.process(sawBuffer); return sawDecimator.process(sawBuffer);
// sawBuffer[0];
} }
float sqr() {
T sqr() {
return sqrDecimator.process(sqrBuffer); return sqrDecimator.process(sqrBuffer);
// sqrBuffer[0];
} }
float light() {
return std::sin(2*M_PI * phase);
T light() {
return simd::sin(2 * T(M_PI) * phase);
} }
}; };


@@ -182,12 +220,12 @@ struct VCO : Module {
NUM_OUTPUTS NUM_OUTPUTS
}; };
enum LightIds { enum LightIds {
PHASE_POS_LIGHT,
PHASE_NEG_LIGHT,
ENUMS(PHASE_LIGHT, 3),
NUM_LIGHTS NUM_LIGHTS
}; };


VoltageControlledOscillator<16, 16> oscillator;
VoltageControlledOscillator<16, 16, float_4> oscillators[4];
dsp::ClockDivider lightDivider;


VCO() { VCO() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
@@ -198,35 +236,58 @@ struct VCO : Module {
configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f); configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
configParam(PW_PARAM, 0.f, 1.f, 0.5f, "Pulse width", "%", 0.f, 100.f); configParam(PW_PARAM, 0.f, 1.f, 0.5f, "Pulse width", "%", 0.f, 100.f);
configParam(PWM_PARAM, 0.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f); configParam(PWM_PARAM, 0.f, 1.f, 0.f, "Pulse width modulation", "%", 0.f, 100.f);
lightDivider.setDivision(16);
} }


void process(const ProcessArgs &args) override { void process(const ProcessArgs &args) override {
oscillator.analog = params[MODE_PARAM].getValue() > 0.f;
oscillator.soft = params[SYNC_PARAM].getValue() <= 0.f;
int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);


float pitchFine = 3.f * dsp::quadraticBipolar(params[FINE_PARAM].getValue());
float pitchCv = 12.f * inputs[PITCH_INPUT].getVoltage();
if (inputs[FM_INPUT].isConnected()) {
pitchCv += dsp::quadraticBipolar(params[FM_PARAM].getValue()) * 12.f * inputs[FM_INPUT].getVoltage();
for (int c = 0; c < channels; c += 4) {
auto *oscillator = &oscillators[c / 4];
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);
if (inputs[FM_INPUT].isConnected()) {
pitchCv += dsp::quadraticBipolar(params[FM_PARAM].getValue()) * 12.f * inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c);
}
oscillator->setPitch(params[FREQ_PARAM].getValue(), pitchFine + pitchCv);
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();
oscillator->process(args.sampleTime, inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c));

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

outputs[SIN_OUTPUT].setChannels(channels);
outputs[TRI_OUTPUT].setChannels(channels);
outputs[SAW_OUTPUT].setChannels(channels);
outputs[SQR_OUTPUT].setChannels(channels);

// Light
if (lightDivider.process()) {
if (channels == 1) {
float lightValue = oscillators[0].light().s[0];
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 2].setBrightness(0.f);
}
else {
lights[PHASE_LIGHT + 0].setBrightness(0.f);
lights[PHASE_LIGHT + 1].setBrightness(0.f);
lights[PHASE_LIGHT + 2].setBrightness(1.f);
}
} }
oscillator.setPitch(params[FREQ_PARAM].getValue(), pitchFine + pitchCv);
oscillator.setPulseWidth(params[PW_PARAM].getValue() + params[PWM_PARAM].getValue() * inputs[PW_INPUT].getVoltage() / 10.f);
oscillator.syncEnabled = inputs[SYNC_INPUT].isConnected();

oscillator.process(args.sampleTime, inputs[SYNC_INPUT].getVoltage());

// Set output
if (outputs[SIN_OUTPUT].isConnected())
outputs[SIN_OUTPUT].setVoltage(5.f * oscillator.sin());
if (outputs[TRI_OUTPUT].isConnected())
outputs[TRI_OUTPUT].setVoltage(5.f * oscillator.tri());
if (outputs[SAW_OUTPUT].isConnected())
outputs[SAW_OUTPUT].setVoltage(5.f * oscillator.saw());
if (outputs[SQR_OUTPUT].isConnected())
outputs[SQR_OUTPUT].setVoltage(5.f * oscillator.sqr());

lights[PHASE_POS_LIGHT].setSmoothBrightness(oscillator.light(), args.sampleTime);
lights[PHASE_NEG_LIGHT].setSmoothBrightness(-oscillator.light(), args.sampleTime);
} }
}; };


@@ -237,9 +298,9 @@ struct VCOWidget : ModuleWidget {
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/VCO-1.svg"))); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/VCO-1.svg")));


addChild(createWidget<ScrewSilver>(Vec(15, 0))); addChild(createWidget<ScrewSilver>(Vec(15, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0)));
addChild(createWidget<ScrewSilver>(Vec(15, 365))); addChild(createWidget<ScrewSilver>(Vec(15, 365)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 365)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365)));


addParam(createParam<CKSS>(Vec(15, 77), module, VCO::MODE_PARAM)); addParam(createParam<CKSS>(Vec(15, 77), module, VCO::MODE_PARAM));
addParam(createParam<CKSS>(Vec(119, 77), module, VCO::SYNC_PARAM)); addParam(createParam<CKSS>(Vec(119, 77), module, VCO::SYNC_PARAM));
@@ -260,7 +321,7 @@ struct VCOWidget : ModuleWidget {
addOutput(createOutput<PJ301MPort>(Vec(80, 320), module, VCO::SAW_OUTPUT)); addOutput(createOutput<PJ301MPort>(Vec(80, 320), module, VCO::SAW_OUTPUT));
addOutput(createOutput<PJ301MPort>(Vec(114, 320), module, VCO::SQR_OUTPUT)); addOutput(createOutput<PJ301MPort>(Vec(114, 320), module, VCO::SQR_OUTPUT));


addChild(createLight<SmallLight<GreenRedLight>>(Vec(99, 42.5f), module, VCO::PHASE_POS_LIGHT));
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(99, 42.5f), module, VCO::PHASE_LIGHT));
} }
}; };


@@ -288,12 +349,12 @@ struct VCO2 : Module {
NUM_OUTPUTS NUM_OUTPUTS
}; };
enum LightIds { enum LightIds {
PHASE_POS_LIGHT,
PHASE_NEG_LIGHT,
ENUMS(PHASE_LIGHT, 3),
NUM_LIGHTS NUM_LIGHTS
}; };


VoltageControlledOscillator<8, 8> oscillator;
VoltageControlledOscillator<8, 8, float_4> oscillators[4];
dsp::ClockDivider lightDivider;


VCO2() { VCO2() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
@@ -302,32 +363,55 @@ struct VCO2 : Module {
configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4); configParam(FREQ_PARAM, -54.f, 54.f, 0.f, "Frequency", " Hz", dsp::FREQ_SEMITONE, dsp::FREQ_C4);
configParam(WAVE_PARAM, 0.f, 3.f, 1.5f, "Wave"); configParam(WAVE_PARAM, 0.f, 3.f, 1.5f, "Wave");
configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f); configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
lightDivider.setDivision(16);
} }


void process(const ProcessArgs &args) override { void process(const ProcessArgs &args) override {
float deltaTime = args.sampleTime;
oscillator.analog = params[MODE_PARAM].getValue() > 0.f;
oscillator.soft = params[SYNC_PARAM].getValue() <= 0.f;

float pitchCv = params[FREQ_PARAM].getValue() + dsp::quadraticBipolar(params[FM_PARAM].getValue()) * 12.f * inputs[FM_INPUT].getVoltage();
oscillator.setPitch(0.f, pitchCv);
oscillator.syncEnabled = inputs[SYNC_INPUT].isConnected();

oscillator.process(deltaTime, inputs[SYNC_INPUT].getVoltage());

// Set output
float wave = clamp(params[WAVE_PARAM].getValue() + inputs[WAVE_INPUT].getVoltage(), 0.f, 3.f);
float out;
if (wave < 1.f)
out = crossfade(oscillator.sin(), oscillator.tri(), wave);
else if (wave < 2.f)
out = crossfade(oscillator.tri(), oscillator.saw(), wave - 1.f);
else
out = crossfade(oscillator.saw(), oscillator.sqr(), wave - 2.f);
outputs[OUT_OUTPUT].setVoltage(5.f * out);

lights[PHASE_POS_LIGHT].setSmoothBrightness(oscillator.light(), deltaTime);
lights[PHASE_NEG_LIGHT].setSmoothBrightness(-oscillator.light(), deltaTime);
float freqParam = params[FREQ_PARAM].getValue();
float fmParam = params[FM_PARAM].getValue();
float waveParam = params[WAVE_PARAM].getValue();

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

for (int c = 0; c < channels; c += 4) {
auto *oscillator = &oscillators[c / 4];
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);

oscillator->syncEnabled = inputs[SYNC_INPUT].isConnected();
oscillator->process(args.sampleTime, inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c));

// Outputs
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));
outputs[OUT_OUTPUT].setVoltageSimd(5.f * v, c);
}
}

outputs[OUT_OUTPUT].setChannels(channels);

// Light
if (lightDivider.process()) {
if (channels == 1) {
float lightValue = oscillators[0].light().s[0];
lights[PHASE_LIGHT + 0].setSmoothBrightness(-lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 1].setSmoothBrightness(lightValue, args.sampleTime * lightDivider.getDivision());
lights[PHASE_LIGHT + 2].setBrightness(0.f);
}
else {
lights[PHASE_LIGHT + 0].setBrightness(0.f);
lights[PHASE_LIGHT + 1].setBrightness(0.f);
lights[PHASE_LIGHT + 2].setBrightness(1.f);
}
}
} }
}; };


@@ -340,9 +424,9 @@ struct VCO2Widget : ModuleWidget {
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/VCO-2.svg"))); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/VCO-2.svg")));


addChild(createWidget<ScrewSilver>(Vec(15, 0))); addChild(createWidget<ScrewSilver>(Vec(15, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0)));
addChild(createWidget<ScrewSilver>(Vec(15, 365))); addChild(createWidget<ScrewSilver>(Vec(15, 365)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 365)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365)));


addParam(createParam<CKSS>(Vec(62, 150), module, VCO2::MODE_PARAM)); addParam(createParam<CKSS>(Vec(62, 150), module, VCO2::MODE_PARAM));
addParam(createParam<CKSS>(Vec(62, 215), module, VCO2::SYNC_PARAM)); addParam(createParam<CKSS>(Vec(62, 215), module, VCO2::SYNC_PARAM));
@@ -357,7 +441,7 @@ struct VCO2Widget : ModuleWidget {


addOutput(createOutput<PJ301MPort>(Vec(54, 320), module, VCO2::OUT_OUTPUT)); addOutput(createOutput<PJ301MPort>(Vec(54, 320), module, VCO2::OUT_OUTPUT));


addChild(createLight<SmallLight<GreenRedLight>>(Vec(68, 42.5f), module, VCO2::PHASE_POS_LIGHT));
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(68, 42.5f), module, VCO2::PHASE_LIGHT));
} }
}; };




Loading…
Cancel
Save