BogaudioModules

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

commit 884518392929eed7299642a8a42e7b47c3d95f3f
parent 5e2a88313d7e76141f7b26525280f99667df413e
Author: Matt Demanett <matt@demanett.net>
Date:   Thu, 13 Feb 2020 22:15:23 -0500

Filters.

Diffstat:
Mbenchmarks/filter_benchmark.cpp | 34++++++++++++++++++++++++++++++++++
Mplugin.json | 36++++++++++++++++++++++++++++++++++++
Ares-src/EQ-src.svg | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/FFB-src.svg | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/LVCF-src.svg | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/VCF-src.svg | 327+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/EQ.svg | 0
Ares/FFB.svg | 0
Ares/LVCF.svg | 0
Ares/VCF.svg | 0
Asrc/EQ.cpp | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/EQ.hpp | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/FFB.cpp | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/FFB.hpp | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/LVCF.cpp | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/LVCF.hpp | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/TestVCF.cpp | 1+
Asrc/VCF.cpp | 262+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/VCF.hpp | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/bogaudio.cpp | 9+++++++++
Msrc/dsp/filter.cpp | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/dsp/filter.hpp | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/param_quantities.hpp | 2++
Mtest/scatter.cpp | 45++++++++++++++++++++++++++++++++++++++++++++-
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;