commit d16b50fae7f4a3e68f35c91910d266c2afe015a1
parent 9978b182776b395b85760d25a7c915183818ed5b
Author: Matt Demanett <matt@demanett.net>
Date: Mon, 9 Dec 2019 00:26:03 -0500
LVCO and SINE: two new compact derivatives of VCO. #88
Diffstat:
M | plugin.json | | | 18 | ++++++++++++++++++ |
A | res-src/LVCO-src.svg | | | 209 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | res-src/Sine-src.svg | | | 168 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | res/LVCO.svg | | | 0 | |
A | res/Sine.svg | | | 0 | |
A | src/LVCO.cpp | | | 160 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/LVCO.hpp | | | 77 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/Sine.cpp | | | 163 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/Sine.hpp | | | 68 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/VCO.cpp | | | 230 | +++++++------------------------------------------------------------------------ |
M | src/VCO.hpp | | | 71 | +++++++++++------------------------------------------------------------ |
M | src/bogaudio.cpp | | | 4 | ++++ |
A | src/vco_base.cpp | | | 214 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/vco_base.hpp | | | 93 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
14 files changed, 1204 insertions(+), 271 deletions(-)
diff --git a/plugin.json b/plugin.json
@@ -22,6 +22,24 @@
]
},
{
+ "slug": "Bogaudio-LVCO",
+ "name": "LVCO",
+ "description": "Compact oscillator",
+ "tags": [
+ "Oscillator",
+ "Polyphonic"
+ ]
+ },
+ {
+ "slug": "Bogaudio-Sine",
+ "name": "SINE",
+ "description": "Compact oscillator with phase offset",
+ "tags": [
+ "Oscillator",
+ "Polyphonic"
+ ]
+ },
+ {
"slug": "Bogaudio-XCO",
"name": "XCO",
"description": "Oscillator with wave mixer, wave mods, FM, hard sync",
diff --git a/res-src/LVCO-src.svg b/res-src/LVCO-src.svg
@@ -0,0 +1,209 @@
+<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-frequency-lvco" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <polyline points="0,0 3.5,0" stroke-width="0.7" stroke="#333" transform="rotate(-240) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-206.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-173.33) translate(15 0)" />
+
+ <g transform="rotate(-140) translate(14 0)">
+ <polyline points="0,0 2.5,0" stroke-width="1.0" stroke="#333" transform="translate(0 0)" />
+ <text font-size="5.0pt" transform="translate(5 0) rotate(140) translate(-5.5 0)">OV</text>
+ </g>
+
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-106.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-73.33) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-40) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-6.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(26.67) translate(15 0)" />
+ <polyline points="0,0 3.5,0" stroke-width="0.7" stroke="#333" transform="rotate(60) translate(15 0)" />
+ </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="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">LVCO</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>
+
+ <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(22.5 0)" /> -->
+
+ <g transform="translate(0 25)">
+ <!-- <polyline points="0,0 45,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 0)" /> -->
+ <use id="FREQUENCY_PARAM" xlink:href="#knob-medium" transform="translate(9.5 2)" />
+ <use xlink:href="#knobguide-frequency-lvco" transform="translate(0 -7.5)" />
+ </g>
+
+ <g transform="translate(0 63)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(4 6.1)">SLOW</text>
+ <use id="SLOW_PARAM" xlink:href="#button-small" transform="translate(31 -1)" />
+ </g>
+
+ <g transform="translate(0.5 86)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -8)" /> -->
+ <g transform="translate(9 0)">
+ <g transform="translate(0 0)">
+ <use id="SINE_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <path d="M 0 4 A 2 3 0 0 1 6 4" stroke="#333" stroke-width="1" fill="none" />
+ <path d="M 6 4 A 2 3 0 0 0 12 4" stroke="#333" stroke-width="1" fill="none" />
+ </g>
+ </g>
+
+ <g transform="translate(0 13)">
+ <use id="SAW_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <polyline points="0,8 12,0 12,8" stroke-width="1" stroke="#333" fill="none" />
+ </g>
+ </g>
+
+ <g transform="translate(0 26)">
+ <use id="PULSE_25_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <polyline points="0,4 0,0 4.5,0 4.5,8 12,8 12,4" stroke-width="1" stroke="#333" fill="none" />
+ </g>
+ </g>
+ </g>
+
+ <g transform="translate(31 0)">
+ <g transform="translate(0 0)">
+ <use id="TRIANGLE_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <polyline points="0,4 3,0 9,8 12,4" stroke-width="1" stroke="#333" fill="none" />
+ </g>
+ </g>
+
+ <g transform="translate(0 13)">
+ <use id="SQUARE_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <polyline points="0,4 0,0 6,0 6,8 12,8 12,4" stroke-width="1" stroke="#333" fill="none" />
+ </g>
+ </g>
+
+ <g transform="translate(0 26)">
+ <use id="PULSE_10_LIGHT" xlink:href="#light-small" transform="translate(-7.5 1)" />
+ <g transform="translate(1 1) scale(0.8)">
+ <!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#f0f" /> -->
+ <polyline points="0,4 0,0 3,0 3,8 12,8 12,4" stroke-width="1" stroke="#333" fill="none" />
+ </g>
+ </g>
+ </g>
+
+ <use id="WAVE_PARAM" xlink:href="#button-small" transform="translate(17.5 38)" />
+ </g>
+
+ <g transform="translate(0 150.5)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -14)" /> -->
+ <text font-size="6pt" letter-spacing="2px" transform="translate(16 0)">FM</text>
+ <use id="FM_DEPTH_PARAM" xlink:href="#knob-smallest" transform="translate(14.5 9.5)" />
+ <use xlink:href="#knobguide-maxtick" transform="translate(2.2 -2.5)" />
+ </g>
+
+ <g transform="translate(0 193)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -8)" /> -->
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 98)" />
+ <rect width="34" height="105" rx="5" fill="#fafafa" />
+ <use id="PITCH_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(3.5 35)">V/OCT</text>
+ <use id="FM_INPUT" xlink:href="#input" transform="translate(5 38)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(11 70)">FM</text>
+ <use id="SYNC_INPUT" xlink:href="#input" transform="translate(5 73)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(5 105)">SYNC</text>
+ </g>
+ <g transform="translate(5.5 111)">
+ <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/Sine-src.svg b/res-src/Sine-src.svg
@@ -0,0 +1,168 @@
+<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-frequency-sine" viewBox="0 0 45px 45px">
+ <g transform="translate(22.5 22.5)">
+ <polyline points="0,0 3.5,0" stroke-width="0.7" stroke="#333" transform="rotate(-240) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-206.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-173.33) translate(15 0)" />
+
+ <g transform="rotate(-140) translate(14 0)">
+ <polyline points="0,0 2.5,0" stroke-width="1.0" stroke="#333" transform="translate(0 0)" />
+ <text font-size="5.0pt" transform="translate(5 0) rotate(140) translate(-5.5 0)">OV</text>
+ </g>
+
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-106.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-73.33) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-40) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-6.67) translate(15 0)" />
+ <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(26.67) translate(15 0)" />
+ <polyline points="0,0 3.5,0" stroke-width="0.7" stroke="#333" transform="rotate(60) translate(15 0)" />
+ </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-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>
+ </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">SINE</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>
+
+ <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(22.5 0)" /> -->
+
+ <g transform="translate(0 25)">
+ <!-- <polyline points="0,0 45,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 0)" /> -->
+ <use id="FREQUENCY_PARAM" xlink:href="#knob-medium" transform="translate(9.5 2)" />
+ <use xlink:href="#knobguide-frequency-sine" transform="translate(0 -7.5)" />
+ </g>
+
+ <g transform="translate(0 63)">
+ <text font-size="6pt" letter-spacing="1px" transform="translate(4 6.1)">SLOW</text>
+ <use id="SLOW_PARAM" xlink:href="#button-small" transform="translate(31 -1)" />
+ </g>
+
+ <g transform="translate(0 83)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -14)" /> -->
+ <text font-size="6pt" letter-spacing="2px" transform="translate(16 0)">FM</text>
+ <use id="FM_DEPTH_PARAM" xlink:href="#knob-smallest" transform="translate(14.5 9.5)" />
+ <use xlink:href="#knobguide-maxtick" transform="translate(2.2 -2.5)" />
+ </g>
+
+ <g transform="translate(0 125)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -14)" /> -->
+ <text font-size="6pt" letter-spacing="2px" transform="translate(6 0)">PHASE</text>
+ <use id="PHASE_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 158)">
+ <!-- <rect width="45" height="8" fill="#f0f" transform="translate(0 -8)" /> -->
+ <g transform="translate(5.5 0)">
+ <rect width="34" height="10" fill="#fafafa" transform="translate(0 133)" />
+ <rect width="34" height="140" rx="5" fill="#fafafa" />
+ <use id="PITCH_INPUT" xlink:href="#input" transform="translate(5 3)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(3.5 35)">V/OCT</text>
+ <use id="FM_INPUT" xlink:href="#input" transform="translate(5 38)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(11 70)">FM</text>
+ <use id="PHASE_INPUT" xlink:href="#input" transform="translate(5 73)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(2.5 105)">PHASE</text>
+ <use id="SYNC_INPUT" xlink:href="#input" transform="translate(5 108)" />
+ <text font-size="5pt" letter-spacing="2px" transform="translate(5 140)">SYNC</text>
+ </g>
+ <g transform="translate(5.5 146)">
+ <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/LVCO.svg b/res/LVCO.svg
Binary files differ.
diff --git a/res/Sine.svg b/res/Sine.svg
Binary files differ.
diff --git a/src/LVCO.cpp b/src/LVCO.cpp
@@ -0,0 +1,160 @@
+
+#include "LVCO.hpp"
+
+#define FM_MODE "fm_mode"
+#define LINEAR_MODE "linear_mode"
+
+json_t* LVCO::dataToJson() {
+ json_t* root = VCOBase::dataToJson();
+ json_object_set_new(root, FM_MODE, json_boolean(_fmLinearMode));
+ json_object_set_new(root, LINEAR_MODE, json_boolean(_linearMode));
+ return root;
+}
+
+void LVCO::dataFromJson(json_t* root) {
+ VCOBase::dataFromJson(root);
+
+ json_t* fm = json_object_get(root, FM_MODE);
+ if (fm) {
+ _fmLinearMode = json_is_true(fm);
+ }
+
+ json_t* l = json_object_get(root, LINEAR_MODE);
+ if (l) {
+ _linearMode = json_is_true(l);
+ }
+}
+
+bool LVCO::active() {
+ return outputs[OUT_OUTPUT].isConnected();
+}
+
+void LVCO::modulate() {
+ _slowMode = params[SLOW_PARAM].getValue() > 0.5f;
+ _wave = (Wave)params[WAVE_PARAM].getValue();
+ _fmDepth = params[FM_DEPTH_PARAM].getValue();
+}
+
+void LVCO::modulateChannel(int c) {
+ VCOBase::modulateChannel(c);
+ Engine& e = *_engines[c];
+
+ e.squareActive = false;
+ switch (_wave) {
+ case SQUARE_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.5f));
+ break;
+ }
+ case PULSE_25_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.25f));
+ break;
+ }
+ case PULSE_10_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.1f));
+ break;
+ }
+ default: {
+ }
+ }
+ e.sawActive = _wave == SAW_WAVE;
+ e.triangleActive = _wave == TRIANGLE_WAVE;
+ e.sineActive = _wave == SINE_WAVE;
+}
+
+void LVCO::always(const ProcessArgs& args) {
+ Wave wave = (Wave)params[WAVE_PARAM].getValue();
+ lights[SINE_LIGHT].value = wave == SINE_WAVE;
+ lights[TRIANGLE_LIGHT].value = wave == TRIANGLE_WAVE;
+ lights[SAW_LIGHT].value = wave == SAW_WAVE;
+ lights[SQUARE_LIGHT].value = wave == SQUARE_WAVE;
+ lights[PULSE_25_LIGHT].value = wave == PULSE_25_WAVE;
+ lights[PULSE_10_LIGHT].value = wave == PULSE_10_WAVE;
+}
+
+void LVCO::processChannel(const ProcessArgs& args, int c) {
+ VCOBase::processChannel(args, c);
+ Engine& e = *_engines[c];
+
+ outputs[OUT_OUTPUT].setChannels(_channels);
+ outputs[OUT_OUTPUT].setVoltage(e.squareOut + e.sawOut + e.triangleOut + e.sineOut, c);
+}
+
+struct LVCOWidget : ModuleWidget {
+ static constexpr int hp = 3;
+
+ LVCOWidget(LVCO* 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/LVCO.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, 27.0);
+ auto slowParamPosition = Vec(31.0, 62.0);
+ auto waveParamPosition = Vec(18.0, 124.0);
+ auto fmDepthParamPosition = Vec(14.5, 160.0);
+
+ auto pitchInputPosition = Vec(10.5, 196.0);
+ auto fmInputPosition = Vec(10.5, 231.0);
+ auto syncInputPosition = Vec(10.5, 266.0);
+
+ auto outOutputPosition = Vec(10.5, 304.0);
+
+ auto sineLightPosition = Vec(2.0, 87.0);
+ auto sawLightPosition = Vec(2.0, 100.0);
+ auto pulse25LightPosition = Vec(2.0, 113.0);
+ auto triangleLightPosition = Vec(24.0, 87.0);
+ auto squareLightPosition = Vec(24.0, 100.0);
+ auto pulse10LightPosition = Vec(24.0, 113.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob26>(frequencyParamPosition, module, LVCO::FREQUENCY_PARAM));
+ addParam(createParam<IndicatorButtonGreen9>(slowParamPosition, module, LVCO::SLOW_PARAM));
+ addParam(createParam<StatefulButton9>(waveParamPosition, module, LVCO::WAVE_PARAM));
+ addParam(createParam<Knob16>(fmDepthParamPosition, module, LVCO::FM_DEPTH_PARAM));
+
+ addInput(createInput<Port24>(pitchInputPosition, module, LVCO::PITCH_INPUT));
+ addInput(createInput<Port24>(fmInputPosition, module, LVCO::FM_INPUT));
+ addInput(createInput<Port24>(syncInputPosition, module, LVCO::SYNC_INPUT));
+
+ addOutput(createOutput<Port24>(outOutputPosition, module, LVCO::OUT_OUTPUT));
+
+ addChild(createLight<SmallLight<GreenLight>>(sineLightPosition, module, LVCO::SINE_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(sawLightPosition, module, LVCO::SAW_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(pulse25LightPosition, module, LVCO::PULSE_25_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(triangleLightPosition, module, LVCO::TRIANGLE_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(squareLightPosition, module, LVCO::SQUARE_LIGHT));
+ addChild(createLight<SmallLight<GreenLight>>(pulse10LightPosition, module, LVCO::PULSE_10_LIGHT));
+ }
+
+ void appendContextMenu(Menu* menu) override {
+ LVCO* m = dynamic_cast<LVCO*>(module);
+ assert(m);
+ menu->addChild(new MenuLabel());
+
+ OptionsMenuItem* fm = new OptionsMenuItem("FM mode");
+ fm->addItem(OptionMenuItem("Exponential", [m]() { return !m->_fmLinearMode; }, [m]() { m->_fmLinearMode = false; }));
+ fm->addItem(OptionMenuItem("Linear", [m]() { return m->_fmLinearMode; }, [m]() { m->_fmLinearMode = true; }));
+ OptionsMenuItem::addToMenu(fm, menu);
+
+ menu->addChild(new BoolOptionMenuItem("Lineary frequency mode", [m]() { return &m->_linearMode; }));
+
+ OptionsMenuItem* p = new OptionsMenuItem("Polyphony channels from");
+ p->addItem(OptionMenuItem("V/OCT input", [m]() { return m->_polyInputID == LVCO::PITCH_INPUT; }, [m]() { m->_polyInputID = LVCO::PITCH_INPUT; }));
+ p->addItem(OptionMenuItem("FM input", [m]() { return m->_polyInputID == LVCO::FM_INPUT; }, [m]() { m->_polyInputID = LVCO::FM_INPUT; }));
+ OptionsMenuItem::addToMenu(p, menu);
+ }
+};
+
+Model* modelLVCO = createModel<LVCO, LVCOWidget>("Bogaudio-LVCO", "LVCO", "Compact oscillator", "Oscillator", "Polyphonic");
diff --git a/src/LVCO.hpp b/src/LVCO.hpp
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "vco_base.hpp"
+
+extern Model* modelLVCO;
+
+namespace bogaudio {
+
+struct LVCO : VCOBase {
+ enum ParamsIds {
+ FREQUENCY_PARAM,
+ SLOW_PARAM,
+ WAVE_PARAM,
+ FM_DEPTH_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ PITCH_INPUT,
+ FM_INPUT,
+ SYNC_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ SINE_LIGHT,
+ SAW_LIGHT,
+ PULSE_25_LIGHT,
+ TRIANGLE_LIGHT,
+ SQUARE_LIGHT,
+ PULSE_10_LIGHT,
+ NUM_LIGHTS
+ };
+
+ enum Wave {
+ SINE_WAVE,
+ TRIANGLE_WAVE,
+ SAW_WAVE,
+ SQUARE_WAVE,
+ PULSE_25_WAVE,
+ PULSE_10_WAVE
+ };
+
+ Wave _wave = SINE_WAVE;
+
+ LVCO()
+ : VCOBase(
+ FREQUENCY_PARAM,
+ -1,
+ PITCH_INPUT,
+ SYNC_INPUT,
+ FM_INPUT
+ )
+ {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam<VCOFrequencyParamQuantity>(FREQUENCY_PARAM, -3.0f, 6.0f, 0.0f, "Frequency", " Hz");
+ configParam(SLOW_PARAM, 0.0f, 1.0f, 0.0f, "Slow mode");
+ configParam(WAVE_PARAM, 0.0f, 5.0f, 0.0f, "Waveform");
+ configParam(FM_DEPTH_PARAM, 0.0f, 1.0f, 0.0f, "FM depth", "%", 0.0f, 100.0f);
+ }
+
+ json_t* dataToJson() override;
+ void dataFromJson(json_t* root) override;
+ bool active() override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void always(const ProcessArgs& args) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/Sine.cpp b/src/Sine.cpp
@@ -0,0 +1,163 @@
+
+#include "Sine.hpp"
+
+#define WAVE "wave"
+#define FM_MODE "fm_mode"
+#define LINEAR_MODE "linear_mode"
+
+json_t* Sine::dataToJson() {
+ json_t* root = VCOBase::dataToJson();
+ json_object_set_new(root, WAVE, json_integer((int)_wave));
+ json_object_set_new(root, FM_MODE, json_boolean(_fmLinearMode));
+ json_object_set_new(root, LINEAR_MODE, json_boolean(_linearMode));
+ return root;
+}
+
+void Sine::dataFromJson(json_t* root) {
+ VCOBase::dataFromJson(root);
+
+ json_t* w = json_object_get(root, WAVE);
+ if (w) {
+ _wave = (Wave)json_integer_value(w);
+ if (!(_wave == TRIANGLE_WAVE || _wave == SAW_WAVE || _wave == SQUARE_WAVE || _wave == PULSE_25_WAVE || _wave == PULSE_10_WAVE)) {
+ _wave = SINE_WAVE;
+ }
+ }
+
+ json_t* fm = json_object_get(root, FM_MODE);
+ if (fm) {
+ _fmLinearMode = json_is_true(fm);
+ }
+
+ json_t* l = json_object_get(root, LINEAR_MODE);
+ if (l) {
+ _linearMode = json_is_true(l);
+ }
+}
+
+bool Sine::active() {
+ return outputs[OUT_OUTPUT].isConnected();
+}
+
+void Sine::modulate() {
+ _slowMode = params[SLOW_PARAM].getValue() > 0.5f;
+ _fmDepth = params[FM_DEPTH_PARAM].getValue();
+}
+
+void Sine::modulateChannel(int c) {
+ VCOBase::modulateChannel(c);
+ Engine& e = *_engines[c];
+
+ e.squareActive = false;
+ switch (_wave) {
+ case SQUARE_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.5f));
+ break;
+ }
+ case PULSE_25_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.25f));
+ break;
+ }
+ case PULSE_10_WAVE: {
+ e.squareActive = true;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(0.1f));
+ break;
+ }
+ default: {
+ }
+ }
+ e.sawActive = _wave == SAW_WAVE;
+ e.triangleActive = _wave == TRIANGLE_WAVE;
+ e.sineActive = _wave == SINE_WAVE;
+}
+
+void Sine::processChannel(const ProcessArgs& args, int c) {
+ Engine& e = *_engines[c];
+
+ float phaseOffset = params[PHASE_PARAM].getValue();
+ if (inputs[PHASE_INPUT].isConnected()) {
+ phaseOffset *= clamp(inputs[PHASE_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
+ }
+ e.additionalPhaseOffset = -phaseOffset * 0.5f * Phasor::maxPhase;
+
+ VCOBase::processChannel(args, c);
+
+ outputs[OUT_OUTPUT].setChannels(_channels);
+ outputs[OUT_OUTPUT].setVoltage(e.squareOut + e.sawOut + e.triangleOut + e.sineOut, c);
+}
+
+struct SineWidget : ModuleWidget {
+ static constexpr int hp = 3;
+
+ SineWidget(Sine* 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/Sine.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, 27.0);
+ auto slowParamPosition = Vec(31.0, 62.0);
+ auto fmDepthParamPosition = Vec(14.5, 92.5);
+ auto phaseParamPosition = Vec(14.5, 134.5);
+
+ auto pitchInputPosition = Vec(10.5, 161.0);
+ auto fmInputPosition = Vec(10.5, 196.0);
+ auto phaseInputPosition = Vec(10.5, 231.0);
+ auto syncInputPosition = Vec(10.5, 266.0);
+
+ auto outOutputPosition = Vec(10.5, 304.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob26>(frequencyParamPosition, module, Sine::FREQUENCY_PARAM));
+ addParam(createParam<IndicatorButtonGreen9>(slowParamPosition, module, Sine::SLOW_PARAM));
+ addParam(createParam<Knob16>(fmDepthParamPosition, module, Sine::FM_DEPTH_PARAM));
+ addParam(createParam<Knob16>(phaseParamPosition, module, Sine::PHASE_PARAM));
+
+ addInput(createInput<Port24>(pitchInputPosition, module, Sine::PITCH_INPUT));
+ addInput(createInput<Port24>(fmInputPosition, module, Sine::FM_INPUT));
+ addInput(createInput<Port24>(phaseInputPosition, module, Sine::PHASE_INPUT));
+ addInput(createInput<Port24>(syncInputPosition, module, Sine::SYNC_INPUT));
+
+ addOutput(createOutput<Port24>(outOutputPosition, module, Sine::OUT_OUTPUT));
+ }
+
+ void appendContextMenu(Menu* menu) override {
+ Sine* m = dynamic_cast<Sine*>(module);
+ assert(m);
+ menu->addChild(new MenuLabel());
+
+ OptionsMenuItem* w = new OptionsMenuItem("Waveform");
+ w->addItem(OptionMenuItem("Sine", [m]() { return m->_wave == Sine::SINE_WAVE; }, [m]() { m->_wave = Sine::SINE_WAVE; }));
+ w->addItem(OptionMenuItem("Triangle", [m]() { return m->_wave == Sine::TRIANGLE_WAVE; }, [m]() { m->_wave = Sine::TRIANGLE_WAVE; }));
+ w->addItem(OptionMenuItem("Saw", [m]() { return m->_wave == Sine::SAW_WAVE; }, [m]() { m->_wave = Sine::SAW_WAVE; }));
+ w->addItem(OptionMenuItem("Square", [m]() { return m->_wave == Sine::SQUARE_WAVE; }, [m]() { m->_wave = Sine::SQUARE_WAVE; }));
+ w->addItem(OptionMenuItem("25% pulse", [m]() { return m->_wave == Sine::PULSE_25_WAVE; }, [m]() { m->_wave = Sine::PULSE_25_WAVE; }));
+ w->addItem(OptionMenuItem("10% pulse", [m]() { return m->_wave == Sine::PULSE_10_WAVE; }, [m]() { m->_wave = Sine::PULSE_10_WAVE; }));
+ OptionsMenuItem::addToMenu(w, menu);
+
+ OptionsMenuItem* fm = new OptionsMenuItem("FM mode");
+ fm->addItem(OptionMenuItem("Exponential", [m]() { return !m->_fmLinearMode; }, [m]() { m->_fmLinearMode = false; }));
+ fm->addItem(OptionMenuItem("Linear", [m]() { return m->_fmLinearMode; }, [m]() { m->_fmLinearMode = true; }));
+ OptionsMenuItem::addToMenu(fm, menu);
+
+ menu->addChild(new BoolOptionMenuItem("Lineary frequency mode", [m]() { return &m->_linearMode; }));
+
+ OptionsMenuItem* p = new OptionsMenuItem("Polyphony channels from");
+ p->addItem(OptionMenuItem("V/OCT input", [m]() { return m->_polyInputID == Sine::PITCH_INPUT; }, [m]() { m->_polyInputID = Sine::PITCH_INPUT; }));
+ p->addItem(OptionMenuItem("FM input", [m]() { return m->_polyInputID == Sine::FM_INPUT; }, [m]() { m->_polyInputID = Sine::FM_INPUT; }));
+ OptionsMenuItem::addToMenu(p, menu);
+ }
+};
+
+Model* modelSine = createModel<Sine, SineWidget>("Bogaudio-Sine", "SINE", "Compact oscillator with phase offset", "Oscillator", "Polyphonic");
diff --git a/src/Sine.hpp b/src/Sine.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "vco_base.hpp"
+
+extern Model* modelSine;
+
+namespace bogaudio {
+
+struct Sine : VCOBase {
+ enum ParamsIds {
+ FREQUENCY_PARAM,
+ SLOW_PARAM,
+ FM_DEPTH_PARAM,
+ PHASE_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ PITCH_INPUT,
+ FM_INPUT,
+ PHASE_INPUT,
+ SYNC_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum Wave {
+ SINE_WAVE,
+ TRIANGLE_WAVE,
+ SAW_WAVE,
+ SQUARE_WAVE,
+ PULSE_25_WAVE,
+ PULSE_10_WAVE
+ };
+
+ Wave _wave = SINE_WAVE;
+
+ Sine()
+ : VCOBase(
+ FREQUENCY_PARAM,
+ -1,
+ PITCH_INPUT,
+ SYNC_INPUT,
+ FM_INPUT
+ )
+ {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
+ configParam<VCOFrequencyParamQuantity>(FREQUENCY_PARAM, -3.0f, 6.0f, 0.0f, "Frequency", " Hz");
+ configParam(SLOW_PARAM, 0.0f, 1.0f, 0.0f, "Slow mode");
+ configParam(FM_DEPTH_PARAM, 0.0f, 1.0f, 0.0f, "FM depth", "%", 0.0f, 100.0f);
+ configParam(PHASE_PARAM, -1.0f, 1.0f, 0.0f, "Phase offset", "ยบ", 0.0f, 180.0f);
+
+ }
+
+ json_t* dataToJson() override;
+ void dataFromJson(json_t* root) override;
+ bool active() override;
+ void modulate() override;
+ void modulateChannel(int c) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio
diff --git a/src/VCO.cpp b/src/VCO.cpp
@@ -1,93 +1,5 @@
#include "VCO.hpp"
-#include "dsp/pitch.hpp"
-
-#define POLY_INPUT "poly_input"
-
-float VCO::VCOFrequencyParamQuantity::offset() {
- VCO* vco = dynamic_cast<VCO*>(module);
- return vco->_slowMode ? vco->slowModeOffset : 0.0f;
-}
-
-float VCO::VCOFrequencyParamQuantity::getDisplayValue() {
- float v = getValue();
- if (!module) {
- return v;
- }
-
- VCO* vco = dynamic_cast<VCO*>(module);
- return vco->_linearMode ? (vco->_slowMode ? v : v * 1000.0f) : FrequencyParamQuantity::getDisplayValue();
-}
-
-void VCO::VCOFrequencyParamQuantity::setDisplayValue(float v) {
- if (!module) {
- return;
- }
-
- VCO* vco = dynamic_cast<VCO*>(module);
- if (vco->_linearMode) {
- if (vco->_slowMode) {
- setValue(v / 1000.0f);
- }
- else {
- setValue(v);
- }
- }
- else {
- FrequencyParamQuantity::setDisplayValue(v);
- }
-}
-
-void VCO::Engine::reset() {
- syncTrigger.reset();
-}
-
-void VCO::Engine::sampleRateChange(float sampleRate) {
- phasor.setSampleRate(sampleRate);
- square.setSampleRate(sampleRate);
- saw.setSampleRate(sampleRate);
- squareDecimator.setParams(sampleRate, oversample);
- sawDecimator.setParams(sampleRate, oversample);
- triangleDecimator.setParams(sampleRate, oversample);
- squarePulseWidthSL.setParams(sampleRate, 0.1f, 2.0f);
-}
-
-void VCO::Engine::setFrequency(float f) {
- if (frequency != f && f < 0.475f * phasor._sampleRate) {
- frequency = f;
- phasor.setFrequency(frequency / (float)oversample);
- square.setFrequency(frequency);
- saw.setFrequency(frequency);
- }
-}
-
-void VCO::reset() {
- for (int c = 0; c < _channels; ++c) {
- _engines[c]->reset();
- }
-}
-
-void VCO::sampleRateChange() {
- float sampleRate = APP->engine->getSampleRate();
- _oversampleThreshold = 0.06f * sampleRate;
-
- for (int c = 0; c < _channels; ++c) {
- _engines[c]->sampleRateChange(sampleRate);
- }
-}
-
-json_t* VCO::dataToJson() {
- json_t* root = json_object();
- json_object_set_new(root, POLY_INPUT, json_integer(_polyInputID));
- return root;
-}
-
-void VCO::dataFromJson(json_t* root) {
- json_t* p = json_object_get(root, POLY_INPUT);
- if (p) {
- _polyInputID = json_integer_value(p);
- }
-}
bool VCO::active() {
return (
@@ -98,24 +10,6 @@ bool VCO::active() {
);
}
-int VCO::channels() {
- return std::max(1, _polyInputID == FM_INPUT ? inputs[FM_INPUT].getChannels() : inputs[PITCH_INPUT].getChannels());
-}
-
-void VCO::addChannel(int c) {
- _engines[c] = new Engine();
- _engines[c]->reset();
- _engines[c]->sampleRateChange(APP->engine->getSampleRate());
- if (c > 0) {
- _engines[c]->phasor.syncPhase(_engines[0]->phasor);
- }
-}
-
-void VCO::removeChannel(int c) {
- delete _engines[c];
- _engines[c] = NULL;
-}
-
void VCO::modulate() {
_slowMode = params[SLOW_PARAM].getValue() > 0.5f;
_linearMode = params[LINEAR_PARAM].getValue() > 0.5f;
@@ -124,124 +18,38 @@ void VCO::modulate() {
}
void VCO::modulateChannel(int c) {
+ VCOBase::modulateChannel(c);
Engine& e = *_engines[c];
- e.baseVOct = params[FREQUENCY_PARAM].getValue();
- e.baseVOct += params[FINE_PARAM].getValue() / 12.0f;
- if (inputs[PITCH_INPUT].isConnected()) {
- e.baseVOct += clamp(inputs[PITCH_INPUT].getVoltage(c), -5.0f, 5.0f);
- }
- if (_linearMode) {
- if (_slowMode) {
- e.baseHz = e.baseVOct;
- }
- else {
- e.baseHz = e.baseVOct * 1000.0f;
- }
- }
- else {
- if (_slowMode) {
- e.baseVOct += slowModeOffset;
- }
- e.baseHz = cvToFrequency(e.baseVOct);
- }
+ e.squareActive = outputs[SQUARE_OUTPUT].isConnected();
+ e.sawActive = outputs[SAW_OUTPUT].isConnected();
+ e.triangleActive = outputs[TRIANGLE_OUTPUT].isConnected();
+ e.sineActive = outputs[SINE_OUTPUT].isConnected();
- float pw = params[PW_PARAM].getValue();
- if (inputs[PW_INPUT].isConnected()) {
- pw *= clamp(inputs[PW_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
+ if (e.squareActive) {
+ float pw = params[PW_PARAM].getValue();
+ if (inputs[PW_INPUT].isConnected()) {
+ pw *= clamp(inputs[PW_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
+ }
+ pw *= 1.0f - 2.0f * e.square.minPulseWidth;
+ pw *= 0.5f;
+ pw += 0.5f;
+ e.square.setPulseWidth(e.squarePulseWidthSL.next(pw));
}
- pw *= 1.0f - 2.0f * e.square.minPulseWidth;
- pw *= 0.5f;
- pw += 0.5f;
- e.square.setPulseWidth(e.squarePulseWidthSL.next(pw));
}
void VCO::processChannel(const ProcessArgs& args, int c) {
+ VCOBase::processChannel(args, c);
Engine& e = *_engines[c];
- if (e.syncTrigger.next(inputs[SYNC_INPUT].getPolyVoltage(c))) {
- e.phasor.resetPhase();
- }
-
- float frequency = e.baseHz;
- Phasor::phase_delta_t phaseOffset = 0;
- if (inputs[FM_INPUT].isConnected() && _fmDepth > 0.01f) {
- float fm = inputs[FM_INPUT].getPolyVoltage(c) * _fmDepth;
- if (_fmLinearMode) {
- phaseOffset = Phasor::radiansToPhase(2.0f * fm);
- }
- else {
- frequency = cvToFrequency(e.baseVOct + fm);
- }
- }
- e.setFrequency(frequency);
-
- const float oversampleWidth = 100.0f;
- float mix, oMix;
- if (frequency > _oversampleThreshold) {
- if (frequency > _oversampleThreshold + oversampleWidth) {
- mix = 0.0f;
- oMix = 1.0f;
- }
- else {
- oMix = (frequency - _oversampleThreshold) / oversampleWidth;
- mix = 1.0f - oMix;
- }
- }
- else {
- mix = 1.0f;
- oMix = 0.0f;
- }
-
- float squareOut = 0.0f;
- float sawOut = 0.0f;
- float triangleOut = 0.0f;
- if (oMix > 0.0f) {
- for (int i = 0; i < Engine::oversample; ++i) {
- e.phasor.advancePhase();
- if (outputs[SQUARE_OUTPUT].isConnected()) {
- e.squareBuffer[i] = e.square.nextFromPhasor(e.phasor, phaseOffset);
- }
- if (outputs[SAW_OUTPUT].isConnected()) {
- e.sawBuffer[i] = e.saw.nextFromPhasor(e.phasor, phaseOffset);
- }
- if (outputs[TRIANGLE_OUTPUT].isConnected()) {
- e.triangleBuffer[i] = e.triangle.nextFromPhasor(e.phasor, phaseOffset);
- }
- }
- if (outputs[SQUARE_OUTPUT].isConnected()) {
- squareOut += oMix * amplitude * e.squareDecimator.next(e.squareBuffer);
- }
- if (outputs[SAW_OUTPUT].isConnected()) {
- sawOut += oMix * amplitude * e.sawDecimator.next(e.sawBuffer);
- }
- if (outputs[TRIANGLE_OUTPUT].isConnected()) {
- triangleOut += oMix * amplitude * e.triangleDecimator.next(e.triangleBuffer);
- }
- }
- else {
- e.phasor.advancePhase(Engine::oversample);
- }
- if (mix > 0.0f) {
- if (outputs[SQUARE_OUTPUT].isConnected()) {
- squareOut += mix * amplitude * e.square.nextFromPhasor(e.phasor, phaseOffset);
- }
- if (outputs[SAW_OUTPUT].isConnected()) {
- sawOut += mix * amplitude * e.saw.nextFromPhasor(e.phasor, phaseOffset);
- }
- if (outputs[TRIANGLE_OUTPUT].isConnected()) {
- triangleOut += mix * amplitude * e.triangle.nextFromPhasor(e.phasor, phaseOffset);
- }
- }
-
outputs[SQUARE_OUTPUT].setChannels(_channels);
- outputs[SQUARE_OUTPUT].setVoltage(squareOut, c);
+ outputs[SQUARE_OUTPUT].setVoltage(e.squareOut, c);
outputs[SAW_OUTPUT].setChannels(_channels);
- outputs[SAW_OUTPUT].setVoltage(sawOut, c);
+ outputs[SAW_OUTPUT].setVoltage(e.sawOut, c);
outputs[TRIANGLE_OUTPUT].setChannels(_channels);
- outputs[TRIANGLE_OUTPUT].setVoltage(triangleOut, c);
+ outputs[TRIANGLE_OUTPUT].setVoltage(e.triangleOut, c);
outputs[SINE_OUTPUT].setChannels(_channels);
- outputs[SINE_OUTPUT].setVoltage(outputs[SINE_OUTPUT].isConnected() ? (amplitude * e.sine.nextFromPhasor(e.phasor, phaseOffset)) : 0.0f, c);
+ outputs[SINE_OUTPUT].setVoltage(e.sineOut, c);
}
struct VCOWidget : ModuleWidget {
diff --git a/src/VCO.hpp b/src/VCO.hpp
@@ -1,17 +1,13 @@
#pragma once
#include "bogaudio.hpp"
-#include "dsp/filter.hpp"
-#include "dsp/oscillator.hpp"
-#include "dsp/signal.hpp"
-
-using namespace bogaudio::dsp;
+#include "vco_base.hpp"
extern Model* modelVCO;
namespace bogaudio {
-struct VCO : BGModule {
+struct VCO : VCOBase {
enum ParamsIds {
FREQUENCY_PARAM,
FINE_PARAM,
@@ -39,53 +35,15 @@ struct VCO : BGModule {
NUM_OUTPUTS
};
- struct Engine {
- static constexpr int oversample = 8;
-
- float frequency = 0.0f;
- float baseVOct = 0.0f;
- float baseHz = 0.0f;
-
- Phasor phasor;
- BandLimitedSquareOscillator square;
- BandLimitedSawOscillator saw;
- TriangleOscillator triangle;
- SineTableOscillator sine;
- CICDecimator squareDecimator;
- CICDecimator sawDecimator;
- CICDecimator triangleDecimator;
- float squareBuffer[oversample];
- float sawBuffer[oversample];
- float triangleBuffer[oversample];
- PositiveZeroCrossing syncTrigger;
- bogaudio::dsp::SlewLimiter squarePulseWidthSL;
-
- Engine() {
- saw.setQuality(12);
- square.setQuality(12);
- }
- void reset();
- void sampleRateChange(float sampleRate);
- void setFrequency(float frequency);
- };
-
- const float amplitude = 5.0f;
- const float slowModeOffset = -7.0f;
- Engine* _engines[maxChannels] {};
- float _oversampleThreshold = 0.0f;
- bool _slowMode = false;
- bool _linearMode = false;
- float _fmDepth = 0.0f;
- bool _fmLinearMode = false;
- int _polyInputID = PITCH_INPUT;
-
- struct VCOFrequencyParamQuantity : FrequencyParamQuantity {
- float offset() override;
- float getDisplayValue() override;
- void setDisplayValue(float v) override;
- };
-
- VCO() {
+ VCO()
+ : VCOBase(
+ FREQUENCY_PARAM,
+ FINE_PARAM,
+ PITCH_INPUT,
+ SYNC_INPUT,
+ FM_INPUT
+ )
+ {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
configParam<VCOFrequencyParamQuantity>(FREQUENCY_PARAM, -3.0f, 6.0f, 0.0f, "Frequency", " Hz");
configParam(FINE_PARAM, -1.0f, 1.0f, 0.0f, "Fine tune", " cents", 0.0f, 100.0f);
@@ -96,14 +54,7 @@ struct VCO : BGModule {
configParam(LINEAR_PARAM, 0.0f, 1.0f, 0.0f, "Linear Freq");
}
- void reset() override;
- void sampleRateChange() override;
- 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 processChannel(const ProcessArgs& args, int c) override;
diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp
@@ -29,6 +29,7 @@
#include "LFO.hpp"
#include "LLFO.hpp"
#include "Lmtr.hpp"
+#include "LVCO.hpp"
#include "Manual.hpp"
#include "Matrix88.hpp"
#include "Mix1.hpp"
@@ -47,6 +48,7 @@
#include "SampleHold.hpp"
#include "Shaper.hpp"
#include "ShaperPlus.hpp"
+#include "Sine.hpp"
#include "Slew.hpp"
#include "Stack.hpp"
#include "Sums.hpp"
@@ -75,6 +77,8 @@ void init(rack::Plugin *p) {
pluginInstance = p;
p->addModel(modelVCO);
+ p->addModel(modelLVCO);
+ p->addModel(modelSine);
p->addModel(modelXCO);
p->addModel(modelAdditator);
p->addModel(modelFMOp);
diff --git a/src/vco_base.cpp b/src/vco_base.cpp
@@ -0,0 +1,214 @@
+#include "vco_base.hpp"
+#include "dsp/pitch.hpp"
+
+#define POLY_INPUT "poly_input"
+
+float VCOBase::VCOFrequencyParamQuantity::offset() {
+ VCOBase* vco = dynamic_cast<VCOBase*>(module);
+ return vco->_slowMode ? vco->slowModeOffset : 0.0f;
+}
+
+float VCOBase::VCOFrequencyParamQuantity::getDisplayValue() {
+ float v = getValue();
+ if (!module) {
+ return v;
+ }
+
+ VCOBase* vco = dynamic_cast<VCOBase*>(module);
+ return vco->_linearMode ? (vco->_slowMode ? v : v * 1000.0f) : FrequencyParamQuantity::getDisplayValue();
+}
+
+void VCOBase::VCOFrequencyParamQuantity::setDisplayValue(float v) {
+ if (!module) {
+ return;
+ }
+
+ VCOBase* vco = dynamic_cast<VCOBase*>(module);
+ if (vco->_linearMode) {
+ if (vco->_slowMode) {
+ setValue(v / 1000.0f);
+ }
+ else {
+ setValue(v);
+ }
+ }
+ else {
+ FrequencyParamQuantity::setDisplayValue(v);
+ }
+}
+
+void VCOBase::Engine::reset() {
+ syncTrigger.reset();
+}
+
+void VCOBase::Engine::sampleRateChange(float sampleRate) {
+ phasor.setSampleRate(sampleRate);
+ square.setSampleRate(sampleRate);
+ saw.setSampleRate(sampleRate);
+ squareDecimator.setParams(sampleRate, oversample);
+ sawDecimator.setParams(sampleRate, oversample);
+ triangleDecimator.setParams(sampleRate, oversample);
+ squarePulseWidthSL.setParams(sampleRate, 0.1f, 2.0f);
+}
+
+void VCOBase::Engine::setFrequency(float f) {
+ if (frequency != f && f < 0.475f * phasor._sampleRate) {
+ frequency = f;
+ phasor.setFrequency(frequency / (float)oversample);
+ square.setFrequency(frequency);
+ saw.setFrequency(frequency);
+ }
+}
+
+void VCOBase::reset() {
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->reset();
+ }
+}
+
+void VCOBase::sampleRateChange() {
+ float sampleRate = APP->engine->getSampleRate();
+ _oversampleThreshold = 0.06f * sampleRate;
+
+ for (int c = 0; c < _channels; ++c) {
+ _engines[c]->sampleRateChange(sampleRate);
+ }
+}
+
+json_t* VCOBase::dataToJson() {
+ json_t* root = json_object();
+ json_object_set_new(root, POLY_INPUT, json_integer(_polyInputID));
+ return root;
+}
+
+void VCOBase::dataFromJson(json_t* root) {
+ json_t* p = json_object_get(root, POLY_INPUT);
+ if (p) {
+ _polyInputID = json_integer_value(p);
+ }
+}
+
+int VCOBase::channels() {
+ return std::max(1, _polyInputID == _fmInputID ? inputs[_fmInputID].getChannels() : inputs[_pitchInputID].getChannels());
+}
+
+void VCOBase::addChannel(int c) {
+ _engines[c] = new Engine();
+ _engines[c]->reset();
+ _engines[c]->sampleRateChange(APP->engine->getSampleRate());
+ if (c > 0) {
+ _engines[c]->phasor.syncPhase(_engines[0]->phasor);
+ }
+}
+
+void VCOBase::removeChannel(int c) {
+ delete _engines[c];
+ _engines[c] = NULL;
+}
+
+void VCOBase::modulateChannel(int c) {
+ Engine& e = *_engines[c];
+
+ e.baseVOct = params[_frequencyParamID].getValue();
+ if (_fineFrequencyParamID >= 0) {
+ e.baseVOct += params[_fineFrequencyParamID].getValue() / 12.0f;
+ }
+ if (inputs[_pitchInputID].isConnected()) {
+ e.baseVOct += clamp(inputs[_pitchInputID].getVoltage(c), -5.0f, 5.0f);
+ }
+ if (_linearMode) {
+ if (_slowMode) {
+ e.baseHz = e.baseVOct;
+ }
+ else {
+ e.baseHz = e.baseVOct * 1000.0f;
+ }
+ }
+ else {
+ if (_slowMode) {
+ e.baseVOct += slowModeOffset;
+ }
+ e.baseHz = cvToFrequency(e.baseVOct);
+ }
+}
+
+void VCOBase::processChannel(const ProcessArgs& args, int c) {
+ Engine& e = *_engines[c];
+
+ if (e.syncTrigger.next(inputs[_syncInputID].getPolyVoltage(c))) {
+ e.phasor.resetPhase();
+ }
+
+ float frequency = e.baseHz;
+ Phasor::phase_delta_t phaseOffset = 0;
+ if (inputs[_fmInputID].isConnected() && _fmDepth > 0.01f) {
+ float fm = inputs[_fmInputID].getPolyVoltage(c) * _fmDepth;
+ if (_fmLinearMode) {
+ phaseOffset = Phasor::radiansToPhase(2.0f * fm);
+ }
+ else {
+ frequency = cvToFrequency(e.baseVOct + fm);
+ }
+ }
+ e.setFrequency(frequency);
+
+ const float oversampleWidth = 100.0f;
+ float mix, oMix;
+ if (frequency > _oversampleThreshold) {
+ if (frequency > _oversampleThreshold + oversampleWidth) {
+ mix = 0.0f;
+ oMix = 1.0f;
+ }
+ else {
+ oMix = (frequency - _oversampleThreshold) / oversampleWidth;
+ mix = 1.0f - oMix;
+ }
+ }
+ else {
+ mix = 1.0f;
+ oMix = 0.0f;
+ }
+
+ e.squareOut = 0.0f;
+ e.sawOut = 0.0f;
+ e.triangleOut = 0.0f;
+ if (oMix > 0.0f) {
+ for (int i = 0; i < Engine::oversample; ++i) {
+ e.phasor.advancePhase();
+ if (e.squareActive) {
+ e.squareBuffer[i] = e.square.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ if (e.sawActive) {
+ e.sawBuffer[i] = e.saw.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ if (e.triangleActive) {
+ e.triangleBuffer[i] = e.triangle.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ }
+ if (e.squareActive) {
+ e.squareOut += oMix * amplitude * e.squareDecimator.next(e.squareBuffer);
+ }
+ if (e.sawActive) {
+ e.sawOut += oMix * amplitude * e.sawDecimator.next(e.sawBuffer);
+ }
+ if (e.triangleActive) {
+ e.triangleOut += oMix * amplitude * e.triangleDecimator.next(e.triangleBuffer);
+ }
+ }
+ else {
+ e.phasor.advancePhase(Engine::oversample);
+ }
+ if (mix > 0.0f) {
+ if (e.squareActive) {
+ e.squareOut += mix * amplitude * e.square.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ if (e.sawActive) {
+ e.sawOut += mix * amplitude * e.saw.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ if (e.triangleActive) {
+ e.triangleOut += mix * amplitude * e.triangle.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
+ }
+ }
+
+ e.sineOut = e.sineActive ? (amplitude * e.sine.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset)) : 0.0f;
+}
diff --git a/src/vco_base.hpp b/src/vco_base.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "bogaudio.hpp"
+#include "dsp/filter.hpp"
+#include "dsp/oscillator.hpp"
+#include "dsp/signal.hpp"
+
+using namespace bogaudio::dsp;
+
+namespace bogaudio {
+
+struct VCOBase : BGModule {
+ struct Engine {
+ static constexpr int oversample = 8;
+
+ float frequency = 0.0f;
+ float baseVOct = 0.0f;
+ float baseHz = 0.0f;
+
+ Phasor phasor;
+ BandLimitedSquareOscillator square;
+ BandLimitedSawOscillator saw;
+ TriangleOscillator triangle;
+ SineTableOscillator sine;
+ CICDecimator squareDecimator;
+ CICDecimator sawDecimator;
+ CICDecimator triangleDecimator;
+ float squareBuffer[oversample];
+ float sawBuffer[oversample];
+ float triangleBuffer[oversample];
+ PositiveZeroCrossing syncTrigger;
+ bogaudio::dsp::SlewLimiter squarePulseWidthSL;
+ bool squareActive = false;
+ bool sawActive = false;
+ bool triangleActive = false;
+ bool sineActive = false;
+ float squareOut = 0.0f;
+ float sawOut = 0.0f;
+ float triangleOut = 0.0f;
+ float sineOut = 0.0f;
+ Phasor::phase_delta_t additionalPhaseOffset = 0;
+
+ Engine() {
+ saw.setQuality(12);
+ square.setQuality(12);
+ }
+ void reset();
+ void sampleRateChange(float sampleRate);
+ void setFrequency(float frequency);
+ };
+
+ const float amplitude = 5.0f;
+ const float slowModeOffset = -7.0f;
+ Engine* _engines[maxChannels] {};
+ float _oversampleThreshold = 0.0f;
+ bool _slowMode = false;
+ bool _linearMode = false;
+ float _fmDepth = 0.0f;
+ bool _fmLinearMode = false;
+ int _frequencyParamID;
+ int _fineFrequencyParamID;
+ int _pitchInputID;
+ int _syncInputID;
+ int _fmInputID;
+ int _polyInputID;
+
+ struct VCOFrequencyParamQuantity : FrequencyParamQuantity {
+ float offset() override;
+ float getDisplayValue() override;
+ void setDisplayValue(float v) override;
+ };
+
+ VCOBase(int fpID, int ffpID, int piID, int siID, int fiID)
+ : _frequencyParamID(fpID)
+ , _fineFrequencyParamID(ffpID)
+ , _pitchInputID(piID)
+ , _syncInputID(siID)
+ , _fmInputID(fiID)
+ , _polyInputID(piID)
+ {}
+
+ void reset() override;
+ void sampleRateChange() override;
+ json_t* dataToJson() override;
+ void dataFromJson(json_t* root) override;
+ int channels() override;
+ void addChannel(int c) override;
+ void removeChannel(int c) override;
+ void modulateChannel(int c) override;
+ void processChannel(const ProcessArgs& args, int c) override;
+};
+
+} // namespace bogaudio