| @@ -33,10 +33,9 @@ float cliph(float in, float clip) { | |||||
| */ | */ | ||||
| float wrapTWOPI(float n) { | float wrapTWOPI(float n) { | ||||
| float b = 1.f / TWOPI * n; | float b = 1.f / TWOPI * n; | ||||
| return (b - lround(b)) * TWOPI; | |||||
| return (b - lroundf(b)) * TWOPI; | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief Get PLL increment depending on frequency | * @brief Get PLL increment depending on frequency | ||||
| * @param frq Frequency | * @param frq Frequency | ||||
| @@ -6,10 +6,9 @@ | |||||
| #include "dsp/resampler.hpp" | #include "dsp/resampler.hpp" | ||||
| #include "DSPEffect.hpp" | #include "DSPEffect.hpp" | ||||
| #define LAMBERT_W_THRESHOLD 10e-10 | |||||
| using namespace rack; | using namespace rack; | ||||
| const static float TWOPI = (float) M_PI * 2; | |||||
| const static float TWOPI = lroundf(M_PI * 2.); | |||||
| namespace dsp { | namespace dsp { | ||||
| @@ -50,13 +50,14 @@ struct DSPSystem { | |||||
| protected: | protected: | ||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||
| DSPPort input[NUM_IN + 1]; | |||||
| DSPPort output[NUM_OUT + 1]; | |||||
| DSPParam param[NUM_PARAM + 1]; | |||||
| // (note) +1 to avoid "illegal zero-sized array" errors | |||||
| DSPPort input[NUM_IN + 1]; | |||||
| DSPPort output[NUM_OUT + 1]; | |||||
| DSPParam param[NUM_PARAM + 1]; | |||||
| #else | #else | ||||
| DSPPort input[NUM_IN + 1] = {}; | |||||
| DSPPort output[NUM_OUT + 1] = {}; | |||||
| DSPParam param[NUM_PARAM] = {}; | |||||
| DSPPort input[NUM_IN] = {}; | |||||
| DSPPort output[NUM_OUT] = {}; | |||||
| DSPParam param[NUM_PARAM] = {}; | |||||
| #endif | #endif | ||||
| float sr; | float sr; | ||||
| @@ -18,15 +18,24 @@ | |||||
| #include "Korg35Filter.hpp" | #include "Korg35Filter.hpp" | ||||
| #include "DSPEffect.hpp" | #include "DSPEffect.hpp" | ||||
| #include "DSPMath.hpp" | |||||
| /** | |||||
| * @brief Construct a new Korg35 filter stage | |||||
| * @param sr SampleRate | |||||
| * @param type Lowpass / Highpass | |||||
| */ | |||||
| dsp::Korg35FilterStage::Korg35FilterStage(float sr, FilterType type) : DSPEffect(sr) { | dsp::Korg35FilterStage::Korg35FilterStage(float sr, FilterType type) : DSPEffect(sr) { | ||||
| this->type = type; | this->type = type; | ||||
| } | } | ||||
| /** | |||||
| * @brief Init stage | |||||
| */ | |||||
| void dsp::Korg35FilterStage::init() { | void dsp::Korg35FilterStage::init() { | ||||
| type = LPF1; | |||||
| type = LP_STAGE; | |||||
| alpha = 1.f; | alpha = 1.f; | ||||
| beta = 1.f; | beta = 1.f; | ||||
| zn1 = 0; | zn1 = 0; | ||||
| @@ -34,19 +43,25 @@ void dsp::Korg35FilterStage::init() { | |||||
| } | } | ||||
| /** | |||||
| * @brief Recompute filter parameter | |||||
| */ | |||||
| void dsp::Korg35FilterStage::invalidate() { | void dsp::Korg35FilterStage::invalidate() { | ||||
| // only process in dedicated mode | // only process in dedicated mode | ||||
| if (!dedicated) return; | if (!dedicated) return; | ||||
| float wd = 2 * PI * fc; | |||||
| float T = 1 / sr; | |||||
| float wa = (2 / T) * tan(wd * T / 2); | |||||
| float g = wa * T / 2; | |||||
| float wd = TWOPI * fc; | |||||
| float T = 1.f / sr; | |||||
| float wa = (2.f / T) * tanf(wd * T / 2.f); | |||||
| float g = wa * T / 2.f; | |||||
| alpha = g / (1.f + g); | alpha = g / (1.f + g); | ||||
| } | } | ||||
| /** | |||||
| * @brief Update filter and compute next sample | |||||
| */ | |||||
| void dsp::Korg35FilterStage::process() { | void dsp::Korg35FilterStage::process() { | ||||
| // v(n) | // v(n) | ||||
| float vn = (in - zn1) * alpha; | float vn = (in - zn1) * alpha; | ||||
| @@ -55,80 +70,149 @@ void dsp::Korg35FilterStage::process() { | |||||
| zn1 = vn + lpf; | zn1 = vn + lpf; | ||||
| float hpf = in - lpf; | |||||
| // switch filter type | // switch filter type | ||||
| if (type == LPF1) { | |||||
| if (type == LP_STAGE) { | |||||
| out = lpf; | out = lpf; | ||||
| } else { | } else { | ||||
| float hpf = in - lpf; | |||||
| out = hpf; | out = hpf; | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * @brief Init main filter | |||||
| */ | |||||
| void dsp::Korg35Filter::init() { | void dsp::Korg35Filter::init() { | ||||
| fc = sr / 2; | |||||
| fc = sr / 2.f; | |||||
| peak = 0.f; | peak = 0.f; | ||||
| lpf->init(); | |||||
| /* lowpass stages */ | |||||
| lpf1->init(); | |||||
| lpf2->init(); | |||||
| /* highpass stages */ | |||||
| hpf1->init(); | hpf1->init(); | ||||
| hpf2->init(); | hpf2->init(); | ||||
| } | } | ||||
| /** | |||||
| * @brief Recompute filter parameter | |||||
| */ | |||||
| void dsp::Korg35Filter::invalidate() { | void dsp::Korg35Filter::invalidate() { | ||||
| float frqHz = MAX_FREQUENCY / 1000.f * powf(1000.f, fc); | float frqHz = MAX_FREQUENCY / 1000.f * powf(1000.f, fc); | ||||
| float wd = 2 * PI * frqHz; | |||||
| float T = 1 / sr; | |||||
| float wa = (2 / T) * tan(wd * T / 2); | |||||
| float g = wa * T / 2; | |||||
| float wd = TWOPI * frqHz; | |||||
| float T = 1.f / sr; | |||||
| float wa = (2.f / T) * tanf(wd * T / 2.f); | |||||
| float g = wa * T / 2.f; | |||||
| float G = g / (1.f + g); | float G = g / (1.f + g); | ||||
| // set alphas | |||||
| lpf->alpha = G; | |||||
| hpf1->alpha = G; | |||||
| hpf2->alpha = G; | |||||
| hpf2->beta = -1.f * G / (1.f + g); | |||||
| lpf->beta = 1.f / (1.f + g); | |||||
| if (type == HPF) { | |||||
| /* HIGHPASS */ | |||||
| lpf1->alpha = G; | |||||
| hpf1->alpha = G; | |||||
| hpf2->alpha = G; | |||||
| hpf2->beta = -1.f * G / (1.f + g); | |||||
| lpf1->beta = 1.f / (1.f + g); | |||||
| } else { | |||||
| /* LOWPASS */ | |||||
| lpf1->alpha = G; | |||||
| lpf2->alpha = G; | |||||
| hpf1->alpha = G; | |||||
| lpf2->beta = (peak - peak * G) / (1.f + g); | |||||
| hpf1->beta = -1.f / (1.f + g); | |||||
| } | |||||
| Ga = 1.f / (1.f - peak * G + peak * G * G); | Ga = 1.f / (1.f - peak * G + peak * G * G); | ||||
| } | } | ||||
| /** | |||||
| * @brief Compute next sample for output depending on filter type | |||||
| */ | |||||
| void dsp::Korg35Filter::process() { | void dsp::Korg35Filter::process() { | ||||
| type == LPF ? processLPF() : processHPF(); | |||||
| } | |||||
| /** | |||||
| * @brief Do the lowpass filtering and oversampling | |||||
| */ | |||||
| void dsp::Korg35Filter::processLPF() { | |||||
| lpf1->in = in; | |||||
| lpf1->process(); | |||||
| float y1 = lpf1->out; | |||||
| float s35h = hpf1->getFeedback() + lpf2->getFeedback(); | |||||
| float u = Ga * (y1 + s35h); | |||||
| //float y = peak * fastatan(sat * u * 0.1) * 10.f; | |||||
| u = tanhf(sat * u * 0.1) * 10.f; | |||||
| lpf2->in = u; | |||||
| lpf2->process(); | |||||
| float y = peak * lpf2->out; | |||||
| hpf1->in = y; | |||||
| hpf1->process(); | |||||
| if (peak > 0) { | |||||
| y *= 1.f / peak; // normalize | |||||
| } | |||||
| out = y; | |||||
| } | |||||
| /** | |||||
| * @brief Do the highpass filtering and oversampling | |||||
| */ | |||||
| void dsp::Korg35Filter::processHPF() { | |||||
| hpf1->in = in; | hpf1->in = in; | ||||
| hpf1->process(); | hpf1->process(); | ||||
| float y1 = hpf1->out; | float y1 = hpf1->out; | ||||
| float s35h = hpf2->getFeedback() + lpf->getFeedback(); | |||||
| float s35h = hpf2->getFeedback() + lpf1->getFeedback(); | |||||
| float u = Ga * (y1 + s35h); | float u = Ga * (y1 + s35h); | ||||
| float y = peak * u; | |||||
| y = tanh(sat * y); | |||||
| float y = peak * fastatan(sat * u * 0.1) * 10.f; | |||||
| hpf2->in = y; | hpf2->in = y; | ||||
| hpf2->process(); | hpf2->process(); | ||||
| lpf->in = hpf2->out; | |||||
| lpf->process(); | |||||
| lpf1->in = hpf2->out; | |||||
| lpf1->process(); | |||||
| if (peak > 0) { | if (peak > 0) { | ||||
| y *= 1 / peak; // normalize | |||||
| y *= 1.f / peak; // normalize | |||||
| } | } | ||||
| out = y; | out = y; | ||||
| } | } | ||||
| /** | |||||
| * @brief Update samplerate | |||||
| * @param sr SR | |||||
| */ | |||||
| void dsp::Korg35Filter::setSamplerate(float sr) { | void dsp::Korg35Filter::setSamplerate(float sr) { | ||||
| DSPEffect::setSamplerate(sr); | DSPEffect::setSamplerate(sr); | ||||
| // derive samplerate change | // derive samplerate change | ||||
| lpf->setSamplerate(sr); | |||||
| lpf1->setSamplerate(sr); | |||||
| lpf2->setSamplerate(sr); | |||||
| hpf1->setSamplerate(sr); | hpf1->setSamplerate(sr); | ||||
| hpf2->setSamplerate(sr); | hpf2->setSamplerate(sr); | ||||
| @@ -23,20 +23,22 @@ | |||||
| #include "engine.hpp" | #include "engine.hpp" | ||||
| #include "DSPMath.hpp" | #include "DSPMath.hpp" | ||||
| #define PI 3.14159265358979323846f | |||||
| namespace dsp { | namespace dsp { | ||||
| /** | |||||
| * @brief Represents one filter stage | |||||
| */ | |||||
| struct Korg35FilterStage : DSPEffect { | struct Korg35FilterStage : DSPEffect { | ||||
| enum FilterType { | enum FilterType { | ||||
| LPF1, // lowpass stage | |||||
| HPF1 // highpass stage | |||||
| LP_STAGE, // lowpass stage | |||||
| HP_STAGE // highpass stage | |||||
| }; | }; | ||||
| bool dedicated = false; | bool dedicated = false; | ||||
| float fc; | |||||
| FilterType type; | FilterType type; | ||||
| float fc; | |||||
| float alpha, beta; | float alpha, beta; | ||||
| float zn1; | float zn1; | ||||
| @@ -56,10 +58,21 @@ struct Korg35FilterStage : DSPEffect { | |||||
| }; | }; | ||||
| /** | |||||
| * @brief Actual Korg35 Filter Class | |||||
| */ | |||||
| struct Korg35Filter : DSPEffect { | struct Korg35Filter : DSPEffect { | ||||
| static constexpr float MAX_FREQUENCY = 20000.f; | static constexpr float MAX_FREQUENCY = 20000.f; | ||||
| Korg35FilterStage *lpf, *hpf1, *hpf2; | |||||
| enum FilterType { | |||||
| LPF, // lowpass | |||||
| HPF // highpass | |||||
| }; | |||||
| Korg35FilterStage *lpf1, *lpf2, *hpf1, *hpf2; | |||||
| FilterType type; | |||||
| float Ga; | float Ga; | ||||
| float in, out; | float in, out; | ||||
| @@ -68,16 +81,21 @@ struct Korg35Filter : DSPEffect { | |||||
| float fc, peak, sat; | float fc, peak, sat; | ||||
| Korg35Filter(float sr) : DSPEffect(sr) { | |||||
| lpf = new Korg35FilterStage(sr, Korg35FilterStage::LPF1); | |||||
| hpf1 = new Korg35FilterStage(sr, Korg35FilterStage::HPF1); | |||||
| hpf2 = new Korg35FilterStage(sr, Korg35FilterStage::HPF1); | |||||
| Korg35Filter(float sr, FilterType type) : DSPEffect(sr) { | |||||
| Korg35Filter::type = type; | |||||
| lpf1 = new Korg35FilterStage(sr, Korg35FilterStage::LP_STAGE); | |||||
| lpf2 = new Korg35FilterStage(sr, Korg35FilterStage::LP_STAGE); | |||||
| hpf1 = new Korg35FilterStage(sr, Korg35FilterStage::HP_STAGE); | |||||
| hpf2 = new Korg35FilterStage(sr, Korg35FilterStage::HP_STAGE); | |||||
| } | } | ||||
| void init() override; | void init() override; | ||||
| void invalidate() override; | void invalidate() override; | ||||
| void process() override; | void process() override; | ||||
| void processLPF(); | |||||
| void processHPF(); | |||||
| void setSamplerate(float sr) override; | void setSamplerate(float sr) override; | ||||
| }; | }; | ||||
| @@ -16,7 +16,7 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| #include "Horner.h" | #include "Horner.h" | ||||
| namespace dsp { | |||||
| using namespace dsp; | |||||
| // fork macro to keep the tree balanced | // fork macro to keep the tree balanced | ||||
| #define Y2(d1, c12, d2) \ | #define Y2(d1, c12, d2) \ | ||||
| @@ -32,6 +32,9 @@ namespace dsp { | |||||
| #define Y7(d1, c12, d2, c23, d3, c34, d4, c45, d5, c56, d6, c67, d7) \ | #define Y7(d1, c12, d2, c23, d3, c34, d4, c45, d5, c56, d6, c67, d7) \ | ||||
| Y6(d1, c12, d2, c23, Y2(d3, c34, d4), c45, d5, c56, d6, c67, d7) | Y6(d1, c12, d2, c23, Y2(d3, c34, d4), c45, d5, c56, d6, c67, d7) | ||||
| namespace dsp { | |||||
| class BranchPoint { | class BranchPoint { | ||||
| }; | }; | ||||
| @@ -67,14 +67,14 @@ void WaveShaper::process() { | |||||
| return; | return; | ||||
| } | } | ||||
| rs->doUpsample(STD_CHANNEL, in); | |||||
| rs->doUpsample(STD_CHANNEL, beforeComputation(in)); | |||||
| for (int i = 0; i < rs->getFactor(); i++) { | for (int i = 0; i < rs->getFactor(); i++) { | ||||
| double x = rs->getUpsampled(STD_CHANNEL)[i]; | double x = rs->getUpsampled(STD_CHANNEL)[i]; | ||||
| rs->data[STD_CHANNEL][i] = compute(x); | rs->data[STD_CHANNEL][i] = compute(x); | ||||
| } | } | ||||
| out = rs->getDownsampled(STD_CHANNEL); | |||||
| out = afterComputation(rs->getDownsampled(STD_CHANNEL)); | |||||
| } | } | ||||
| @@ -86,6 +86,24 @@ public: | |||||
| * @return Output sample | * @return Output sample | ||||
| */ | */ | ||||
| virtual double compute(double x) { return x; } | virtual double compute(double x) { return x; } | ||||
| /** | |||||
| * @brief Virtual function called before actual oversampling is performed | |||||
| * | |||||
| * @param x Input sample | |||||
| * @return Ouput sample | |||||
| */ | |||||
| virtual double beforeComputation(double x) { return x; } | |||||
| /** | |||||
| * @brief Virtual function called after actual oversampling is performed | |||||
| * | |||||
| * @param x Input sample | |||||
| * @return Output sample | |||||
| */ | |||||
| virtual double afterComputation(double x) { return x; } | |||||
| }; | }; | ||||
| @@ -47,7 +47,7 @@ struct Korg35 : LRModule { | |||||
| }; | }; | ||||
| LRKnob *frqKnob, *peakKnob, *saturateKnob; | LRKnob *frqKnob, *peakKnob, *saturateKnob; | ||||
| Korg35Filter *filter = new Korg35Filter(engineGetSampleRate()); | |||||
| Korg35Filter *filter = new Korg35Filter(engineGetSampleRate(), Korg35Filter::LPF); | |||||
| Korg35() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | Korg35() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | ||||
| @@ -55,7 +55,7 @@ struct Korg35 : LRModule { | |||||
| void step() override { | void step() override { | ||||
| filter->fc = params[FREQ_PARAM].value; | filter->fc = params[FREQ_PARAM].value; | ||||
| filter->peak = params[PEAK_PARAM].value; | filter->peak = params[PEAK_PARAM].value; | ||||
| filter->sat = params[SAT_PARAM].value; | |||||
| filter->sat = quadraticBipolar(params[SAT_PARAM].value); | |||||
| filter->in = inputs[FILTER_INPUT].value; | filter->in = inputs[FILTER_INPUT].value; | ||||
| filter->invalidate(); | filter->invalidate(); | ||||
| @@ -100,7 +100,7 @@ Korg35Widget::Korg35Widget(Korg35 *module) : LRModuleWidget(module) { | |||||
| // ***** MAIN KNOBS ****** | // ***** MAIN KNOBS ****** | ||||
| module->frqKnob = LRKnob::create<LRBigKnob>(Vec(32.5, 74.4), module, Korg35::FREQ_PARAM, 0.f, 1.f, 1.f); | module->frqKnob = LRKnob::create<LRBigKnob>(Vec(32.5, 74.4), module, Korg35::FREQ_PARAM, 0.f, 1.f, 1.f); | ||||
| module->peakKnob = LRKnob::create<LRBigKnob>(Vec(32.5, 144.4), module, Korg35::PEAK_PARAM, 0.001f, 2.0, 0.001f); | module->peakKnob = LRKnob::create<LRBigKnob>(Vec(32.5, 144.4), module, Korg35::PEAK_PARAM, 0.001f, 2.0, 0.001f); | ||||
| module->saturateKnob = LRKnob::create<LRMiddleKnob>(Vec(40, 244.4), module, Korg35::SAT_PARAM, 1.f, 1.5, 0.0f); | |||||
| module->saturateKnob = LRKnob::create<LRMiddleKnob>(Vec(40, 244.4), module, Korg35::SAT_PARAM, 1.f, 2.5, 1.0f); | |||||
| module->frqKnob->setIndicatorColors(nvgRGBAf(0.9f, 0.9f, 0.9f, 1.0f)); | module->frqKnob->setIndicatorColors(nvgRGBAf(0.9f, 0.9f, 0.9f, 1.0f)); | ||||
| module->peakKnob->setIndicatorColors(nvgRGBAf(0.9f, 0.9f, 0.9f, 1.0f)); | module->peakKnob->setIndicatorColors(nvgRGBAf(0.9f, 0.9f, 0.9f, 1.0f)); | ||||
| @@ -31,7 +31,7 @@ void LRPanel::init() { | |||||
| addChild(patinaWidgetClassic); | addChild(patinaWidgetClassic); | ||||
| /* setup gradient variants */ | /* setup gradient variants */ | ||||
| auto gradientDark = new LRGradientWidget(box.size, nvgRGBAf(.6f, .6f, .6f, 0.3f), nvgRGBAf(0.0f, 0.0f, 0.0f, 0.2f), Vec(100, -120)); | |||||
| auto gradientDark = new LRGradientWidget(box.size, nvgRGBAf(.6f, .6f, .6f, 0.3f), nvgRGBAf(0.2f, 0.0f, 0.0f, 0.2f), Vec(50, 20)); | |||||
| gradientDark->visible = false; | gradientDark->visible = false; | ||||
| addChild(gradientDark); | addChild(gradientDark); | ||||
| gradients[LRGestalt::DARK] = gradientDark; | gradients[LRGestalt::DARK] = gradientDark; | ||||