Browse Source

PonyVCO (v2.3.0) (#38)

* Added Pony VCO
* Improve Oversampling filter code
* remove DC from EvenVCO pulse
* Fix #37 DC on saw based waves
* update README with hardware differences
* Fix CPU spike with StereoStrip, fixes #39
tags/v2.3.0^0
Ewan GitHub 2 years ago
parent
commit
adcc920324
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2455 additions and 24 deletions
  1. +8
    -0
      CHANGELOG.md
  2. +8
    -1
      README.md
  3. +16
    -1
      plugin.json
  4. +100
    -0
      res/components/SwitchTallVert_bg.svg
  5. +150
    -0
      res/components/SwitchTallVert_fg.svg
  6. +97
    -0
      res/components/SwitchWideHoriz_bg.svg
  7. +136
    -0
      res/components/SwitchWideHoriz_fg.svg
  8. +1423
    -0
      res/panels/PonyVCO.svg
  9. +10
    -15
      src/ChoppingKinky.cpp
  10. +1
    -1
      src/ChowDSP.hpp
  11. +35
    -2
      src/EvenVCO.cpp
  12. +404
    -0
      src/PonyVCO.cpp
  13. +8
    -1
      src/StereoStrip.cpp
  14. +1
    -0
      src/plugin.cpp
  15. +58
    -3
      src/plugin.hpp

+ 8
- 0
CHANGELOG.md View File

@@ -1,5 +1,13 @@
# Change Log # Change Log


## v2.3.0
* PonyVCO
* Initial release
* EvenVCO
* Optionally remove DC from pulse wave output
* StereoStrip
* Address high CPU usage when using EQ sliders

## v2.2.0 ## v2.2.0


* StereoStrip * StereoStrip


+ 8
- 1
README.md View File

@@ -19,4 +19,11 @@ We have tried to make the VCV implementations as authentic as possible, however


* The hardware Muxlicer assigns multiple functions to the "Speed Div/Mult" dial, that cannot be reproduced with a single mouse click. Some of these have been moved to the context menu, specifically: quadratic gates, the "All In" normalled voltage, and the input/output clock division/mult. The "Speed Div/Mult" dial remains only for main clock div/mult. * The hardware Muxlicer assigns multiple functions to the "Speed Div/Mult" dial, that cannot be reproduced with a single mouse click. Some of these have been moved to the context menu, specifically: quadratic gates, the "All In" normalled voltage, and the input/output clock division/mult. The "Speed Div/Mult" dial remains only for main clock div/mult.


* The Noise Plethora filters self-oscillate on the hardware version but not the software version.
* The Noise Plethora filters self-oscillate on the hardware version but not the software version.

* EvenVCO has the option (default true) to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles)

* PonyVCO optionally allows the user:
* to filter DC from the TZFM input signal (hardware filters below 15mHz)
* to limit the pulsewidth from 5% to 95% (hardware is full range)
* to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles)

+ 16
- 1
plugin.json View File

@@ -1,6 +1,6 @@
{ {
"slug": "Befaco", "slug": "Befaco",
"version": "2.2.0",
"version": "2.3.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"name": "Befaco", "name": "Befaco",
"brand": "Befaco", "brand": "Befaco",
@@ -246,6 +246,8 @@
"slug": "StereoStrip", "slug": "StereoStrip",
"name": "Stereo Strip", "name": "Stereo Strip",
"description": "Stereo VCA, panning, and EQ", "description": "Stereo VCA, panning, and EQ",
"manualUrl": "https://www.befaco.org/stereo-strip/",
"modularGridUrl": "https://www.modulargrid.net/e/divkid-stereo-strip",
"tags": [ "tags": [
"Equalizer", "Equalizer",
"Hardware clone", "Hardware clone",
@@ -254,6 +256,19 @@
"Panning", "Panning",
"Polyphonic" "Polyphonic"
] ]
},
{
"slug": "PonyVCO",
"name": "PonyVCO",
"description": "Compact Thru-Zero (TZFM) oscillator with wavefolder and VCA",
"manualUrl": "https://www.befaco.org/pony-vco/",
"modularGridUrl": "https://www.modulargrid.net/e/befaco-pony-vco",
"tags": [
"Hardware clone",
"Low-frequency oscillator",
"Oscillator",
"Waveshaper"
]
} }
] ]
} }

+ 100
- 0
res/components/SwitchTallVert_bg.svg View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="svg56722"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
x="0px"
y="0px"
width="4.0999999mm"
height="18.299999mm"
viewBox="0 0 15.496063 69.165353"
enable-background="new 0 0 10 20.64111"
xml:space="preserve"
sodipodi:docname="SwitchTallVert_bg.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata32"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs30"><linearGradient
inkscape:collect="always"
id="linearGradient2486"><stop
style="stop-color:#404040;stop-opacity:1;"
offset="0"
id="stop2482" /><stop
style="stop-color:#202020;stop-opacity:1;"
offset="1"
id="stop2484" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2486"
id="linearGradient2488"
x1="16.68667"
y1="10.886667"
x2="16.664148"
y2="1.7359135"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.43691906,0,0,5.6869247,0.31696623,-2.6596619)" /></defs>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1.0"
fit-margin-bottom="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-top="0"
id="base"
inkscape:current-layer="svg56722"
inkscape:cx="-51.421173"
inkscape:cy="38.414244"
inkscape:document-rotation="0"
inkscape:document-units="mm"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="1094"
inkscape:window-maximized="0"
inkscape:window-width="2264"
inkscape:window-x="2661"
inkscape:window-y="231"
inkscape:zoom="7.4191229"
pagecolor="#ffffff"
showgrid="false"
units="px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="false">
</sodipodi:namedview>




