BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit 2f0584a2ddafbf452298f0083caa2bcecf654bcd
parent 297b9b9a6ba2f7bf069ce58aa30124d3b425f466
Author: Matt Demanett <matt@demanett.net>
Date:   Thu,  5 Mar 2020 22:57:50 -0500

VCF, LVCF: more fixes for low end ringing; drop type B. #101

Diffstat:
Mres-src/VCF-src.svg | 80++++++++++++++++++++++++++++++++-----------------------------------------------
Mres/VCF.svg | 0
Msrc/LVCF.cpp | 75+++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/LVCF.hpp | 27+++++++++++++++++++++++----
Msrc/VCF.cpp | 44++++++++++++++++----------------------------
Msrc/VCF.hpp | 9+++------
Msrc/dsp/filter.cpp | 4++--
Msrc/dsp/filter.hpp | 2+-
8 files changed, 118 insertions(+), 123 deletions(-)

diff --git a/res-src/VCF-src.svg b/res-src/VCF-src.svg @@ -39,14 +39,6 @@ </g> </symbol> - <symbol id="knob-medium" viewBox="0 0 26px 26px"> - <g transform="translate(13 13)"> - <polyline points="-3,0 3,0" stroke-width="1" stroke="#00f" /> - <polyline points="0,-3 0,3" stroke-width="1" stroke="#00f" /> - <circle cx="0" cy="0" r="12.5" stroke-width="1" stroke="#00f" fill="none" /> - </g> - </symbol> - <symbol id="knob-smallest" viewBox="0 0 16px 16px"> <g transform="translate(8 8)"> <polyline points="-3,0 3,0" stroke-width="1" stroke="#00f" /> @@ -160,21 +152,21 @@ </g> </symbol> - <symbol id="knobguide-slope" viewBox="0 0 45px 45px"> - <g transform="translate(22.5 22.5)"> - <text font-size="5.0pt" transform="rotate(-240) translate(18 0) rotate(240) translate(-2 3)">1</text> - <polyline points="0,0 1.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-176) translate(15 0)" /> - <text font-size="5.0pt" transform="rotate(-149.6) translate(18 0) rotate(149.6) translate(-1.5 1)">2</text> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-112.1) translate(15 0)" /> - <text font-size="5.0pt" transform="rotate(-83.3) translate(18 0) rotate(83.3) translate(-2 2)">4</text> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-59.1) translate(15 0)" /> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-37.7) translate(15 0)" /> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-18.4) translate(15 0)" /> - <text font-size="5.0pt" transform="rotate(-0.7) translate(18 0) rotate(0.7) translate(-2 2)">8</text> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(15.8) translate(15 0)" /> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(31.4) translate(15 0)" /> - <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(46) translate(15 0)" /> - <text font-size="5.0pt" transform="rotate(60) translate(18 0) rotate(-60) translate(-5 3)">12</text> + <symbol id="knobguide-slope" viewBox="0 0 50px 60px"> + <g transform="translate(25 30)"> + <text font-size="6.0pt" transform="rotate(-240) translate(24 0) rotate(240) translate(-2 3)">1</text> + <polyline points="0,0 2,0" stroke-width="0.5" stroke="#333" transform="rotate(-176) translate(22 0)" /> + <text font-size="6.0pt" transform="rotate(-149.6) translate(24 0) rotate(149.6) translate(-1.5 1)">2</text> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-112.1) translate(22 0)" /> + <text font-size="6.0pt" transform="rotate(-83.3) translate(24 0) rotate(83.3) translate(-2 2)">4</text> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-59.1) translate(22 0)" /> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-37.7) translate(22 0)" /> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-18.4) translate(22 0)" /> + <text font-size="6.0pt" transform="rotate(-0.7) translate(24 0) rotate(0.7) translate(-2 2)">8</text> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(15.8) translate(22 0)" /> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(31.4) translate(22 0)" /> + <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(46) translate(22 0)" /> + <text font-size="6.0pt" transform="rotate(60) translate(24 0) rotate(-60) translate(-5 3)">12</text> </g> </symbol> @@ -208,11 +200,6 @@ </g> </symbol> - <symbol id="switch" viewBox="0 0 14px 24px"> - <rect width="14px" height="24px" stroke-width="1" stroke="#000" fill="#ddd" /> - <rect width="14px" height="12px" stroke-width="0" fill="#000" /> - </symbol> - <symbol id="button-small" viewBox="0 0 9px 9px"> <g transform="translate(4.5 4.5)"> <circle r="4" stroke-width="1" stroke="#00f" fill="#f00" /> @@ -239,48 +226,45 @@ <rect width="3.0" height="3" fill="#ddd" transform="translate(24 -5)" /> </g> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(75 0)" /> --> + <g transform="translate(21 25)"> <use id="FREQUENCY_PARAM" xlink:href="#knob-large" transform="translate(20 20)" /> <use xlink:href="#knobguide-frequency" transform="translate(-21 0)" /> </g> - <g transform="translate(27 126)"> + <g transform="translate(25 126)"> <text font-size="8pt" letter-spacing="2px" transform="translate(10 27) rotate(270)">CV</text> <use id="FREQUENCY_CV_PARAM" xlink:href="#knob-smallest" transform="translate(20 12)" /> <use xlink:href="#knobguide-centertick" transform="translate(8 0)" /> </g> - <g transform="translate(80 126)"> + <g transform="translate(82 126)"> <text font-size="8pt" letter-spacing="2px" transform="translate(10 27) rotate(270)">FM</text> <use id="FM_PARAM" xlink:href="#knob-smallest" transform="translate(20 12)" /> <use xlink:href="#knobguide-maxtick" transform="translate(8 0)" /> </g> - <g transform="translate(9 171)"> - <text font-size="8pt" letter-spacing="2px" transform="translate(7 43.5) rotate(270)">RES/BW</text> - <use id="Q_PARAM" xlink:href="#knob" transform="translate(18 0)" /> - <use xlink:href="#knobguide-linear-38" transform="translate(12 -6)" /> - </g> - - <g transform="translate(87 170)"> + <g transform="translate(54 165)"> <text font-size="8pt" letter-spacing="2px" transform="translate(7 38) rotate(270)">MODE</text> <use id="MODE_PARAM" xlink:href="#knob-smallest" transform="translate(13 11)" /> <use xlink:href="#knobguide-mode" transform="translate(-9 -11)" /> </g> - <g transform="translate(20 228)"> - <text font-size="8pt" letter-spacing="2px" transform="translate(7 33.5) rotate(270)">SLOPE</text> - <use id="SLOPE_PARAM" xlink:href="#knob-medium" transform="translate(18 0)" /> - <use xlink:href="#knobguide-slope" transform="translate(8.5 -9.5)" /> + <g transform="translate(0 220)"> + <!-- <rect width="75" height="54" fill="#ff0" transform="translate(1 -9)" /> --> + <!-- <rect width="64" height="54" fill="#f0f" transform="translate(5.5 -9)" /> --> + <text font-size="8pt" letter-spacing="2px" transform="translate(13.5 43.5) rotate(270)">RES/BW</text> + <use id="Q_PARAM" xlink:href="#knob" transform="translate(24.5 0)" /> + <use xlink:href="#knobguide-linear-38" transform="translate(18.5 -6)" /> </g> - <g transform="translate(93 222)"> - <text font-size="8pt" letter-spacing="2px" transform="translate(6 36) rotate(270)">TYPE</text> - <g transform="translate(14 6)"> - <text font-size="5pt" letter-spacing="2px" transform="translate(3.5 -1)">A</text> - <use id="TYPE_PARAM" xlink:href="#switch" transform="translate(-1 2)" /> - <text font-size="5pt" letter-spacing="2px" transform="translate(4 34)">B</text> - </g> + <g transform="translate(75 220)"> + <!-- <rect width="75" height="54" fill="#ff0" transform="translate(0 -9)" /> --> + <!-- <rect width="64" height="54" fill="#f0f" transform="translate(5.5 -9)" /> --> + <text font-size="8pt" letter-spacing="2px" transform="translate(13.4 38.5) rotate(270)">SLOPE</text> + <use id="SLOPE_PARAM" xlink:href="#knob" transform="translate(24.5 0)" /> + <use xlink:href="#knobguide-slope" transform="translate(18.5 -11)" /> </g> <g transform="translate(0 269)"> diff --git a/res/VCF.svg b/res/VCF.svg Binary files differ. diff --git a/src/LVCF.cpp b/src/LVCF.cpp @@ -1,29 +1,48 @@ #include "LVCF.hpp" -#define TYPE_KEY "type" -#define A_TYPE_KEY "a" -#define B_TYPE_KEY "b" #define POLES_KEY "poles" #define BANDWIDTH_MODE_KEY "bandwidthMode" #define LINEAR_BANDWIDTH_MODE_KEY "linear" #define PITCH_BANDWIDTH_MODE_KEY "pitched" +void LVCF::Engine::setParams( + int poles, + MultimodeFilter::Mode mode, + float frequency, + float qbw, + MultimodeFilter::BandwidthMode bwm +) { + frequency = semitoneToFrequency(_frequencySL.next(frequencyToSemitone(frequency))); + + _filter.setParams( + _sampleRate, + MultimodeFilter::BUTTERWORTH_TYPE, + poles, + mode, + frequency, + qbw, + bwm + ); +} + +void LVCF::Engine::sampleRateChange(int modulationSteps) { + _sampleRate = APP->engine->getSampleRate(); + _frequencySL.setParams(_sampleRate, 100.0f / (float)modulationSteps, frequencyToSemitone(MultimodeFilter::maxFrequency - MultimodeFilter::minFrequency)); + _finalHP.setParams(_sampleRate, MultimodeFilter::BUTTERWORTH_TYPE, 2, MultimodeFilter::HIGHPASS_MODE, 80.0f, MultimodeFilter::minQbw); +} + +void LVCF::Engine::reset() { + _filter.reset(); +} + +float LVCF::Engine::next(float sample) { + return _finalHP.next(_filter.next(sample)); +} + json_t* LVCF::dataToJson() { json_t* root = json_object(); - switch (_typeSetting) { - case MultimodeFilter::BUTTERWORTH_TYPE: { - json_object_set_new(root, TYPE_KEY, json_string(A_TYPE_KEY)); - break; - } - case MultimodeFilter::CHEBYSHEV_TYPE: { - json_object_set_new(root, TYPE_KEY, json_string(B_TYPE_KEY)); - break; - } - default: {} - } - json_object_set_new(root, POLES_KEY, json_integer(_polesSetting)); switch (_bandwidthMode) { @@ -42,16 +61,6 @@ json_t* LVCF::dataToJson() { } void LVCF::dataFromJson(json_t* root) { - json_t* t = json_object_get(root, TYPE_KEY); - if (t) { - if (strcmp(json_string_value(t), B_TYPE_KEY) == 0) { - _typeSetting = MultimodeFilter::CHEBYSHEV_TYPE; - } - else { - _typeSetting = MultimodeFilter::BUTTERWORTH_TYPE; - } - } - json_t* p = json_object_get(root, POLES_KEY); if (p) { _polesSetting = clamp(json_integer_value(p), 1, 12); @@ -68,6 +77,12 @@ void LVCF::dataFromJson(json_t* root) { } } +void LVCF::sampleRateChange() { + for (int c = 0; c < _channels; ++c) { + _engines[c]->sampleRateChange(_modulationSteps); + } +} + bool LVCF::active() { return outputs[OUT_OUTPUT].isConnected(); } @@ -87,8 +102,7 @@ void LVCF::removeChannel(int c) { void LVCF::modulate() { MultimodeFilter::Mode mode = (MultimodeFilter::Mode)(1 + clamp((int)params[MODE_PARAM].getValue(), 0, 4)); - if (_type != _typeSetting || _mode != mode || _poles != _polesSetting) { - _type = _typeSetting; + if (_mode != mode || _poles != _polesSetting) { _mode = mode; _poles = _polesSetting; for (int c = 0; c < _channels; ++c) { @@ -124,8 +138,6 @@ void LVCF::modulateChannel(int c) { f = clamp(f, MultimodeFilter::minFrequency, MultimodeFilter::maxFrequency); e.setParams( - APP->engine->getSampleRate(), - _type, _poles, _mode, f, @@ -204,11 +216,6 @@ struct LVCFWidget : ModuleWidget { assert(m); menu->addChild(new MenuLabel()); - OptionsMenuItem* t = new OptionsMenuItem("Type"); - t->addItem(OptionMenuItem("A", [m]() { return m->_typeSetting == MultimodeFilter::BUTTERWORTH_TYPE; }, [m]() { m->_typeSetting = MultimodeFilter::BUTTERWORTH_TYPE; })); - t->addItem(OptionMenuItem("B", [m]() { return m->_typeSetting == MultimodeFilter::CHEBYSHEV_TYPE; }, [m]() { m->_typeSetting = MultimodeFilter::CHEBYSHEV_TYPE; })); - OptionsMenuItem::addToMenu(t, menu); - OptionsMenuItem* s = new OptionsMenuItem("Slope"); s->addItem(OptionMenuItem("1 pole", [m]() { return m->_polesSetting == 1; }, [m]() { m->_polesSetting = 1; })); s->addItem(OptionMenuItem("2 poles", [m]() { return m->_polesSetting == 2; }, [m]() { m->_polesSetting = 2; })); diff --git a/src/LVCF.hpp b/src/LVCF.hpp @@ -37,16 +37,34 @@ struct LVCF : BGModule { NUM_LIGHTS }; - typedef MultimodeFilter Engine; + struct Engine { + MultimodeFilter _filter; + float _sampleRate; + bogaudio::dsp::SlewLimiter _frequencySL; + MultimodeFilter _finalHP; + + Engine() { + sampleRateChange(); + } + + void setParams( + int poles, + MultimodeFilter::Mode mode, + float frequency, + float qbw, + MultimodeFilter::BandwidthMode bwm + ); + void sampleRateChange(int modulationSteps = 100); + void reset(); + float next(float sample); + }; - MultimodeFilter::Type _typeSetting = MultimodeFilter::BUTTERWORTH_TYPE; - MultimodeFilter::Type _type = MultimodeFilter::UNKNOWN_TYPE; MultimodeFilter::Mode _mode = MultimodeFilter::UNKNOWN_MODE; int _polesSetting = 4; int _poles = 0; float _q = 0.0f; MultimodeFilter::BandwidthMode _bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE; - Engine* _engines[maxChannels] {}; + Engine* _engines[maxChannels]; float _lastFrequency = 0.0f; LVCF() { @@ -59,6 +77,7 @@ struct LVCF : BGModule { json_t* dataToJson() override; void dataFromJson(json_t* root) override; + void sampleRateChange() override; bool active() override; int channels() override; void addChannel(int c) override; diff --git a/src/VCF.cpp b/src/VCF.cpp @@ -6,13 +6,14 @@ #define PITCH_BANDWIDTH_MODE_KEY "pitched" void VCF::Engine::setParams( - MultimodeFilter::Type type, float slope, MultimodeFilter::Mode mode, float frequency, float qbw, MultimodeFilter::BandwidthMode bwm ) { + frequency = semitoneToFrequency(_frequencySL.next(frequencyToSemitone(frequency))); + int i = -1, j = -1; std::fill(_gains, _gains + nFilters, 0.0f); if (slope >= 1.0f) { @@ -27,7 +28,7 @@ void VCF::Engine::setParams( _filters[i].setParams( _sampleRate, - type, + MultimodeFilter::BUTTERWORTH_TYPE, i + 1, mode, frequency, @@ -37,7 +38,7 @@ void VCF::Engine::setParams( if (j >= 0) { _filters[j].setParams( _sampleRate, - type, + MultimodeFilter::BUTTERWORTH_TYPE, j + 1, mode, frequency, @@ -47,8 +48,10 @@ void VCF::Engine::setParams( } } -void VCF::Engine::sampleRateChange() { +void VCF::Engine::sampleRateChange(int modulationSteps) { _sampleRate = APP->engine->getSampleRate(); + _frequencySL.setParams(_sampleRate, 100.0f / (float)modulationSteps, frequencyToSemitone(MultimodeFilter::maxFrequency - MultimodeFilter::minFrequency)); + _finalHP.setParams(_sampleRate, MultimodeFilter::BUTTERWORTH_TYPE, 2, MultimodeFilter::HIGHPASS_MODE, 80.0f, MultimodeFilter::minQbw); for (int i = 0; i < nFilters; ++i) { _gainSLs[i].setParams(_sampleRate, 50.0f, 1.0f); } @@ -68,7 +71,7 @@ float VCF::Engine::next(float sample) { out += g * _filters[i].next(sample); } } - return out; + return _finalHP.next(out); } json_t* VCF::dataToJson() { @@ -101,7 +104,7 @@ void VCF::dataFromJson(json_t* root) { void VCF::sampleRateChange() { for (int c = 0; c < _channels; ++c) { - _engines[c]->sampleRateChange(); + _engines[c]->sampleRateChange(_modulationSteps); } } @@ -123,10 +126,8 @@ void VCF::removeChannel(int c) { } void VCF::modulate() { - MultimodeFilter::Type type = params[TYPE_PARAM].getValue() > 0.5f ? MultimodeFilter::BUTTERWORTH_TYPE : MultimodeFilter::CHEBYSHEV_TYPE; MultimodeFilter::Mode mode = (MultimodeFilter::Mode)(1 + clamp((int)params[MODE_PARAM].getValue(), 0, 4)); - if (_type != type || _mode != mode) { - _type = type; + if (_mode != mode) { _mode = mode; for (int c = 0; c < _channels; ++c) { _engines[c]->reset(); @@ -163,20 +164,9 @@ void VCF::modulateChannel(int c) { pitch += fm; f += cvToFrequency(pitch); } - const float lowThreshold = 100.0f; - if (f < lowThreshold) { - float deltaF = std::max(1.0f, _lastFrequency) / MultimodeFilter::maxFrequency; - deltaF = std::pow(deltaF, 1.5f); - deltaF *= std::max(5.0f, 0.5f * MultimodeFilter::maxFrequency); - f = std::max(_lastFrequency - deltaF, f); - - q = std::min(f / lowThreshold, q); - } - _lastFrequency = f; f = clamp(f, MultimodeFilter::minFrequency, MultimodeFilter::maxFrequency); e.setParams( - _type, slope, _mode, f, @@ -214,12 +204,11 @@ struct VCFWidget : ModuleWidget { // generated by svg_widgets.rb auto frequencyParamPosition = Vec(41.0, 45.0); - auto frequencyCvParamPosition = Vec(47.0, 138.0); - auto fmParamPosition = Vec(100.0, 138.0); - auto qParamPosition = Vec(27.0, 171.0); - auto modeParamPosition = Vec(100.0, 181.0); - auto slopeParamPosition = Vec(38.0, 228.0); - auto typeParamPosition = Vec(105.5, 229.5); + auto frequencyCvParamPosition = Vec(45.0, 138.0); + auto fmParamPosition = Vec(102.0, 138.0); + auto modeParamPosition = Vec(67.0, 176.0); + auto qParamPosition = Vec(24.5, 220.0); + auto slopeParamPosition = Vec(99.5, 220.0); auto frequencyCvInputPosition = Vec(31.0, 274.0); auto pitchInputPosition = Vec(63.0, 274.0); @@ -245,8 +234,7 @@ struct VCFWidget : ModuleWidget { k->speed = 3.0; addParam(w); } - addParam(createParam<Knob26>(slopeParamPosition, module, VCF::SLOPE_PARAM)); - addParam(createParam<SliderSwitch2State14>(typeParamPosition, module, VCF::TYPE_PARAM)); + addParam(createParam<Knob38>(slopeParamPosition, module, VCF::SLOPE_PARAM)); addInput(createInput<Port24>(frequencyCvInputPosition, module, VCF::FREQUENCY_CV_INPUT)); addInput(createInput<Port24>(fmInputPosition, module, VCF::FM_INPUT)); diff --git a/src/VCF.hpp b/src/VCF.hpp @@ -18,7 +18,6 @@ struct VCF : BGModule { Q_PARAM, MODE_PARAM, SLOPE_PARAM, - TYPE_PARAM, NUM_PARAMS }; @@ -45,13 +44,14 @@ struct VCF : BGModule { float _gains[nFilters] {}; bogaudio::dsp::SlewLimiter _gainSLs[nFilters]; float _sampleRate; + bogaudio::dsp::SlewLimiter _frequencySL; + MultimodeFilter _finalHP; Engine() { sampleRateChange(); } void setParams( - MultimodeFilter::Type type, float slope, MultimodeFilter::Mode mode, float frequency, @@ -59,15 +59,13 @@ struct VCF : BGModule { MultimodeFilter::BandwidthMode bwm ); void reset(); - void sampleRateChange(); + void sampleRateChange(int modulationSteps = 100); float next(float sample); }; - MultimodeFilter::Type _type = MultimodeFilter::UNKNOWN_TYPE; MultimodeFilter::Mode _mode = MultimodeFilter::UNKNOWN_MODE; MultimodeFilter::BandwidthMode _bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE; Engine* _engines[maxChannels] {}; - float _lastFrequency = 0.0f; VCF() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); @@ -77,7 +75,6 @@ struct VCF : BGModule { configParam(Q_PARAM, 0.0f, 1.0f, 0.0f, "Resonance / bandwidth", "%", 0.0f, 100.0f); configParam(MODE_PARAM, 0.0f, 3.0f, 0.0f, "Mode"); configParam<ScaledSquaringParamQuantity<Engine::maxPoles - Engine::minPoles>>(SLOPE_PARAM, 0.0f, 1.0f, 0.52222f, "Slope", " poles", 0.0f, 1.0f, Engine::minPoles); - configParam(TYPE_PARAM, 0.0f, 1.0f, 1.0f, "Type"); } json_t* dataToJson() override; diff --git a/src/dsp/filter.cpp b/src/dsp/filter.cpp @@ -246,8 +246,8 @@ void MultimodeFilter::setParams( _poles[j] = Pole(-re, im, re + re, re * re + im * im); } - // _outGain = 1.0 / (e * std::pow(2.0, (T)(_nPoles - 1))); - _outGain = 1.0f / std::pow(2.0f, (T)(_nPoles - 1)); + _outGain = 1.0 / (e * std::pow(2.0, (T)(_nPoles - 1))); + // _outGain = 1.0f / std::pow(2.0f, (T)(_nPoles - 1)); break; } diff --git a/src/dsp/filter.hpp b/src/dsp/filter.hpp @@ -179,7 +179,7 @@ struct MultimodeFilter : Filter { static constexpr int minPoles = 1; static constexpr int maxPoles = 16; static constexpr int modPoles = 1; - static constexpr float minFrequency = 10.0f; + static constexpr float minFrequency = 1.0f; static constexpr float maxFrequency = 21000.0f; static constexpr float minQbw = 0.0f; static constexpr float maxQbw = 1.0f;