Browse Source

Implement drive CV and resonance CV of VCF. Refactor freq scaling.

tags/v2.0.1
Andrew Belt 3 years ago
parent
commit
57da45f242
1 changed files with 62 additions and 43 deletions
  1. +62
    -43
      src/VCF.cpp

+ 62
- 43
src/VCF.cpp View File

@@ -57,7 +57,6 @@ struct LadderFilter {
return state[3]; return state[3];
} }
T highpass() { T highpass() {
// TODO This is incorrect when `resonance > 0`. Is the math wrong?
return clip((input - resonance * state[3]) - 4 * state[0] + 6 * state[1] - 4 * state[2] + state[3]); return clip((input - resonance * state[3]) - 4 * state[0] + 6 * state[1] - 4 * state[2] + state[3]);
} }
}; };
@@ -97,20 +96,32 @@ struct VCF : Module {


VCF() { VCF() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
// Multiply and offset for backward patch compatibility
configParam(FREQ_PARAM, 0.f, 1.f, 0.5f, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f));
configParam(FINE_PARAM, 0.f, 1.f, 0.5f, "Fine frequency");
// To preserve backward compatibility with <2.0, FREQ_PARAM follows
// freq = C4 * 2^(10 * param - 5)
// or
// param = (log2(freq / C4) + 5) / 10
const float minFreq = (std::log2(dsp::FREQ_C4 / 8000.f) + 5) / 10;
const float maxFreq = (std::log2(8000.f / dsp::FREQ_C4) + 5) / 10;
const float defaultFreq = (0.f + 5) / 10;
configParam(FREQ_PARAM, minFreq, maxFreq, defaultFreq, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f));
configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance", "%", 0.f, 100.f); configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance", "%", 0.f, 100.f);
configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance CV", "%", 0.f, 100.f); configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance CV", "%", 0.f, 100.f);
configParam(FREQ_CV_PARAM, -1.f, 1.f, 0.f, "Cutoff frequency CV", "%", 0.f, 100.f); configParam(FREQ_CV_PARAM, -1.f, 1.f, 0.f, "Cutoff frequency CV", "%", 0.f, 100.f);
configParam(DRIVE_PARAM, 0.f, 1.f, 0.f, "Drive", "", 0, 11);
// gain(drive) = (1 + drive)^5
// gain(0) = 1
// gain(-1) = 0
// gain(1) = 5
configParam(DRIVE_PARAM, -1.f, 1.f, 0.f, "Drive", "%", 0, 100, 100);
configParam(DRIVE_CV_PARAM, -1.f, 1.f, 0.f, "Drive CV", "%", 0, 100); configParam(DRIVE_CV_PARAM, -1.f, 1.f, 0.f, "Drive CV", "%", 0, 100);

configInput(FREQ_INPUT, "Frequency"); configInput(FREQ_INPUT, "Frequency");
configInput(RES_INPUT, "Resonance"); configInput(RES_INPUT, "Resonance");
configInput(DRIVE_INPUT, "Drive"); configInput(DRIVE_INPUT, "Drive");
configInput(IN_INPUT, "Audio"); configInput(IN_INPUT, "Audio");

configOutput(LPF_OUTPUT, "Lowpass filter"); configOutput(LPF_OUTPUT, "Lowpass filter");
configOutput(HPF_OUTPUT, "Highpass filter"); configOutput(HPF_OUTPUT, "Highpass filter");