<rect
x="15.30993"
y="-2.0567799"
fill="none"
width="0.68290001"
height="0.89523"
id="rect12"
transform="rotate(90)" />
<rect
style="fill:url(#linearGradient2488);fill-opacity:1;stroke:#000000;stroke-width:1.12938;stroke-linejoin:round;stroke-miterlimit:1.5;stroke-dasharray:none;stroke-opacity:1"
id="rect852"
width="14.366684"
height="68.03598"
x="0.56468892"
y="0.56468892"
ry="1.382229"
rx="1.569275" /><rect
x="1.2046784"
y="-14.393458"
opacity="0.3"
width="1.1500931"
height="13.371155"
id="rect14"
style="stroke-width:1.05818"
transform="rotate(90)" /></svg>

+ 150
- 0
res/components/SwitchTallVert_fg.svg View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="svg56722"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
x="0px"
y="0px"
width="3.4820001mm"
height="3.5mm"
viewBox="0 0 13.160316 13.228347"
enable-background="new 0 0 10 20.64111"
xml:space="preserve"
sodipodi:docname="SwitchTallVert_fg.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata32"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs30"><linearGradient
inkscape:collect="always"
id="linearGradient1577"><stop
style="stop-color:#000000;stop-opacity:0.38225257;"
offset="0"
id="stop1573" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop1575" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1577"
id="linearGradient1579"
x1="6.4409394"
y1="10.29908"
x2="6.4493213"
y2="13.185839"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.003741,0,0,1,-0.03019842,0)" /></defs>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1.0"
fit-margin-bottom="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-top="0"
id="base"
inkscape:current-layer="svg56722"
inkscape:cx="-0.8903963"
inkscape:cy="7.1981511"
inkscape:document-rotation="0"
inkscape:document-units="mm"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="1094"
inkscape:window-maximized="0"
inkscape:window-width="2264"
inkscape:window-x="295"
inkscape:window-y="131"
inkscape:zoom="53.347032"
pagecolor="#ffffff"
showgrid="false"
units="px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
</sodipodi:namedview>




<rect
x="-46.937611"
y="-0.88922244"
fill="none"
width="0.68290001"
height="0.89523"
id="rect12"
transform="rotate(90)" />
<g
id="g4303"
transform="matrix(-1.2623015,0,0,-1.1600953,14.812981,22.65209)"><g
id="g27"
style="display:inline">
<linearGradient
id="path5815_00000176018740175221449750000008623012870990731937_"
gradientUnits="userSpaceOnUse"
x1="4.9991698"
y1="10.67955"
x2="4.9991698"
y2="19.52606">
<stop
offset="0.00559"
style="stop-color:#3D3B3B"
id="stop16" />
<stop
offset="1"
style="stop-color:#2D2C2C"
id="stop18" />
</linearGradient>
<path
id="path5815_4_"
inkscape:connector-curvature="0"
fill="url(#path5815_00000176018740175221449750000008623012870990731937_)"
d="m 1.30891,10.67955 h 10.425989 v 8.84651 H 1.30891 Z"
style="fill:url(#path5815_00000176018740175221449750000008623012870990731937_)"
sodipodi:nodetypes="ccccc" />
<path
id="path5817_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,14.22647 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />
<path
id="path5819_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,17.77339 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />
<path
id="path5821_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,16.00079 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />
<path
id="path5823_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,12.45307 h 10.423762 v 0.282779 0.594601 H 1.30891 Z"
sodipodi:nodetypes="cccccc" />
<path
id="path5825_4_"
inkscape:connector-curvature="0"
fill="#797979"
d="m 1.30891,10.67955 h 10.423762 v 0.87738 H 1.30891 Z"
style="fill:#5e5e5e;fill-opacity:1"
sodipodi:nodetypes="ccccc" />
</g></g><rect
style="fill:url(#linearGradient1579);fill-opacity:1;stroke:none;stroke-width:1.33393;stroke-linejoin:round;stroke-miterlimit:1.5"
id="rect1517"
width="13.160316"
height="2.9625583"
x="0"
y="10.26118" /></svg>

+ 97
- 0
res/components/SwitchWideHoriz_bg.svg View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="svg56722"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
x="0px"
y="0px"
width="9mm"
height="3.46mm"
viewBox="0 0 34.015748 13.077166"
enable-background="new 0 0 10 20.64111"
xml:space="preserve"
sodipodi:docname="SwitchWideHoriz_bg.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata32"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs30"><linearGradient
inkscape:collect="always"
id="linearGradient2486"><stop
style="stop-color:#404040;stop-opacity:1;"
offset="0"
id="stop2482" /><stop
style="stop-color:#202020;stop-opacity:1;"
offset="1"
id="stop2484" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2486"
id="linearGradient2488"
x1="16.68667"
y1="10.886667"
x2="16.664148"
y2="1.7359135"
gradientUnits="userSpaceOnUse" /></defs>
<sodipodi:namedview
bordercolor="#666666"
borderopacity="1.0"
fit-margin-bottom="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-top="0"
id="base"
inkscape:current-layer="svg56722"
inkscape:cx="17.108046"
inkscape:cy="5.2105723"
inkscape:document-rotation="0"
inkscape:document-units="mm"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:window-height="1094"
inkscape:window-maximized="0"
inkscape:window-width="2264"
inkscape:window-x="295"
inkscape:window-y="187"
inkscape:zoom="34.54515"
pagecolor="#ffffff"
showgrid="false"
units="px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
</sodipodi:namedview>




