BogaudioModules

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

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:
Mplugin.json | 18++++++++++++++++++
Ares-src/LVCO-src.svg | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/Sine-src.svg | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/LVCO.svg | 0
Ares/Sine.svg | 0
Asrc/LVCO.cpp | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/LVCO.hpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Sine.cpp | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Sine.hpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/VCO.cpp | 230+++++++------------------------------------------------------------------------
Msrc/VCO.hpp | 71+++++++++++------------------------------------------------------------
Msrc/bogaudio.cpp | 4++++
Asrc/vco_base.cpp | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/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