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:
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