<rect
x="15.30993"
y="-2.0567799"
fill="none"
width="0.68290001"
height="0.89523"
id="rect12"
transform="rotate(90)" />
<rect
style="fill:url(#linearGradient2488);fill-opacity:1;stroke:#000000;stroke-width:1.13395;stroke-miterlimit:1.5;stroke-dasharray:none;stroke-opacity:1"
id="rect852"
width="32.881798"
height="11.963581"
x="0.56697619"
y="0.56697619"
ry="2.10818" /><rect
x="1.0889899"
y="-32.968193"
opacity="0.3"
width="1.1500931"
height="32.342274"
id="rect14"
style="stroke-width:1.64574"
transform="rotate(90)" /></svg>

+ 136
- 0
res/components/SwitchWideHoriz_fg.svg View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
width="2.3406391mm"
height="2.8726232mm"
viewBox="0 0 2.3406391 2.8726232"
version="1.1"
id="svg3573"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
sodipodi:docname="SwitchWideHoriz_fg.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview3575"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="21.315782"
inkscape:cx="7.787657"
inkscape:cy="14.895067"
inkscape:window-width="1390"
inkscape:window-height="1205"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs3570">
<linearGradient
id="path5815_00000176018740175221449750000008623012870990731937_"
gradientUnits="userSpaceOnUse"
x1="4.9991698"
y1="10.67955"
x2="4.9991698"
y2="19.52606">
<stop
offset="0.00559"
style="stop-color:#3D3B3B"
id="stop16" />

<stop
offset="1"
style="stop-color:#2D2C2C"
id="stop18" />

</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-58.765578,-147.06369)">
<g
id="g4303"
transform="matrix(0,0.27552525,-0.26458333,0,63.931848,146.70305)">
<g
id="g27"
style="display:inline">
<linearGradient
id="linearGradient1363"
gradientUnits="userSpaceOnUse"
x1="4.9991698"
y1="10.67955"
x2="4.9991698"
y2="19.52606">
<stop
offset="0.00559"
style="stop-color:#3D3B3B"
id="stop1359" />

<stop
offset="1"
style="stop-color:#2D2C2C"
id="stop1361" />

</linearGradient>

<path
id="path5815_4_"
inkscape:connector-curvature="0"
fill="url(#path5815_00000176018740175221449750000008623012870990731937_)"
d="m 1.30891,10.67955 h 10.425989 v 8.84651 H 1.30891 Z"
style="fill:url(#path5815_00000176018740175221449750000008623012870990731937_)"
sodipodi:nodetypes="ccccc" />

<path
id="path5817_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,14.22647 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />

<path
id="path5819_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,17.77339 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />

<path
id="path5821_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,16.00079 h 10.423762 v 0.87738 H 1.30891 Z"
sodipodi:nodetypes="ccccc" />

<path
id="path5823_4_"
inkscape:connector-curvature="0"
fill="#5e5e5e"
d="m 1.30891,12.45307 h 10.423762 v 0.282779 0.594601 H 1.30891 Z"
sodipodi:nodetypes="cccccc" />

<path
id="path5825_4_"
inkscape:connector-curvature="0"
fill="#797979"
d="m 1.30891,10.67955 h 10.423762 v 0.87738 H 1.30891 Z"
style="fill:#5e5e5e;fill-opacity:1"
sodipodi:nodetypes="ccccc" />

</g>
</g>
</g>
</svg>

+ 1423
- 0
res/panels/PonyVCO.svg
File diff suppressed because it is too large
View File


+ 10
- 15
src/ChoppingKinky.cpp View File

@@ -47,7 +47,7 @@ struct ChoppingKinky : Module {
bool outputAToChopp = false; bool outputAToChopp = false;
float previousA = 0.0; float previousA = 0.0;


chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS];
chowdsp::VariableOversampling<6> oversampler[NUM_CHANNELS]; // uses a 2*6=12th order Butterworth filter
int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling


DCBlocker blockDCFilter; DCBlocker blockDCFilter;
@@ -345,21 +345,16 @@ struct ChoppingKinkyWidget : ModuleWidget {


menu->addChild(createMenuLabel("Oversampling mode")); menu->addChild(createMenuLabel("Oversampling mode"));


struct ModeItem : MenuItem {
ChoppingKinky* module;
int oversamplingIndex;
void onAction(const event::Action& e) override {
module->oversamplingIndex = oversamplingIndex;
module->onSampleRateChange();
}
};
for (int i = 0; i < 5; i++) {
ModeItem* modeItem = createMenuItem<ModeItem>(string::f("%dx", int (1 << i)));
modeItem->rightText = CHECKMARK(module->oversamplingIndex == i);
modeItem->module = module;
modeItem->oversamplingIndex = i;
menu->addChild(modeItem);
menu->addChild(createIndexSubmenuItem("Oversampling",
{"Off", "x2", "x4", "x8", "x16"},
[ = ]() {
return module->oversamplingIndex;
},
[ = ](int mode) {
module->oversamplingIndex = mode;
module->onSampleRateChange();
} }
));
} }
}; };




