BogaudioModules

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

commit aa49da07eff2e29dae76cb655a4ce6f565e05318
parent ffd40cdea4a803b6b174129a9707bac44aa55e17
Author: Matt Demanett <matt@demanett.net>
Date:   Mon, 15 Jun 2020 23:49:28 -0400

PEQ: 3-channel parametric equalizer.

Diffstat:
Mplugin.json | 10++++++++++
Ares-src/PEQ-src.svg | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/PEQ.svg | 0
Asrc/PEQ.cpp | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PEQ.hpp | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/bogaudio.cpp | 2++
Msrc/dsp/filters/multimode.cpp | 2+-
Msrc/dsp/filters/multimode.hpp | 24++++++++++++++++++++++++
Asrc/parametric_eq.cpp | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/parametric_eq.hpp | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 775 insertions(+), 1 deletion(-)

diff --git a/plugin.json b/plugin.json @@ -164,6 +164,16 @@ ] }, { + "slug": "Bogaudio-PEQ", + "name": "PEQ", + "description": "3-channel parametric equalizer", + "manualUrl": "https://github.com/bogaudio/BogaudioModules/blob/master/README.md#peq", + "tags": [ + "Filter", + "Polyphonic" + ] + }, + { "slug": "Bogaudio-DADSRH", "name": "DADSR(H)", "description": "Advanced envelope generator", diff --git a/res-src/PEQ-src.svg b/res-src/PEQ-src.svg @@ -0,0 +1,279 @@ +<svg + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="150" + height="380" + viewBox="0 0 150 380" +> + <style> + text { + fill: #333; + font-family: 'Roboto', sans-serif; + font-weight: bold; + } + text.title { + font-family: 'Comfortaa', sans-serif; + font-weight: normal; + } + text.brand { + font-family: 'Audiowide', sans-serif; + font-weight: bold; + } + </style> + + <defs> + <symbol id="knob" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <polyline points="-5,0 5,0" stroke-width="1" stroke="#00f" /> + <polyline points="0,-5 0,5" stroke-width="1" stroke="#00f" /> + <circle cx="0" cy="0" r="14" stroke-width="1" stroke="#00f" fill="none" /> + </g> + </symbol> + + <symbol id="knobguide-level" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <text font-size="5pt" transform="rotate(32.73) translate(20 0) rotate(-32.73) translate(-1.9 2.2)">0</text> + <text font-size="5pt" transform="rotate(-76.36) translate(20 0) rotate(76.36) translate(-4 2.2)">-24</text> + <text font-size="5pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-8 4)">-60dB</text> + + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(60) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(5.5) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-21.8) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-49.1) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-103.6) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-131) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-130.9) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-158.2) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-185.5) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-212.7) translate(17 0)" /> + </g> + </symbol> + + <symbol id="knobguide-frequency" viewBox="0 0 61px 45px"> + <g transform="translate(25.5 22.5)"> + <text font-size="5pt" transform="rotate(60) translate(20 0) rotate(-60) translate(-5 4)">20K</text> + <text font-size="5pt" transform="rotate(-90) translate(20 0) rotate(90) translate(-4 2.2)">5K</text> + <text font-size="5pt" transform="rotate(-172.9) translate(20 0) rotate(172.9) translate(-5 2.2)">1K</text> + <text font-size="5pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-7 4)">0HZ</text> + + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(19.8) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-27.9) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-133.9) translate(17 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-192.6) translate(17 0)" /> + </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" /> + <polyline points="0,-3 0,3" stroke-width="1" stroke="#00f" /> + <circle r="7.5" stroke-width="1" stroke="#00f" fill="none" /> + </g> + </symbol> + + <symbol id="knobguide-centertick" viewBox="0 0 40px 40px"> + <g transform="translate(20 20)"> + <g transform="rotate(-90) translate(10 0)"> + <polyline points="0,0 4,0" stroke-width="1" stroke="#333" /> + </g> + <path d="M 0 -12.5 A 12.5 12.5 0 0 1 12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(20)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 1 12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(43)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 0 -12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(-20)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 0 -12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(-43)" /> + </g> + </symbol> + + <symbol id="knobguide-maxtick" viewBox="0 0 40px 40px"> + <g transform="translate(20 20)"> + <g transform="rotate(60) translate(10 0)"> + <polyline points="0,0 4,0" stroke-width="1" stroke="#333" /> + </g> + <path d="M 0 -12.5 A 12.5 12.5 0 0 1 12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(0)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 1 12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(43)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 0 -12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(0)" /> + <path d="M 0 -12.5 A 12.5 12.5 0 0 0 -12.5 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(-43)" /> + </g> + </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" /> + </g> + </symbol> + + <symbol id="input" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#0f0" fill="none" /> + </g> + </symbol> + + <symbol id="output" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#f00" fill="#f00" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#f00" fill="none" /> + </g> + </symbol> + + <symbol id="light-small" viewBox="0 0 6.4px 6.4px"> + <rect width="6.4" height="6.4" fill="#0f0" /> + <circle cx="0" cy="0" r="2.7" stroke-width="1" stroke="#f0f" fill="#f0f" transform="translate(3.2 3.2)" /> + </symbol> + </defs> + + <rect width="100%" height="100%" fill="#ddd" /> + <polyline points="1,1 149,1 149,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" /> + <polyline points="0.5,0.5 149.5,0.5 149.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" /> + <polyline points="0,0 150,0 150,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" /> + + <!-- <rect width="50" height="20" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <rect width="50" height="20" fill="#0f0" transform="translate(100 0)" /> --> + + <text class="title" x="53" y="19" font-size="12pt" letter-spacing="4px">PEQ</text> + <g transform="translate(35.5 374)"> + <text class="brand" font-size="8pt" letter-spacing="2px">BOGAUDIO</text> + <rect width="3.0" height="3" fill="#ddd" transform="translate(24 -5)" /> + </g> + + <g transform="translate(0 25)"> + <!-- <rect width="150" height="60" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <polyline points="0,30 150,30" stroke="#f00" stroke-width="1" fill="none" /> --> + <!-- <polyline points="0,0 150,0" stroke="#0ff" stroke-width="1" fill="none" transform="translate(0 26)" /> --> + <use id="A_LIGHT" xlink:href="#light-small" transform="translate(5.5 18)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(11.5 33) rotate(270)">A</text> + + <text font-size="6pt" letter-spacing="2px" transform="translate(20 58)">LEVEL</text> + <use id="A_LEVEL_PARAM" xlink:href="#knob" transform="translate(13 3.5)" /> + <use xlink:href="#knobguide-level" transform="translate(13 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(69 58)">FREQ</text> + <use id="A_FREQUENCY_PARAM" xlink:href="#knob" transform="translate(59 3.5)" /> + <use xlink:href="#knobguide-frequency" transform="translate(56 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 20) rotate(270)">BW</text> + <use id="A_BANDWIDTH_PARAM" xlink:href="#knob-smallest" transform="translate(122 5)" /> + <use xlink:href="#knobguide-maxtick" transform="translate(110 -7)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 58.5) rotate(270)">FCV</text> + <use id="A_CV_PARAM" xlink:href="#knob-smallest" transform="translate(122 42)" /> + <use xlink:href="#knobguide-centertick" transform="translate(110 30)" /> + + <g transform="translate(104 26.5)"> + <text font-size="6pt" letter-spacing="1px" transform="translate(0 6.1)">LP</text> + <use id="A_MODE_PARAM" xlink:href="#button-small" transform="translate(13.5 -1)" /> + </g> + </g> + + <g transform="translate(0 93)"> + <!-- <rect width="150" height="60" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <polyline points="0,30 150,30" stroke="#f00" stroke-width="1" fill="none" /> --> + <!-- <polyline points="0,0 150,0" stroke="#0ff" stroke-width="1" fill="none" transform="translate(0 26)" /> --> + <use id="B_LIGHT" xlink:href="#light-small" transform="translate(5.5 18)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(11.5 33) rotate(270)">B</text> + + <text font-size="6pt" letter-spacing="2px" transform="translate(20 58)">LEVEL</text> + <use id="B_LEVEL_PARAM" xlink:href="#knob" transform="translate(13 3.5)" /> + <use xlink:href="#knobguide-level" transform="translate(13 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(69 58)">FREQ</text> + <use id="B_FREQUENCY_PARAM" xlink:href="#knob" transform="translate(59 3.5)" /> + <use xlink:href="#knobguide-frequency" transform="translate(56 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 22) rotate(270)">BW</text> + <use id="B_BANDWIDTH_PARAM" xlink:href="#knob-smallest" transform="translate(122 7)" /> + <use xlink:href="#knobguide-maxtick" transform="translate(110 -5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 56.5) rotate(270)">FCV</text> + <use id="B_CV_PARAM" xlink:href="#knob-smallest" transform="translate(122 40)" /> + <use xlink:href="#knobguide-centertick" transform="translate(110 28)" /> + </g> + + <g transform="translate(0 159)"> + <!-- <rect width="150" height="60" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <polyline points="0,30 150,30" stroke="#f00" stroke-width="1" fill="none" /> --> + <!-- <polyline points="0,0 150,0" stroke="#0ff" stroke-width="1" fill="none" transform="translate(0 26)" /> --> + <use id="C_LIGHT" xlink:href="#light-small" transform="translate(5.5 18)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(11.5 33) rotate(270)">C</text> + + <text font-size="6pt" letter-spacing="2px" transform="translate(20 58)">LEVEL</text> + <use id="C_LEVEL_PARAM" xlink:href="#knob" transform="translate(13 3.5)" /> + <use xlink:href="#knobguide-level" transform="translate(13 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(69 58)">FREQ</text> + <use id="C_FREQUENCY_PARAM" xlink:href="#knob" transform="translate(59 3.5)" /> + <use xlink:href="#knobguide-frequency" transform="translate(56 3.5)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 20) rotate(270)">BW</text> + <use id="C_BANDWIDTH_PARAM" xlink:href="#knob-smallest" transform="translate(122 5)" /> + <use xlink:href="#knobguide-maxtick" transform="translate(110 -7)" /> + + <text font-size="6pt" letter-spacing="2px" transform="translate(113 58.5) rotate(270)">FCV</text> + <use id="C_CV_PARAM" xlink:href="#knob-smallest" transform="translate(122 42)" /> + <use xlink:href="#knobguide-centertick" transform="translate(110 30)" /> + + <g transform="translate(104 26.5)"> + <text font-size="6pt" letter-spacing="1px" transform="translate(-0.5 6.1)">HP</text> + <use id="C_MODE_PARAM" xlink:href="#button-small" transform="translate(13.5 -1)" /> + </g> + </g> + + <g transform="translate(0 226)"> + <rect width="130" height="134" rx="5" fill="#fafafa" transform="translate(10 0)" /> + <rect width="32.5" height="46" rx="5" fill="#bbb" transform="translate(107.5 88)" /> + <rect width="32.5" height="8" fill="#bbb" transform="translate(107.5 88)" /> + <rect width="8" height="46" fill="#bbb" transform="translate(107.5 88)" /> + + <g transform="translate(12 0)"> + <use id="A_LEVEL_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(3 40)">A-LVL</text> + </g> + <g transform="translate(44 0)"> + <use id="B_LEVEL_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(1 40)">B-LVL</text> + </g> + <g transform="translate(76 0)"> + <use id="C_LEVEL_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(2 40)">C-LVL</text> + </g> + <g transform="translate(108 0)"> + <use id="ALL_CV_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(6 40)">FCV</text> + </g> + + <g transform="translate(12 44)"> + <use id="A_FREQUENCY_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(1 40)">A-FCV</text> + </g> + <g transform="translate(44 44)"> + <use id="B_FREQUENCY_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(1 40)">B-FCV</text> + </g> + <g transform="translate(76 44)"> + <use id="C_FREQUENCY_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(1 40)">C-FCV</text> + </g> + <g transform="translate(108 44)"> + <use id="IN_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(10 40)">IN</text> + </g> + + <g transform="translate(12 88)"> + <use id="A_BANDWIDTH_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(3 40)">A-BW</text> + </g> + <g transform="translate(44 88)"> + <use id="B_BANDWIDTH_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(2 40)">B-BW</text> + </g> + <g transform="translate(76 88)"> + <use id="C_BANDWIDTH_INPUT" xlink:href="#input" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(3 40)">C-BW</text> + </g> + <g transform="translate(108 88)"> + <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(3 5)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(5 40)">OUT</text> + </g> + </g> +</svg> diff --git a/res/PEQ.svg b/res/PEQ.svg Binary files differ. diff --git a/src/PEQ.cpp b/src/PEQ.cpp @@ -0,0 +1,160 @@ + +#include "PEQ.hpp" + +void PEQ::sampleRateChange() { + float sr = APP->engine->getSampleRate(); + for (int c = 0; c < _channels; ++c) { + _engines[c]->setSampleRate(sr); + } +} + +bool PEQ::active() { + return outputs[OUT_OUTPUT].isConnected(); +} + +int PEQ::channels() { + return inputs[IN_INPUT].getChannels(); +} + +void PEQ::addChannel(int c) { + const int n = 3; + _engines[c] = new PEQEngine(n); + for (int i = 0; i < n; ++i) { + _engines[c]->configChannel( + i, + c, + params[A_LEVEL_PARAM + i*4], + params[A_FREQUENCY_PARAM + i*4], + params[A_CV_PARAM + i*4], + params[A_BANDWIDTH_PARAM + i*4], + inputs[A_LEVEL_INPUT + i], + inputs[A_FREQUENCY_INPUT + i], + inputs[ALL_CV_INPUT], + &inputs[A_BANDWIDTH_INPUT + i] + ); + } + _engines[c]->setSampleRate(APP->engine->getSampleRate()); +} + +void PEQ::removeChannel(int c) { + delete _engines[c]; + _engines[c] = NULL; +} + +void PEQ::modulate() { + auto lowMode = params[A_MODE_PARAM].getValue() > 0.5f ? MultimodeFilter::LOWPASS_MODE : MultimodeFilter::BANDPASS_MODE; + auto highMode = params[C_MODE_PARAM].getValue() > 0.5f ? MultimodeFilter::HIGHPASS_MODE : MultimodeFilter::BANDPASS_MODE; + for (int c = 0; c < _channels; ++c) { + PEQEngine& e = *_engines[c]; + e.setLowFilterMode(lowMode); + e.setHighFilterMode(highMode); + e.modulate(); + } +} + +void PEQ::processAlways(const ProcessArgs& args) { + outputs[OUT_OUTPUT].setChannels(_channels); + std::fill(_rmsSums, _rmsSums + 3, 0.0f); +} + +void PEQ::processChannel(const ProcessArgs& args, int c) { + outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c), _rmsSums), c); +} + +void PEQ::postProcessAlways(const ProcessArgs& args) { + float ic = 1.0f / (float)std::max(1, _channels); + lights[A_LIGHT].value = _rmsSums[0] * ic; + lights[B_LIGHT].value = _rmsSums[1] * ic; + lights[C_LIGHT].value = _rmsSums[2] * ic; +} + +struct PEQWidget : ModuleWidget { + static constexpr int hp = 10; + + PEQWidget(PEQ* module) { + setModule(module); + box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); + + { + SvgPanel *panel = new SvgPanel(); + panel->box.size = box.size; + panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/PEQ.svg"))); + addChild(panel); + } + + addChild(createWidget<ScrewSilver>(Vec(0, 0))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 0))); + addChild(createWidget<ScrewSilver>(Vec(0, 365))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365))); + + // generated by svg_widgets.rb + auto aLevelParamPosition = Vec(21.0, 36.5); + auto aFrequencyParamPosition = Vec(67.0, 36.5); + auto aBandwidthParamPosition = Vec(122.0, 30.0); + auto aCvParamPosition = Vec(122.0, 67.0); + auto aModeParamPosition = Vec(117.5, 50.5); + auto bLevelParamPosition = Vec(21.0, 104.5); + auto bFrequencyParamPosition = Vec(67.0, 104.5); + auto bBandwidthParamPosition = Vec(122.0, 100.0); + auto bCvParamPosition = Vec(122.0, 133.0); + auto cLevelParamPosition = Vec(21.0, 170.5); + auto cFrequencyParamPosition = Vec(67.0, 170.5); + auto cBandwidthParamPosition = Vec(122.0, 164.0); + auto cCvParamPosition = Vec(122.0, 201.0); + auto cModeParamPosition = Vec(117.5, 184.5); + + auto aLevelInputPosition = Vec(15.0, 231.0); + auto bLevelInputPosition = Vec(47.0, 231.0); + auto cLevelInputPosition = Vec(79.0, 231.0); + auto allCvInputPosition = Vec(111.0, 231.0); + auto aFrequencyInputPosition = Vec(15.0, 275.0); + auto bFrequencyInputPosition = Vec(47.0, 275.0); + auto cFrequencyInputPosition = Vec(79.0, 275.0); + auto inInputPosition = Vec(111.0, 275.0); + auto aBandwidthInputPosition = Vec(15.0, 319.0); + auto bBandwidthInputPosition = Vec(47.0, 319.0); + auto cBandwidthInputPosition = Vec(79.0, 319.0); + + auto outOutputPosition = Vec(111.0, 319.0); + + auto aLightPosition = Vec(5.5, 43.0); + auto bLightPosition = Vec(5.5, 111.0); + auto cLightPosition = Vec(5.5, 177.0); + // end generated by svg_widgets.rb + + addParam(createParam<Knob29>(aLevelParamPosition, module, PEQ::A_LEVEL_PARAM)); + addParam(createParam<Knob29>(aFrequencyParamPosition, module, PEQ::A_FREQUENCY_PARAM)); + addParam(createParam<Knob16>(aBandwidthParamPosition, module, PEQ::A_BANDWIDTH_PARAM)); + addParam(createParam<Knob16>(aCvParamPosition, module, PEQ::A_CV_PARAM)); + addParam(createParam<IndicatorButtonGreen9>(aModeParamPosition, module, PEQ::A_MODE_PARAM)); + addParam(createParam<Knob29>(bLevelParamPosition, module, PEQ::B_LEVEL_PARAM)); + addParam(createParam<Knob29>(bFrequencyParamPosition, module, PEQ::B_FREQUENCY_PARAM)); + addParam(createParam<Knob16>(bBandwidthParamPosition, module, PEQ::B_BANDWIDTH_PARAM)); + addParam(createParam<Knob16>(bCvParamPosition, module, PEQ::B_CV_PARAM)); + addParam(createParam<Knob29>(cLevelParamPosition, module, PEQ::C_LEVEL_PARAM)); + addParam(createParam<Knob29>(cFrequencyParamPosition, module, PEQ::C_FREQUENCY_PARAM)); + addParam(createParam<Knob16>(cBandwidthParamPosition, module, PEQ::C_BANDWIDTH_PARAM)); + addParam(createParam<Knob16>(cCvParamPosition, module, PEQ::C_CV_PARAM)); + addParam(createParam<IndicatorButtonGreen9>(cModeParamPosition, module, PEQ::C_MODE_PARAM)); + + addInput(createInput<Port24>(aLevelInputPosition, module, PEQ::A_LEVEL_INPUT)); + addInput(createInput<Port24>(bLevelInputPosition, module, PEQ::B_LEVEL_INPUT)); + addInput(createInput<Port24>(cLevelInputPosition, module, PEQ::C_LEVEL_INPUT)); + addInput(createInput<Port24>(allCvInputPosition, module, PEQ::ALL_CV_INPUT)); + addInput(createInput<Port24>(aFrequencyInputPosition, module, PEQ::A_FREQUENCY_INPUT)); + addInput(createInput<Port24>(bFrequencyInputPosition, module, PEQ::B_FREQUENCY_INPUT)); + addInput(createInput<Port24>(cFrequencyInputPosition, module, PEQ::C_FREQUENCY_INPUT)); + addInput(createInput<Port24>(inInputPosition, module, PEQ::IN_INPUT)); + addInput(createInput<Port24>(aBandwidthInputPosition, module, PEQ::A_BANDWIDTH_INPUT)); + addInput(createInput<Port24>(bBandwidthInputPosition, module, PEQ::B_BANDWIDTH_INPUT)); + addInput(createInput<Port24>(cBandwidthInputPosition, module, PEQ::C_BANDWIDTH_INPUT)); + + addOutput(createOutput<Port24>(outOutputPosition, module, PEQ::OUT_OUTPUT)); + + addChild(createLight<SmallLight<GreenLight>>(aLightPosition, module, PEQ::A_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(bLightPosition, module, PEQ::B_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(cLightPosition, module, PEQ::C_LIGHT)); + } +}; + +Model* modelPEQ = createModel<PEQ, PEQWidget>("Bogaudio-PEQ", "PEQ", "3-channel parametric equalizer", "Filter", "Polyphonic"); diff --git a/src/PEQ.hpp b/src/PEQ.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "bogaudio.hpp" +#include "parametric_eq.hpp" + +extern Model* modelPEQ; + +namespace bogaudio { + +struct PEQ : BGModule { + enum ParamsIds { + A_LEVEL_PARAM, + A_FREQUENCY_PARAM, + A_BANDWIDTH_PARAM, + A_CV_PARAM, + B_LEVEL_PARAM, + B_FREQUENCY_PARAM, + B_BANDWIDTH_PARAM, + B_CV_PARAM, + C_LEVEL_PARAM, + C_FREQUENCY_PARAM, + C_BANDWIDTH_PARAM, + C_CV_PARAM, + A_MODE_PARAM, + C_MODE_PARAM, + NUM_PARAMS + }; + + enum InputsIds { + A_LEVEL_INPUT, + B_LEVEL_INPUT, + C_LEVEL_INPUT, + A_FREQUENCY_INPUT, + B_FREQUENCY_INPUT, + C_FREQUENCY_INPUT, + A_BANDWIDTH_INPUT, + B_BANDWIDTH_INPUT, + C_BANDWIDTH_INPUT, + ALL_CV_INPUT, + IN_INPUT, + NUM_INPUTS + }; + + enum OutputsIds { + OUT_OUTPUT, + NUM_OUTPUTS + }; + + enum LightsIds { + A_LIGHT, + B_LIGHT, + C_LIGHT, + NUM_LIGHTS + }; + + PEQEngine* _engines[maxChannels] {}; + float _rmsSums[3] {}; + + PEQ() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + float levelDefault = fabsf(PEQChannel::minDecibels) / (PEQChannel::maxDecibels - PEQChannel::minDecibels); + configParam(A_LEVEL_PARAM, 0.0f, 1.0f, levelDefault, "Channel A level", " dB", 0.0f, PEQChannel::maxDecibels - PEQChannel::minDecibels, PEQChannel::minDecibels); + configParam<ScaledSquaringParamQuantity<(int)PEQChannel::maxFrequency>>(A_FREQUENCY_PARAM, 0.0f, 1.0f, 0.0707107f, "Channel A frequency", " HZ"); + configParam(A_BANDWIDTH_PARAM, 0.0f, 1.0f, 0.5f, "Channel A bandwidth", "%", 0.0f, 100.0f); + configParam(A_CV_PARAM, -1.0f, 1.0f, 0.0f, "Channel A frequency CV attenuation", "%", 0.0f, 100.0f); + configParam(A_MODE_PARAM, 0.0f, 1.0f, 1.0f, "Channel A LP/BP"); + configParam(B_LEVEL_PARAM, 0.0f, 1.0f, levelDefault, "Channel B level", " dB", 0.0f, PEQChannel::maxDecibels - PEQChannel::minDecibels, PEQChannel::minDecibels); + configParam<ScaledSquaringParamQuantity<(int)PEQChannel::maxFrequency>>(B_FREQUENCY_PARAM, 0.0f, 1.0f, 0.1322876f, "Channel B frequency", " HZ"); + configParam(B_BANDWIDTH_PARAM, 0.0f, 1.0f, 0.5f, "Channel B bandwidth", "%", 0.0f, 100.0f); + configParam(B_CV_PARAM, -1.0f, 1.0f, 0.0f, "Channel B frequency CV attenuation", "%", 0.0f, 100.0f); + configParam(C_LEVEL_PARAM, 0.0f, 1.0f, levelDefault, "Channel C level", " dB", 0.0f, PEQChannel::maxDecibels - PEQChannel::minDecibels, PEQChannel::minDecibels); + configParam<ScaledSquaringParamQuantity<(int)PEQChannel::maxFrequency>>(C_FREQUENCY_PARAM, 0.0f, 1.0f, 0.223607f, "Channel C frequency", " HZ"); + configParam(C_BANDWIDTH_PARAM, 0.0f, 1.0f, 0.5f, "Channel C bandwidth", "%", 0.0f, 100.0f); + configParam(C_CV_PARAM, -1.0f, 1.0f, 0.0f, "Channel C frequency CV attenuation", "%", 0.0f, 100.0f); + configParam(C_MODE_PARAM, 0.0f, 1.0f, 1.0f, "Channel C HP/BP"); + } + + void sampleRateChange() override; + bool active() override; + int channels() override; + void addChannel(int c) override; + void removeChannel(int c) override; + void modulate() override; + void processAlways(const ProcessArgs& args) override; + void processChannel(const ProcessArgs& args, int c) override; + void postProcessAlways(const ProcessArgs& args) override; +}; + +} // namespace bogaudio diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp @@ -54,6 +54,7 @@ #include "Offset.hpp" #include "OneEight.hpp" #include "Pan.hpp" +#include "PEQ.hpp" #include "Pgmr.hpp" #include "PolyCon8.hpp" #include "PolyCon16.hpp" @@ -117,6 +118,7 @@ void init(rack::Plugin *p) { p->addModel(modelFFB); p->addModel(modelEQ); p->addModel(modelEQS); + p->addModel(modelPEQ); p->addModel(modelDADSRH); p->addModel(modelDADSRHPlus); diff --git a/src/dsp/filters/multimode.cpp b/src/dsp/filters/multimode.cpp @@ -160,7 +160,7 @@ template<int N> void MultimodeDesigner<N>::setParams( DelayMode dm ) { assert(N >= minPoles && N <= maxPoles); - assert(poles >= minPoles && poles <= N); + assert(poles >= minPoles && (poles <= N || (poles <= 2*N && (mode == LOWPASS_MODE || mode == HIGHPASS_MODE)))); assert(poles % modPoles == 0); assert(frequency >= minFrequency - 0.00001f && frequency <= maxFrequency); assert(qbw >= minQbw && qbw <= maxQbw); diff --git a/src/dsp/filters/multimode.hpp b/src/dsp/filters/multimode.hpp @@ -182,6 +182,30 @@ struct MultimodeFilter : MultimodeBase<16> { } }; +struct FourPoleMultimodeFilter : MultimodeBase<4> { + inline void setParams( + float sampleRate, + Type type, + int poles, + Mode mode, + float frequency, + float qbw, + BandwidthMode bwm = PITCH_BANDWIDTH_MODE, + DelayMode dm = FIXED_DELAY_MODE + ) { + design( + sampleRate, + type, + poles, + mode, + frequency, + qbw, + bwm, + dm + ); + } +}; + struct FourPoleButtworthLowpassFilter : MultimodeBase<4> { inline void setParams( float sampleRate, diff --git a/src/parametric_eq.cpp b/src/parametric_eq.cpp @@ -0,0 +1,91 @@ +#include "parametric_eq.hpp" +#include "dsp/pitch.hpp" + +const float PEQChannel::maxDecibels = 6.0f; +const float PEQChannel::minDecibels = Amplifier::minDecibels; +constexpr float PEQChannel::maxFrequency; +constexpr float PEQChannel::minFrequency; + +void PEQChannel::setSampleRate(float sampleRate) { + _sampleRate = sampleRate; + _levelSL.setParams(sampleRate, 0.05f, maxDecibels - minDecibels); + _frequencySL.setParams(sampleRate, 1.0f, frequencyToSemitone(maxFrequency - minFrequency)); + _bandwidthSL.setParams(sampleRate, 0.05f, MultimodeFilter::maxQbw - MultimodeFilter::minQbw); + _rms.setSampleRate(sampleRate); +} + +void PEQChannel::setFilterMode(MultimodeFilter::Mode mode) { + _mode = mode; + _poles = _mode == MultimodeFilter::BANDPASS_MODE ? 2 : 4; +} + +void PEQChannel::modulate() { + float level = clamp(_levelParam.getValue(), 0.0f, 1.0f); + if (_levelInput.isConnected()) { + level *= clamp(_levelInput.getPolyVoltage(_c) / 10.0f, 0.0f, 1.0f); + } + level *= maxDecibels - minDecibels; + level += minDecibels; + _amplifier.setLevel(_levelSL.next(level)); + + float fcv = 0.0f; + if (_frequency1Input.isConnected()) { + fcv += clamp(_frequency1Input.getPolyVoltage(_c) / 5.0f, -1.0f, 1.0f); + } + if (_frequency2Input.isConnected()) { + fcv += clamp(_frequency2Input.getPolyVoltage(_c) / 5.0f, -1.0f, 1.0f); + } + fcv *= _frequencyCvParam.getValue(); + float f = _frequencyParam.getValue(); + f += fcv; + f *= f; + f *= maxFrequency; + f = clamp(f, minFrequency, maxFrequency); + f = semitoneToFrequency(_frequencySL.next(frequencyToSemitone(f))); + + float bw = MultimodeFilter::minQbw; + if (_mode == MultimodeFilter::BANDPASS_MODE) { + bw = clamp(_bandwidthParam.getValue(), 0.0f, 1.0f); + if (_bandwidthInput && _bandwidthInput->isConnected()) { + bw *= clamp(_bandwidthInput->getPolyVoltage(_c) / 10.0f, 0.0f, 1.0f); + } + } + _filter.setParams( + _sampleRate, + MultimodeFilter::BUTTERWORTH_TYPE, + _poles, + _mode, + f, + bw, + MultimodeFilter::PITCH_BANDWIDTH_MODE + ); +} + +void PEQChannel::next(float sample) { + out = _amplifier.next(_filter.next(sample)); + rms = _rms.next(out / 5.0f); +} + + +void PEQEngine::setSampleRate(float sr) { + for (int i = 0; i < _n; ++i) { + _channels[i]->setSampleRate(sr); + } +} + +void PEQEngine::modulate() { + for (int i = 0; i < _n; ++i) { + _channels[i]->modulate(); + } +} + +float PEQEngine::next(float sample, float* rmsSums) { + float out = 0.0f; + for (int i = 0; i < _n; ++i) { + PEQChannel& c = *_channels[i]; + c.next(sample); + out += c.out; + rmsSums[i] += c.rms; + } + return _saturator.next(out); +} diff --git a/src/parametric_eq.hpp b/src/parametric_eq.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include "bogaudio.hpp" +#include "filters/multimode.hpp" +#include "dsp/signal.hpp" + +using namespace bogaudio::dsp; + +namespace bogaudio { + +struct PEQChannel { + static const float maxDecibels; + static const float minDecibels; + static constexpr float maxFrequency = 20000.0f; + static constexpr float minFrequency = 100.0f; // MultimodeFilter::minFrequency; + + float _sampleRate; + Amplifier _amplifier; + bogaudio::dsp::SlewLimiter _levelSL; + FourPoleMultimodeFilter _filter; + bogaudio::dsp::SlewLimiter _frequencySL; + bogaudio::dsp::SlewLimiter _bandwidthSL; + RootMeanSquare _rms; + + int _c; + MultimodeFilter::Mode _mode; + int _poles; + Param& _levelParam; + Param& _frequencyParam; + Param& _frequencyCvParam; + Param& _bandwidthParam; + Input& _levelInput; + Input& _frequency1Input; + Input& _frequency2Input; + Input* _bandwidthInput; + + float out = 0.0f; + float rms = 0.0f; + + PEQChannel( + int polyChannel, + Param& level, + Param& frequency, + Param& frequencyCv, + Param& bandwidth, + Input& levelCv, + Input& frequencyCv1, + Input& frequencyCv2, + Input* bandwidthCv = NULL, + float sampleRate = 1000.0f + ) + : _c(polyChannel) + , _levelParam(level) + , _frequencyParam(frequency) + , _frequencyCvParam(frequencyCv) + , _bandwidthParam(bandwidth) + , _levelInput(levelCv) + , _frequency1Input(frequencyCv2) + , _frequency2Input(frequencyCv1) + , _bandwidthInput(bandwidthCv) + { + setSampleRate(sampleRate); + setFilterMode(MultimodeFilter::BANDPASS_MODE); + _rms.setSensitivity(0.05f); + } + + void setSampleRate(float sampleRate); + void setFilterMode(MultimodeFilter::Mode mode); + void modulate(); + void next(float sample); // outputs on members out, rms. +}; + +struct PEQEngine { + int _n; + PEQChannel** _channels; + Saturator _saturator; + + PEQEngine(int channels) : _n(channels) { + _channels = new PEQChannel*[_n] {}; + } + ~PEQEngine() { + for (int i = 0; i < _n; ++i) { + delete _channels[i]; + } + delete[] _channels; + } + + inline void configChannel( + int i, + int polyChannel, + Param& level, + Param& frequency, + Param& frequencyCv, + Param& bandwidth, + Input& levelCv, + Input& frequencyCv1, + Input& frequencyCv2, + Input* bandwidthCv = NULL + ) { + _channels[i] = new PEQChannel( + polyChannel, + level, + frequency, + frequencyCv, + bandwidth, + levelCv, + frequencyCv1, + frequencyCv2, + bandwidthCv + ); + } + inline void setLowFilterMode(MultimodeFilter::Mode mode) { _channels[0]->setFilterMode(mode); } + inline void setHighFilterMode(MultimodeFilter::Mode mode) { _channels[_n - 1]->setFilterMode(mode); } + void setSampleRate(float sr); + void modulate(); + float next(float sample, float* rmsSums); +}; + +} // namespace bogaudio