Browse Source

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

tags/v1.0.1
Andrew Belt 3 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;
}
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) {
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;
// 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
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);

// 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);

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

// 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
if (lightDivider.process()) {
if (channels == 1) {
@@ -269,46 +255,36 @@ struct LFO2 : Module {

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

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

for (int c = 0; c < channels; 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->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->setReset(inputs[RESET_INPUT].getVoltage());
oscillator->setReset(inputs[RESET_INPUT].getPolyVoltageSimd<float_4>(c));

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

outputs[INTERP_OUTPUT].setChannels(channels);

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


+ 221
- 137
src/VCO.cpp View File

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


using simd::float_4;


BINARY(src_sawTable_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 {
bool analog = 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 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
float pitchSlew = 0.f;
T pitchSlew = 0.f;
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
pitch = pitchKnob;
T pitch = pitchKnob + pitchCv;
if (analog) {
// Apply pitch slew
const float pitchSlewAmount = 3.f;
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;
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) {
// Adjust pitch slew
if (++pitchSlewIndex > 32) {
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;
}
}

// 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++) {
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) {
syncDirection = !syncDirection;
deltaPhase *= -1.f;
syncDirection = simd::ifelse(reset, ~syncDirection, syncDirection);
}
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) {
// 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 {
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) {
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 {
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) {
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 {
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) {
// Simply filter here
// Add a highpass filter here
sqrFilter.setCutoff(10.f * deltaTime);
sqrFilter.process(sqrBuffer[i]);
sqrBuffer[i] = 0.71f * sqrFilter.highpass();
sqrBuffer[i] = 0.771f * sqrFilter.highpass();
}

// 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);
// sinBuffer[0];
}
float tri() {
T tri() {
return triDecimator.process(triBuffer);
// triBuffer[0];
}
float saw() {
T saw() {
return sawDecimator.process(sawBuffer);
// sawBuffer[0];
}
float sqr() {
T sqr() {
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
};
enum LightIds {
PHASE_POS_LIGHT,
PHASE_NEG_LIGHT,
ENUMS(PHASE_LIGHT, 3),
NUM_LIGHTS
};

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

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

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")));

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(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(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(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
};
enum LightIds {
PHASE_POS_LIGHT,
PHASE_NEG_LIGHT,
ENUMS(PHASE_LIGHT, 3),
NUM_LIGHTS
};

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

VCO2() {
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(WAVE_PARAM, 0.f, 3.f, 1.5f, "Wave");
configParam(FM_PARAM, 0.f, 1.f, 0.f, "Frequency modulation", "%", 0.f, 100.f);
lightDivider.setDivision(16);
}

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")));

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(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, 215), module, VCO2::SYNC_PARAM));
@@ -357,7 +441,7 @@ struct VCO2Widget : ModuleWidget {

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