+ 1
- 1
src/ChowDSP.hpp View File

@@ -251,7 +251,7 @@ public:
* @param osRatio: The oversampling ratio at which the filter is being used * @param osRatio: The oversampling ratio at which the filter is being used
*/ */
void reset(float sampleRate, int osRatio) { void reset(float sampleRate, int osRatio) {
float fc = 0.98f * (sampleRate / 2.0f);
float fc = 0.85f * (sampleRate / 2.0f);
auto Qs = calculateButterQs(2 * N); auto Qs = calculateButterQs(2 * N);
for (int i = 0; i < N; ++i) for (int i = 0; i < N; ++i)


+ 35
- 2
src/EvenVCO.cpp View File

@@ -34,10 +34,9 @@ struct EvenVCO : Module {
/** The outputs */ /** The outputs */
/** Whether we are past the pulse width already */ /** Whether we are past the pulse width already */
bool halfPhase[PORT_MAX_CHANNELS] = {}; bool halfPhase[PORT_MAX_CHANNELS] = {};
bool removePulseDC = true;


dsp::MinBlepGenerator<16, 32> triSquareMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> triSquareMinBlep[PORT_MAX_CHANNELS];
dsp::MinBlepGenerator<16, 32> triMinBlep[PORT_MAX_CHANNELS];
dsp::MinBlepGenerator<16, 32> sineMinBlep[PORT_MAX_CHANNELS];
dsp::MinBlepGenerator<16, 32> doubleSawMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> doubleSawMinBlep[PORT_MAX_CHANNELS];
dsp::MinBlepGenerator<16, 32> sawMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> sawMinBlep[PORT_MAX_CHANNELS];
dsp::MinBlepGenerator<16, 32> squareMinBlep[PORT_MAX_CHANNELS]; dsp::MinBlepGenerator<16, 32> squareMinBlep[PORT_MAX_CHANNELS];
@@ -183,17 +182,25 @@ struct EvenVCO : Module {


sine[c / 4] = 5.f * simd::cos(2 * M_PI * phase[c / 4]); sine[c / 4] = 5.f * simd::cos(2 * M_PI * phase[c / 4]);


// minBlep adds a small amount of DC that becomes significant at higher frequencies,
// this subtracts DC based on empirical observvations about the scaling relationship
const float sawCorrect = -5.7;
const float_4 sawDCComp = deltaPhase[c / 4] * sawCorrect;

doubleSaw[c / 4] = simd::ifelse((phase[c / 4] < 0.5), (-1.f + 4.f * phase[c / 4]), (-1.f + 4.f * (phase[c / 4] - 0.5f))); doubleSaw[c / 4] = simd::ifelse((phase[c / 4] < 0.5), (-1.f + 4.f * phase[c / 4]), (-1.f + 4.f * (phase[c / 4] - 0.5f)));
doubleSaw[c / 4] += doubleSawMinBlepOut[c / 4]; doubleSaw[c / 4] += doubleSawMinBlepOut[c / 4];
doubleSaw[c / 4] += 2.f * sawDCComp;
doubleSaw[c / 4] *= 5.f; doubleSaw[c / 4] *= 5.f;


even[c / 4] = 0.55 * (doubleSaw[c / 4] + 1.27 * sine[c / 4]); even[c / 4] = 0.55 * (doubleSaw[c / 4] + 1.27 * sine[c / 4]);
saw[c / 4] = -1.f + 2.f * phase[c / 4]; saw[c / 4] = -1.f + 2.f * phase[c / 4];
saw[c / 4] += sawMinBlepOut[c / 4]; saw[c / 4] += sawMinBlepOut[c / 4];
saw[c / 4] += sawDCComp;
saw[c / 4] *= 5.f; saw[c / 4] *= 5.f;


square[c / 4] = simd::ifelse((phase[c / 4] < pw[c / 4]), -1.f, +1.f); square[c / 4] = simd::ifelse((phase[c / 4] < pw[c / 4]), -1.f, +1.f);
square[c / 4] += squareMinBlepOut[c / 4]; square[c / 4] += squareMinBlepOut[c / 4];
square[c / 4] += removePulseDC * 2.f * (pw[c / 4] - 0.5f);
square[c / 4] *= 5.f; square[c / 4] *= 5.f;


// Set outputs // Set outputs
@@ -211,6 +218,20 @@ struct EvenVCO : Module {
outputs[SAW_OUTPUT].setChannels(channels); outputs[SAW_OUTPUT].setChannels(channels);
outputs[SQUARE_OUTPUT].setChannels(channels); outputs[SQUARE_OUTPUT].setChannels(channels);
} }


json_t* dataToJson() override {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
return rootJ;
}

void dataFromJson(json_t* rootJ) override {
json_t* pulseDCJ = json_object_get(rootJ, "removePulseDC");
if (pulseDCJ) {
removePulseDC = json_boolean_value(pulseDCJ);
}
}
}; };




@@ -241,6 +262,18 @@ struct EvenVCOWidget : ModuleWidget {
addOutput(createOutput<BefacoOutputPort>(Vec(10, 327), module, EvenVCO::SAW_OUTPUT)); addOutput(createOutput<BefacoOutputPort>(Vec(10, 327), module, EvenVCO::SAW_OUTPUT));
addOutput(createOutput<BefacoOutputPort>(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT)); addOutput(createOutput<BefacoOutputPort>(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT));
} }

