commit 884518392929eed7299642a8a42e7b47c3d95f3f
parent 5e2a88313d7e76141f7b26525280f99667df413e
Author: Matt Demanett <matt@demanett.net>
Date: Thu, 13 Feb 2020 22:15:23 -0500
Filters.
Diffstat:
24 files changed, 2538 insertions(+), 17 deletions(-)
diff --git a/benchmarks/filter_benchmark.cpp b/benchmarks/filter_benchmark.cpp
@@ -48,3 +48,37 @@ BENCHMARK(BM_CICDecimator);
// }
// }
// BENCHMARK(BM_RackDecimator);
+
+static void BM_Biquad(benchmark::State& state) {
+ WhiteNoiseGenerator r;
+ const int n = 8;
+ float buf[n];
+ for (int i = 0; i < n; ++i) {
+ buf[i] = r.next();
+ }
+
+ BiquadFilter<double> f;
+ f.setParams(0.012672, 0.025345, 0.012672, 1.102730, -1.974655, 0.922615);
+ int i = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(f.next(buf[i]));
+ i = (i + 1) % n;
+ }
+}
+BENCHMARK(BM_Biquad);
+
+static void BM_AnalogFrequency(benchmark::State& state) {
+ WhiteNoiseGenerator r;
+ const int n = 128;
+ float buf[n];
+ for (int i = 0; i < n; ++i) {
+ buf[i] = std::abs(r.next()) * 0.5f * M_PI;
+ }
+
+ int i = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(std::tan(buf[i]));
+ i = (i + 1) % n;
+ }
+}
+BENCHMARK(BM_AnalogFrequency);
diff --git a/plugin.json b/plugin.json
@@ -104,6 +104,42 @@
]
},
{
+ "slug": "Bogaudio-VCF",
+ "name": "VCF",
+ "description": "Multimode filter",
+ "tags": [
+ "Filter",
+ "Polyphonic"
+ ]
+ },
+ {
+ "slug": "Bogaudio-LVCF",
+ "name": "LVCF",
+ "description": "Compact multimode filter",
+ "tags": [
+ "Filter",
+ "Polyphonic"
+ ]
+ },
+ {
+ "slug": "Bogaudio-FFB",
+ "name": "FFB",
+ "description": "Fixed filter bank",
+ "tags": [
+ "Filter",
+ "Polyphonic"
+ ]
+ },
+ {
+ "slug": "Bogaudio-EQ",
+ "name": "EQ",
+ "description": "Compact 3-channel equalizer",
+ "tags": [
+ "Equalizer",
+ "Polyphonic"
+ ]
+ },
+ {
"slug": "Bogaudio-DADSRH",
"name": "DADSR(H)",
"description": "Advanced envelope generator",
diff --git a/res-src/EQ-src.svg b/res-src/EQ-src.svg
@@ -0,0 +1,112 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="45"
+ height="380"
+ viewBox="0 0 45 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-eq" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <text font-size="5.0pt" transform="rotate(-240) translate(20 0) rotate(240) translate(-6 3)">-36</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-202.5) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-165) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-127.5) translate(17 0)" />
+ <text font-size="5.0pt" transform="rotate(-90) translate(20 0) rotate(90) translate(-5 3)">-12</text>
+ <text font-size="5.0pt" transform="rotate(-15) translate(20 0) rotate(15) translate(-3.5 3)">0</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-52.5) translate(17 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(22.5) translate(17 0)" />
+ <text font-size="5.0pt" transform="rotate(60) translate(20 0) rotate(-60) translate(-3.5 3)">12</text>
+ <text font-size="5.0pt" transform="rotate(90) translate(20 0) rotate(-90) translate(-4 3)">dB</text>
+ </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>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 44,1 44,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 44.5,0.5 44.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 45,0 45,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <g transform="rotate(-90) translate(-376 13)">
+ <text class="title" font-size="7pt" letter-spacing="2.5px">EQ</text>
+ <g transform="translate(0 12)">
+ <text class="brand" font-size="7pt" letter-spacing="2px">BGA</text>
+ <rect width="3.0" height="3" fill="#ddd" transform="translate(11.5 -5)" />
+ </g>
+ </g>
+
+ <g transform="translate(0 36)">
+ <text font-size="6pt" letter-spacing="2.0px" transform="translate(12.5 0)">LOW</text>
+ <use id="LOW_PARAM" xlink:href="#knob" transform="translate(0 3)" />
+ <use xlink:href="#knobguide-eq" transform="translate(0 3)" />
+ <!-- <rect width="45" height="24" fill="#f0f" transform="translate(0 48)" /> -->
+ </g>
+
+ <g transform="translate(0 114)">
+ <text font-size="6pt" letter-spacing="2.0px" transform="translate(13.5 0)">MID</text>
+ <use id="MID_PARAM" xlink:href="#knob" transform="translate(0 3)" />
+ <use xlink:href="#knobguide-eq" transform="translate(0 3)" />
+ <!-- <rect width="45" height="24" fill="#f0f" transform="translate(0 48)" /> -->
+ </g>
+
+ <g transform="translate(0 192)">
+ <text font-size="6pt" letter-spacing="2.0px" transform="translate(10 0)">HIGH</text>
+ <use id="HIGH_PARAM" xlink:href="#knob" transform="translate(0 3)" />
+ <use xlink:href="#knobguide-eq" transform="translate(0 3)" />
+ <!-- <rect width="45" height="24" fill="#f0f" transform="translate(0 48)" /> -->
+ </g>
+
+ <g transform="translate(0 264)">
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 28)" />
+ <rect width="34" height="35" rx="5" fill="#fafafa" />
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(12.5 35)">IN</text>
+ </g>
+ <g transform="translate(5.5 41)">
+ <rect width="34" height="10" fill="#bbb" transform="translate(0 -3)" />
+ <rect width="34" height="35" rx="5" fill="#bbb" />
+ <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(5 0)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(8.3 32)">OUT</text>
+ </g>
+ </g>
+</svg>
diff --git a/res-src/FFB-src.svg b/res-src/FFB-src.svg
@@ -0,0 +1,224 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="120"
+ height="380"
+ viewBox="0 0 120 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-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" />
+ <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-linear" 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 2)">0</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(15 0)" />
+ <polyline points="0,0 3.5,0" stroke-width="1" stroke="#333" transform="rotate(60) translate(15 0)" />
+ </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="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 119,1 119,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 119.5,0.5 119.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 120,0 120,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <text class="title" x="82" y="17" font-size="9pt" letter-spacing="3px">FFB</text>
+ <g transform="translate(5.5 374)">
+ <text class="brand" font-size="6.5pt" letter-spacing="2px">BOGAUDIO</text>
+ <rect width="1.5" height="2" fill="#ddd" transform="translate(21 -4)" />
+ </g>
+ <!-- <rect width="120" height="254" fill="#0ff" transform="translate(0 24)" /> -->
+
+ <g transform="translate(1.5 24)">
+ <!-- <rect width="38" height="44" fill="#f0f" transform="translate(0 0)" /> -->
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">125</text>
+ <use id="BAND_1_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_1_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(41 24)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">500</text>
+ <use id="BAND_5_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_5_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(80.5 24)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(7 6)">2000</text>
+ <use id="BAND_9_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_9_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+
+ <g transform="translate(1.5 75)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">175</text>
+ <use id="BAND_2_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_2_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(41 75)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">700</text>
+ <use id="BAND_6_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_6_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(80.5 75)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(7 6)">2800</text>
+ <use id="BAND_10_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_10_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+
+ <g transform="translate(1.5 126)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">250</text>
+ <use id="BAND_3_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_3_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(41 126)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(6.5 6)">1000</text>
+ <use id="BAND_7_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_7_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(80.5 126)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(6.5 6)">4000</text>
+ <use id="BAND_11_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_11_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+
+ <g transform="translate(1.5 177)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 6)">350</text>
+ <use id="BAND_4_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_4_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(41 177)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(6.5 6)">1400</text>
+ <use id="BAND_8_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_8_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(80.5 177)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(7 6)">5600</text>
+ <use id="BAND_12_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="BAND_12_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+
+ <g transform="translate(1.5 228)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(13.5 6)">LP</text>
+ <use id="LOWPASS_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="LOWWPASS_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+ <g transform="translate(41 228)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(12.5 6)">CV</text>
+ <use id="CV_PARAM" xlink:href="#knob-smallest" transform="translate(11 18.5)" />
+ <use xlink:href="#knobguide-centertick" transform="translate(-1 6.5)" />
+ </g>
+ <g transform="translate(80.5 228)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(12.5 6)">HP</text>
+ <use id="HIGHPASS_PARAM" xlink:href="#knob-medium" transform="translate(6 13.5)" />
+ <use xlink:href="#knobguide-linear" transform="translate(-3.5 4)" />
+ <use id="HIGHPASS_LIGHT" xlink:href="#light-small" transform="translate(1 8.5)" />
+ </g>
+
+ <g transform="translate(28.5 278)">
+ <rect width="63" height="43" rx="5" fill="#fafafa" />
+ <rect width="63" height="10" fill="#fafafa" transform="translate(0 33)" />
+ <g transform="translate(1 0)">
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(3 4)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(10 39)">IN</text>
+ </g>
+ <g transform="translate(32 0)">
+ <use id="CV_INPUT" xlink:href="#input" transform="translate(3 4)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(8.5 39)">CV</text>
+ </g>
+ </g>
+ <g transform="translate(13 320)">
+ <rect width="94" height="43" rx="5" fill="#bbb" />
+ <g transform="translate(1 0)">
+ <use id="ALL_OUTPUT" xlink:href="#output" transform="translate(3 4)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(6 39)">ALL</text>
+ </g>
+ <g transform="translate(32 0)">
+ <use id="ODD_OUTPUT" xlink:href="#output" transform="translate(3 4)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(5 39)">ODD</text>
+ </g>
+ <g transform="translate(63 0)">
+ <use id="EVEN_OUTPUT" xlink:href="#output" transform="translate(3 4)" />
+ <text font-size="6pt" letter-spacing="1.5px" transform="translate(3 39)">EVEN</text>
+ </g>
+ </g>
+</svg>
diff --git a/res-src/LVCF-src.svg b/res-src/LVCF-src.svg
@@ -0,0 +1,188 @@
+<svg
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="45"
+ height="380"
+ viewBox="0 0 45 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-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" />
+ <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-cutoff-lvcf" 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 2)">0</text>
+ <polyline points="0,0 1.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-218.8) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-192.6) translate(15 0)" />
+
+ <text font-size="5.0pt" transform="rotate(-172.2) translate(18 0) rotate(172.2) translate(-1 2)">1</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(15 0)" />
+
+ <text font-size="5.0pt" transform="rotate(-27.9) translate(18 0) rotate(27.9) translate(-4 2)">10</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(19.8) translate(15 0)" />
+
+ <text font-size="5.0pt" transform="rotate(60) translate(18 0) rotate(-60) translate(-2 2)">20</text>
+
+ <text font-size="4.0pt" transform="rotate(90) translate(18 0) rotate(-90) translate(-5 2)">KHZ</text>
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-q-lvcf" 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 2)">0</text>
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-90) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(15 0)" />
+ <polyline points="0,0 3.5,0" stroke-width="1" stroke="#333" transform="rotate(60) translate(15 0)" />
+ </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="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="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="light-small" viewBox="0 0 6.4px 6.4px">
+ <rect width="6.4" height="6.4" fill="#0f0" />
+ </symbol>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 44,1 44,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 44.5,0.5 44.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 45,0 45,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <g transform="rotate(-90) translate(-376 13)">
+ <text class="title" font-size="7pt" letter-spacing="2.5px">LVCF</text>
+ <g transform="translate(0 12)">
+ <text class="brand" font-size="7pt" letter-spacing="2px">BGA</text>
+ <rect width="3.0" height="3" fill="#ddd" transform="translate(11.5 -5)" />
+ </g>
+ </g>
+
+ <g transform="translate(0 31)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(5 0)">CUTOFF</text>
+ <use id="FREQUENCY_PARAM" xlink:href="#knob-medium" transform="translate(9.5 8)" />
+ <use xlink:href="#knobguide-cutoff-lvcf" transform="translate(0 -1.5)" />
+ </g>
+
+ <g transform="translate(0 84)">
+ <text font-size="6pt" letter-spacing="2px" transform="translate(16 0)">CV</text>
+ <use id="FREQUENCY_CV_PARAM" xlink:href="#knob-smallest" transform="translate(14.5 9.5)" />
+ <use xlink:href="#knobguide-centertick" transform="translate(2.2 -2.5)" />
+ </g>
+
+ <g transform="translate(0 130)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(5 0)">RES/BW</text>
+ <use id="Q_PARAM" xlink:href="#knob-medium" transform="translate(9.5 8)" />
+ <use xlink:href="#knobguide-q-lvcf" transform="translate(0 -1.5)" />
+ </g>
+
+ <g transform="translate(0.5 180)">
+ <g transform="translate(10 0)">
+ <g transform="translate(0 0)">
+ <use id="LOWPASS_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="0.5px" transform="translate(1 6.7)">LP</text>
+ </g>
+
+ <g transform="translate(0 13)">
+ <use id="BANDPASS_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="0.5px" transform="translate(1 6.7)">BP</text>
+ </g>
+ </g>
+
+ <g transform="translate(32 0)">
+ <g transform="translate(0 0)">
+ <use id="HIGHPASS_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="0.5px" transform="translate(1 6.7)">HP</text>
+ </g>
+
+ <g transform="translate(0 13)">
+ <use id="BANDREJECT_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <text font-size="5pt" letter-spacing="0.5px" transform="translate(1 6.7)">BR</text>
+ </g>
+ </g>
+
+ <use id="MODE_PARAM" xlink:href="#button-small" transform="translate(17.5 24)" />
+ </g>
+
+ <g transform="translate(0 225)">
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="73" rx="5" fill="#fafafa" />
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" />
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(12.5 35)">IN</text>
+ <use id="FREQUENCY_CV_INPUT" xlink:href="#input" transform="translate(5 38)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(11 70)">CV</text>
+ </g>
+ <g transform="translate(5.5 76)">
+ <rect width="34" height="10" fill="#bbb" transform="translate(0 -3)" />
+ <rect width="34" height="35" rx="5" fill="#bbb" />
+ <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(5 0)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(8.3 32)">OUT</text>
+ </g>
+ </g>
+</svg>
diff --git a/res-src/VCF-src.svg b/res-src/VCF-src.svg
@@ -0,0 +1,327 @@
+<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-large" viewBox="0 0 68px 68px">
+ <g transform="translate(34 34)">
+ <polyline points="-10,0 10,0" stroke-width="1" stroke="#00f" />
+ <polyline points="0,-10 0,10" stroke-width="1" stroke="#00f" />
+ <circle cx="0" cy="0" r="33.5" stroke-width="1" stroke="#00f" fill="none" />
+ </g>
+ </symbol>
+
+ <symbol id="knob" viewBox="0 0 38px 38px">
+ <g transform="translate(19 19)">
+ <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="18.5" stroke-width="1" stroke="#00f" fill="none" />
+ </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" />
+ <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-frequency" viewBox="0 0 150px 108px">
+ <g transform="translate(75 54)">
+ <g transform="rotate(-240) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(240) translate(-2 3)">0</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-225) translate(37 0)" />
+
+ <g transform="rotate(-218.8) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(218.8) translate(-12.5 3.5)">100</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-206.5) translate(37 0)" />
+
+ <g transform="rotate(-192.6) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(192.6) translate(-11.5 3)">500</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-181.9) translate(37 0)" />
+
+ <g transform="rotate(-172.2) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(172.2) translate(-7.5 3)">1K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-151.3) translate(37 0)" />
+
+ <g transform="rotate(-133.9) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(133.9) translate(-16 3)">2.5K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-110.1) translate(37 0)" />
+
+ <g transform="rotate(-90) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(90) translate(-6 4)">5K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-41.6) translate(37 0)" />
+
+ <g transform="rotate(-56.3) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(56.3) translate(-3.5 3)">7.5K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-72.3) translate(37 0)" />
+
+ <g transform="rotate(-27.9) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(27.9) translate(-5 5)">10K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(-2.9) translate(37 0)" />
+
+ <g transform="rotate(19.8) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(-19.8) translate(-5 2)">15K</text>
+ </g>
+ <polyline points="0,0 3,0" stroke-width="0.7" stroke="#333" transform="rotate(40.6) translate(37 0)" />
+
+ <g transform="rotate(60) translate(34 0)">
+ <polyline points="0,0 5,0" stroke-width="1" stroke="#333" transform="translate(3 0)" />
+ <text font-size="7.0pt" transform="translate(14 0) rotate(-60) translate(-2.5 3)">20K</text>
+ </g>
+
+ <text font-size="7.0pt" transform="rotate(90) translate(44.5 0) rotate(-90) translate(-6.5 0)">HZ</text>
+ </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="knobguide-linear-38" viewBox="0 0 50px 50px">
+ <g transform="translate(25 25)">
+ <text font-size="6.0pt" transform="rotate(-240) translate(24 0) rotate(240) translate(-4 3)">0</text>
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-210) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-180) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-150) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-120) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-90) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-60) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-30) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(0) translate(22 0)" />
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(30) translate(22 0)" />
+ <polyline points="0,0 4,0" stroke-width="1" stroke="#333" transform="rotate(60) translate(22 0)" />
+ </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>
+ </g>
+ </symbol>
+
+ <symbol id="knobguide-mode" viewBox="0 0 60px 60px">
+ <g transform="translate(30 30)">
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-67.5) translate(11 0)" />
+ <text font-size="6.0pt" transform="rotate(-67.5) translate(24 0) rotate(67.5) translate(-3 7)">LP</text>
+
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(-22.5) translate(11 0)" />
+ <text font-size="6.0pt" transform="rotate(-22.5) translate(24 0) rotate(22.5) translate(-7 5)">HP</text>
+
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(22.5) translate(11 0)" />
+ <text font-size="6.0pt" transform="rotate(22.5) translate(24 0) rotate(-22.5) translate(-7 0)">BP</text>
+
+ <polyline points="0,0 3,0" stroke-width="0.5" stroke="#333" transform="rotate(67.5) translate(11 0)" />
+ <text font-size="6.0pt" transform="rotate(67.5) translate(24 0) rotate(-67.5) translate(-3 -1.5)">BR</text>
+ </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="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" />
+ </g>
+ </symbol>
+
+ <symbol id="light-small" viewBox="0 0 6.4px 6.4px">
+ <rect width="6.4" height="6.4" fill="#0f0" />
+ </symbol>
+
+ <symbol id="light-tiny" viewBox="0 0 1.1px 1.1px">
+ <rect width="3.2" height="3.2" fill="#0f0" />
+ </symbol>
+ </defs>
+
+ <rect width="100%" height="100%" fill="#ddd" />
+ <polyline points="1,1 179,1 179,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" />
+ <polyline points="0.5,0.5 179.5,0.5 179.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" />
+ <polyline points="0,0 180,0 180,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" />
+
+ <text class="title" x="54.5" y="19" font-size="12pt" letter-spacing="4px">VCF</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(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)">
+ <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)">
+ <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)">
+ <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>
+
+ <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>
+
+ <g transform="translate(0 269)">
+ <rect width="130" height="47" rx="5" fill="#bbb" transform="translate(10 44)" />
+ <rect width="98" height="44" rx="5" fill="#fafafa" transform="translate(26 0)" />
+ <rect width="98" height="10" fill="#fafafa" transform="translate(26 34)" />
+ <rect width="97" height="47" rx="5" fill="#fafafa" transform="translate(10 44)" />
+ <rect width="10" height="47" fill="#fafafa" transform="translate(97 44)" />
+
+ <g transform="translate(16 0)">
+ <g transform="translate(12 0)">
+ <use id="FREQUENCY_CV_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(9 40)">CV</text>
+ </g>
+ <g transform="translate(44 0)">
+ <use id="PITCH_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(1 40)">V/OCT</text>
+ </g>
+ <g transform="translate(76 0)">
+ <use id="FM_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(9 40)">FM</text>
+ </g>
+ </g>
+
+ <g transform="translate(10 44)">
+ <g transform="translate(2 0)">
+ <use id="IN_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(10.5 40)">IN</text>
+ </g>
+ <g transform="translate(34 0)">
+ <use id="Q_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(3 40)">R/BW</text>
+ </g>
+ <g transform="translate(66 0)">
+ <use id="SLOPE_INPUT" xlink:href="#input" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(7 40)">SLP</text>
+ </g>
+ <g transform="translate(98 0)">
+ <use id="OUT_OUTPUT" xlink:href="#output" transform="translate(3 5)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(6 40)">OUT</text>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/res/EQ.svg b/res/EQ.svg
Binary files differ.
diff --git a/res/FFB.svg b/res/FFB.svg
Binary files differ.
diff --git a/res/LVCF.svg b/res/LVCF.svg
Binary files differ.
diff --git a/res/VCF.svg b/res/VCF.svg
Binary files differ.
diff --git a/src/EQ.cpp b/src/EQ.cpp
@@ -0,0 +1,81 @@
+
+#include "EQ.hpp"
+
+bool EQ::active() {
+ return outputs[OUT_OUTPUT].isConnected();
+}
+
+int EQ::channels() {
+ return inputs[IN_INPUT].getChannels();
+}
+
+void EQ::addChannel(int c) {
+ _engines[c] = new Engine();
+}
+
+void EQ::removeChannel(int c) {
+ delete _engines[c];
+ _engines[c] = NULL;
+}
+
+void EQ::modulate() {
+ _lowDb = clamp(params[LOW_PARAM].getValue(), Engine::cutDb, Engine::gainDb);
+ _midDb = clamp(params[MID_PARAM].getValue(), Engine::cutDb, Engine::gainDb);
+ _highDb = clamp(params[HIGH_PARAM].getValue(), Engine::cutDb, Engine::gainDb);
+}
+
+void EQ::modulateChannel(int c) {
+ _engines[c]->setParams(
+ APP->engine->getSampleRate(),
+ _lowDb,
+ _midDb,
+ _highDb
+ );
+}
+
+void EQ::processAll(const ProcessArgs& args) {
+ outputs[OUT_OUTPUT].setChannels(_channels);
+}
+
+void EQ::processChannel(const ProcessArgs& args, int c) {
+ outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c)), c);
+}
+
+struct EQWidget : ModuleWidget {
+ static constexpr int hp = 3;
+
+ EQWidget(EQ* 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/EQ.svg")));
+ addChild(panel);
+ }
+
+ addChild(createWidget<ScrewSilver>(Vec(0, 0)));
+ addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto lowParamPosition = Vec(8.0, 47.0);
+ auto midParamPosition = Vec(8.0, 125.0);
+ auto highParamPosition = Vec(8.0, 203.0);
+
+ auto inInputPosition = Vec(10.5, 267.0);
+
+ auto outOutputPosition = Vec(10.5, 305.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob29>(lowParamPosition, module, EQ::LOW_PARAM));
+ addParam(createParam<Knob29>(midParamPosition, module, EQ::MID_PARAM));
+ addParam(createParam<Knob29>(highParamPosition, module, EQ::HIGH_PARAM));
+
+ addInput(createInput<Port24>(inInputPosition, module, EQ::IN_INPUT));
+
+ addOutput(createOutput<Port24>(outOutputPosition, module, EQ::OUT_OUTPUT));
+ }
+};
+
+Model* modelEQ = createModel<EQ, EQWidget>("Bogaudio-EQ", "EQ", "Compact 3-channel equalizer", "Equalizer", "Polyphonic");
diff --git a/src/EQ.hpp b/src/EQ.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "filter.hpp"
+
+extern Model* modelEQ;
+
+namespace bogaudio {
+
+struct EQ : BGModule {
+ enum ParamsIds {
+ LOW_PARAM,
+ MID_PARAM,
+ HIGH_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ IN_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ typedef bogaudio::dsp::Equalizer Engine;
+
+ float _lowDb = 0.0f;
+ float _midDb = 0.0f;
+ float _highDb = 0.0f;
+ Engine* _engines[maxChannels] {};
+
+ EQ() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
+ configParam(LOW_PARAM, Engine::cutDb, Engine::gainDb, 0.0f, "Low", " dB");
+ configParam(MID_PARAM, Engine::cutDb, Engine::gainDb, 0.0f, "Mid", " dB");
+ configParam(HIGH_PARAM, Engine::cutDb, Engine::gainDb, 0.0f, "High", " dB");
+ }
+
+ bool active() override;
+ int channels() override;
+ void addChannel(int c) override;
+ void removeChannel(int c) override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void processAll(const ProcessArgs& args) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/FFB.cpp b/src/FFB.cpp
@@ -0,0 +1,202 @@
+
+#include "FFB.hpp"
+
+void FFB::Engine::sampleRateChange() {
+ float sr = APP->engine->getSampleRate();
+ for (int i = 0; i < 14; i++) {
+ _slews[i].setParams(sr, 5.0f, 1.0f);
+ }
+
+ auto bp = [this, sr](int i, float cutoff) {
+ _filters[i].setParams(
+ sr,
+ MultimodeFilter::BUTTERWORTH_TYPE,
+ 4.0f,
+ MultimodeFilter::BANDPASS_MODE,
+ cutoff,
+ 0.22f / MultimodeFilter::maxBWPitch,
+ MultimodeFilter::PITCH_BANDWIDTH_MODE
+ );
+ };
+ _filters[ 0].setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::LOWPASS_MODE, 95.0f, 0.0f);
+ bp(1, 125.0f);
+ bp(2, 175.0f);
+ bp(3, 250.0f);
+ bp(4, 350.0f);
+ bp(5, 500.0f);
+ bp(6, 700.0f);
+ bp(7, 1000.0f);
+ bp(8, 1400.0f);
+ bp(9, 2000.0f);
+ bp(10, 2800.0f);
+ bp(11, 4000.0f);
+ bp(12, 5600.0f);
+ _filters[13].setParams(sr, MultimodeFilter::BUTTERWORTH_TYPE, 12.0f, MultimodeFilter::HIGHPASS_MODE, 6900.0f, 0.0f);
+}
+
+void FFB::sampleRateChange() {
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->sampleRateChange();
+ }
+}
+
+bool FFB::active() {
+ return outputs[ALL_OUTPUT].isConnected() || outputs[ODD_OUTPUT].isConnected() || outputs[EVEN_OUTPUT].isConnected();
+}
+
+int FFB::channels() {
+ return inputs[IN_INPUT].getChannels();
+}
+
+void FFB::addChannel(int c) {
+ _engines[c] = new Engine();
+}
+
+void FFB::removeChannel(int c) {
+ delete _engines[c];
+ _engines[c] = NULL;
+}
+
+void FFB::modulate() {
+ for (int i = 0; i < 14; ++i) {
+ _levels[i] = clamp(params[LOWPASS_PARAM + i].getValue(), 0.0f, 1.0f);
+ }
+}
+
+void FFB::modulateChannel(int c) {
+ Engine& e = *_engines[c];
+
+ float cv = 1.0f;
+ if (inputs[CV_INPUT].isConnected()) {
+ cv = clamp(inputs[CV_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f);
+ cv *= clamp(params[CV_PARAM].getValue(), -1.0f, 1.0f);
+ }
+
+ for (int i = 0; i < 14; ++i) {
+ float level = e._slews[i].next(_levels[i] * cv);
+ level = 1.0f - level;
+ level *= Amplifier::minDecibels;
+ e._amplifiers[i].setLevel(level);
+ }
+}
+
+void FFB::processChannel(const ProcessArgs& args, int c) {
+ Engine& e = *_engines[c];
+
+ float in = inputs[IN_INPUT].getVoltage(c);
+ float outAll = 0.0f;
+ float outOdd = 0.0f;
+ float outEven = 0.0f;
+ for (int i = 0; i < 14; ++i) {
+ float out = e._amplifiers[i].next(e._filters[i].next(in));
+ outAll += out;
+ outOdd += (i == 0 || i == 13 || i % 2 == 1) * out;
+ outEven += (i == 0 || i == 13 || i % 2 == 0) * out;
+ }
+
+ outputs[ALL_OUTPUT].setChannels(_channels);
+ outputs[ALL_OUTPUT].setVoltage(outAll, c);
+ outputs[ODD_OUTPUT].setChannels(_channels);
+ outputs[ODD_OUTPUT].setVoltage(outOdd, c);
+ outputs[EVEN_OUTPUT].setChannels(_channels);
+ outputs[EVEN_OUTPUT].setVoltage(outEven, c);
+}
+
+struct FFBWidget : ModuleWidget {
+ static constexpr int hp = 8;
+
+ FFBWidget(FFB* 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/FFB.svg")));
+ addChild(panel);
+ }
+
+ addChild(createWidget<ScrewSilver>(Vec(0, 0)));
+ addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto band1ParamPosition = Vec(7.5, 37.5);
+ auto band5ParamPosition = Vec(47.0, 37.5);
+ auto band9ParamPosition = Vec(86.5, 37.5);
+ auto band2ParamPosition = Vec(7.5, 88.5);
+ auto band6ParamPosition = Vec(47.0, 88.5);
+ auto band10ParamPosition = Vec(86.5, 88.5);
+ auto band3ParamPosition = Vec(7.5, 139.5);
+ auto band7ParamPosition = Vec(47.0, 139.5);
+ auto band11ParamPosition = Vec(86.5, 139.5);
+ auto band4ParamPosition = Vec(7.5, 190.5);
+ auto band8ParamPosition = Vec(47.0, 190.5);
+ auto band12ParamPosition = Vec(86.5, 190.5);
+ auto lowpassParamPosition = Vec(7.5, 241.5);
+ auto cvParamPosition = Vec(52.0, 246.5);
+ auto highpassParamPosition = Vec(86.5, 241.5);
+
+ auto inInputPosition = Vec(32.5, 282.0);
+ auto cvInputPosition = Vec(63.5, 282.0);
+
+ auto allOutputPosition = Vec(17.0, 324.0);
+ auto oddOutputPosition = Vec(48.0, 324.0);
+ auto evenOutputPosition = Vec(79.0, 324.0);
+
+ auto band1LightPosition = Vec(2.5, 32.5);
+ auto band5LightPosition = Vec(42.0, 32.5);
+ auto band9LightPosition = Vec(81.5, 32.5);
+ auto band2LightPosition = Vec(2.5, 83.5);
+ auto band6LightPosition = Vec(42.0, 83.5);
+ auto band10LightPosition = Vec(81.5, 83.5);
+ auto band3LightPosition = Vec(2.5, 134.5);
+ auto band7LightPosition = Vec(42.0, 134.5);
+ auto band11LightPosition = Vec(81.5, 134.5);
+ auto band4LightPosition = Vec(2.5, 185.5);
+ auto band8LightPosition = Vec(42.0, 185.5);
+ auto band12LightPosition = Vec(81.5, 185.5);
+ auto lowwpassLightPosition = Vec(2.5, 236.5);
+ auto highpassLightPosition = Vec(81.5, 236.5);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob26>(band1ParamPosition, module, FFB::BAND_1_PARAM));
+ addParam(createParam<Knob26>(band5ParamPosition, module, FFB::BAND_5_PARAM));
+ addParam(createParam<Knob26>(band9ParamPosition, module, FFB::BAND_9_PARAM));
+ addParam(createParam<Knob26>(band2ParamPosition, module, FFB::BAND_2_PARAM));
+ addParam(createParam<Knob26>(band6ParamPosition, module, FFB::BAND_6_PARAM));
+ addParam(createParam<Knob26>(band10ParamPosition, module, FFB::BAND_10_PARAM));
+ addParam(createParam<Knob26>(band3ParamPosition, module, FFB::BAND_3_PARAM));
+ addParam(createParam<Knob26>(band7ParamPosition, module, FFB::BAND_7_PARAM));
+ addParam(createParam<Knob26>(band11ParamPosition, module, FFB::BAND_11_PARAM));
+ addParam(createParam<Knob26>(band4ParamPosition, module, FFB::BAND_4_PARAM));
+ addParam(createParam<Knob26>(band8ParamPosition, module, FFB::BAND_8_PARAM));
+ addParam(createParam<Knob26>(band12ParamPosition, module, FFB::BAND_12_PARAM));
+ addParam(createParam<Knob26>(lowpassParamPosition, module, FFB::LOWPASS_PARAM));
+ addParam(createParam<Knob16>(cvParamPosition, module, FFB::CV_PARAM));
+ addParam(createParam<Knob26>(highpassParamPosition, module, FFB::HIGHPASS_PARAM));
+
+ addInput(createInput<Port24>(inInputPosition, module, FFB::IN_INPUT));
+ addInput(createInput<Port24>(cvInputPosition, module, FFB::CV_INPUT));
+
+ addOutput(createOutput<Port24>(allOutputPosition, module, FFB::ALL_OUTPUT));
+ addOutput(createOutput<Port24>(oddOutputPosition, module, FFB::ODD_OUTPUT));
+ addOutput(createOutput<Port24>(evenOutputPosition, module, FFB::EVEN_OUTPUT));
+
+ addChild(createLight<SmallLight<GreenLight>>(band1LightPosition, module, FFB::BAND_1_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band5LightPosition, module, FFB::BAND_5_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band9LightPosition, module, FFB::BAND_9_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band2LightPosition, module, FFB::BAND_2_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band6LightPosition, module, FFB::BAND_6_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band10LightPosition, module, FFB::BAND_10_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band3LightPosition, module, FFB::BAND_3_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band7LightPosition, module, FFB::BAND_7_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band11LightPosition, module, FFB::BAND_11_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band4LightPosition, module, FFB::BAND_4_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band8LightPosition, module, FFB::BAND_8_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(band12LightPosition, module, FFB::BAND_12_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(lowwpassLightPosition, module, FFB::LOWWPASS_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(highpassLightPosition, module, FFB::HIGHPASS_LIGHT));
+ }
+};
+
+Model* modelFFB = createModel<FFB, FFBWidget>("Bogaudio-FFB", "FFB", "Fixed filter bank", "Filter", "Polyphonic");
diff --git a/src/FFB.hpp b/src/FFB.hpp
@@ -0,0 +1,108 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "filter.hpp"
+#include "signal.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelFFB;
+
+namespace bogaudio {
+
+struct FFB : BGModule {
+ enum ParamsIds {
+ LOWPASS_PARAM,
+ BAND_1_PARAM,
+ BAND_2_PARAM,
+ BAND_3_PARAM,
+ BAND_4_PARAM,
+ BAND_5_PARAM,
+ BAND_6_PARAM,
+ BAND_7_PARAM,
+ BAND_8_PARAM,
+ BAND_9_PARAM,
+ BAND_10_PARAM,
+ BAND_11_PARAM,
+ BAND_12_PARAM,
+ HIGHPASS_PARAM,
+ CV_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ IN_INPUT,
+ CV_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ ALL_OUTPUT,
+ ODD_OUTPUT,
+ EVEN_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ BAND_1_LIGHT,
+ BAND_5_LIGHT,
+ BAND_9_LIGHT,
+ BAND_2_LIGHT,
+ BAND_6_LIGHT,
+ BAND_10_LIGHT,
+ BAND_3_LIGHT,
+ BAND_7_LIGHT,
+ BAND_11_LIGHT,
+ BAND_4_LIGHT,
+ BAND_8_LIGHT,
+ BAND_12_LIGHT,
+ LOWWPASS_LIGHT,
+ HIGHPASS_LIGHT,
+ NUM_LIGHTS
+ };
+
+ struct Engine {
+ MultimodeFilter _filters[14];
+ Amplifier _amplifiers[14];
+ bogaudio::dsp::SlewLimiter _slews[14];
+
+ Engine() {
+ sampleRateChange();
+ }
+
+ void sampleRateChange();
+ };
+
+ Engine* _engines[maxChannels] {};
+ float _levels[14] {};
+
+ FFB() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam<AmpliferParamQuantity>(BAND_1_PARAM, 0.0f, 1.0f, 1.0f, "Band 1 level");
+ configParam<AmpliferParamQuantity>(BAND_5_PARAM, 0.0f, 1.0f, 1.0f, "Band 5 level");
+ configParam<AmpliferParamQuantity>(BAND_9_PARAM, 0.0f, 1.0f, 1.0f, "Band 9 level");
+ configParam<AmpliferParamQuantity>(BAND_2_PARAM, 0.0f, 1.0f, 1.0f, "Band 2 level");
+ configParam<AmpliferParamQuantity>(BAND_6_PARAM, 0.0f, 1.0f, 1.0f, "Band 6 level");
+ configParam<AmpliferParamQuantity>(BAND_10_PARAM, 0.0f, 1.0f, 1.0f, "Band 10 level");
+ configParam<AmpliferParamQuantity>(BAND_3_PARAM, 0.0f, 1.0f, 1.0f, "Band 3 level");
+ configParam<AmpliferParamQuantity>(BAND_7_PARAM, 0.0f, 1.0f, 1.0f, "Band 7 level");
+ configParam<AmpliferParamQuantity>(BAND_11_PARAM, 0.0f, 1.0f, 1.0f, "Band 11 level");
+ configParam<AmpliferParamQuantity>(BAND_4_PARAM, 0.0f, 1.0f, 1.0f, "Band 4 level");
+ configParam<AmpliferParamQuantity>(BAND_8_PARAM, 0.0f, 1.0f, 1.0f, "Band 8 level");
+ configParam<AmpliferParamQuantity>(BAND_12_PARAM, 0.0f, 1.0f, 1.0f, "Band 12 level");
+ configParam<AmpliferParamQuantity>(LOWPASS_PARAM, 0.0f, 1.0f, 1.0f, "Lowpass level");
+ configParam(CV_PARAM, -1.0f, 1.0f, 0.0f, "Level CV", "%", 0.0f, 100.0f);
+ configParam<AmpliferParamQuantity>(HIGHPASS_PARAM, 0.0f, 1.0f, 1.0f, "Highpass level");
+ }
+
+ void sampleRateChange() override;
+ bool active() override;
+ int channels() override;
+ void addChannel(int c) override;
+ void removeChannel(int c) override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/LVCF.cpp b/src/LVCF.cpp
@@ -0,0 +1,223 @@
+
+#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"
+
+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) {
+ case MultimodeFilter::LINEAR_BANDWIDTH_MODE: {
+ json_object_set_new(root, BANDWIDTH_MODE_KEY, json_string(LINEAR_BANDWIDTH_MODE_KEY));
+ break;
+ }
+ case MultimodeFilter::PITCH_BANDWIDTH_MODE: {
+ json_object_set_new(root, BANDWIDTH_MODE_KEY, json_string(PITCH_BANDWIDTH_MODE_KEY));
+ break;
+ }
+ default: {}
+ }
+
+ return root;
+}
+
+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);
+ }
+
+ json_t* bwm = json_object_get(root, BANDWIDTH_MODE_KEY);
+ if (bwm) {
+ if (strcmp(json_string_value(bwm), LINEAR_BANDWIDTH_MODE_KEY) == 0) {
+ _bandwidthMode = MultimodeFilter::LINEAR_BANDWIDTH_MODE;
+ }
+ else {
+ _bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE;
+ }
+ }
+}
+
+bool LVCF::active() {
+ return outputs[OUT_OUTPUT].isConnected();
+}
+
+int LVCF::channels() {
+ return inputs[IN_INPUT].getChannels();
+}
+
+void LVCF::addChannel(int c) {
+ _engines[c] = new Engine();
+}
+
+void LVCF::removeChannel(int c) {
+ delete _engines[c];
+ _engines[c] = NULL;
+}
+
+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;
+ _mode = mode;
+ _poles = _polesSetting;
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->reset();
+ }
+ }
+
+ _q = clamp(params[Q_PARAM].getValue(), 0.0f, 1.0f);
+}
+
+void LVCF::modulateChannel(int c) {
+ Engine& e = *_engines[c];
+
+ float f = clamp(params[FREQUENCY_PARAM].getValue(), 0.0f, 1.0f);
+ f *= f;
+ if (inputs[FREQUENCY_CV_INPUT].isConnected()) {
+ float fcv = clamp(inputs[FREQUENCY_CV_INPUT].getPolyVoltage(c) / 10.0f, -1.0f, 1.0f);
+ fcv *= clamp(params[FREQUENCY_CV_PARAM].getValue(), -1.0f, 1.0f);
+ f += fcv;
+ }
+ f *= MultimodeFilter::maxFrequency;
+ f = clamp(f, MultimodeFilter::minFrequency, MultimodeFilter::maxFrequency);
+
+ e.setParams(
+ APP->engine->getSampleRate(),
+ _type,
+ _poles,
+ _mode,
+ f,
+ _q,
+ _bandwidthMode
+ );
+}
+
+void LVCF::processAlways(const ProcessArgs& args) {
+ lights[LOWPASS_LIGHT].value = _mode == MultimodeFilter::LOWPASS_MODE;
+ lights[HIGHPASS_LIGHT].value = _mode == MultimodeFilter::HIGHPASS_MODE;
+ lights[BANDPASS_LIGHT].value = _mode == MultimodeFilter::BANDPASS_MODE;
+ lights[BANDREJECT_LIGHT].value = _mode == MultimodeFilter::BANDREJECT_MODE;
+}
+
+void LVCF::processAll(const ProcessArgs& args) {
+ outputs[OUT_OUTPUT].setChannels(_channels);
+}
+
+void LVCF::processChannel(const ProcessArgs& args, int c) {
+ outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c)), c);
+}
+
+struct LVCFWidget : ModuleWidget {
+ static constexpr int hp = 3;
+
+ LVCFWidget(LVCF* 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/LVCF.svg")));
+ addChild(panel);
+ }
+
+ addChild(createWidget<ScrewSilver>(Vec(0, 0)));
+ addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto frequencyParamPosition = Vec(9.5, 39.0);
+ auto frequencyCvParamPosition = Vec(14.5, 93.5);
+ auto qParamPosition = Vec(9.5, 138.0);
+ auto modeParamPosition = Vec(18.0, 204.0);
+
+ auto inInputPosition = Vec(10.5, 228.0);
+ auto frequencyCvInputPosition = Vec(10.5, 263.0);
+
+ auto outOutputPosition = Vec(10.5, 301.0);
+
+ auto lowpassLightPosition = Vec(3.0, 181.0);
+ auto bandpassLightPosition = Vec(3.0, 194.0);
+ auto highpassLightPosition = Vec(25.0, 181.0);
+ auto bandrejectLightPosition = Vec(25.0, 194.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob26>(frequencyParamPosition, module, LVCF::FREQUENCY_PARAM));
+ addParam(createParam<Knob16>(frequencyCvParamPosition, module, LVCF::FREQUENCY_CV_PARAM));
+ addParam(createParam<Knob26>(qParamPosition, module, LVCF::Q_PARAM));
+ addParam(createParam<StatefulButton9>(modeParamPosition, module, LVCF::MODE_PARAM));
+
+ addInput(createInput<Port24>(inInputPosition, module, LVCF::IN_INPUT));
+ addInput(createInput<Port24>(frequencyCvInputPosition, module, LVCF::FREQUENCY_CV_INPUT));
+
+ addOutput(createOutput<Port24>(outOutputPosition, module, LVCF::OUT_OUTPUT));
+
+ addChild(createLight<SmallLight<GreenLight>>(lowpassLightPosition, module, LVCF::LOWPASS_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(bandpassLightPosition, module, LVCF::BANDPASS_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(highpassLightPosition, module, LVCF::HIGHPASS_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(bandrejectLightPosition, module, LVCF::BANDREJECT_LIGHT));
+ }
+
+ void appendContextMenu(Menu* menu) override {
+ LVCF* m = dynamic_cast<LVCF*>(module);
+ 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; }));
+ s->addItem(OptionMenuItem("3 poles", [m]() { return m->_polesSetting == 3; }, [m]() { m->_polesSetting = 3; }));
+ s->addItem(OptionMenuItem("4 poles", [m]() { return m->_polesSetting == 4; }, [m]() { m->_polesSetting = 4; }));
+ s->addItem(OptionMenuItem("5 poles", [m]() { return m->_polesSetting == 5; }, [m]() { m->_polesSetting = 5; }));
+ s->addItem(OptionMenuItem("6 poles", [m]() { return m->_polesSetting == 6; }, [m]() { m->_polesSetting = 6; }));
+ s->addItem(OptionMenuItem("7 poles", [m]() { return m->_polesSetting == 7; }, [m]() { m->_polesSetting = 7; }));
+ s->addItem(OptionMenuItem("8 poles", [m]() { return m->_polesSetting == 8; }, [m]() { m->_polesSetting = 8; }));
+ s->addItem(OptionMenuItem("9 poles", [m]() { return m->_polesSetting == 9; }, [m]() { m->_polesSetting = 9; }));
+ s->addItem(OptionMenuItem("10 poles", [m]() { return m->_polesSetting == 10; }, [m]() { m->_polesSetting = 10; }));
+ s->addItem(OptionMenuItem("11 poles", [m]() { return m->_polesSetting == 11; }, [m]() { m->_polesSetting = 11; }));
+ s->addItem(OptionMenuItem("12 poles", [m]() { return m->_polesSetting == 12; }, [m]() { m->_polesSetting = 12; }));
+ OptionsMenuItem::addToMenu(s, menu);
+
+ OptionsMenuItem* bwm = new OptionsMenuItem("Bandwidth mode");
+ bwm->addItem(OptionMenuItem("Pitched", [m]() { return m->_bandwidthMode == MultimodeFilter::PITCH_BANDWIDTH_MODE; }, [m]() { m->_bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE; }));
+ bwm->addItem(OptionMenuItem("Linear", [m]() { return m->_bandwidthMode == MultimodeFilter::LINEAR_BANDWIDTH_MODE; }, [m]() { m->_bandwidthMode = MultimodeFilter::LINEAR_BANDWIDTH_MODE; }));
+ OptionsMenuItem::addToMenu(bwm, menu);
+ }
+};
+
+Model* modelLVCF = createModel<LVCF, LVCFWidget>("Bogaudio-LVCF", "LVCF", "Compact multimode filter", "Filter", "Polyphonic");
diff --git a/src/LVCF.hpp b/src/LVCF.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "filter.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelLVCF;
+
+namespace bogaudio {
+
+struct LVCF : BGModule {
+ enum ParamsIds {
+ FREQUENCY_PARAM,
+ FREQUENCY_CV_PARAM,
+ Q_PARAM,
+ MODE_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ IN_INPUT,
+ FREQUENCY_CV_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ LOWPASS_LIGHT,
+ BANDPASS_LIGHT,
+ HIGHPASS_LIGHT,
+ BANDREJECT_LIGHT,
+ NUM_LIGHTS
+ };
+
+ typedef MultimodeFilter Engine;
+
+ 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] {};
+
+ LVCF() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam<ScaledSquaringParamQuantity<(int)MultimodeFilter::maxFrequency>>(FREQUENCY_PARAM, 0.0f, 1.0f, 0.22361f, "Center/cutoff frequency", " HZ");
+ configParam(FREQUENCY_CV_PARAM, -1.0f, 1.0f, 0.0f, "Frequency CV attenuation", "%", 0.0f, 100.0f);
+ 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");
+ }
+
+ json_t* dataToJson() override;
+ void dataFromJson(json_t* root) override;
+ bool active() override;
+ int channels() override;
+ void addChannel(int c) override;
+ void removeChannel(int c) override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void processAlways(const ProcessArgs& args) override;
+ void processAll(const ProcessArgs& args) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/TestVCF.cpp b/src/TestVCF.cpp
@@ -359,6 +359,7 @@ float TestVCF::AllPassModel::next(float sample) {
return _filter.next(sample);
}
+
template<typename T>
void TestVCF::ChebyshevModel<T>::setParams(float cutoff, float bandwidth, float resonance, Mode mode, Poles poles, float topology) {
switch (mode) {
diff --git a/src/VCF.cpp b/src/VCF.cpp
@@ -0,0 +1,262 @@
+
+#include "VCF.hpp"
+
+#define BANDWIDTH_MODE_KEY "bandwidthMode"
+#define LINEAR_BANDWIDTH_MODE_KEY "linear"
+#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
+) {
+ int i = -1, j = -1;
+ std::fill(_gains, _gains + nFilters, 0.0f);
+ if (slope >= 1.0f) {
+ _gains[i = nFilters - 1] = 1.0f;
+ }
+ else {
+ slope *= nFilters - 1;
+ float r = std::fmod(slope, 1.0f);
+ _gains[i = slope] = 1.0f - r;
+ _gains[j = i + 1] = r;
+ }
+
+ _filters[i].setParams(
+ _sampleRate,
+ type,
+ i + 1,
+ mode,
+ frequency,
+ qbw,
+ bwm
+ );
+ if (j >= 0) {
+ _filters[j].setParams(
+ _sampleRate,
+ type,
+ j + 1,
+ mode,
+ frequency,
+ qbw,
+ bwm
+ );
+ }
+}
+
+void VCF::Engine::sampleRateChange() {
+ _sampleRate = APP->engine->getSampleRate();
+ for (int i = 0; i < nFilters; ++i) {
+ _gainSLs[i].setParams(_sampleRate, 50.0f, 1.0f);
+ }
+}
+
+void VCF::Engine::reset() {
+ for (int i = 0; i < nFilters; ++i) {
+ _filters[i].reset();
+ }
+}
+
+float VCF::Engine::next(float sample) {
+ float out = 0.0f;
+ for (int i = 0; i < nFilters; ++i) {
+ float g = _gainSLs[i].next(_gains[i]);
+ if (g > 0.0f) {
+ out += g * _filters[i].next(sample);
+ }
+ }
+ return out;
+}
+
+json_t* VCF::dataToJson() {
+ json_t* root = json_object();
+ switch (_bandwidthMode) {
+ case MultimodeFilter::LINEAR_BANDWIDTH_MODE: {
+ json_object_set_new(root, BANDWIDTH_MODE_KEY, json_string(LINEAR_BANDWIDTH_MODE_KEY));
+ break;
+ }
+ case MultimodeFilter::PITCH_BANDWIDTH_MODE: {
+ json_object_set_new(root, BANDWIDTH_MODE_KEY, json_string(PITCH_BANDWIDTH_MODE_KEY));
+ break;
+ }
+ default: {}
+ }
+ return root;
+}
+
+void VCF::dataFromJson(json_t* root) {
+ json_t* bwm = json_object_get(root, BANDWIDTH_MODE_KEY);
+ if (bwm) {
+ if (strcmp(json_string_value(bwm), LINEAR_BANDWIDTH_MODE_KEY) == 0) {
+ _bandwidthMode = MultimodeFilter::LINEAR_BANDWIDTH_MODE;
+ }
+ else {
+ _bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE;
+ }
+ }
+}
+
+void VCF::sampleRateChange() {
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->sampleRateChange();
+ }
+}
+
+bool VCF::active() {
+ return outputs[OUT_OUTPUT].isConnected();
+}
+
+int VCF::channels() {
+ return inputs[IN_INPUT].getChannels();
+}
+
+void VCF::addChannel(int c) {
+ _engines[c] = new Engine();
+}
+
+void VCF::removeChannel(int c) {
+ delete _engines[c];
+ _engines[c] = NULL;
+}
+
+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;
+ _mode = mode;
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->reset();
+ }
+ }
+}
+
+void VCF::modulateChannel(int c) {
+ Engine& e = *_engines[c];
+
+ float slope = clamp(params[SLOPE_PARAM].getValue(), 0.0f, 1.0f);
+ if (inputs[SLOPE_INPUT].isConnected()) {
+ slope *= clamp(inputs[SLOPE_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f);
+ }
+ slope *= slope;
+
+ float q = clamp(params[Q_PARAM].getValue(), 0.0f, 1.0f);
+ if (inputs[Q_INPUT].isConnected()) {
+ q *= clamp(inputs[Q_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f);
+ }
+
+ float f = clamp(params[FREQUENCY_PARAM].getValue(), 0.0f, 1.0f);
+ f *= f;
+ if (inputs[FREQUENCY_CV_INPUT].isConnected()) {
+ float fcv = clamp(inputs[FREQUENCY_CV_INPUT].getPolyVoltage(c) / 10.0f, -1.0f, 1.0f);
+ fcv *= clamp(params[FREQUENCY_CV_PARAM].getValue(), -1.0f, 1.0f);
+ f += fcv;
+ }
+ f *= MultimodeFilter::maxFrequency;
+ if (inputs[PITCH_INPUT].isConnected() || inputs[FM_INPUT].isConnected()) {
+ float fm = inputs[FM_INPUT].getPolyVoltage(c);
+ fm *= clamp(params[FM_PARAM].getValue(), 0.0f, 1.0f);
+ float pitch = clamp(inputs[PITCH_INPUT].getPolyVoltage(c), -5.0f, 5.0f);
+ pitch += fm;
+ f += cvToFrequency(pitch);
+ }
+ f = clamp(f, MultimodeFilter::minFrequency, MultimodeFilter::maxFrequency);
+
+ e.setParams(
+ _type,
+ slope,
+ _mode,
+ f,
+ q,
+ _bandwidthMode
+ );
+}
+
+void VCF::processAll(const ProcessArgs& args) {
+ outputs[OUT_OUTPUT].setChannels(_channels);
+}
+
+void VCF::processChannel(const ProcessArgs& args, int c) {
+ outputs[OUT_OUTPUT].setVoltage(_engines[c]->next(inputs[IN_INPUT].getVoltage(c)), c);
+}
+
+struct VCFWidget : ModuleWidget {
+ static constexpr int hp = 10;
+
+ VCFWidget(VCF* 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/VCF.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 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 frequencyCvInputPosition = Vec(31.0, 274.0);
+ auto pitchInputPosition = Vec(63.0, 274.0);
+ auto fmInputPosition = Vec(95.0, 274.0);
+ auto inInputPosition = Vec(15.0, 318.0);
+ auto qInputPosition = Vec(47.0, 318.0);
+ auto slopeInputPosition = Vec(79.0, 318.0);
+
+ auto outOutputPosition = Vec(111.0, 318.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob68>(frequencyParamPosition, module, VCF::FREQUENCY_PARAM));
+ addParam(createParam<Knob16>(frequencyCvParamPosition, module, VCF::FREQUENCY_CV_PARAM));
+ addParam(createParam<Knob16>(fmParamPosition, module, VCF::FM_PARAM));
+ addParam(createParam<Knob38>(qParamPosition, module, VCF::Q_PARAM));
+ {
+ auto w = createParam<Knob16>(modeParamPosition, module, VCF::MODE_PARAM);
+ auto k = dynamic_cast<SvgKnob*>(w);
+ k->snap = true;
+ float a = (22.5 / 180.0) * M_PI;
+ k->minAngle = a;
+ k->maxAngle = M_PI - a;
+ k->speed = 3.0;
+ addParam(w);
+ }
+ addParam(createParam<Knob26>(slopeParamPosition, module, VCF::SLOPE_PARAM));
+ addParam(createParam<SliderSwitch2State14>(typeParamPosition, module, VCF::TYPE_PARAM));
+
+ addInput(createInput<Port24>(frequencyCvInputPosition, module, VCF::FREQUENCY_CV_INPUT));
+ addInput(createInput<Port24>(fmInputPosition, module, VCF::FM_INPUT));
+ addInput(createInput<Port24>(pitchInputPosition, module, VCF::PITCH_INPUT));
+ addInput(createInput<Port24>(inInputPosition, module, VCF::IN_INPUT));
+ addInput(createInput<Port24>(qInputPosition, module, VCF::Q_INPUT));
+ addInput(createInput<Port24>(slopeInputPosition, module, VCF::SLOPE_INPUT));
+
+ addOutput(createOutput<Port24>(outOutputPosition, module, VCF::OUT_OUTPUT));
+ }
+
+ void appendContextMenu(Menu* menu) override {
+ VCF* m = dynamic_cast<VCF*>(module);
+ assert(m);
+ menu->addChild(new MenuLabel());
+ OptionsMenuItem* bwm = new OptionsMenuItem("Bandwidth mode");
+ bwm->addItem(OptionMenuItem("Pitched", [m]() { return m->_bandwidthMode == MultimodeFilter::PITCH_BANDWIDTH_MODE; }, [m]() { m->_bandwidthMode = MultimodeFilter::PITCH_BANDWIDTH_MODE; }));
+ bwm->addItem(OptionMenuItem("Linear", [m]() { return m->_bandwidthMode == MultimodeFilter::LINEAR_BANDWIDTH_MODE; }, [m]() { m->_bandwidthMode = MultimodeFilter::LINEAR_BANDWIDTH_MODE; }));
+ OptionsMenuItem::addToMenu(bwm, menu);
+ }
+};
+
+Model* modelVCF = createModel<VCF, VCFWidget>("Bogaudio-VCF", "VCF", "Multimode filter", "Filter", "Polyphonic");
diff --git a/src/VCF.hpp b/src/VCF.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "filter.hpp"
+#include "signal.hpp"
+
+using namespace bogaudio::dsp;
+
+extern Model* modelVCF;
+
+namespace bogaudio {
+
+struct VCF : BGModule {
+ enum ParamsIds {
+ FREQUENCY_PARAM,
+ FREQUENCY_CV_PARAM,
+ FM_PARAM,
+ Q_PARAM,
+ MODE_PARAM,
+ SLOPE_PARAM,
+ TYPE_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ FREQUENCY_CV_INPUT,
+ FM_INPUT,
+ PITCH_INPUT,
+ IN_INPUT,
+ Q_INPUT,
+ SLOPE_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ struct Engine {
+ static constexpr int maxPoles = 12;
+ static constexpr int minPoles = 1;
+ static constexpr int nFilters = maxPoles;
+ MultimodeFilter _filters[nFilters];
+ float _gains[nFilters] {};
+ bogaudio::dsp::SlewLimiter _gainSLs[nFilters];
+ float _sampleRate;
+
+ Engine() {
+ sampleRateChange();
+ }
+
+ void setParams(
+ MultimodeFilter::Type type,
+ float slope,
+ MultimodeFilter::Mode mode,
+ float frequency,
+ float qbw,
+ MultimodeFilter::BandwidthMode bwm
+ );
+ void reset();
+ void sampleRateChange();
+ 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] {};
+
+ VCF() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
+ configParam<ScaledSquaringParamQuantity<(int)MultimodeFilter::maxFrequency>>(FREQUENCY_PARAM, 0.0f, 1.0f, 0.22361f, "Center/cutoff frequency", " HZ");
+ configParam(FREQUENCY_CV_PARAM, -1.0f, 1.0f, 0.0f, "Frequency CV attenuation", "%", 0.0f, 100.0f);
+ configParam(FM_PARAM, 0.0f, 1.0f, 0.0f, "FM", "%", 0.0f, 100.0f);
+ 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;
+ void dataFromJson(json_t* root) override;
+ void sampleRateChange() override;
+ bool active() override;
+ int channels() override;
+ void addChannel(int c) override;
+ void removeChannel(int c) override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void processAll(const ProcessArgs& args) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp
@@ -24,6 +24,8 @@
#include "Edge.hpp"
#include "EightFO.hpp"
#include "EightOne.hpp"
+#include "EQ.hpp"
+#include "FFB.hpp"
#include "FMOp.hpp"
#include "FlipFlop.hpp"
#include "Follow.hpp"
@@ -31,6 +33,7 @@
#include "LFO.hpp"
#include "LLFO.hpp"
#include "Lmtr.hpp"
+#include "LVCF.hpp"
#include "LVCO.hpp"
#include "Manual.hpp"
#include "Matrix44.hpp"
@@ -67,6 +70,7 @@
#include "Unison.hpp"
#include "VCA.hpp"
#include "VCAmp.hpp"
+#include "VCF.hpp"
#include "VCM.hpp"
#include "VCO.hpp"
#include "VU.hpp"
@@ -99,6 +103,11 @@ void init(rack::Plugin *p) {
p->addModel(modelEightFO);
p->addModel(modelLLFO);
+ p->addModel(modelVCF);
+ p->addModel(modelLVCF);
+ p->addModel(modelFFB);
+ p->addModel(modelEQ);
+
p->addModel(modelDADSRH);
p->addModel(modelDADSRHPlus);
p->addModel(modelShaper);
diff --git a/src/dsp/filter.cpp b/src/dsp/filter.cpp
@@ -177,6 +177,361 @@ float MultipoleFilter::next(float sample) {
}
+void MultimodeFilter::setParams(
+ float sampleRate,
+ Type type,
+ int poles,
+ Mode mode,
+ float frequency,
+ float qbw,
+ BandwidthMode bwm
+) {
+ assert(poles >= minPoles && poles <= maxPoles);
+ assert(poles % modPoles == 0);
+ assert(frequency >= minFrequency && frequency <= maxFrequency);
+ assert(qbw >= minQbw && qbw <= maxQbw);
+
+ bool repole = _type != type || _mode != mode || _nPoles != poles || (type == CHEBYSHEV_TYPE && (mode == LOWPASS_MODE || mode == HIGHPASS_MODE) && _qbw != qbw);
+ bool redesign = repole || _frequency != frequency || _qbw != qbw || _sampleRate != sampleRate || _bandwidthMode != bwm;
+ _sampleRate = sampleRate;
+ _half2PiST = M_PI * (1.0f / sampleRate);
+ _type = type;
+ _nPoles = poles;
+ _mode = mode;
+ _frequency = frequency;
+ _qbw = qbw;
+ _bandwidthMode = bwm;
+
+ if (repole) {
+ switch (_type) {
+ case BUTTERWORTH_TYPE: {
+ int np = _nPoles / 2 + (_nPoles % 2 == 1);
+ for (int k = 1, j = np - 1; k <= np; ++k, --j) {
+ T a = (T)(2 * k + _nPoles - 1) * M_PI / (T)(2 * _nPoles);
+ T re = std::cos(a);
+ T im = std::sin(a);
+ _poles[j] = Pole(-re, im, re + re, re * re + im * im);
+ }
+
+ _outGain = 1.0f;
+ break;
+ }
+
+ case CHEBYSHEV_TYPE: {
+ T ripple = 3.0;
+ if (mode == LOWPASS_MODE || mode == HIGHPASS_MODE) {
+ ripple += std::max(0.0f, 12.0f * qbw);
+ }
+ T e = ripple / (T)10.0;
+ e = std::pow((T)10.0, e);
+ e -= (T)1.0;
+ e = std::sqrt(e);
+ T ef = std::asinh((T)1.0 / e) / (float)_nPoles;
+ T efr = -std::sinh(ef);
+ T efi = std::cosh(ef);
+
+ int np = _nPoles / 2 + (_nPoles % 2 == 1);
+ for (int k = 1, j = np - 1; k <= np; ++k, --j) {
+ T a = (T)(2 * k - 1) * M_PI / (T)(2 * _nPoles);
+ T re = efr * std::sin(a);
+ T im = efi * std::cos(a);
+ _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));
+ break;
+ }
+
+ default: {
+ assert(false);
+ }
+ }
+ }
+
+ if (redesign) {
+ switch (_mode) {
+ case LOWPASS_MODE:
+ case HIGHPASS_MODE: {
+ int nf = _nPoles / 2 + _nPoles % 2;
+ for (int i = _nFilters; i < nf; ++i) {
+ _filters[i].reset();
+ }
+ _nFilters = nf;
+
+ // T iq = (1.0 / std::sqrt(2.0)) - 0.65 * _qbw;
+ T iq = (T)0.8 - (T)0.75 * _qbw;
+ T wa = std::tan(_frequency * _half2PiST);
+ T wa2 = wa * wa;
+
+ if (_mode == LOWPASS_MODE) {
+ int ni = 0;
+ int nf = _nFilters;
+ if (_nPoles % 2 == 1) {
+ ++ni;
+ --nf;
+ T wap = wa * std::real(_poles[0].p);
+ _filters[0].setParams(wa, wa, 0.0, wap + (T)1.0, wap - (T)1.0, (T)0.0);
+ }
+ T a0 = wa2;
+ T a1 = wa2 + wa2;
+ T a2 = wa2;
+ for (int i = 0; i < nf; ++i) {
+ Pole& pole = _poles[ni + i];
+ T ywa2 = pole.y * wa2;
+ T ywa21 = ywa2 + (T)1.0;
+ T x = (((T)(i == nf / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x;
+ T xwa = x * wa;
+ T b0 = ywa21 - xwa;
+ T b1 = (T)-2.0 + (ywa2 + ywa2);
+ T b2 = ywa21 + xwa;
+ _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ }
+ else {
+ int ni = 0;
+ int nf = _nFilters;
+ if (_nPoles % 2 == 1) {
+ ++ni;
+ --nf;
+ T rp = std::real(_poles[0].p);
+ _filters[0].setParams(1.0, -1.0, 0.0, wa + rp, wa - rp, 0.0);
+ }
+ T a0 = 1.0;
+ T a1 = -2.0f;
+ T a2 = 1.0;
+ for (int i = 0; i < nf; ++i) {
+ Pole& pole = _poles[ni + i];
+ T wa2y = wa2 + pole.y;
+ T x = (((T)(i == nf / 2) * (iq - (T)1.0)) + (T)1.0) * pole.x;
+ T xwa = x * wa;
+ T b0 = wa2y - xwa;
+ T b1 = (wa2 + wa2) - (pole.y + pole.y);
+ T b2 = wa2y + xwa;
+ _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ }
+ break;
+ }
+
+ case BANDPASS_MODE:
+ case BANDREJECT_MODE: {
+ int nf = ((_nPoles / 2) * 2) + (_nPoles % 2);
+ for (int i = _nFilters; i < nf; ++i) {
+ _filters[i].reset();
+ }
+ _nFilters = nf;
+
+ T wdl = 0.0;
+ T wdh = 0.0;
+ switch (_bandwidthMode) {
+ case LINEAR_BANDWIDTH_MODE: {
+ float bandwidth = std::max(minBWLinear, maxBWLinear * _qbw);
+ wdl = std::max(1.0f, _frequency - 0.5f * bandwidth);
+ wdh = std::min(maxFrequency, _frequency + 0.5f * bandwidth);
+ break;
+ }
+ case PITCH_BANDWIDTH_MODE: {
+ float bandwidth = std::max(minBWPitch, maxBWPitch * _qbw);
+ wdl = std::max(1.0f, powf(2.0f, -bandwidth) * _frequency);
+ wdh = std::min(maxFrequency, powf(2.0f, bandwidth) * _frequency);
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+ T wal = std::tan(wdl * _half2PiST);
+ T wah = std::tan(wdh * _half2PiST);
+ T w = wah - wal;
+ T w2 = w * w;
+ T w02 = wah * wal;
+
+ if (_mode == BANDPASS_MODE) {
+ T a0 = w;
+ T a1 = 0.0;
+ T a2 = -w;
+
+ int ni = 0;
+ int nf = _nFilters;
+ if (_nPoles % 2 == 1) {
+ ++ni;
+ --nf;
+ T wp = w * std::real(_poles[0].p);
+ _filters[0].setParams(
+ a0,
+ a1,
+ a2,
+ (T)1.0 + wp + w02,
+ (T)-2.0 + (w02 + w02),
+ (T)1.0 - wp + w02
+ );
+ }
+ for (int i = 0; i < nf; i += 2) {
+ Pole& pole = _poles[ni + i / 2];
+ TC x = pole.p2;
+ x *= w2;
+ x -= (T)4.0 * w02;
+ x = std::sqrt(x);
+ TC xc = std::conj(x);
+ TC wp = w * pole.p;
+ TC wpc = w * pole.pc;
+ TC y1 = (x - wp) * (T)0.5;
+ TC y1c = (xc - wpc) * (T)0.5;
+ TC y2 = (-x - wp) * (T)0.5;
+ TC y2c = (-xc - wpc) * (T)0.5;
+ TC cf1a = -(y1 + y1c);
+ TC cf2a = y1 * y1c;
+ TC cf1b = -(y2 + y2c);
+ TC cf2b = y2 * y2c;
+ T f1a = std::real(cf1a);
+ T f1b = std::real(cf1b);
+ T f2a = std::real(cf2a);
+ T f2b = std::real(cf2b);
+
+ {
+ T b0 = (T)1.0 + f1a + f2a;
+ T b1 = (T)-2.0 + (f2a + f2a);
+ T b2 = (T)1.0 - f1a + f2a;
+ _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ {
+ T b0 = (T)1.0 + f1b + f2b;
+ T b1 = (T)-2.0 + (f2b + f2b);
+ T b2 = (T)1.0 - f1b + f2b;
+ _filters[ni + i + 1].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ }
+ }
+ else {
+ T a0 = (T)1.0 + w02;
+ T a1 = (T)-2.0 + (w02 + w02);
+ T a2 = a0;
+
+ int ni = 0;
+ int nf = _nFilters;
+ if (_nPoles % 2 == 1) {
+ ++ni;
+ --nf;
+ T rp = std::real(_poles[0].p);
+ T rpw02 = rp * w02;
+ _filters[0].setParams(
+ a0,
+ a1,
+ a2,
+ rp + w + rpw02,
+ (T)-2.0 * rp + (rpw02 + rpw02),
+ rp - w + rpw02
+ );
+ }
+ for (int i = 0; i < nf; i += 2) {
+ Pole& pole = _poles[ni + i / 2];
+ TC x = pole.p2;
+ x *= (T)-4.0 * w02;
+ x += w2;
+ x = std::sqrt(x);
+ TC xc = std::conj(x);
+ TC y1 = (x - w) * pole.i2p;
+ TC y1c = (xc - w) * pole.i2pc;
+ TC y2 = (-x - w) * pole.i2p;
+ TC y2c = (-xc - w) * pole.i2pc;
+ TC cf1a = -pole.r * (y1 + y1c);
+ TC cf2a = pole.r * y1 * y1c;
+ TC cf1b = -pole.r * (y2 + y2c);
+ TC cf2b = pole.r * y2 * y2c;
+ T f1a = std::real(cf1a);
+ T f1b = std::real(cf1b);
+ T f2a = std::real(cf2a);
+ T f2b = std::real(cf2b);
+
+ {
+ T b0 = pole.r + f1a + f2a;
+ T b1 = (T)-2.0 * pole.r + (f2a + f2a);
+ T b2 = pole.r - f1a + f2a;
+ _filters[ni + i].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ {
+ T b0 = pole.r + f1b + f2b;
+ T b1 = (T)-2.0 * pole.r + (f2b + f2b);
+ T b2 = pole.r - f1b + f2b;
+ _filters[ni + i + 1].setParams(a0, a1, a2, b0, b1, b2);
+ }
+ }
+ }
+ break;
+ }
+
+ default: {
+ assert(false);
+ }
+ }
+ }
+}
+
+float MultimodeFilter::next(float sample) {
+ for (int i = 0; i < _nFilters; ++i) {
+ sample = _filters[i].next(sample);
+ }
+ return _outGain * sample;
+}
+
+void MultimodeFilter::reset() {
+ for (int i = 0; i < _nFilters; ++i) {
+ _filters[i].reset();
+ }
+}
+
+
+void Equalizer::setParams(
+ float sampleRate,
+ float lowDb,
+ float midDb,
+ float highDb
+) {
+ assert(lowDb >= cutDb && lowDb <= gainDb);
+ assert(midDb >= cutDb && midDb <= gainDb);
+ assert(highDb >= cutDb && highDb <= gainDb);
+
+ _lowAmp.setLevel(lowDb);
+ _lowFilter.setParams(
+ sampleRate,
+ MultimodeFilter::BUTTERWORTH_TYPE,
+ 4,
+ MultimodeFilter::LOWPASS_MODE,
+ 100.0f,
+ 0.0f
+ );
+
+ _midAmp.setLevel(midDb);
+ _midFilter.setParams(
+ sampleRate,
+ MultimodeFilter::BUTTERWORTH_TYPE,
+ 2,
+ MultimodeFilter::BANDPASS_MODE,
+ 350.0f,
+ 0.55f,
+ MultimodeFilter::PITCH_BANDWIDTH_MODE
+ );
+
+ _highAmp.setLevel(highDb);
+ _highFilter.setParams(
+ sampleRate,
+ MultimodeFilter::BUTTERWORTH_TYPE,
+ 4,
+ MultimodeFilter::HIGHPASS_MODE,
+ 1000.0f,
+ 0.0f
+ );
+}
+
+float Equalizer::next(float sample) {
+ float low = _lowAmp.next(_lowFilter.next(sample));
+ float mid = _midAmp.next(_midFilter.next(sample));
+ float high = _highAmp.next(_highFilter.next(sample));
+ return low + mid + high;
+}
+
+
void LPFDecimator::setParams(float sampleRate, int factor) {
_factor = factor;
_filter.setParams(
diff --git a/src/dsp/filter.hpp b/src/dsp/filter.hpp
@@ -2,8 +2,10 @@
#include <stdint.h>
#include <math.h>
+#include <complex>
#include "buffer.hpp"
+#include "signal.hpp"
namespace bogaudio {
namespace dsp {
@@ -26,23 +28,13 @@ struct BiquadFilter : Filter {
T _x[3] {};
T _y[3] {};
- BiquadFilter() {}
-
void setParams(T a0, T a1, T a2, T b0, T b1, T b2) {
- if (b0 == 1.0) {
- _a0 = a0;
- _a1 = a1;
- _a2 = a2;
- _b1 = b1;
- _b2 = b2;
- }
- else {
- _a0 = a0 / b0;
- _a1 = a1 / b0;
- _a2 = a2 / b0;
- _b1 = b1 / b0;
- _b2 = b2 / b0;
- }
+ T ib0 = 1.0 / b0;
+ _a0 = a0 * ib0;
+ _a1 = a1 * ib0;
+ _a2 = a2 * ib0;
+ _b1 = b1 * ib0;
+ _b2 = b2 * ib0;
}
void reset() {
@@ -133,6 +125,109 @@ struct MultipoleFilter : Filter {
float next(float sample) override;
};
+struct MultimodeFilter : Filter {
+ typedef float T;
+ typedef std::complex<T> TC;
+
+ struct Pole {
+ TC p;
+ T x = 0.0;
+ T y = 0.0;
+ TC pc;
+ TC p2;
+ TC i2p;
+ TC i2pc;
+ T r = 0.0;
+
+ Pole() {}
+ Pole(T re, T im, T x, T y) : p(TC(re, im)), x(x), y(y) {
+ pc = std::conj(p);
+ p2 = p * p;
+ i2p = (T)1.0 / ((T)2.0 * p);
+ i2pc = (T)1.0 / ((T)2.0 * pc);
+ r = std::abs(p);
+ }
+ };
+
+ enum Type {
+ UNKNOWN_TYPE,
+ BUTTERWORTH_TYPE,
+ CHEBYSHEV_TYPE
+ };
+
+ enum Mode {
+ UNKNOWN_MODE,
+ LOWPASS_MODE,
+ HIGHPASS_MODE,
+ BANDPASS_MODE,
+ BANDREJECT_MODE
+ };
+
+ enum BandwidthMode {
+ UNKNOWN_BANDWIDTH_MODE,
+ LINEAR_BANDWIDTH_MODE,
+ PITCH_BANDWIDTH_MODE
+ };
+
+ static constexpr int minPoles = 1;
+ static constexpr int maxPoles = 16;
+ static constexpr int modPoles = 1;
+ static constexpr float minFrequency = 2.0f;
+ static constexpr float maxFrequency = 20000.0f;
+ static constexpr float minQbw = 0.0f;
+ static constexpr float maxQbw = 1.0f;
+ static constexpr float minBWLinear = 10.0f;
+ static constexpr float maxBWLinear = 5000.0f;
+ static constexpr float minBWPitch = 1.0f / (1.0f * 12.0f * 100.0f / 25.0f);
+ static constexpr float maxBWPitch = 2.0f;
+
+ float _sampleRate = 44100.0f;
+ float _half2PiST = 0.0f;
+ Type _type = UNKNOWN_TYPE;
+ Mode _mode = UNKNOWN_MODE;
+ int _nPoles = 0;
+ float _frequency = -1.0f;
+ float _qbw = -1.0f;
+ BandwidthMode _bandwidthMode = UNKNOWN_BANDWIDTH_MODE;
+
+ Pole _poles[maxPoles / 2];
+ BiquadFilter<T> _filters[maxPoles] {};
+ int _nFilters = 1;
+ float _outGain = 1.0f;
+
+ void setParams(
+ float sampleRate,
+ Type type,
+ int poles,
+ Mode mode,
+ float frequency,
+ float qbw,
+ BandwidthMode bwm = PITCH_BANDWIDTH_MODE
+ );
+ float next(float sample) override;
+ void reset();
+};
+
+struct Equalizer : Filter {
+ static constexpr float gainDb = 12.0f;
+ static constexpr float cutDb = -36.0f;
+
+ Amplifier _lowAmp;
+ Amplifier _midAmp;
+ Amplifier _highAmp;
+ MultimodeFilter _lowFilter;
+ MultimodeFilter _midFilter;
+ MultimodeFilter _highFilter;
+
+ void setParams(
+ float sampleRate,
+ float lowDb,
+ float midDb,
+ float highDb
+ );
+ float next(float sample) override;
+};
+
struct Decimator {
Decimator() {}
virtual ~Decimator() {}
diff --git a/src/param_quantities.hpp b/src/param_quantities.hpp
@@ -19,6 +19,7 @@ struct ScaledSquaringParamQuantity : ParamQuantity {
float vv = v * v;
vv *= (float)scale;
+ vv += displayOffset;
if (v < 0.0f) {
return -vv;
}
@@ -29,6 +30,7 @@ struct ScaledSquaringParamQuantity : ParamQuantity {
if (!module) {
return;
}
+ displayValue -= displayOffset;
float v = fabsf(displayValue) / (float)scale;
v = powf(v, 0.5f);
if (displayValue < 0.0f) {
diff --git a/test/scatter.cpp b/test/scatter.cpp
@@ -74,10 +74,53 @@ struct ChebyshevPoles : Scatter {
}
};
+struct ChebyshevPoles2 : Scatter {
+ int k = 1;
+ int n;
+ double ripple;
+ double e;
+ double ef;
+
+ ChebyshevPoles2(int n, double ripple) : n(n), ripple(ripple) {
+ e = ripple / 10.0;
+ e = std::pow(10.0, e);
+ e -= 1.0f;
+ e = std::sqrt(e);
+
+ ef = std::asinh(1.0 / e) / (float)n;
+ }
+
+ bool next(float& x, float &y) override {
+ double a = (double)(2 * k - 1) * M_PI / (double)(2 * n);
+ x = -std::sinh(ef) * std::sin(a);
+ y = std::cosh(ef) * std::cos(a);
+ ++k;
+ return k <= n;
+ }
+};
+
+struct ButterworthPoles2 : Scatter {
+ int k = 1;
+ int n;
+
+ ButterworthPoles2(int n) : n(n) {}
+
+ bool next(float& x, float &y) override {
+ float a = ((float)(2 * k + n - 1)) * M_PI / (float)(2 * n);
+
+ x = std::cos(a);
+ y = std::sin(a);
+ ++k;
+ return k <= n;
+ }
+};
+
int main() {
// std::unique_ptr<Scatter> s(new TestScatter());
// std::unique_ptr<Scatter> s(new ButterworthPoles(16));
- std::unique_ptr<Scatter> s(new ChebyshevPoles(16, 0.01));
+ // std::unique_ptr<Scatter> s(new ButterworthPoles2(7));
+ // std::unique_ptr<Scatter> s(new ChebyshevPoles(16, 0.01));
+ std::unique_ptr<Scatter> s(new ChebyshevPoles2(4, 3.0));
float x = 0.0f;
float y = 0.0f;