BogaudioModules

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

commit 36b7f203c8538b4be4a76e5144ae2a688fecbe26
parent 0d24f03ee4834de6c04365e44a5f61abeaea89a3
Author: Matt Demanett <matt@demanett.net>
Date:   Sun,  5 Apr 2020 22:10:53 -0400

PGMR, PGMRX: four-step programmer and sequencer, with chainable expander. #100

Diffstat:
Mplugin.json | 19+++++++++++++++++++
Ares-src/Pgmr-src.svg | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares-src/PgmrX-src.svg | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ares/Pgmr.svg | 0
Ares/PgmrX.svg | 0
Msrc/AddrSeq.cpp | 66++++++++++++++----------------------------------------------------
Msrc/AddrSeq.hpp | 20++++++--------------
Msrc/EightOne.cpp | 28+++++++++++++++++-----------
Msrc/EightOne.hpp | 7++++++-
Msrc/Mix4.cpp | 6+++---
Msrc/Mix4.hpp | 4++--
Msrc/Mix8.cpp | 6+++---
Msrc/Mix8.hpp | 4++--
Msrc/OneEight.cpp | 26++++++++++++++++----------
Msrc/OneEight.hpp | 7++++++-
Asrc/Pgmr.cpp | 511+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Pgmr.hpp | 303+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/TestExpander.cpp | 8++++----
Msrc/TestExpander.hpp | 4++--
Msrc/addressable_sequence.cpp | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/addressable_sequence.hpp | 50++++++++++++++++++++++++++++++++++++--------------
Msrc/bogaudio.cpp | 3+++
Msrc/expanders.hpp | 80+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
23 files changed, 1581 insertions(+), 164 deletions(-)