void appendContextMenu(Menu* menu) override {
EvenVCO* module = dynamic_cast<EvenVCO*>(this->module);
assert(module);

menu->addChild(new MenuSeparator());
menu->addChild(createSubmenuItem("Hardware compatibility", "",
[ = ](Menu * menu) {
menu->addChild(createBoolPtrMenuItem("Remove DC from pulse", "", &module->removePulseDC));
}
));
}
}; };






+ 404
- 0
src/PonyVCO.cpp View File

@@ -0,0 +1,404 @@
#include "plugin.hpp"
#include "ChowDSP.hpp"


// references:
// * "REDUCING THE ALIASING OF NONLINEAR WAVESHAPING USING CONTINUOUS-TIME CONVOLUTION" (https://www.dafx.de/paper-archive/2016/dafxpapers/20-DAFx-16_paper_41-PN.pdf)
// * "Antiderivative Antialiasing for Memoryless Nonlinearities" https://acris.aalto.fi/ws/portalfiles/portal/27135145/ELEC_bilbao_et_al_antiderivative_antialiasing_IEEESPL.pdf
// * https://ccrma.stanford.edu/~jatin/Notebooks/adaa.html
// * Pony waveshape https://www.desmos.com/calculator/1kvahyl4ti

class FoldStage1 {
public:

float process(float x, float xt) {
float y;

if (fabs(x - xPrev) < 1e-5) {
y = f(0.5 * (xPrev + x), xt);
}
else {
y = (F(x, xt) - F(xPrev, xt)) / (x - xPrev);
}
xPrev = x;
return y;
}

// xt - threshold x
static float f(float x, float xt) {
if (x > xt) {
return +5 * xt - 4 * x;
}
else if (x < -xt) {
return -5 * xt - 4 * x;
}
else {
return x;
}
}

static float F(float x, float xt) {
if (x > xt) {
return 5 * xt * x - 2 * x * x - 2.5 * xt * xt;
}
else if (x < -xt) {
return -5 * xt * x - 2 * x * x - 2.5 * xt * xt;

}
else {
return x * x / 2.f;
}
}
private:
float xPrev = 0.f;
};

class FoldStage2 {
public:
float process(float x) {
float y;

if (fabs(x - xPrev) < 1e-5) {
y = f(0.5 * (xPrev + x));
}
else {
y = (F(x) - F(xPrev)) / (x - xPrev);
}
xPrev = x;
return y;
}

static float f(float x) {
if (-(x + 2) > c) {
return c;
}
else if (x < -1) {
return -(x + 2);
}
else if (x < 1) {
return x;
}
else if (-x + 2 > -c) {
return -x + 2;
}
else {
return -c;
}
}

static float F(float x) {
if (x < 0) {
return F(-x);
}
else if (x < 1) {
return x * x * 0.5;
}
else if (x < 2 + c) {
return 2 * x * (1.f - x * 0.25f) - 1.f;
}
else {
return 2 * (2 + c) * (1 - (2 + c) * 0.25f) - 1.f - c * (x - 2 - c);
}
}

private:
float xPrev = 0.f;
static constexpr float c = 0.1;
};