configBypass(IN_INPUT, LPF_OUTPUT); configBypass(IN_INPUT, LPF_OUTPUT);
configBypass(IN_INPUT, HPF_OUTPUT); configBypass(IN_INPUT, HPF_OUTPUT);
} }
@@ -129,24 +140,21 @@ struct VCF : Module {
float driveCvParam = params[DRIVE_CV_PARAM].getValue(); float driveCvParam = params[DRIVE_CV_PARAM].getValue();
float resParam = params[RES_PARAM].getValue(); float resParam = params[RES_PARAM].getValue();
float resCvParam = params[RES_CV_PARAM].getValue(); float resCvParam = params[RES_CV_PARAM].getValue();
float fineParam = params[FINE_PARAM].getValue();
fineParam = dsp::quadraticBipolar(fineParam * 2.f - 1.f) * 7.f / 12.f;
float freqCvParam = params[FREQ_CV_PARAM].getValue();
freqCvParam = dsp::quadraticBipolar(freqCvParam);
float freqParam = params[FREQ_PARAM].getValue(); float freqParam = params[FREQ_PARAM].getValue();
// Rescale for backward compatibility
freqParam = freqParam * 10.f - 5.f; freqParam = freqParam * 10.f - 5.f;
float freqCvParam = params[FREQ_CV_PARAM].getValue();


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


for (int c = 0; c < channels; c += 4) { for (int c = 0; c < channels; c += 4) {
auto* filter = &filters[c / 4];
auto& filter = filters[c / 4];


float_4 input = float_4::load(inputs[IN_INPUT].getVoltages(c)) / 5.f;
float_4 input = inputs[IN_INPUT].getVoltageSimd<float_4>(c) / 5.f;


// Drive gain // Drive gain
// TODO Make center of knob unity gain, 0 is off like a VCA
float_4 drive = driveParam + inputs[DRIVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * driveCvParam; float_4 drive = driveParam + inputs[DRIVE_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * driveCvParam;
drive = clamp(drive, 0.f, 1.f);
drive = clamp(drive, -1.f, 1.f);
float_4 gain = simd::pow(1.f + drive, 5); float_4 gain = simd::pow(1.f + drive, 5);
input *= gain; input *= gain;


@@ -156,48 +164,59 @@ struct VCF : Module {
// Set resonance // Set resonance
float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * resCvParam; float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * resCvParam;
resonance = clamp(resonance, 0.f, 1.f); resonance = clamp(resonance, 0.f, 1.f);
filter->resonance = simd::pow(resonance, 2) * 10.f;
filter.resonance = simd::pow(resonance, 2) * 10.f;


// Get pitch // Get pitch
float_4 pitch = freqParam + fineParam + inputs[FREQ_INPUT].getPolyVoltageSimd<float_4>(c) * freqCvParam;
float_4 pitch = freqParam + inputs[FREQ_INPUT].getPolyVoltageSimd<float_4>(c) * freqCvParam;
// Set cutoff // Set cutoff
float_4 cutoff = dsp::FREQ_C4 * simd::pow(2.f, pitch); float_4 cutoff = dsp::FREQ_C4 * simd::pow(2.f, pitch);
cutoff = clamp(cutoff, 1.f, 8000.f);
filter->setCutoff(cutoff);
// Without oversampling, we must limit to 8000 Hz or so @ 44100 Hz
cutoff = clamp(cutoff, 1.f, args.sampleRate * 0.18f);
filter.setCutoff(cutoff);

// Upsample input
// float dt = args.sampleTime / UPSAMPLE;
// float inputBuf[UPSAMPLE];
// float lowpassBuf[UPSAMPLE];
// float highpassBuf[UPSAMPLE];
// inputUpsampler.process(input, inputBuf);
// for (int i = 0; i < UPSAMPLE; i++) {
// // Step the filter
// filter.process(inputBuf[i], dt);
// if (outputs[LPF_OUTPUT].isConnected())
// lowpassBuf[i] = filter.lowpass();
// if (outputs[HPF_OUTPUT].isConnected())
// highpassBuf[i] = filter.highpass();
// }

// // Set outputs
// if (outputs[LPF_OUTPUT].isConnected()) {
// outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf));
// }
// if (outputs[HPF_OUTPUT].isConnected()) {
// outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf));
// }


// Set outputs // Set outputs
filter->process(input, args.sampleTime);
float_4 lowpass = 5.f * filter->lowpass();
lowpass.store(outputs[LPF_OUTPUT].getVoltages(c));
float_4 highpass = 5.f * filter->highpass();
highpass.store(outputs[HPF_OUTPUT].getVoltages(c));
filter.process(input, args.sampleTime);
if (outputs[LPF_OUTPUT].isConnected()) {
outputs[LPF_OUTPUT].setVoltageSimd(5.f * filter.lowpass(), c);
}
if (outputs[HPF_OUTPUT].isConnected()) {
outputs[HPF_OUTPUT].setVoltageSimd(5.f * filter.highpass(), c);
}
} }


outputs[LPF_OUTPUT].setChannels(channels); outputs[LPF_OUTPUT].setChannels(channels);
outputs[HPF_OUTPUT].setChannels(channels); outputs[HPF_OUTPUT].setChannels(channels);
}


/*
// Process sample
float dt = args.sampleTime / UPSAMPLE;
float inputBuf[UPSAMPLE];
float lowpassBuf[UPSAMPLE];
float highpassBuf[UPSAMPLE];
inputUpsampler.process(input, inputBuf);
for (int i = 0; i < UPSAMPLE; i++) {
// Step the filter
filter.process(inputBuf[i], dt);
lowpassBuf[i] = filter.lowpass;
highpassBuf[i] = filter.highpass;
}
void paramsFromJson(json_t* rootJ) override {
// These attenuators didn't exist in version <2.0, so set to 1 in case they are not overwritten.
params[RES_CV_PARAM].setValue(1.f);
params[DRIVE_CV_PARAM].setValue(1.f);


// Set outputs
if (outputs[LPF_OUTPUT].isConnected()) {
outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf));
}
if (outputs[HPF_OUTPUT].isConnected()) {
outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf));
}
*/
Module::paramsFromJson(rootJ);
} }
}; };




Loading…
Cancel
Save