Browse Source

Add VCO-2

tags/v0.4.0
Andrew Belt 7 years ago
parent
commit
cba5ad5c68
3 changed files with 236 additions and 121 deletions
  1. +1
    -0
      src/Fundamental.cpp
  2. +4
    -0
      src/Fundamental.hpp
  3. +231
    -121
      src/VCO.cpp

+ 1
- 0
src/Fundamental.cpp View File

@@ -9,6 +9,7 @@ void init(rack::Plugin *p) {
plugin->name = "Fundamental";
plugin->homepageUrl = "https://github.com/VCVRack/Fundamental";
createModel<VCOWidget>(plugin, "VCO", "VCO-1");
createModel<VCO2Widget>(plugin, "VCO2", "VCO-2");
createModel<VCFWidget>(plugin, "VCF", "VCF");
createModel<VCAWidget>(plugin, "VCA", "VCA");
createModel<LFOWidget>(plugin, "LFO", "LFO-1");


+ 4
- 0
src/Fundamental.hpp View File

@@ -14,6 +14,10 @@ struct VCOWidget : ModuleWidget {
VCOWidget();
};

struct VCO2Widget : ModuleWidget {
VCO2Widget();
};

struct VCFWidget : ModuleWidget {
VCFWidget();
};


+ 231
- 121
src/VCO.cpp View File

@@ -3,42 +3,20 @@
#include "dsp/filter.hpp"


#define OVERSAMPLE 16
#define QUALITY 16


extern float sawTable[2048];
extern float triTable[2048];


struct VCO : Module {
enum ParamIds {
MODE_PARAM,
SYNC_PARAM,
FREQ_PARAM,
FINE_PARAM,
FM_PARAM,
PW_PARAM,
PWM_PARAM,
NUM_PARAMS
};
enum InputIds {
PITCH_INPUT,
FM_INPUT,
SYNC_INPUT,
PW_INPUT,
NUM_INPUTS
};
enum OutputIds {
SIN_OUTPUT,
TRI_OUTPUT,
SAW_OUTPUT,
SQR_OUTPUT,
NUM_OUTPUTS
};

float lastSync = 0.0;
template <int OVERSAMPLE, int QUALITY>
struct VoltageControlledOscillator {
bool analog = false;
bool soft = false;
float lastSyncValue = 0.0;
float phase = 0.0;
float freq;
float pw = 0.5;
float pitch;
bool syncEnabled = false;
bool syncDirection = false;

Decimator<OVERSAMPLE, QUALITY> sinDecimator;
@@ -47,93 +25,81 @@ struct VCO : Module {
Decimator<OVERSAMPLE, QUALITY> sqrDecimator;
RCFilter sqrFilter;

float lights[1] = {};

// For analog detuning effect
float pitchSlew = 0.0;
int pitchSlewIndex = 0;

VCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
void step();
};


void VCO::step() {
bool analog = params[MODE_PARAM].value > 0.0;
bool soft = params[SYNC_PARAM].value <= 0.0;

if (analog) {
// Adjust pitch slew
if (++pitchSlewIndex > 32) {
const float pitchSlewTau = 100.0; // Time constant for leaky integrator in seconds
pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) / gSampleRate;
pitchSlewIndex = 0;
float sinBuffer[OVERSAMPLE] = {};
float triBuffer[OVERSAMPLE] = {};
float sawBuffer[OVERSAMPLE] = {};
float sqrBuffer[OVERSAMPLE] = {};

void setPitch(float pitchKnob, float pitchCv) {
// Compute frequency
pitch = pitchKnob;
if (analog) {
// Apply pitch slew
const float pitchSlewAmount = 3.0;
pitch += pitchSlew * pitchSlewAmount;
}
}

// Compute frequency
float pitch = params[FREQ_PARAM].value;
if (analog) {
// Apply pitch slew
const float pitchSlewAmount = 3.0;
pitch += pitchSlew * pitchSlewAmount;
}
else {
// Quantize coarse knob if digital mode
pitch = roundf(pitch);
}
pitch += 12.0 * inputs[PITCH_INPUT].value;
pitch += 3.0 * quadraticBipolar(params[FINE_PARAM].value);
if (inputs[FM_INPUT].active) {
pitch += quadraticBipolar(params[FM_PARAM].value) * 12.0 * inputs[FM_INPUT].value;
}
float freq = 261.626 * powf(2.0, pitch / 12.0);

// Pulse width
const float pwMin = 0.01;
float pw = clampf(params[PW_PARAM].value + params[PWM_PARAM].value * inputs[PW_INPUT].value / 10.0, pwMin, 1.0 - pwMin);

// Advance phase
float deltaPhase = clampf(freq / gSampleRate, 1e-6, 0.5);

// Detect sync
int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE)
float syncCrossing = 0.0; // Offset that sync occurs [0.0, 1.0)
if (inputs[SYNC_INPUT].active) {
float sync = inputs[SYNC_INPUT].value - 0.01;
if (sync > 0.0 && lastSync <= 0.0) {
float deltaSync = sync - lastSync;
syncCrossing = 1.0 - sync / deltaSync;
syncCrossing *= OVERSAMPLE;
syncIndex = (int)syncCrossing;
syncCrossing -= syncIndex;
else {
// Quantize coarse knob if digital mode
pitch = roundf(pitch);
}
lastSync = sync;
pitch += pitchCv;
// Note C3
freq = 261.626 * powf(2.0, pitch / 12.0);
}
void setPulseWidth(float pulseWidth) {
const float pwMin = 0.01;
pw = clampf(pulseWidth, pwMin, 1.0 - pwMin);
}

if (syncDirection)
deltaPhase *= -1.0;

// Oversample loop
float sinBuffer[OVERSAMPLE];
float triBuffer[OVERSAMPLE];
float sawBuffer[OVERSAMPLE];
float sqrBuffer[OVERSAMPLE];
sqrFilter.setCutoff(40.0 / gSampleRate);

for (int i = 0; i < OVERSAMPLE; i++) {
if (syncIndex == i) {
if (soft) {
syncDirection = !syncDirection;
deltaPhase *= -1.0;
void process(float deltaTime, float syncValue) {
if (analog) {
// Adjust pitch slew
if (++pitchSlewIndex > 32) {
const float pitchSlewTau = 100.0; // Time constant for leaky integrator in seconds
pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) / gSampleRate;
pitchSlewIndex = 0;
}
else {
// phase = syncCrossing * deltaPhase / OVERSAMPLE;
phase = 0.0;
}

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

// Detect sync
int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE)
float syncCrossing = 0.0; // Offset that sync occurs [0.0, 1.0)
if (syncEnabled) {
syncValue -= 0.01;
if (syncValue > 0.0 && lastSyncValue <= 0.0) {
float deltaSync = syncValue - lastSyncValue;
syncCrossing = 1.0 - syncValue / deltaSync;
syncCrossing *= OVERSAMPLE;
syncIndex = (int)syncCrossing;
syncCrossing -= syncIndex;
}
lastSyncValue = syncValue;
}

if (outputs[SIN_OUTPUT].active) {
if (syncDirection)
deltaPhase *= -1.0;

sqrFilter.setCutoff(40.0 * deltaTime);

for (int i = 0; i < OVERSAMPLE; i++) {
if (syncIndex == i) {
if (soft) {
syncDirection = !syncDirection;
deltaPhase *= -1.0;
}
else {
// phase = syncCrossing * deltaPhase / OVERSAMPLE;
phase = 0.0;
}
}

if (analog) {
// Quadratic approximation of sine, slightly richer harmonics
if (phase < 0.5f)
@@ -145,8 +111,6 @@ void VCO::step() {
else {
sinBuffer[i] = sinf(2.f*M_PI * phase);
}
}
if (outputs[TRI_OUTPUT].active) {
if (analog) {
triBuffer[i] = 1.25f * interpf(triTable, phase * 2047.f);
}
@@ -158,8 +122,6 @@ void VCO::step() {
else
triBuffer[i] = -4.f + 4.f * phase;
}
}
if (outputs[SAW_OUTPUT].active) {
if (analog) {
sawBuffer[i] = 1.66f * interpf(sawTable, phase * 2047.f);
}
@@ -169,32 +131,93 @@ void VCO::step() {
else
sawBuffer[i] = -2.f + 2.f * phase;
}
}
if (outputs[SQR_OUTPUT].active) {
sqrBuffer[i] = (phase < pw) ? 1.f : -1.f;
if (analog) {
// Simply filter here
sqrFilter.process(sqrBuffer[i]);
sqrBuffer[i] = 0.71f * sqrFilter.highpass();
}

// Advance phase
phase += deltaPhase / OVERSAMPLE;
phase = eucmodf(phase, 1.0);
}
}

// Advance phase
phase += deltaPhase / OVERSAMPLE;
phase = eucmodf(phase, 1.0);
float sin() {
return sinDecimator.process(sinBuffer);
}
float tri() {
return triDecimator.process(triBuffer);
}
float saw() {
return sawDecimator.process(sawBuffer);
}
float sqr() {
return sqrDecimator.process(sqrBuffer);
}
};


struct VCO : Module {
enum ParamIds {
MODE_PARAM,
SYNC_PARAM,
FREQ_PARAM,
FINE_PARAM,
FM_PARAM,
PW_PARAM,
PWM_PARAM,
NUM_PARAMS
};
enum InputIds {
PITCH_INPUT,
FM_INPUT,
SYNC_INPUT,
PW_INPUT,
NUM_INPUTS
};
enum OutputIds {
SIN_OUTPUT,
TRI_OUTPUT,
SAW_OUTPUT,
SQR_OUTPUT,
PITCH_LIGHT,
NUM_OUTPUTS
};

VoltageControlledOscillator<16, 16> oscillator;

VCO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
void step();
};


void VCO::step() {
oscillator.analog = params[MODE_PARAM].value > 0.0;
oscillator.soft = params[SYNC_PARAM].value <= 0.0;

float pitchCv = 12.0 * inputs[PITCH_INPUT].value + 3.0 * quadraticBipolar(params[FINE_PARAM].value);
if (inputs[FM_INPUT].active) {
pitchCv += quadraticBipolar(params[FM_PARAM].value) * 12.0 * inputs[FM_INPUT].value;
}
oscillator.setPitch(params[FREQ_PARAM].value, pitchCv);
oscillator.setPulseWidth(params[PW_PARAM].value + params[PWM_PARAM].value * inputs[PW_INPUT].value / 10.0);
oscillator.syncEnabled = inputs[SYNC_INPUT].active;

oscillator.process(1.0 / gSampleRate, inputs[SYNC_INPUT].value);

// Set output
if (outputs[SIN_OUTPUT].active)
outputs[SIN_OUTPUT].value = 5.0 * sinDecimator.process(sinBuffer);
outputs[SIN_OUTPUT].value = 5.0 * oscillator.sin();
if (outputs[TRI_OUTPUT].active)
outputs[TRI_OUTPUT].value = 5.0 * triDecimator.process(triBuffer);
outputs[TRI_OUTPUT].value = 5.0 * oscillator.tri();
if (outputs[SAW_OUTPUT].active)
outputs[SAW_OUTPUT].value = 5.0 * sawDecimator.process(sawBuffer);
outputs[SAW_OUTPUT].value = 5.0 * oscillator.saw();
if (outputs[SQR_OUTPUT].active)
outputs[SQR_OUTPUT].value = 5.0 * sqrDecimator.process(sqrBuffer);
outputs[SQR_OUTPUT].value = 5.0 * oscillator.sqr();

lights[0] = rescalef(pitch, -48.0, 48.0, -1.0, 1.0);
outputs[PITCH_LIGHT].value = rescalef(oscillator.pitch, -48.0, 48.0, -1.0, 1.0);
}


@@ -234,7 +257,94 @@ VCOWidget::VCOWidget() {
addOutput(createOutput<PJ301MPort>(Vec(80, 320), module, VCO::SAW_OUTPUT));
addOutput(createOutput<PJ301MPort>(Vec(114, 320), module, VCO::SQR_OUTPUT));

addChild(createValueLight<SmallLight<GreenRedPolarityLight>>(Vec(99, 42), &module->lights[0]));
addChild(createValueLight<SmallLight<GreenRedPolarityLight>>(Vec(99, 42), &module->outputs[VCO::PITCH_LIGHT].value));
}


struct VCO2 : Module {
enum ParamIds {
MODE_PARAM,
SYNC_PARAM,
FREQ_PARAM,
WAVE_PARAM,
FM_PARAM,
NUM_PARAMS
};
enum InputIds {
FM_INPUT,
SYNC_INPUT,
WAVE_INPUT,
NUM_INPUTS
};
enum OutputIds {
OUT_OUTPUT,
PITCH_LIGHT,
NUM_OUTPUTS
};

VoltageControlledOscillator<8, 8> oscillator;

VCO2() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
void step();
};


void VCO2::step() {
oscillator.analog = params[MODE_PARAM].value > 0.0;
oscillator.soft = params[SYNC_PARAM].value <= 0.0;

float pitchCv = params[FREQ_PARAM].value + quadraticBipolar(params[FM_PARAM].value) * 12.0 * inputs[FM_INPUT].value;
oscillator.setPitch(0.0, pitchCv);
oscillator.syncEnabled = inputs[SYNC_INPUT].active;

oscillator.process(1.0 / gSampleRate, inputs[SYNC_INPUT].value);

// Set output
float wave = clampf(params[WAVE_PARAM].value + inputs[WAVE_INPUT].value, 0.0, 3.0);
float out;
if (wave < 1.0)
out = crossf(oscillator.sin(), oscillator.tri(), wave);
else if (wave < 2.0)
out = crossf(oscillator.tri(), oscillator.saw(), wave - 1.0);
else
out = crossf(oscillator.saw(), oscillator.sqr(), wave - 2.0);
outputs[OUT_OUTPUT].value = 5.0 * out;

outputs[PITCH_LIGHT].value = rescalef(oscillator.pitch, -48.0, 48.0, -1.0, 1.0);
}


VCO2Widget::VCO2Widget() {
VCO2 *module = new VCO2();
setModule(module);
box.size = Vec(15*6, 380);

{
SVGPanel *panel = new SVGPanel();
panel->box.size = box.size;
panel->setBackground(SVG::load(assetPlugin(plugin, "res/VCO-2.svg")));
addChild(panel);
}

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

addParam(createParam<CKSS>(Vec(62, 150), module, VCO2::MODE_PARAM, 0.0, 1.0, 1.0));
addParam(createParam<CKSS>(Vec(62, 215), module, VCO2::SYNC_PARAM, 0.0, 1.0, 1.0));

addParam(createParam<RoundHugeBlackKnob>(Vec(17, 60), module, VCO2::FREQ_PARAM, -54.0, 54.0, 0.0));
addParam(createParam<RoundBlackKnob>(Vec(12, 143), module, VCO2::WAVE_PARAM, 0.0, 3.0, 1.5));
addParam(createParam<RoundBlackKnob>(Vec(12, 208), module, VCO2::FM_PARAM, 0.0, 1.0, 0.0));

addInput(createInput<PJ301MPort>(Vec(11, 276), module, VCO2::FM_INPUT));
addInput(createInput<PJ301MPort>(Vec(54, 276), module, VCO2::SYNC_INPUT));
addInput(createInput<PJ301MPort>(Vec(11, 320), module, VCO2::WAVE_INPUT));

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

addChild(createValueLight<SmallLight<GreenRedPolarityLight>>(Vec(68, 41), &module->outputs[VCO2::PITCH_LIGHT].value));
}




Loading…
Cancel
Save