struct PonyVCO : Module {
enum ParamId {
FREQ_PARAM,
RANGE_PARAM,
TIMBRE_PARAM,
OCT_PARAM,
WAVE_PARAM,
PARAMS_LEN
};
enum InputId {
TZFM_INPUT,
TIMBRE_INPUT,
VOCT_INPUT,
SYNC_INPUT,
VCA_INPUT,
INPUTS_LEN
};
enum OutputId {
OUT_OUTPUT,
OUTPUTS_LEN
};
enum LightId {
LIGHTS_LEN
};
enum Waveform {
WAVE_SIN,
WAVE_TRI,
WAVE_SAW,
WAVE_PULSE
};

float range[4] = {8.f, 1.f, 1.f / 12.f, 10.f};
chowdsp::VariableOversampling<6> oversampler; // uses a 2*6=12th order Butterworth filter
int oversamplingIndex = 1; // default is 2^oversamplingIndex == x2 oversampling

DCBlocker blockTZFMDCFilter;
bool blockTZFMDC = true;

// hardware doesn't limit PW but some user might want to (to 5%->95%)
bool limitPW = true;

// hardware has DC for non-50% duty cycle, optionally add/remove it
bool removePulseDC = true;

dsp::SchmittTrigger syncTrigger;

FoldStage1 stage1;
FoldStage2 stage2;

PonyVCO() {
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
configParam(FREQ_PARAM, -0.5f, 0.5f, 0.0f, "Frequency");
auto rangeParam = configSwitch(RANGE_PARAM, 0.f, 3.f, 0.f, "Range", {"VCO: Full", "VCO: Octave", "VCO: Semitone", "LFO"});
rangeParam->snapEnabled = true;

configParam(TIMBRE_PARAM, 0.f, 1.f, 0.f, "Timbre");
auto octParam = configSwitch(OCT_PARAM, 0.f, 6.f, 4.f, "Octave", {"C1", "C2", "C3", "C4", "C5", "C6", "C7"});
octParam->snapEnabled = true;

auto waveParam = configSwitch(WAVE_PARAM, 0.f, 3.f, 0.f, "Wave", {"Sin", "Triangle", "Sawtooth", "Pulse"});
waveParam->snapEnabled = true;

configInput(TZFM_INPUT, "Through-zero FM");
configInput(TIMBRE_INPUT, "Timber (wavefolder/PWM)");
configInput(VOCT_INPUT, "Volt per octave");
configInput(SYNC_INPUT, "Hard sync");
configInput(VCA_INPUT, "VCA");
configOutput(OUT_OUTPUT, "Waveform");

// calculate up/downsampling rates
onSampleRateChange();
}

void onSampleRateChange() override {
float sampleRate = APP->engine->getSampleRate();
blockTZFMDCFilter.setFrequency(5. / sampleRate);
oversampler.setOversamplingIndex(oversamplingIndex);
oversampler.reset(sampleRate);
}

// implementation taken from "Alias-Suppressed Oscillators Based on Differentiated Polynomial Waveforms",
// also the notes from Surge Synthesier repo:
// https://github.com/surge-synthesizer/surge/blob/09f1ec8e103265bef6fc0d8a0fc188238197bf8c/src/common/dsp/oscillators/ModernOscillator.cpp#L19
// Calculation is performed at double precision, as the differencing equations appeared to work poorly with only float.

double phase = 0.0; // phase at current (sub)sample
double phases[3] = {}; // phase as extrapolated to the current and two previous samples
double sawBuffer[3] = {}, sawOffsetBuff[3] = {}, triBuffer[3] = {}; // buffers for storing the terms in the difference equation

void process(const ProcessArgs& args) override {

const int rangeIndex = params[RANGE_PARAM].getValue();
const bool lfoMode = rangeIndex == 3;

const Waveform waveform = (Waveform) params[WAVE_PARAM].getValue();
const float mult = lfoMode ? 1.0 : dsp::FREQ_C4;
const float baseFreq = std::pow(2, (int)(params[OCT_PARAM].getValue() - 3)) * mult;
const int oversamplingRatio = lfoMode ? 1 : oversampler.getOversamplingRatio();
const float timbre = clamp(params[TIMBRE_PARAM].getValue() + inputs[TIMBRE_INPUT].getVoltage() / 10.f, 0.f, 1.f);

float tzfmVoltage = inputs[TZFM_INPUT].getVoltage();
if (blockTZFMDC) {
tzfmVoltage = blockTZFMDCFilter.process(tzfmVoltage);
}

const double pitch = inputs[VOCT_INPUT].getVoltage() + params[FREQ_PARAM].getValue() * range[rangeIndex];
const double freq = baseFreq * simd::pow(2.f, pitch);
const double deltaBasePhase = clamp(freq * args.sampleTime / oversamplingRatio, -0.5f, 0.5f);
// denominator for the second-order FD
const double denominator = 0.25 / (deltaBasePhase * deltaBasePhase);
// not clamped, but _total_ phase treated later with floor/ceil
const double deltaFMPhase = freq * tzfmVoltage * args.sampleTime / oversamplingRatio;

float pw = timbre;
if (limitPW) {
pw = clamp(pw, 0.05, 0.95);
}
// pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option
// for it to be added back in for hardware compatibility reasons
const float pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw);

// hard sync
if (syncTrigger.process(inputs[SYNC_INPUT].getVoltage())) {
// hardware waveform is actually cos, so pi/2 phase offset is required
// - variable phase is defined on [0, 1] rather than [0, 2pi] so pi/2 -> 0.25
phase = (waveform == WAVE_SIN) ? 0.25f : 0.f;
}

float* osBuffer = oversampler.getOSBuffer();
for (int i = 0; i < oversamplingRatio; ++i) {

phase += deltaBasePhase + deltaFMPhase;
if (phase > 1.f) {
phase -= floor(phase);
}
else if (phase < 0.f) {
phase += -ceil(phase) + 1;
}

// sin is simple
if (waveform == WAVE_SIN) {
osBuffer[i] = sin2pi_pade_05_5_4(phase);
}
else {

phases[0] = phase - 2 * deltaBasePhase + (phase < 2 * deltaBasePhase);
phases[1] = phase - deltaBasePhase + (phase < deltaBasePhase);
phases[2] = phase;

switch (waveform) {
case WAVE_TRI: {
osBuffer[i] = aliasSuppressedTri() * denominator;
break;
}
case WAVE_SAW: {
osBuffer[i] = aliasSuppressedSaw() * denominator;
break;
}
case WAVE_PULSE: {
double saw = aliasSuppressedSaw();
double sawOffset = aliasSuppressedOffsetSaw(pw);

osBuffer[i] = (sawOffset - saw) * denominator;
osBuffer[i] += pulseDCOffset;
break;
}
default: break;
}
}

if (waveform != WAVE_PULSE) {
osBuffer[i] = wavefolder(osBuffer[i], (1 - 0.85 * timbre));
}
}

// downsample (if required)
const float out = (oversamplingRatio > 1) ? oversampler.downsample() : osBuffer[0];

// end of chain VCA
const float gain = std::max(0.f, inputs[VCA_INPUT].getNormalVoltage(10.f) / 10.f);
outputs[OUT_OUTPUT].setVoltage(5.f * out * gain);
}