diff --git a/plugin.json b/plugin.json @@ -518,6 +518,25 @@ ] }, { + "slug": "Bogaudio-Pgmr", + "name": "PGMR", + "description": "4-step programmer and sequencer", + "tags": [ + "Sequencer", + "Polyphonic" + ] + }, + { + "slug": "Bogaudio-PgmrX", + "name": "PGMRX", + "description": "4-step chainable expander for PGMR", + "tags": [ + "Sequencer", + "Expander", + "Polyphonic" + ] + }, + { "slug": "Bogaudio-Analyzer", "name": "ANALYZER", "description": "4-channel spectrum analyzer", diff --git a/res-src/Pgmr-src.svg b/res-src/Pgmr-src.svg @@ -0,0 +1,276 @@ +<svg + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="225" + height="380" + viewBox="0 0 225 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="knob26" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <polyline points="-5,0 5,0" stroke-width="1" stroke="#00f" /> + <polyline points="0,-5 0,5" stroke-width="1" stroke="#00f" /> + <circle cx="0" cy="0" r="12.5" stroke-width="1" stroke="#00f" fill="none" /> + </g> + </symbol> + + <symbol id="knobguide-pgmr" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-240) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-225) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-195) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-165) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-135) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-105) translate(15 0)" /> + + <text font-size="5pt" transform="rotate(-90) translate(15 0) rotate(90) translate(-1.9 0)">0</text> + + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-75) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-45) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-15) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(15) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(45) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(60) translate(15 0)" /> + + <text font-size="9pt" transform="rotate(-225) translate(20 0) rotate(225) translate(-1 8.8)">-</text> + <text font-size="7pt" transform="rotate(45) translate(20 0) rotate(-45) translate(-3.9 8.5)">+</text> + </g> + </symbol> + + <symbol id="button" viewBox="0 0 18px 18px"> + <g transform="translate(9 9)"> + <circle cx="0" cy="0" r="8.5" stroke-width="1" stroke="#00f" fill="#f00" /> + </g> + </symbol> + + <symbol id="light-small" viewBox="0 0 6.4px 6.4px"> + <rect width="6.4" height="6.4" fill="#0f0" /> + </symbol> + + <symbol id="button-small" viewBox="0 0 9px 9px"> + <g transform="translate(4.5 4.5)"> + <circle r="4" stroke-width="1" stroke="#00f" fill="#f00" /> + </g> + </symbol> + + <symbol id="input" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#0f0" fill="none" /> + </g> + </symbol> + + <symbol id="output" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#f00" fill="#f00" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#f00" fill="none" /> + </g> + </symbol> + </defs> + + <rect width="100%" height="100%" fill="#ddd" /> + <polyline points="1,1 224,1 224,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" /> + <polyline points="0.5,0.5 224.5,0.5 224.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" /> + <polyline points="0,0 225,0 225,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" /> + + <!-- <rect width="80" height="20" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <rect width="80" height="20" fill="#0f0" transform="translate(145 0)" /> --> + <text class="title" x="82" y="19" font-size="12pt" letter-spacing="4px">PGMR</text> + <g transform="translate(72.5 374)"> + <text class="brand" font-size="8pt" letter-spacing="2px">BOGAUDIO</text> + <rect width="3.0" height="3" fill="#ddd" transform="translate(24 -5)" /> + </g> + + <!-- <polyline points="0,0 225,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 30)" /> --> + <!-- <polyline points="0,0 225,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 135.5)" /> --> + <!-- <polyline points="0,0 225,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 360)" /> --> + + <g transform="translate(0 56)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="155" rx="5" fill="#bbb" transform="translate(0 -10)" /> + <use id="A_OUTPUT" xlink:href="#output" transform="translate(5 3)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(8.5 0)">OUT</text> + <text font-size="5pt" letter-spacing="2px" transform="translate(15 35)">A</text> + <use id="B_OUTPUT" xlink:href="#output" transform="translate(5 38)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(15 70)">B</text> + <use id="C_OUTPUT" xlink:href="#output" transform="translate(5 73)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(15 105)">C</text> + <use id="D_OUTPUT" xlink:href="#output" transform="translate(5 107)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(15 140)">D</text> + </g> + </g> + + <g transform="translate(0 224)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 92)" /> + <rect width="34" height="102" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="CLOCK_INPUT" xlink:href="#input" transform="translate(5 2)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(2.5 34)">CLOCK</text> + <text font-size="5pt" letter-spacing="1px" transform="translate(5 46.5)">FWD</text> + <use id="DIRECTION_PARAM" xlink:href="#button-small" transform="translate(22.5 39.7)" /> + <text font-size="5pt" letter-spacing="0.1px" transform="translate(2 60)">S.O.C.</text> + <use id="SELECT_ON_CLOCK_PARAM" xlink:href="#button-small" transform="translate(22.5 53.2)" /> + <use id="SELECT_INPUT" xlink:href="#input" transform="translate(5 66)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 98)">SELECT</text> + </g> + <g transform="translate(5.5 105)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 -3)" /> + <rect width="34" height="30" rx="5" fill="#bbb" /> + <use id="SELECT_ALL_OUTPUT" xlink:href="#output" transform="translate(5 1)" /> + </g> + </g> + + <g transform="translate(45 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.5 -1)">1</text> + <use id="CVA1_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB1_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC1_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD1_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT1_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT1_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT1_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT1_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(90 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.5 -1)">2</text> + <use id="CVA2_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB2_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC2_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD2_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT2_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT2_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT2_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT2_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(135 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.5 -1)">3</text> + <use id="CVA3_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB3_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC3_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD3_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT3_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT3_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT3_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT3_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(180 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.5 -1)">4</text> + <use id="CVA4_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB4_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC4_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD4_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT4_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT4_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT4_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT4_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g>Z + </g> + <!-- <polyline points="0,0 225,0" stroke="#0f0" stroke-width="1" fill="none" transform="translate(0 315)" /> --> +</svg> diff --git a/res-src/PgmrX-src.svg b/res-src/PgmrX-src.svg @@ -0,0 +1,230 @@ +<svg + version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="180" + height="380" + viewBox="0 0 180 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="knob26" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <polyline points="-5,0 5,0" stroke-width="1" stroke="#00f" /> + <polyline points="0,-5 0,5" stroke-width="1" stroke="#00f" /> + <circle cx="0" cy="0" r="12.5" stroke-width="1" stroke="#00f" fill="none" /> + </g> + </symbol> + + <symbol id="knobguide-pgmr" viewBox="0 0 45px 45px"> + <g transform="translate(22.5 22.5)"> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-240) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-225) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-210) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-195) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-180) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-165) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-150) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-135) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-120) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-105) translate(15 0)" /> + + <text font-size="5pt" transform="rotate(-90) translate(15 0) rotate(90) translate(-1.9 0)">0</text> + + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-75) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-60) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-45) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(-30) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(-15) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(0) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(15) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(30) translate(15 0)" /> + <polyline points="0,0 2.5,0" stroke-width="0.3" stroke="#333" transform="rotate(45) translate(15 0)" /> + <polyline points="0,0 3.5,0" stroke-width="1.0" stroke="#333" transform="rotate(60) translate(15 0)" /> + + <text font-size="9pt" transform="rotate(-225) translate(20 0) rotate(225) translate(-1 8.8)">-</text> + <text font-size="7pt" transform="rotate(45) translate(20 0) rotate(-45) translate(-3.9 8.5)">+</text> + </g> + </symbol> + + <symbol id="button" viewBox="0 0 18px 18px"> + <g transform="translate(9 9)"> + <circle cx="0" cy="0" r="8.5" stroke-width="1" stroke="#00f" fill="#f00" /> + </g> + </symbol> + + <symbol id="light-small" viewBox="0 0 6.4px 6.4px"> + <rect width="6.4" height="6.4" fill="#0f0" /> + </symbol> + + <symbol id="input" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#0f0" fill="#0f0" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#0f0" fill="none" /> + </g> + </symbol> + + <symbol id="output" viewBox="0 0 24px 24px"> + <g transform="translate(12 12)"> + <circle cx="0" cy="0" r="5" stroke-width="1" stroke="#f00" fill="#f00" /> + <circle cx="0" cy="0" r="10.5" stroke-width="3" stroke="#f00" fill="none" /> + </g> + </symbol> + </defs> + + <rect width="100%" height="100%" fill="#ddd" /> + <polyline points="1,1 179,1 179,379 1,379 1,1" stroke="#e4e4e4" stroke-width="0.5" fill="none" /> + <polyline points="0.5,0.5 179.5,0.5 179.5,379.5 0.5,379.5 0.5,0.5" stroke="#ebebeb" stroke-width="0.8" fill="none" /> + <polyline points="0,0 180,0 180,380 0,380 0,0" stroke="#f2f2f2" stroke-width="1" fill="none" /> + + <!-- <rect width="50" height="20" fill="#0f0" transform="translate(0 0)" /> --> + <!-- <rect width="50" height="20" fill="#0f0" transform="translate(130 0)" /> --> + <text class="title" x="52" y="19" font-size="12pt" letter-spacing="4px">PGMRX</text> + <g transform="translate(50 374)"> + <text class="brand" font-size="8pt" letter-spacing="2px">BOGAUDIO</text> + <rect width="3.0" height="3" fill="#ddd" transform="translate(24 -5)" /> + </g> + + <g transform="translate(0 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(18 -1)">+1</text> + <use id="CVA1_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB1_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC1_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD1_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT1_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT1_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT1_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT1_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(45 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(18 -1)">+2</text> + <use id="CVA2_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB2_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC2_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD2_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT2_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT2_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT2_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT2_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(90 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(18 -1)">+3</text> + <use id="CVA3_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB3_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC3_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD3_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT3_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT3_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT3_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT3_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> + + <g transform="translate(135 30)"> + <!-- <rect width="34" height="330" fill="#ccc" transform="translate(5.5 0)" /> --> + <text font-size="5pt" letter-spacing="2px" transform="translate(17.5 -1)">+4</text> + <use id="CVA4_PARAM" xlink:href="#knob26" transform="translate(0 1)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 1)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 48)">A</text> + <use id="CVB4_PARAM" xlink:href="#knob26" transform="translate(0 55)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 55)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 102)">B</text> + <use id="CVC4_PARAM" xlink:href="#knob26" transform="translate(0 109)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 109)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 156)">C</text> + <use id="CVD4_PARAM" xlink:href="#knob26" transform="translate(0 163)" /> + <use xlink:href="#knobguide-pgmr" transform="translate(0 163)" /> + <text font-size="5pt" letter-spacing="2px" transform="translate(20.3 210)">D</text> + <g transform="translate(0 223)"> + <g transform="translate(5.5 0)"> + <rect width="34" height="10" fill="#fafafa" transform="translate(0 63)" /> + <rect width="34" height="76" rx="5" fill="#fafafa" transform="translate(0 -3)" /> + <use id="SELECT4_LIGHT" xlink:href="#light-small" transform="translate(13.8 2)" /> + <use id="SELECT4_PARAM" xlink:href="#button" transform="translate(8 14)" /> + <use id="SELECT4_INPUT" xlink:href="#input" transform="translate(5 37)" /> + <text font-size="5pt" letter-spacing="1px" transform="translate(2.5 69)">SELECT</text> + </g> + <g transform="translate(5.5 73)"> + <rect width="34" height="10" fill="#bbb" transform="translate(0 0)" /> + <rect width="34" height="33" rx="5" fill="#bbb" /> + <use id="SELECT4_OUTPUT" xlink:href="#output" transform="translate(5 4)" /> + </g> + </g> + </g> +</svg> diff --git a/res/Pgmr.svg b/res/Pgmr.svg Binary files differ. diff --git a/res/PgmrX.svg b/res/PgmrX.svg Binary files differ. diff --git a/src/AddrSeq.cpp b/src/AddrSeq.cpp @@ -1,61 +1,18 @@ #include "AddrSeq.hpp" -#define RANGE_OFFSET "range_offset" -#define RANGE_SCALE "range_scale" - -float AddrSeq::OutputParamQuantity::getDisplayValue() { - float v = getValue(); - if (!module) { - return v; - } - - AddrSeq* m = dynamic_cast<AddrSeq*>(module); - v += m->_rangeOffset; - v *= m->_rangeScale; - return v; -} - -void AddrSeq::OutputParamQuantity::setDisplayValue(float v) { - if (!module) { - return; - } - - AddrSeq* m = dynamic_cast<AddrSeq*>(module); - v /= m->_rangeScale; - v -= m->_rangeOffset; - setValue(v); -} - -json_t* AddrSeq::dataToJson() { - json_t* root = AddressableSequenceModule::dataToJson(); - json_object_set_new(root, RANGE_OFFSET, json_real(_rangeOffset)); - json_object_set_new(root, RANGE_SCALE, json_real(_rangeScale)); - return root; -} - -void AddrSeq::dataFromJson(json_t* root) { - AddressableSequenceModule::dataFromJson(root); - - json_t* ro = json_object_get(root, RANGE_OFFSET); - if (ro) { - _rangeOffset = json_real_value(ro); - } - - json_t* rs = json_object_get(root, RANGE_SCALE); - if (rs) { - _rangeScale = json_real_value(rs); - } +void AddrSeq::processAlways(const ProcessArgs& args) { + std::fill(_lightSums, _lightSums + 8, 0.0f); } void AddrSeq::processChannel(const ProcessArgs& args, int c) { int step = nextStep( c, - inputs[RESET_INPUT], + &inputs[RESET_INPUT], inputs[CLOCK_INPUT], - params[STEPS_PARAM], + &params[STEPS_PARAM], params[DIRECTION_PARAM], - params[SELECT_PARAM], + &params[SELECT_PARAM], inputs[SELECT_INPUT] ); @@ -64,10 +21,15 @@ void AddrSeq::processChannel(const ProcessArgs& args, int c) { out *= _rangeScale; outputs[OUT_OUTPUT].setChannels(_channels); outputs[OUT_OUTPUT].setVoltage(out, c); - if (c == 0) { - for (int i = 0; i < 8; ++i) { - lights[OUT1_LIGHT + i].value = step == i; - } + + for (int i = 0; i < 8; ++i) { + _lightSums[i] += step == i; + } +} + +void AddrSeq::postProcessAlways(const ProcessArgs& args) { + for (int i = 0; i < 8; ++i) { + lights[OUT1_LIGHT + i].value = _lightSums[i] / (float)_channels; } } diff --git a/src/AddrSeq.hpp b/src/AddrSeq.hpp @@ -2,15 +2,12 @@ #include "bogaudio.hpp" #include "addressable_sequence.hpp" -#include "dsp/signal.hpp" - -using namespace bogaudio::dsp; extern Model* modelAddrSeq; namespace bogaudio { -struct AddrSeq : AddressableSequenceModule { +struct AddrSeq : OutputRangeAddressableSequenceModule { enum ParamsIds { STEPS_PARAM, DIRECTION_PARAM, @@ -50,15 +47,9 @@ struct AddrSeq : AddressableSequenceModule { NUM_LIGHTS }; - float _rangeOffset = 0.0f; - float _rangeScale = 10.0f; - - struct OutputParamQuantity : ParamQuantity { - float getDisplayValue() override; - void setDisplayValue(float v) override; - }; + float _lightSums[8] {}; - AddrSeq() : AddressableSequenceModule(CLOCK_INPUT, SELECT_INPUT) { + AddrSeq() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(STEPS_PARAM, 1.0f, 8.0f, 8.0f, "Steps"); configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Direction"); @@ -71,11 +62,12 @@ struct AddrSeq : AddressableSequenceModule { configParam<OutputParamQuantity>(OUT6_PARAM, -1.0f, 1.0f, 0.0f, "Step 6", " V"); configParam<OutputParamQuantity>(OUT7_PARAM, -1.0f, 1.0f, 0.0f, "Step 7", " V"); configParam<OutputParamQuantity>(OUT8_PARAM, -1.0f, 1.0f, 0.0f, "Step 8", " V"); + setInputIDs(CLOCK_INPUT, SELECT_INPUT); } - json_t* dataToJson() override; - void dataFromJson(json_t* root) override; + void processAlways(const ProcessArgs& args) override; void processChannel(const ProcessArgs& args, int c) override; + void postProcessAlways(const ProcessArgs& args) override; }; } // namespace bogaudio diff --git a/src/EightOne.cpp b/src/EightOne.cpp @@ -1,14 +1,18 @@ #include "EightOne.hpp" +void EightOne::processAlways(const ProcessArgs& args) { + std::fill(_lightSums, _lightSums + 8, 0.0f); +} + void EightOne::processChannel(const ProcessArgs& args, int c) { int step = nextStep( c, - inputs[RESET_INPUT], + &inputs[RESET_INPUT], inputs[CLOCK_INPUT], - params[STEPS_PARAM], + &params[STEPS_PARAM], params[DIRECTION_PARAM], - params[SELECT_PARAM], + &params[SELECT_PARAM], inputs[SELECT_INPUT] ); @@ -16,18 +20,20 @@ void EightOne::processChannel(const ProcessArgs& args, int c) { if (_channels > 1) { outputs[OUT_OUTPUT].setChannels(_channels); outputs[OUT_OUTPUT].setVoltage(in.getPolyVoltage(c), c); - if (c == 0) { - for (int i = 0; i < 8; ++i) { - lights[IN1_LIGHT + i].value = step == i; - } - } } else { outputs[OUT_OUTPUT].setChannels(in.getChannels()); outputs[OUT_OUTPUT].writeVoltages(in.getVoltages()); - for (int i = 0; i < 8; ++i) { - lights[IN1_LIGHT + i].value = step == i; - } + } + + for (int i = 0; i < 8; ++i) { + _lightSums[i] += step == i; + } +} + +void EightOne::postProcessAlways(const ProcessArgs& args) { + for (int i = 0; i < 8; ++i) { + lights[IN1_LIGHT + i].value = _lightSums[i] / (float)_channels; } } diff --git a/src/EightOne.hpp b/src/EightOne.hpp @@ -50,14 +50,19 @@ struct EightOne : AddressableSequenceModule { NUM_LIGHTS }; - EightOne() : AddressableSequenceModule(CLOCK_INPUT, SELECT_INPUT) { + float _lightSums[8] {}; + + EightOne() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(STEPS_PARAM, 1.0f, 8.0f, 8.0f, "Steps"); configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Direction"); configParam(SELECT_PARAM, 0.0f, 7.0f, 0.0f, "Select step"); + setInputIDs(CLOCK_INPUT, SELECT_INPUT); } + void processAlways(const ProcessArgs& args) override; void processChannel(const ProcessArgs& args, int c) override; + void postProcessAlways(const ProcessArgs& args) override; }; } // namespace bogaudio diff --git a/src/Mix4.cpp b/src/Mix4.cpp @@ -29,7 +29,7 @@ void Mix4::sampleRateChange() { void Mix4::processAll(const ProcessArgs& args) { Mix4ExpanderMessage* toExp = &_dummyExpanderMessage; Mix4ExpanderMessage* fromExp = &_dummyExpanderMessage; - if (connected()) { + if (expanderConnected()) { toExp = toExpander(); fromExp = fromExpander(); } @@ -82,7 +82,7 @@ void Mix4::processAll(const ProcessArgs& args) { float mono = 0.0f; float left = 0.0f; float right = 0.0f; - if (connected()) { + if (expanderConnected()) { mono += fromExp->returnA[0] + fromExp->returnB[0]; left += fromExp->returnA[0] + fromExp->returnB[0]; right += fromExp->returnA[1] + fromExp->returnB[1]; @@ -244,7 +244,7 @@ void Mix4x::sampleRateChange() { } void Mix4x::processAll(const ProcessArgs& args) { - if (!connected()) { + if (!baseConnected()) { outputs[SEND_A_OUTPUT].setVoltage(0.0f); outputs[SEND_B_OUTPUT].setVoltage(0.0f); return; diff --git a/src/Mix4.hpp b/src/Mix4.hpp @@ -15,7 +15,7 @@ struct Mix4x; typedef MixerExpanderMessage<4> Mix4ExpanderMessage; -struct Mix4 : ExpandableModule<Mix4ExpanderMessage, Mix4x> { +struct Mix4 : ExpandableModule<Mix4ExpanderMessage, Mix4x, BGModule> { enum ParamsIds { LEVEL1_PARAM, PAN1_PARAM, @@ -105,7 +105,7 @@ struct Mix4 : ExpandableModule<Mix4ExpanderMessage, Mix4x> { void processAll(const ProcessArgs& args) override; }; -struct Mix4x : ExpanderModule<Mix4ExpanderMessage, Mix4> { +struct Mix4x : ExpanderModule<Mix4ExpanderMessage, Mix4, BGModule> { enum ParamsIds { LOW1_PARAM, MID1_PARAM, diff --git a/src/Mix8.cpp b/src/Mix8.cpp @@ -29,7 +29,7 @@ void Mix8::sampleRateChange() { void Mix8::processAll(const ProcessArgs& args) { Mix8ExpanderMessage* toExp = &_dummyExpanderMessage; Mix8ExpanderMessage* fromExp = &_dummyExpanderMessage; - if (connected()) { + if (expanderConnected()) { toExp = toExpander(); fromExp = fromExpander(); } @@ -86,7 +86,7 @@ void Mix8::processAll(const ProcessArgs& args) { float mono = 0.0f; float left = 0.0f; float right = 0.0f; - if (connected()) { + if (expanderConnected()) { mono += fromExp->returnA[0] + fromExp->returnB[0]; left += fromExp->returnA[0] + fromExp->returnB[0]; right += fromExp->returnA[1] + fromExp->returnB[1]; @@ -294,7 +294,7 @@ void Mix8x::sampleRateChange() { } void Mix8x::processAll(const ProcessArgs& args) { - if (!connected()) { + if (!baseConnected()) { outputs[SEND_A_OUTPUT].setVoltage(0.0f); outputs[SEND_B_OUTPUT].setVoltage(0.0f); return; diff --git a/src/Mix8.hpp b/src/Mix8.hpp @@ -15,7 +15,7 @@ struct Mix8x; typedef MixerExpanderMessage<8> Mix8ExpanderMessage; -struct Mix8 : ExpandableModule<Mix8ExpanderMessage, Mix8x> { +struct Mix8 : ExpandableModule<Mix8ExpanderMessage, Mix8x, BGModule> { enum ParamsIds { LEVEL1_PARAM, MUTE1_PARAM, @@ -145,7 +145,7 @@ struct Mix8 : ExpandableModule<Mix8ExpanderMessage, Mix8x> { void processAll(const ProcessArgs& args) override; }; -struct Mix8x : ExpanderModule<Mix8ExpanderMessage, Mix8> { +struct Mix8x : ExpanderModule<Mix8ExpanderMessage, Mix8, BGModule> { enum ParamsIds { LOW1_PARAM, MID1_PARAM, diff --git a/src/OneEight.cpp b/src/OneEight.cpp @@ -1,14 +1,18 @@ #include "OneEight.hpp" +void OneEight::processAlways(const ProcessArgs& args) { + std::fill(_lightSums, _lightSums + 8, 0.0f); +} + void OneEight::processChannel(const ProcessArgs& args, int c) { int step = nextStep( c, - inputs[RESET_INPUT], + &inputs[RESET_INPUT], inputs[CLOCK_INPUT], - params[STEPS_PARAM], + &params[STEPS_PARAM], params[DIRECTION_PARAM], - params[SELECT_PARAM], + &params[SELECT_PARAM], inputs[SELECT_INPUT] ); @@ -17,18 +21,14 @@ void OneEight::processChannel(const ProcessArgs& args, int c) { for (int i = 0; i < 8; ++i) { outputs[OUT1_OUTPUT + i].setChannels(_channels); outputs[OUT1_OUTPUT + i].setVoltage((step == i) * in, c); - } - if (c == 0) { - for (int i = 0; i < 8; ++i) { - lights[OUT1_LIGHT + i].value = step == i; - } + _lightSums[i] += step == i; } } else if (!inputs[IN_INPUT].isConnected()) { for (int i = 0; i < 8; ++i) { outputs[OUT1_OUTPUT + i].setChannels(1); outputs[OUT1_OUTPUT + i].setVoltage((step == i) * 10.0f); - lights[OUT1_LIGHT + i].value = step == i; + _lightSums[i] += step == i; } } else { @@ -37,11 +37,17 @@ void OneEight::processChannel(const ProcessArgs& args, int c) { for (int i = 0; i < 8; ++i) { outputs[OUT1_OUTPUT + i].setChannels(inputs[IN_INPUT].getChannels()); outputs[OUT1_OUTPUT + i].writeVoltages((step == i) ? in : zeroes); - lights[OUT1_LIGHT + i].value = step == i; + _lightSums[i] += step == i; } } } +void OneEight::postProcessAlways(const ProcessArgs& args) { + for (int i = 0; i < 8; ++i) { + lights[OUT1_LIGHT + i].value = _lightSums[i] / (float)_channels; + } +} + struct OneEightWidget : AddressableSequenceModuleWidget { static constexpr int hp = 6; diff --git a/src/OneEight.hpp b/src/OneEight.hpp @@ -50,14 +50,19 @@ struct OneEight : AddressableSequenceModule { NUM_LIGHTS }; - OneEight() : AddressableSequenceModule(CLOCK_INPUT, SELECT_INPUT) { + float _lightSums[8] {}; + + OneEight() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(STEPS_PARAM, 1.0f, 8.0f, 8.0f, "Steps"); configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Direction"); configParam(SELECT_PARAM, 0.0f, 7.0f, 0.0f, "Select step"); + setInputIDs(CLOCK_INPUT, SELECT_INPUT); } + void processAlways(const ProcessArgs& args) override; void processChannel(const ProcessArgs& args, int c) override; + void postProcessAlways(const ProcessArgs& args) override; }; } // namespace bogaudio diff --git a/src/Pgmr.cpp b/src/Pgmr.cpp @@ -0,0 +1,511 @@ + +#include "Pgmr.hpp" + +#define SELECT_TRIGGERS "SELECT_TRIGGERS" + +void PgmrStep::reset() { + for (int c = 0; c < BGModule::maxChannels; ++c) { + triggers[c].reset(); + pulseGens[c].process(1000.0f); + } +} + + +PgmrRegistry::Base::Base(Pgmr& b) : module(b) { + std::copy(b._localSteps, b._localSteps + 4, std::back_inserter(steps)); +} + +int PgmrRegistry::registerBase(Pgmr& b) { + std::lock_guard<std::mutex> lock(_lock); + + int id = _nextID; + ++_nextID; + auto p = _bases.emplace(id, Base(b)); + b.setSteps(p.first->second.steps); + return id; +} + +void PgmrRegistry::deregisterBase(int id) { + std::lock_guard<std::mutex> lock(_lock); + _bases.erase(id); +} + +void PgmrRegistry::registerExpander(int baseID, int position, PgmrX& x) { + std::lock_guard<std::mutex> lock(_lock); + + assert(position > 0); + auto base = _bases.find(baseID); + if (base != _bases.end()) { + int i = 4 * position; + if (i < (int)base->second.steps.size()) { + assert(!base->second.steps[i]); + std::copy(x._localSteps, x._localSteps + 4, base->second.steps.begin() + i); + } + else { + base->second.steps.resize(i + 4, NULL); + std::copy(x._localSteps, x._localSteps + 4, base->second.steps.begin() + i); + } + for (auto i = base->second.steps.begin(), n = base->second.steps.end(); i != n; ++i) { + if (!*i) { + return; + } + } + base->second.module.setSteps(base->second.steps); + } +} + +void PgmrRegistry::deregisterExpander(int baseID, int position) { + std::lock_guard<std::mutex> lock(_lock); + + auto base = _bases.find(baseID); + if (base != _bases.end()) { + int n = 4 * position; + if (n < (int)base->second.steps.size()) { + int i = 0; + for (; i < n; ++i) { + if (!base->second.steps[i]) { + break; + } + } + base->second.steps.resize(i); + base->second.module.setSteps(base->second.steps); + } + } +} + +PgmrRegistry& PgmrRegistry::registry() { + static PgmrRegistry instance; + return instance; +} + + +void Pgmr::reset() { + std::lock_guard<std::mutex> lock(_stepsLock); + + for (int c = 0; c < maxChannels; ++c) { + _lastSteps[c] = -1; + _allPulseGens[c].process(1000.0f); + } + for (int i = 0, n = _steps.size(); i < n; ++i) { + _steps[i]->reset(); + } +} + +void Pgmr::sampleRateChange() { + _sampleTime = APP->engine->getSampleTime(); +} + +json_t* Pgmr::dataToJson() { + json_t* root = OutputRangeAddressableSequenceModule::dataToJson(); + json_object_set_new(root, SELECT_TRIGGERS, json_boolean(_selectTriggers)); + return root; +} + +void Pgmr::dataFromJson(json_t* root) { + OutputRangeAddressableSequenceModule::dataFromJson(root); + json_t* st = json_object_get(root, SELECT_TRIGGERS); + if (st) { + _selectTriggers = json_is_true(st); + } +} + +void Pgmr::modulate() { + _selectOnClock = params[SELECT_ON_CLOCK_PARAM].getValue() > 0.5f; +} + +void Pgmr::processAlways(const ProcessArgs& args) { + if (expanderConnected()) { + PgmrExpanderMessage* te = toExpander(); + te->baseID = _id; + te->position = 1; + te->rangeOffset = _rangeOffset; + te->rangeScale = _rangeScale; + } +} + +void Pgmr::processChannel(const ProcessArgs& args, int c) { + std::lock_guard<std::mutex> lock(_stepsLock); + int steps = _steps.size(); + if (c == 0) { + for (int i = 0, n = _steps.size(); i < n; ++i) { + _steps[i]->lightSum = 0.0f; + } + } + + int step = nextStep( + c, + NULL, + inputs[CLOCK_INPUT], + NULL, + params[DIRECTION_PARAM], + NULL, + inputs[SELECT_INPUT], + steps + ); + for (int i = 0; i < steps; ++i) { + if (_steps[i]->triggers[c].process(_steps[i]->selectParam.getValue() + _steps[i]->selectInput.getPolyVoltage(c))) { + step = setStep(c, i, steps); + } + } + + { + float out = _steps[step]->aParam.getValue(); + out += _rangeOffset; + out *= _rangeScale; + outputs[A_OUTPUT].setChannels(_channels); + outputs[A_OUTPUT].setVoltage(out, c); + } + { + float out = _steps[step]->bParam.getValue(); + out += _rangeOffset; + out *= _rangeScale; + outputs[B_OUTPUT].setChannels(_channels); + outputs[B_OUTPUT].setVoltage(out, c); + } + { + float out = _steps[step]->cParam.getValue(); + out += _rangeOffset; + out *= _rangeScale; + outputs[C_OUTPUT].setChannels(_channels); + outputs[C_OUTPUT].setVoltage(out, c); + } + { + float out = _steps[step]->dParam.getValue(); + out += _rangeOffset; + out *= _rangeScale; + outputs[D_OUTPUT].setChannels(_channels); + outputs[D_OUTPUT].setVoltage(out, c); + } + + if (step != _lastSteps[c]) { + _lastSteps[c] = step; + _allPulseGens[c].trigger(0.001f); + _steps[step]->pulseGens[c].trigger(0.001f); + } + outputs[SELECT_ALL_OUTPUT].setChannels(_channels); + outputs[SELECT_ALL_OUTPUT].setVoltage(_allPulseGens[c].process(_sampleTime) * 5.0f, c); + + for (int i = 0; i < steps; ++i) { + _steps[i]->selectedOutput.setChannels(_channels); + _steps[i]->selectedOutput.setVoltage((_steps[i]->pulseGens[c].process(_sampleTime) || (!_selectTriggers && step == i)) * 5.0f, c); + + _steps[i]->lightSum += step == i; + } + + if (c == _channels - 1) { + for (int i = 0, n = _steps.size(); i < n; ++i) { + _steps[i]->selectedLight.value = _steps[i]->lightSum / (float)_channels; + } + } +} + +void Pgmr::setSteps(std::vector<PgmrStep*>& steps) { + std::lock_guard<std::mutex> lock(_stepsLock); + _steps = steps; +} + + +struct PgmrWidget : AddressableSequenceBaseModuleWidget { + static constexpr int hp = 15; + + PgmrWidget(Pgmr* 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/Pgmr.svg"))); + addChild(panel); + } + + addChild(createWidget<ScrewSilver>(Vec(15, 0))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0))); + addChild(createWidget<ScrewSilver>(Vec(15, 365))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365))); + + // generated by svg_widgets.rb + auto directionParamPosition = Vec(28.0, 263.7); + auto selectOnClockParamPosition = Vec(28.0, 277.2); + auto cva1ParamPosition = Vec(54.5, 40.5); + auto cvb1ParamPosition = Vec(54.5, 94.5); + auto cvc1ParamPosition = Vec(54.5, 148.5); + auto cvd1ParamPosition = Vec(54.5, 202.5); + auto select1ParamPosition = Vec(58.5, 267.0); + auto cva2ParamPosition = Vec(99.5, 40.5); + auto cvb2ParamPosition = Vec(99.5, 94.5); + auto cvc2ParamPosition = Vec(99.5, 148.5); + auto cvd2ParamPosition = Vec(99.5, 202.5); + auto select2ParamPosition = Vec(103.5, 267.0); + auto cva3ParamPosition = Vec(144.5, 40.5); + auto cvb3ParamPosition = Vec(144.5, 94.5); + auto cvc3ParamPosition = Vec(144.5, 148.5); + auto cvd3ParamPosition = Vec(144.5, 202.5); + auto select3ParamPosition = Vec(148.5, 267.0); + auto cva4ParamPosition = Vec(189.5, 40.5); + auto cvb4ParamPosition = Vec(189.5, 94.5); + auto cvc4ParamPosition = Vec(189.5, 148.5); + auto cvd4ParamPosition = Vec(189.5, 202.5); + auto select4ParamPosition = Vec(193.5, 267.0); + + auto clockInputPosition = Vec(10.5, 226.0); + auto selectInputPosition = Vec(10.5, 290.0); + auto select1InputPosition = Vec(55.5, 290.0); + auto select2InputPosition = Vec(100.5, 290.0); + auto select3InputPosition = Vec(145.5, 290.0); + auto select4InputPosition = Vec(190.5, 290.0); + + auto aOutputPosition = Vec(10.5, 59.0); + auto bOutputPosition = Vec(10.5, 94.0); + auto cOutputPosition = Vec(10.5, 129.0); + auto dOutputPosition = Vec(10.5, 163.0); + auto selectAllOutputPosition = Vec(10.5, 330.0); + auto select1OutputPosition = Vec(55.5, 330.0); + auto select2OutputPosition = Vec(100.5, 330.0); + auto select3OutputPosition = Vec(145.5, 330.0); + auto select4OutputPosition = Vec(190.5, 330.0); + + auto select1LightPosition = Vec(64.3, 255.0); + auto select2LightPosition = Vec(109.3, 255.0); + auto select3LightPosition = Vec(154.3, 255.0); + auto select4LightPosition = Vec(199.3, 255.0); + // end generated by svg_widgets.rb + + addParam(createParam<IndicatorButtonGreen9>(directionParamPosition, module, Pgmr::DIRECTION_PARAM)); + addParam(createParam<IndicatorButtonGreen9>(selectOnClockParamPosition, module, Pgmr::SELECT_ON_CLOCK_PARAM)); + addParam(createParam<Knob26>(cva1ParamPosition, module, Pgmr::CVA1_PARAM)); + addParam(createParam<Knob26>(cvb1ParamPosition, module, Pgmr::CVB1_PARAM)); + addParam(createParam<Knob26>(cvc1ParamPosition, module, Pgmr::CVC1_PARAM)); + addParam(createParam<Knob26>(cvd1ParamPosition, module, Pgmr::CVD1_PARAM)); + addParam(createParam<Button18>(select1ParamPosition, module, Pgmr::SELECT1_PARAM)); + addParam(createParam<Knob26>(cva2ParamPosition, module, Pgmr::CVA2_PARAM)); + addParam(createParam<Knob26>(cvb2ParamPosition, module, Pgmr::CVB2_PARAM)); + addParam(createParam<Knob26>(cvc2ParamPosition, module, Pgmr::CVC2_PARAM)); + addParam(createParam<Knob26>(cvd2ParamPosition, module, Pgmr::CVD2_PARAM)); + addParam(createParam<Button18>(select2ParamPosition, module, Pgmr::SELECT2_PARAM)); + addParam(createParam<Knob26>(cva3ParamPosition, module, Pgmr::CVA3_PARAM)); + addParam(createParam<Knob26>(cvb3ParamPosition, module, Pgmr::CVB3_PARAM)); + addParam(createParam<Knob26>(cvc3ParamPosition, module, Pgmr::CVC3_PARAM)); + addParam(createParam<Knob26>(cvd3ParamPosition, module, Pgmr::CVD3_PARAM)); + addParam(createParam<Button18>(select3ParamPosition, module, Pgmr::SELECT3_PARAM)); + addParam(createParam<Knob26>(cva4ParamPosition, module, Pgmr::CVA4_PARAM)); + addParam(createParam<Knob26>(cvb4ParamPosition, module, Pgmr::CVB4_PARAM)); + addParam(createParam<Knob26>(cvc4ParamPosition, module, Pgmr::CVC4_PARAM)); + addParam(createParam<Knob26>(cvd4ParamPosition, module, Pgmr::CVD4_PARAM)); + addParam(createParam<Button18>(select4ParamPosition, module, Pgmr::SELECT4_PARAM)); + + addInput(createInput<Port24>(clockInputPosition, module, Pgmr::CLOCK_INPUT)); + addInput(createInput<Port24>(selectInputPosition, module, Pgmr::SELECT_INPUT)); + addInput(createInput<Port24>(select1InputPosition, module, Pgmr::SELECT1_INPUT)); + addInput(createInput<Port24>(select2InputPosition, module, Pgmr::SELECT2_INPUT)); + addInput(createInput<Port24>(select3InputPosition, module, Pgmr::SELECT3_INPUT)); + addInput(createInput<Port24>(select4InputPosition, module, Pgmr::SELECT4_INPUT)); + + addOutput(createOutput<Port24>(aOutputPosition, module, Pgmr::A_OUTPUT)); + addOutput(createOutput<Port24>(bOutputPosition, module, Pgmr::B_OUTPUT)); + addOutput(createOutput<Port24>(cOutputPosition, module, Pgmr::C_OUTPUT)); + addOutput(createOutput<Port24>(dOutputPosition, module, Pgmr::D_OUTPUT)); + addOutput(createOutput<Port24>(selectAllOutputPosition, module, Pgmr::SELECT_ALL_OUTPUT)); + addOutput(createOutput<Port24>(select1OutputPosition, module, Pgmr::SELECT1_OUTPUT)); + addOutput(createOutput<Port24>(select2OutputPosition, module, Pgmr::SELECT2_OUTPUT)); + addOutput(createOutput<Port24>(select3OutputPosition, module, Pgmr::SELECT3_OUTPUT)); + addOutput(createOutput<Port24>(select4OutputPosition, module, Pgmr::SELECT4_OUTPUT)); + + addChild(createLight<SmallLight<GreenLight>>(select1LightPosition, module, Pgmr::SELECT1_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select2LightPosition, module, Pgmr::SELECT2_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select3LightPosition, module, Pgmr::SELECT3_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select4LightPosition, module, Pgmr::SELECT4_LIGHT)); + } + + struct RangeOptionMenuItem : OptionMenuItem { + RangeOptionMenuItem(Pgmr* module, const char* label, float offset, float scale) + : OptionMenuItem( + label, + [=]() { return module->_rangeOffset == offset && module->_rangeScale == scale; }, + [=]() { + module->_rangeOffset = offset; + module->_rangeScale = scale; + } + ) + {} + }; + + void appendContextMenu(Menu* menu) override { + AddressableSequenceBaseModuleWidget::appendContextMenu(menu); + + auto m = dynamic_cast<Pgmr*>(module); + assert(m); + OptionsMenuItem* mi = new OptionsMenuItem("Range"); + mi->addItem(RangeOptionMenuItem(m, "+/-10V", 0.0f, 10.0f)); + mi->addItem(RangeOptionMenuItem(m, "+/-5V", 0.0f, 5.0f)); + mi->addItem(RangeOptionMenuItem(m, "+/-3V", 0.0f, 3.0f)); + mi->addItem(RangeOptionMenuItem(m, "+/-1V", 0.0f, 1.0f)); + OptionsMenuItem::addToMenu(mi, menu); + + OptionsMenuItem* so = new OptionsMenuItem("Step-selected output"); + so->addItem(OptionMenuItem("Gate", [m]() { return !m->_selectTriggers; }, [m]() { m->_selectTriggers = false; })); + so->addItem(OptionMenuItem("Trigger", [m]() { return m->_selectTriggers; }, [m]() { m->_selectTriggers = true; })); + OptionsMenuItem::addToMenu(so, menu); + } +}; + +Model* modelPgmr = createModel<Pgmr, PgmrWidget>("Bogaudio-Pgmr", "PGMR", "4-step programmer and sequencer", "Sequencer", "Polyphonic"); + + +float PgmrX::OutputParamQuantity::getDisplayValue() { + float v = getValue(); + if (!module) { + return v; + } + + auto m = dynamic_cast<PgmrX*>(module); + v += m->_rangeOffset; + v *= m->_rangeScale; + return v; +} + +void PgmrX::OutputParamQuantity::setDisplayValue(float v) { + if (!module) { + return; + } + + auto m = dynamic_cast<PgmrX*>(module); + v /= m->_rangeScale; + v -= m->_rangeOffset; + setValue(v); +} + +void PgmrX::processAlways(const ProcessArgs& args) { + int position = 0; + int baseID = 0; + if (baseConnected()) { + PgmrExpanderMessage* bm = fromBase(); + baseID = bm->baseID; + position = bm->position; + _rangeOffset = bm->rangeOffset; + _rangeScale = bm->rangeScale; + } + + if (_registered && (position <= 0 || position != _position)) { + PgmrRegistry::registry().deregisterExpander(_baseID, _position); + _registered = false; + _baseID = 0; + _position = 0; + } + else if (!_registered && position > 0) { + _registered = true; + _baseID = baseID; + _position = position; + PgmrRegistry::registry().registerExpander(_baseID, _position, *this); + } + + if (_position < 1) { + for (int i = 0; i < 4; ++i) { + _localSteps[i]->selectedLight.value = 0.0f; + } + } + if (expanderConnected()) { + PgmrExpanderMessage* te = toExpander(); + te->baseID = _baseID; + te->position = _position > 0 ? _position + 1 : 0; + te->rangeOffset = _rangeOffset; + te->rangeScale = _rangeScale; + } +} + +struct PgmrXWidget : ModuleWidget { + static constexpr int hp = 12; + + PgmrXWidget(PgmrX* 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/PgmrX.svg"))); + addChild(panel); + } + + addChild(createWidget<ScrewSilver>(Vec(0, 0))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 0))); + addChild(createWidget<ScrewSilver>(Vec(0, 365))); + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 15, 365))); + + // generated by svg_widgets.rb + auto cva1ParamPosition = Vec(9.5, 40.5); + auto cvb1ParamPosition = Vec(9.5, 94.5); + auto cvc1ParamPosition = Vec(9.5, 148.5); + auto cvd1ParamPosition = Vec(9.5, 202.5); + auto select1ParamPosition = Vec(13.5, 267.0); + auto cva2ParamPosition = Vec(54.5, 40.5); + auto cvb2ParamPosition = Vec(54.5, 94.5); + auto cvc2ParamPosition = Vec(54.5, 148.5); + auto cvd2ParamPosition = Vec(54.5, 202.5); + auto select2ParamPosition = Vec(58.5, 267.0); + auto cva3ParamPosition = Vec(99.5, 40.5); + auto cvb3ParamPosition = Vec(99.5, 94.5); + auto cvc3ParamPosition = Vec(99.5, 148.5); + auto cvd3ParamPosition = Vec(99.5, 202.5); + auto select3ParamPosition = Vec(103.5, 267.0); + auto cva4ParamPosition = Vec(144.5, 40.5); + auto cvb4ParamPosition = Vec(144.5, 94.5); + auto cvc4ParamPosition = Vec(144.5, 148.5); + auto cvd4ParamPosition = Vec(144.5, 202.5); + auto select4ParamPosition = Vec(148.5, 267.0); + + auto select1InputPosition = Vec(10.5, 290.0); + auto select2InputPosition = Vec(55.5, 290.0); + auto select3InputPosition = Vec(100.5, 290.0); + auto select4InputPosition = Vec(145.5, 290.0); + + auto select1OutputPosition = Vec(10.5, 330.0); + auto select2OutputPosition = Vec(55.5, 330.0); + auto select3OutputPosition = Vec(100.5, 330.0); + auto select4OutputPosition = Vec(145.5, 330.0); + + auto select1LightPosition = Vec(19.3, 255.0); + auto select2LightPosition = Vec(64.3, 255.0); + auto select3LightPosition = Vec(109.3, 255.0); + auto select4LightPosition = Vec(154.3, 255.0); + // end generated by svg_widgets.rb + + addParam(createParam<Knob26>(cva1ParamPosition, module, PgmrX::CVA1_PARAM)); + addParam(createParam<Knob26>(cvb1ParamPosition, module, PgmrX::CVB1_PARAM)); + addParam(createParam<Knob26>(cvc1ParamPosition, module, PgmrX::CVC1_PARAM)); + addParam(createParam<Knob26>(cvd1ParamPosition, module, PgmrX::CVD1_PARAM)); + addParam(createParam<Button18>(select1ParamPosition, module, PgmrX::SELECT1_PARAM)); + addParam(createParam<Knob26>(cva2ParamPosition, module, PgmrX::CVA2_PARAM)); + addParam(createParam<Knob26>(cvb2ParamPosition, module, PgmrX::CVB2_PARAM)); + addParam(createParam<Knob26>(cvc2ParamPosition, module, PgmrX::CVC2_PARAM)); + addParam(createParam<Knob26>(cvd2ParamPosition, module, PgmrX::CVD2_PARAM)); + addParam(createParam<Button18>(select2ParamPosition, module, PgmrX::SELECT2_PARAM)); + addParam(createParam<Knob26>(cva3ParamPosition, module, PgmrX::CVA3_PARAM)); + addParam(createParam<Knob26>(cvb3ParamPosition, module, PgmrX::CVB3_PARAM)); + addParam(createParam<Knob26>(cvc3ParamPosition, module, PgmrX::CVC3_PARAM)); + addParam(createParam<Knob26>(cvd3ParamPosition, module, PgmrX::CVD3_PARAM)); + addParam(createParam<Button18>(select3ParamPosition, module, PgmrX::SELECT3_PARAM)); + addParam(createParam<Knob26>(cva4ParamPosition, module, PgmrX::CVA4_PARAM)); + addParam(createParam<Knob26>(cvb4ParamPosition, module, PgmrX::CVB4_PARAM)); + addParam(createParam<Knob26>(cvc4ParamPosition, module, PgmrX::CVC4_PARAM)); + addParam(createParam<Knob26>(cvd4ParamPosition, module, PgmrX::CVD4_PARAM)); + addParam(createParam<Button18>(select4ParamPosition, module, PgmrX::SELECT4_PARAM)); + + addInput(createInput<Port24>(select1InputPosition, module, PgmrX::SELECT1_INPUT)); + addInput(createInput<Port24>(select2InputPosition, module, PgmrX::SELECT2_INPUT)); + addInput(createInput<Port24>(select3InputPosition, module, PgmrX::SELECT3_INPUT)); + addInput(createInput<Port24>(select4InputPosition, module, PgmrX::SELECT4_INPUT)); + + addOutput(createOutput<Port24>(select1OutputPosition, module, PgmrX::SELECT1_OUTPUT)); + addOutput(createOutput<Port24>(select2OutputPosition, module, PgmrX::SELECT2_OUTPUT)); + addOutput(createOutput<Port24>(select3OutputPosition, module, PgmrX::SELECT3_OUTPUT)); + addOutput(createOutput<Port24>(select4OutputPosition, module, PgmrX::SELECT4_OUTPUT)); + + addChild(createLight<SmallLight<GreenLight>>(select1LightPosition, module, PgmrX::SELECT1_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select2LightPosition, module, PgmrX::SELECT2_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select3LightPosition, module, PgmrX::SELECT3_LIGHT)); + addChild(createLight<SmallLight<GreenLight>>(select4LightPosition, module, PgmrX::SELECT4_LIGHT)); + } +}; + +Model* modelPgmrX = createModel<PgmrX, PgmrXWidget>("Bogaudio-PgmrX", "PGMRX", "4-step chainable expander for PGMR", "Sequencer", "Expander", "Polyphonic"); diff --git a/src/Pgmr.hpp b/src/Pgmr.hpp @@ -0,0 +1,303 @@ +#pragma once + +#include <mutex> +#include <unordered_map> + +#include "bogaudio.hpp" +#include "addressable_sequence.hpp" +#include "expanders.hpp" + +extern Model* modelPgmr; +extern Model* modelPgmrX; + +namespace bogaudio { + +struct PgmrExpanderMessage : ExpanderMessage { + int baseID = -1; + int position = -1; + float rangeOffset = 0.0f; + float rangeScale = 10.0f; +}; + +struct PgmrStep { + Param& aParam; + Param& bParam; + Param& cParam; + Param& dParam; + Light& selectedLight; + Param& selectParam; + Input& selectInput; + Output& selectedOutput; + + Trigger triggers[BGModule::maxChannels]; + rack::dsp::PulseGenerator pulseGens[BGModule::maxChannels]; + float lightSum = 0.0f; + + PgmrStep( + Param& aParam, + Param& bParam, + Param& cParam, + Param& dParam, + Light& selectedLight, + Param& selectParam, + Input& selectInput, + Output& selectedOutput + ) + : aParam(aParam) + , bParam(bParam) + , cParam(cParam) + , dParam(dParam) + , selectedLight(selectedLight) + , selectParam(selectParam) + , selectInput(selectInput) + , selectedOutput(selectedOutput) + { + } + + void reset(); +}; + +struct PgmrBase { + PgmrStep* _localSteps[4] {}; + + virtual ~PgmrBase() { + for (int i = 0; i < 4; ++i) { + if (_localSteps[i]) { + delete _localSteps[i]; + } + } + } +}; + +struct Pgmr; +struct PgmrX; + +struct PgmrRegistry { +private: + struct Base { + Pgmr& module; + std::vector<PgmrStep*> steps; + + Base(Pgmr& b); + }; + + std::mutex _lock; + int _nextID = 1; + std::unordered_map<int, Base> _bases; + +public: + int registerBase(Pgmr& b); + void deregisterBase(int id); + void registerExpander(int baseID, int position, PgmrX& x); + void deregisterExpander(int baseID, int position); + + static PgmrRegistry& registry(); +}; + +struct Pgmr : ExpandableModule<PgmrExpanderMessage, PgmrX, OutputRangeAddressableSequenceModule>, PgmrBase { + enum ParamsIds { + DIRECTION_PARAM, + SELECT_ON_CLOCK_PARAM, + CVA1_PARAM, + CVB1_PARAM, + CVC1_PARAM, + CVD1_PARAM, + SELECT1_PARAM, + CVA2_PARAM, + CVB2_PARAM, + CVC2_PARAM, + CVD2_PARAM, + SELECT2_PARAM, + CVA3_PARAM, + CVB3_PARAM, + CVC3_PARAM, + CVD3_PARAM, + SELECT3_PARAM, + CVA4_PARAM, + CVB4_PARAM, + CVC4_PARAM, + CVD4_PARAM, + SELECT4_PARAM, + NUM_PARAMS + }; + + enum InputsIds { + CLOCK_INPUT, + SELECT_INPUT, + SELECT1_INPUT, + SELECT2_INPUT, + SELECT3_INPUT, + SELECT4_INPUT, + NUM_INPUTS + }; + + enum OutputsIds { + A_OUTPUT, + B_OUTPUT, + C_OUTPUT, + D_OUTPUT, + SELECT_ALL_OUTPUT, + SELECT1_OUTPUT, + SELECT2_OUTPUT, + SELECT3_OUTPUT, + SELECT4_OUTPUT, + NUM_OUTPUTS + }; + + enum LightsIds { + SELECT1_LIGHT, + SELECT2_LIGHT, + SELECT3_LIGHT, + SELECT4_LIGHT, + NUM_LIGHTS + }; + + float _sampleTime = 0.001f; + bool _selectTriggers = false; + std::mutex _stepsLock; + std::vector<PgmrStep*> _steps; + int _lastSteps[maxChannels] {}; + rack::dsp::PulseGenerator _allPulseGens[maxChannels]; + int _id = -1; + + Pgmr() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configParam(DIRECTION_PARAM, 0.0f, 1.0f, 1.0f, "Forward"); + configParam(SELECT_ON_CLOCK_PARAM, 0.0f, 1.0f, 0.0f, "Select on clock"); + configParam<OutputParamQuantity>(CVA1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1A", " V"); + configParam<OutputParamQuantity>(CVB1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1B", " V"); + configParam<OutputParamQuantity>(CVC1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1C", " V"); + configParam<OutputParamQuantity>(CVD1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1D", " V"); + configParam(SELECT1_PARAM, 0.0f, 1.0f, 0.0f, "Select 1"); + configParam<OutputParamQuantity>(CVA2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2A", " V"); + configParam<OutputParamQuantity>(CVB2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2B", " V"); + configParam<OutputParamQuantity>(CVC2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2C", " V"); + configParam<OutputParamQuantity>(CVD2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2D", " V"); + configParam(SELECT2_PARAM, 0.0f, 1.0f, 0.0f, "Select 2"); + configParam<OutputParamQuantity>(CVA3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3A", " V"); + configParam<OutputParamQuantity>(CVB3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3B", " V"); + configParam<OutputParamQuantity>(CVC3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3C", " V"); + configParam<OutputParamQuantity>(CVD3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3D", " V"); + configParam(SELECT3_PARAM, 0.0f, 1.0f, 0.0f, "Select 3"); + configParam<OutputParamQuantity>(CVA4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4A", " V"); + configParam<OutputParamQuantity>(CVB4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4B", " V"); + configParam<OutputParamQuantity>(CVC4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4C", " V"); + configParam<OutputParamQuantity>(CVD4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4D", " V"); + configParam(SELECT4_PARAM, 0.0f, 1.0f, 0.0f, "Select 4"); + setInputIDs(CLOCK_INPUT, SELECT_INPUT); + + _localSteps[0] = new PgmrStep(params[CVA1_PARAM], params[CVB1_PARAM], params[CVC1_PARAM], params[CVD1_PARAM], lights[SELECT1_LIGHT], params[SELECT1_PARAM], inputs[SELECT1_INPUT], outputs[SELECT1_OUTPUT]); + _localSteps[1] = new PgmrStep(params[CVA2_PARAM], params[CVB2_PARAM], params[CVC2_PARAM], params[CVD2_PARAM], lights[SELECT2_LIGHT], params[SELECT2_PARAM], inputs[SELECT2_INPUT], outputs[SELECT2_OUTPUT]); + _localSteps[2] = new PgmrStep(params[CVA3_PARAM], params[CVB3_PARAM], params[CVC3_PARAM], params[CVD3_PARAM], lights[SELECT3_LIGHT], params[SELECT3_PARAM], inputs[SELECT3_INPUT], outputs[SELECT3_OUTPUT]); + _localSteps[3] = new PgmrStep(params[CVA4_PARAM], params[CVB4_PARAM], params[CVC4_PARAM], params[CVD4_PARAM], lights[SELECT4_LIGHT], params[SELECT4_PARAM], inputs[SELECT4_INPUT], outputs[SELECT4_OUTPUT]); + _id = PgmrRegistry::registry().registerBase(*this); + } + virtual ~Pgmr() { + PgmrRegistry::registry().deregisterBase(_id); + } + + void reset() override; + void sampleRateChange() override; + json_t* dataToJson() override; + void dataFromJson(json_t* root) override; + void modulate() override; + void processAlways(const ProcessArgs& args) override; + void processChannel(const ProcessArgs& args, int c) override; + void setSteps(std::vector<PgmrStep*>& steps); +}; + +struct PgmrX : ExpanderModule<PgmrExpanderMessage, PgmrBase, ExpandableModule<PgmrExpanderMessage, PgmrX, BGModule>>, PgmrBase { + enum ParamsIds { + CVA1_PARAM, + CVB1_PARAM, + CVC1_PARAM, + CVD1_PARAM, + SELECT1_PARAM, + CVA2_PARAM, + CVB2_PARAM, + CVC2_PARAM, + CVD2_PARAM, + SELECT2_PARAM, + CVA3_PARAM, + CVB3_PARAM, + CVC3_PARAM, + CVD3_PARAM, + SELECT3_PARAM, + CVA4_PARAM, + CVB4_PARAM, + CVC4_PARAM, + CVD4_PARAM, + SELECT4_PARAM, + NUM_PARAMS + }; + + enum InputsIds { + SELECT1_INPUT, + SELECT2_INPUT, + SELECT3_INPUT, + SELECT4_INPUT, + NUM_INPUTS + }; + + enum OutputsIds { + SELECT1_OUTPUT, + SELECT2_OUTPUT, + SELECT3_OUTPUT, + SELECT4_OUTPUT, + NUM_OUTPUTS + }; + + enum LightsIds { + SELECT1_LIGHT, + SELECT2_LIGHT, + SELECT3_LIGHT, + SELECT4_LIGHT, + NUM_LIGHTS + }; + + struct OutputParamQuantity : ParamQuantity { + float getDisplayValue() override; + void setDisplayValue(float v) override; + }; + + bool _registered = false; + int _baseID = -1; + int _position = -1; + float _rangeOffset = 0.0f; + float _rangeScale = 10.0f; + + PgmrX() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configParam<OutputParamQuantity>(CVA1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1A", " V"); + configParam<OutputParamQuantity>(CVB1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1B", " V"); + configParam<OutputParamQuantity>(CVC1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1C", " V"); + configParam<OutputParamQuantity>(CVD1_PARAM, -1.0f, 1.0f, 0.0f, "Step 1D", " V"); + configParam(SELECT1_PARAM, 0.0f, 1.0f, 0.0f, "Select 1"); + configParam<OutputParamQuantity>(CVA2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2A", " V"); + configParam<OutputParamQuantity>(CVB2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2B", " V"); + configParam<OutputParamQuantity>(CVC2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2C", " V"); + configParam<OutputParamQuantity>(CVD2_PARAM, -1.0f, 1.0f, 0.0f, "Step 2D", " V"); + configParam(SELECT2_PARAM, 0.0f, 1.0f, 0.0f, "Select 2"); + configParam<OutputParamQuantity>(CVA3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3A", " V"); + configParam<OutputParamQuantity>(CVB3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3B", " V"); + configParam<OutputParamQuantity>(CVC3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3C", " V"); + configParam<OutputParamQuantity>(CVD3_PARAM, -1.0f, 1.0f, 0.0f, "Step 3D", " V"); + configParam(SELECT3_PARAM, 0.0f, 1.0f, 0.0f, "Select 3"); + configParam<OutputParamQuantity>(CVA4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4A", " V"); + configParam<OutputParamQuantity>(CVB4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4B", " V"); + configParam<OutputParamQuantity>(CVC4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4C", " V"); + configParam<OutputParamQuantity>(CVD4_PARAM, -1.0f, 1.0f, 0.0f, "Step 4D", " V"); + configParam<OutputParamQuantity>(SELECT4_PARAM, 0.0f, 1.0f, 0.0f, "Select 4"); + _localSteps[0] = new PgmrStep(params[CVA1_PARAM], params[CVB1_PARAM], params[CVC1_PARAM], params[CVD1_PARAM], lights[SELECT1_LIGHT], params[SELECT1_PARAM], inputs[SELECT1_INPUT], outputs[SELECT1_OUTPUT]); + _localSteps[1] = new PgmrStep(params[CVA2_PARAM], params[CVB2_PARAM], params[CVC2_PARAM], params[CVD2_PARAM], lights[SELECT2_LIGHT], params[SELECT2_PARAM], inputs[SELECT2_INPUT], outputs[SELECT2_OUTPUT]); + _localSteps[2] = new PgmrStep(params[CVA3_PARAM], params[CVB3_PARAM], params[CVC3_PARAM], params[CVD3_PARAM], lights[SELECT3_LIGHT], params[SELECT3_PARAM], inputs[SELECT3_INPUT], outputs[SELECT3_OUTPUT]); + _localSteps[3] = new PgmrStep(params[CVA4_PARAM], params[CVB4_PARAM], params[CVC4_PARAM], params[CVD4_PARAM], lights[SELECT4_LIGHT], params[SELECT4_PARAM], inputs[SELECT4_INPUT], outputs[SELECT4_OUTPUT]); + } + virtual ~PgmrX() { + PgmrRegistry::registry().deregisterExpander(_baseID, _position); + } + + void processAlways(const ProcessArgs& args) override; +}; + +} // namespace bogaudio diff --git a/src/TestExpander.cpp b/src/TestExpander.cpp @@ -7,11 +7,11 @@ int TestExpanderBase::channels() { void TestExpanderBase::processAll(const ProcessArgs& args) { outputs[OUT_OUTPUT].setChannels(_channels); - lights[COM_LIGHT].value = connected(); + lights[COM_LIGHT].value = expanderConnected(); } void TestExpanderBase::processChannel(const ProcessArgs& args, int c) { - if (connected()) { + if (expanderConnected()) { toExpander()->sample[c] = inputs[IN_INPUT].getPolyVoltage(c); outputs[OUT_OUTPUT].setVoltage(fromExpander()->sample[c], c); } @@ -58,11 +58,11 @@ Model* modelTestExpanderBase = createModel<TestExpanderBase, TestExpanderBaseWid void TestExpanderExtension::processAll(const ProcessArgs& args) { outputs[OUT_OUTPUT].setChannels(_channels); - lights[COM_LIGHT].value = connected(); + lights[COM_LIGHT].value = baseConnected(); } void TestExpanderExtension::processChannel(const ProcessArgs& args, int c) { - if (connected()) { + if (baseConnected()) { float sample = fromBase()->sample[c]; toBase()->sample[c] = -sample; outputs[OUT_OUTPUT].setVoltage(sample, c); diff --git a/src/TestExpander.hpp b/src/TestExpander.hpp @@ -14,7 +14,7 @@ struct TestExpanderMessage : ExpanderMessage { struct TestExpanderExtension; -struct TestExpanderBase : ExpandableModule<TestExpanderMessage, TestExpanderExtension> { +struct TestExpanderBase : ExpandableModule<TestExpanderMessage, TestExpanderExtension, BGModule> { enum ParamsIds { NUM_PARAMS }; @@ -43,7 +43,7 @@ struct TestExpanderBase : ExpandableModule<TestExpanderMessage, TestExpanderExte void processChannel(const ProcessArgs& args, int c) override; }; -struct TestExpanderExtension : ExpanderModule<TestExpanderMessage, TestExpanderBase> { +struct TestExpanderExtension : ExpanderModule<TestExpanderMessage, TestExpanderBase, BGModule> { enum ParamsIds { NUM_PARAMS }; diff --git a/src/addressable_sequence.cpp b/src/addressable_sequence.cpp @@ -48,32 +48,45 @@ void AddressableSequenceModule::dataFromJson(json_t* root) { } int AddressableSequenceModule::channels() { + assert(_polyInputID >= 0); + assert(_clockInputID >= 0); + assert(_selectInputID >= 0); return _polyInputID == _selectInputID ? inputs[_selectInputID].getChannels() : inputs[_clockInputID].getChannels(); } int AddressableSequenceModule::nextStep( int c, - Input& resetInput, + Input* resetInput, Input& clockInput, - Param& stepsParam, + Param* stepsParam, Param& directionParam, - Param& selectParam, - Input& selectInput + Param* selectParam, + Input& selectInput, + int n ) { - bool reset = _reset[c].process(resetInput.getVoltage()); - if (reset) { - _timer[c].reset(); + bool reset = false; + if (resetInput) { + reset = _reset[c].process(resetInput->getPolyVoltage(c)); + if (reset) { + _timer[c].reset(); + } } bool timer = _timer[c].next(); bool clock = _clock[c].process(clockInput.getPolyVoltage(c)) && !timer; - int steps = clamp(stepsParam.getValue(), 1.0f, 8.0f); + int steps = n; + if (stepsParam) { + steps = clamp(stepsParam->getValue(), 1.0f, (float)n); + } int reverse = 1 - 2 * (directionParam.getValue() == 0.0f); _step[c] = (_step[c] + reverse * clock) % steps; _step[c] += (_step[c] < 0) * steps; _step[c] -= _step[c] * reset; - float select = selectParam.getValue(); + float select = 0; + if (selectParam) { + select = clamp(selectParam->getValue(), 0.0f, (float)(n - 1)); + } if (_triggeredSelect) { if (_selectTrigger[c].process(selectInput.getPolyVoltage(c))) { _select[c] = (1 + (int)_select[c]) % ((int)select + 1); @@ -81,10 +94,62 @@ int AddressableSequenceModule::nextStep( _select[c] -= _select[c] * reset; } else { - select += clamp(selectInput.getPolyVoltage(c), 0.0f, 10.0f) * 0.1f * 8.0f; + select += clamp(selectInput.getPolyVoltage(c), 0.0f, 9.99f) * 0.1f * (float)n; if (!_selectOnClock || clock) { _select[c] = select; } } - return (_step[c] + (int)_select[c]) % 8; + return (_step[c] + (int)_select[c]) % n; +} + +int AddressableSequenceModule::setStep(int c, int i, int n) { + return _step[c] = i % n; +} + + +float OutputRangeAddressableSequenceModule::OutputParamQuantity::getDisplayValue() { + float v = getValue(); + if (!module) { + return v; + } + + auto m = dynamic_cast<OutputRangeAddressableSequenceModule*>(module); + v += m->_rangeOffset; + v *= m->_rangeScale; + return v; +} + +void OutputRangeAddressableSequenceModule::OutputParamQuantity::setDisplayValue(float v) { + if (!module) { + return; + } + + auto m = dynamic_cast<OutputRangeAddressableSequenceModule*>(module); + v /= m->_rangeScale; + v -= m->_rangeOffset; + setValue(v); +} + +#define RANGE_OFFSET "range_offset" +#define RANGE_SCALE "range_scale" + +json_t* OutputRangeAddressableSequenceModule::dataToJson() { + json_t* root = AddressableSequenceModule::dataToJson(); + json_object_set_new(root, RANGE_OFFSET, json_real(_rangeOffset)); + json_object_set_new(root, RANGE_SCALE, json_real(_rangeScale)); + return root; +} + +void OutputRangeAddressableSequenceModule::dataFromJson(json_t* root) { + AddressableSequenceModule::dataFromJson(root); + + json_t* ro = json_object_get(root, RANGE_OFFSET); + if (ro) { + _rangeOffset = json_real_value(ro); + } + + json_t* rs = json_object_get(root, RANGE_SCALE); + if (rs) { + _rangeScale = json_real_value(rs); + } } diff --git a/src/addressable_sequence.hpp b/src/addressable_sequence.hpp @@ -8,9 +8,9 @@ using namespace rack; namespace bogaudio { struct AddressableSequenceModule : BGModule { - int _polyInputID; - int _clockInputID; - int _selectInputID; + int _polyInputID = -1; + int _clockInputID = -1; + int _selectInputID = -1; Trigger _clock[maxChannels]; Trigger _reset[maxChannels]; Trigger _selectTrigger[maxChannels]; @@ -20,13 +20,11 @@ struct AddressableSequenceModule : BGModule { bool _selectOnClock = false; bool _triggeredSelect = false; - AddressableSequenceModule(int clockInputID, int selectInputID) - : _polyInputID(clockInputID) - , _clockInputID(clockInputID) - , _selectInputID(selectInputID) - { + void setInputIDs(int clockInputID, int selectInputID) { + _polyInputID = clockInputID; + _clockInputID = clockInputID; + _selectInputID = selectInputID; } - void reset() override; void sampleRateChange() override; json_t* dataToJson() override; @@ -34,16 +32,18 @@ struct AddressableSequenceModule : BGModule { int channels() override; int nextStep( int c, - Input& resetInput, + Input* resetInput, Input& clockInput, - Param& stepsParam, + Param* stepsParam, Param& directionParam, - Param& selectParam, - Input& selectInput + Param* selectParam, + Input& selectInput, + int n = 8 ); + int setStep(int c, int i, int n = 8); }; -struct AddressableSequenceModuleWidget : ModuleWidget { +struct AddressableSequenceBaseModuleWidget : ModuleWidget { void appendContextMenu(Menu* menu) override { AddressableSequenceModule* m = dynamic_cast<AddressableSequenceModule*>(module); assert(m); @@ -53,9 +53,31 @@ struct AddressableSequenceModuleWidget : ModuleWidget { p->addItem(OptionMenuItem("CLOCK input", [m]() { return m->_polyInputID == m->_clockInputID; }, [m]() { m->_polyInputID = m->_clockInputID; })); p->addItem(OptionMenuItem("SELECT input", [m]() { return m->_polyInputID == m->_selectInputID; }, [m]() { m->_polyInputID = m->_selectInputID; })); OptionsMenuItem::addToMenu(p, menu); + } +}; + +struct AddressableSequenceModuleWidget : AddressableSequenceBaseModuleWidget { + void appendContextMenu(Menu* menu) override { + AddressableSequenceBaseModuleWidget::appendContextMenu(menu); + + AddressableSequenceModule* m = dynamic_cast<AddressableSequenceModule*>(module); + assert(m); menu->addChild(new BoolOptionMenuItem("Select on clock mode", [m]() { return &m->_selectOnClock; })); menu->addChild(new BoolOptionMenuItem("Triggered select mode", [m]() { return &m->_triggeredSelect; })); } }; +struct OutputRangeAddressableSequenceModule : AddressableSequenceModule { + float _rangeOffset = 0.0f; + float _rangeScale = 10.0f; + + struct OutputParamQuantity : ParamQuantity { + float getDisplayValue() override; + void setDisplayValue(float v) override; + }; + + json_t* dataToJson() override; + void dataFromJson(json_t* root) override; +}; + } // namespace bogaudio diff --git a/src/bogaudio.cpp b/src/bogaudio.cpp @@ -51,6 +51,7 @@ #include "Offset.hpp" #include "OneEight.hpp" #include "Pan.hpp" +#include "Pgmr.hpp" #include "PolyCon.hpp" #include "PolyMult.hpp" #include "Pressor.hpp" @@ -156,6 +157,8 @@ void init(rack::Plugin *p) { p->addModel(modelOneEight); p->addModel(modelEightOne); p->addModel(modelAddrSeq); + p->addModel(modelPgmr); + p->addModel(modelPgmrX); p->addModel(modelAnalyzer); p->addModel(modelAnalyzerXL); diff --git a/src/expanders.hpp b/src/expanders.hpp @@ -15,85 +15,97 @@ struct ExpanderMessage { virtual ~ExpanderMessage() {} }; -template<class MSG, class EM> -struct ExpandableModule : BGModule { +template<class MSG, class EM, class BASE> +struct ExpandableModule : BASE { MSG _messages[2] {}; + bool _wasConnected = false; ExpandableModule() { static_assert(std::is_base_of<ExpanderMessage, MSG>::value, "type parameter MSG must derive from ExpanderMessage"); + static_assert(std::is_base_of<BGModule, BASE>::value, "type parameter BASE must derive from BGModule"); - rightExpander.producerMessage = &_messages[0]; - rightExpander.consumerMessage = &_messages[1]; + BGModule::rightExpander.producerMessage = &_messages[0]; + BGModule::rightExpander.consumerMessage = &_messages[1]; } - inline bool connected() { - return rightExpander.module && dynamic_cast<EM*>(rightExpander.module); + bool expanderConnected() { + bool connected = BGModule::rightExpander.module && dynamic_cast<EM*>(BGModule::rightExpander.module); + if (!connected && _wasConnected) { + _messages[1] = _messages[0] = MSG(); + } + return _wasConnected = connected; } - inline MSG* toExpander() { - assert(connected()); - MSG* m = (MSG*)rightExpander.module->leftExpander.producerMessage; + MSG* toExpander() { + assert(expanderConnected()); + MSG* m = (MSG*)BGModule::rightExpander.module->BGModule::leftExpander.producerMessage; assert(m); - m->channels = _channels; + m->channels = BGModule::_channels; return m; } - inline MSG* fromExpander() { - assert(connected()); - MSG* m = (MSG*)rightExpander.consumerMessage; + MSG* fromExpander() { + assert(expanderConnected()); + MSG* m = (MSG*)BGModule::rightExpander.consumerMessage; assert(m); return m; } - void process(const ProcessArgs& args) override { - BGModule::process(args); - if (rightExpander.module) { - rightExpander.module->leftExpander.messageFlipRequested = true; + void process(const BGModule::ProcessArgs& args) override { + BASE::process(args); + if (BGModule::rightExpander.module) { + BGModule::rightExpander.module->leftExpander.messageFlipRequested = true; } } }; // An expander must be to the right of the expanded module to work. -template<class MSG, class BM> -struct ExpanderModule : BGModule { +template<class MSG, class BM, class BASE> +struct ExpanderModule : BASE { MSG _messages[2] {}; + bool _wasConnected = false; ExpanderModule() { static_assert(std::is_base_of<ExpanderMessage, MSG>::value, "type parameter MSG must derive from ExpanderMessage"); + static_assert(std::is_base_of<BGModule, BASE>::value, "type parameter BASE must derive from BGModule"); - leftExpander.producerMessage = &_messages[0]; - leftExpander.consumerMessage = &_messages[1]; + BGModule::leftExpander.producerMessage = &_messages[0]; + BGModule::leftExpander.consumerMessage = &_messages[1]; } - inline bool connected() { - return leftExpander.module && dynamic_cast<BM*>(leftExpander.module); + bool baseConnected() { + bool connected = BGModule::leftExpander.module && dynamic_cast<BM*>(BGModule::leftExpander.module); + if (!connected && _wasConnected) { + _messages[1] = _messages[0] = MSG(); + } + return _wasConnected = connected; } - inline MSG* fromBase() { - assert(connected()); - MSG* m = (MSG*)leftExpander.consumerMessage; + MSG* fromBase() { + assert(baseConnected()); + MSG* m = (MSG*)BGModule::leftExpander.consumerMessage; assert(m); return m; } - inline MSG* toBase() { - assert(connected()); - MSG* m = (MSG*)leftExpander.module->rightExpander.producerMessage; + MSG* toBase() { + assert(baseConnected()); + MSG* m = (MSG*)BGModule::leftExpander.module->rightExpander.producerMessage; assert(m); return m; } int channels() override final { - if (connected()) { + if (baseConnected()) { return fromBase()->channels; } return 1; } - void process(const ProcessArgs& args) override { - BGModule::process(args); - if (leftExpander.module) { - leftExpander.module->rightExpander.messageFlipRequested = true; + void process(const BGModule::ProcessArgs& args) override { + BASE::process(args); + if (BGModule::leftExpander.module) { + BGModule::leftExpander.module->rightExpander.messageFlipRequested = true; } } };