|
|
@@ -57,7 +57,6 @@ struct LadderFilter { |
|
|
|
return state[3]; |
|
|
|
} |
|
|
|
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]); |
|
|
|
} |
|
|
|
}; |
|
|
@@ -97,20 +96,32 @@ struct VCF : Module { |
|
|
|
|
|
|
|
VCF() { |
|
|
|
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_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(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); |
|
|
|
|
|
|
|
configInput(FREQ_INPUT, "Frequency"); |
|
|
|
configInput(RES_INPUT, "Resonance"); |
|
|
|
configInput(DRIVE_INPUT, "Drive"); |
|
|
|
configInput(IN_INPUT, "Audio"); |
|
|
|
|
|
|
|
configOutput(LPF_OUTPUT, "Lowpass filter"); |
|
|
|
configOutput(HPF_OUTPUT, "Highpass filter"); |
|
|
|
|
|
|
|
configBypass(IN_INPUT, LPF_OUTPUT); |
|
|
|
configBypass(IN_INPUT, HPF_OUTPUT); |
|
|
|
} |
|
|
@@ -129,24 +140,21 @@ struct VCF : Module { |
|
|
|
float driveCvParam = params[DRIVE_CV_PARAM].getValue(); |
|
|
|
float resParam = params[RES_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(); |
|
|
|
// Rescale for backward compatibility |
|
|
|
freqParam = freqParam * 10.f - 5.f; |
|
|
|
float freqCvParam = params[FREQ_CV_PARAM].getValue(); |
|
|
|
|
|
|
|
int channels = std::max(1, inputs[IN_INPUT].getChannels()); |
|
|
|
|
|
|
|
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 |
|
|
|
// 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; |
|
|
|
drive = clamp(drive, 0.f, 1.f); |
|
|
|
drive = clamp(drive, -1.f, 1.f); |
|
|
|
float_4 gain = simd::pow(1.f + drive, 5); |
|
|
|
input *= gain; |
|
|
|
|
|
|
@@ -156,48 +164,59 @@ struct VCF : Module { |
|
|
|
// Set resonance |
|
|
|
float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f * resCvParam; |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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[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); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|