double aliasSuppressedTri() {
for (int i = 0; i < 3; ++i) {
double p = 2 * phases[i] - 1.0; // range -1.0 to +1.0
double s = 0.5 - std::abs(p); // eq 30
triBuffer[i] = (s * s * s - 0.75 * s) / 3.0; // eq 29
}
return (triBuffer[0] - 2.0 * triBuffer[1] + triBuffer[2]);
}

double aliasSuppressedSaw() {
for (int i = 0; i < 3; ++i) {
double p = 2 * phases[i] - 1.0; // range -1 to +1
sawBuffer[i] = (p * p * p - p) / 6.0; // eq 11
}

return (sawBuffer[0] - 2.0 * sawBuffer[1] + sawBuffer[2]);
}

double aliasSuppressedOffsetSaw(double pw) {
for (int i = 0; i < 3; ++i) {
double p = 2 * phases[i] - 1.0; // range -1 to +1
double pwp = p + 2 * pw; // phase after pw (pw in [0, 1])
pwp += (pwp > 1) * -2; // modulo on [-1, +1]
sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11
}
return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]);
}

float wavefolder(float x, float xt) {
return stage2.process(stage1.process(x, xt));
}

json_t* dataToJson() override {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "blockTZFMDC", json_boolean(blockTZFMDC));
json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC));
json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler.getOversamplingIndex()));
return rootJ;
}

void dataFromJson(json_t* rootJ) override {

json_t* blockTZFMDCJ = json_object_get(rootJ, "blockTZFMDC");
if (blockTZFMDCJ) {
blockTZFMDC = json_boolean_value(blockTZFMDCJ);
}

json_t* removePulseDCJ = json_object_get(rootJ, "removePulseDC");
if (removePulseDCJ) {
removePulseDC = json_boolean_value(removePulseDCJ);
}

json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex");
if (oversamplingIndexJ) {
oversamplingIndex = json_integer_value(oversamplingIndexJ);
onSampleRateChange();
}
}
};


struct PonyVCOWidget : ModuleWidget {
PonyVCOWidget(PonyVCO* module) {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/PonyVCO.svg")));

addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addParam(createParamCentered<Davies1900hDarkGreyKnob>(mm2px(Vec(10.0, 14.999)), module, PonyVCO::FREQ_PARAM));
addParam(createParam<CKSSHoriz4>(mm2px(Vec(5.498, 27.414)), module, PonyVCO::RANGE_PARAM));
addParam(createParam<BefacoSlidePotSmall>(mm2px(Vec(12.65, 37.0)), module, PonyVCO::TIMBRE_PARAM));
addParam(createParam<CKSSVert7>(mm2px(Vec(3.8, 40.54)), module, PonyVCO::OCT_PARAM));
addParam(createParam<CKSSHoriz4>(mm2px(Vec(5.681, 74.436)), module, PonyVCO::WAVE_PARAM));

addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 87.455)), module, PonyVCO::TZFM_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.978, 87.455)), module, PonyVCO::TIMBRE_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 100.413)), module, PonyVCO::VOCT_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.978, 100.413)), module, PonyVCO::SYNC_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.014, 113.409)), module, PonyVCO::VCA_INPUT));

addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.0, 113.363)), module, PonyVCO::OUT_OUTPUT));
}

void appendContextMenu(Menu* menu) override {
PonyVCO* module = dynamic_cast<PonyVCO*>(this->module);
assert(module);

menu->addChild(new MenuSeparator());
menu->addChild(createSubmenuItem("Hardware compatibility", "",
[ = ](Menu * menu) {
menu->addChild(createBoolPtrMenuItem("Filter TZFM DC", "", &module->blockTZFMDC));
menu->addChild(createBoolPtrMenuItem("Limit pulsewidth (5\%-95\%)", "", &module->limitPW));
menu->addChild(createBoolPtrMenuItem("Remove pulse DC", "", &module->removePulseDC));
}
));

menu->addChild(createIndexSubmenuItem("Oversampling",
{"Off", "x2", "x4", "x8"},
[ = ]() {
return module->oversamplingIndex;
},
[ = ](int mode) {
module->oversamplingIndex = mode;
module->onSampleRateChange();
}
));

}
};

Model* modelPonyVCO = createModel<PonyVCO, PonyVCOWidget>("PonyVCO");

+ 8
- 1
src/StereoStrip.cpp View File

@@ -229,6 +229,8 @@ struct StereoStrip : Module {
// for processing mutes // for processing mutes
dsp::SlewLimiter clickFilter; dsp::SlewLimiter clickFilter;


dsp::ClockDivider sliderUpdate;

StereoStrip() { StereoStrip() {
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
configParam(HIGH_PARAM, -15.0f, 15.0f, 0.0f, "High shelf (2000 Hz) gain", " dB"); configParam(HIGH_PARAM, -15.0f, 15.0f, 0.0f, "High shelf (2000 Hz) gain", " dB");
@@ -259,6 +261,9 @@ struct StereoStrip : Module {


clickFilter.rise = 50.f; // Hz clickFilter.rise = 50.f; // Hz
clickFilter.fall = 50.f; // Hz clickFilter.fall = 50.f; // Hz

// only poll EQ sliders every 16 samples
sliderUpdate.setDivision(16);
} }


void onSampleRateChange() override { void onSampleRateChange() override {
@@ -321,7 +326,9 @@ struct StereoStrip : Module {
const float switchGains = (params[IN_BOOST_PARAM].getValue() ? 2.0f : 1.0f) * (params[OUT_CUT_PARAM].getValue() ? 0.5f : 1.0f); const float switchGains = (params[IN_BOOST_PARAM].getValue() ? 2.0f : 1.0f) * (params[OUT_CUT_PARAM].getValue() ? 0.5f : 1.0f);
const float preVCAGain = switchGains * muteGain * std::pow(10, params[LEVEL_PARAM].getValue() / 20.0f); const float preVCAGain = switchGains * muteGain * std::pow(10, params[LEVEL_PARAM].getValue() / 20.0f);


updateEQsIfChanged();
if (sliderUpdate.process()) {
updateEQsIfChanged();
}


for (int c = 0; c < numPolyphonyEngines; c += 4) { for (int c = 0; c < numPolyphonyEngines; c += 4) {




+ 1
- 0
src/plugin.cpp View File

@@ -25,4 +25,5 @@ void init(rack::Plugin *p) {
p->addModel(modelMex); p->addModel(modelMex);
p->addModel(modelNoisePlethora); p->addModel(modelNoisePlethora);
p->addModel(modelChannelStrip); p->addModel(modelChannelStrip);
p->addModel(modelPonyVCO);
} }

+ 58
- 3
src/plugin.hpp View File

@@ -26,6 +26,7 @@ extern Model* modelMuxlicer;
extern Model* modelMex; extern Model* modelMex;
extern Model* modelNoisePlethora; extern Model* modelNoisePlethora;
extern Model* modelChannelStrip; extern Model* modelChannelStrip;
extern Model* modelPonyVCO;


struct Knurlie : SvgScrew { struct Knurlie : SvgScrew {
Knurlie() { Knurlie() {
@@ -139,6 +140,59 @@ struct CKSSHoriz2 : app::SvgSwitch {
} }
}; };


struct CKSSVert7 : app::SvgSlider {
CKSSVert7() {
math::Vec margin = math::Vec(3.5, 3.5);
maxHandlePos = math::Vec(1, 1).plus(margin);
minHandlePos = math::Vec(1, 45).plus(margin);
setBackgroundSvg(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchTallVert_bg.svg")));
setHandleSvg(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchTallVert_fg.svg")));
background->box.pos = margin;
box.size = background->box.size.plus(margin.mult(2));
}

// disable double click as this messes with click to advance
void onDoubleClick(const event::DoubleClick& e) override { }

// cycle through the values (with reset) on click only (not drag)
void onAction(const ActionEvent& e) override {
ParamQuantity* paramQuantity = getParamQuantity();
float range = paramQuantity->maxValue - paramQuantity->minValue;
float newValue = paramQuantity->getValue() + 1.f;
if (newValue > paramQuantity->maxValue) {
newValue -= range + 1.f;
}
paramQuantity->setValue(newValue);
}
};

struct CKSSHoriz4 : app::SvgSlider {
CKSSHoriz4() {
setBackgroundSvg(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchWideHoriz_bg.svg")));
setHandleSvg(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchWideHoriz_fg.svg")));
minHandlePos = mm2px(Vec(0.3f, 0.3f));
maxHandlePos = mm2px(Vec(6.3f, 0.3f));
horizontal = true;
math::Vec margin = math::Vec(0, 0);
background->box.pos = margin;
box.size = background->box.size.plus(margin.mult(2));
}

// disable double click as this messes with click to advance
void onDoubleClick(const event::DoubleClick& e) override { }

// cycle through the values (with reset) on click only (not drag)
void onAction(const ActionEvent& e) override {
ParamQuantity* paramQuantity = getParamQuantity();
float range = paramQuantity->maxValue - paramQuantity->minValue;
float newValue = paramQuantity->getValue() + 1.f;
if (newValue > paramQuantity->maxValue) {
newValue -= range + 1.f;
}
paramQuantity->setValue(newValue);
}
};

struct CKSSNarrow3 : app::SvgSwitch { struct CKSSNarrow3 : app::SvgSwitch {
CKSSNarrow3() { CKSSNarrow3() {
addFrame(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchNarrow_0.svg"))); addFrame(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchNarrow_0.svg")));
@@ -254,9 +308,10 @@ struct DCBlockerT {
} }


float process(float x) { float process(float x) {

x = blockDCFilter[0].process(x);
return blockDCFilter[1].process(x);
for (int idx = 0; idx < N; idx++) {
x = blockDCFilter[idx].process(x);
}
return x;
} }


private: private:


Loading…
Cancel
Save