* 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 #39tags/v2.3.0^0
@@ -1,5 +1,13 @@ | |||
# 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 | |||
* StereoStrip | |||
@@ -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 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) |
@@ -1,6 +1,6 @@ | |||
{ | |||
"slug": "Befaco", | |||
"version": "2.2.0", | |||
"version": "2.3.0", | |||
"license": "GPL-3.0-or-later", | |||
"name": "Befaco", | |||
"brand": "Befaco", | |||
@@ -246,6 +246,8 @@ | |||
"slug": "StereoStrip", | |||
"name": "Stereo Strip", | |||
"description": "Stereo VCA, panning, and EQ", | |||
"manualUrl": "https://www.befaco.org/stereo-strip/", | |||
"modularGridUrl": "https://www.modulargrid.net/e/divkid-stereo-strip", | |||
"tags": [ | |||
"Equalizer", | |||
"Hardware clone", | |||
@@ -254,6 +256,19 @@ | |||
"Panning", | |||
"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" | |||
] | |||
} | |||
] | |||
} |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -47,7 +47,7 @@ struct ChoppingKinky : Module { | |||
bool outputAToChopp = false; | |||
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 | |||
DCBlocker blockDCFilter; | |||
@@ -345,21 +345,16 @@ struct ChoppingKinkyWidget : ModuleWidget { | |||
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(); | |||
} | |||
)); | |||
} | |||
}; | |||
@@ -251,7 +251,7 @@ public: | |||
* @param osRatio: The oversampling ratio at which the filter is being used | |||
*/ | |||
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); | |||
for (int i = 0; i < N; ++i) | |||
@@ -34,10 +34,9 @@ struct EvenVCO : Module { | |||
/** The outputs */ | |||
/** Whether we are past the pulse width already */ | |||
bool halfPhase[PORT_MAX_CHANNELS] = {}; | |||
bool removePulseDC = true; | |||
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> sawMinBlep[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]); | |||
// 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] += doubleSawMinBlepOut[c / 4]; | |||
doubleSaw[c / 4] += 2.f * sawDCComp; | |||
doubleSaw[c / 4] *= 5.f; | |||
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] += sawMinBlepOut[c / 4]; | |||
saw[c / 4] += sawDCComp; | |||
saw[c / 4] *= 5.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] += removePulseDC * 2.f * (pw[c / 4] - 0.5f); | |||
square[c / 4] *= 5.f; | |||
// Set outputs | |||
@@ -211,6 +218,20 @@ struct EvenVCO : Module { | |||
outputs[SAW_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(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)); | |||
} | |||
)); | |||
} | |||
}; | |||
@@ -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"); |
@@ -229,6 +229,8 @@ struct StereoStrip : Module { | |||
// for processing mutes | |||
dsp::SlewLimiter clickFilter; | |||
dsp::ClockDivider sliderUpdate; | |||
StereoStrip() { | |||
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | |||
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.fall = 50.f; // Hz | |||
// only poll EQ sliders every 16 samples | |||
sliderUpdate.setDivision(16); | |||
} | |||
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 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) { | |||
@@ -25,4 +25,5 @@ void init(rack::Plugin *p) { | |||
p->addModel(modelMex); | |||
p->addModel(modelNoisePlethora); | |||
p->addModel(modelChannelStrip); | |||
p->addModel(modelPonyVCO); | |||
} |
@@ -26,6 +26,7 @@ extern Model* modelMuxlicer; | |||
extern Model* modelMex; | |||
extern Model* modelNoisePlethora; | |||
extern Model* modelChannelStrip; | |||
extern Model* modelPonyVCO; | |||
struct Knurlie : SvgScrew { | |||
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 { | |||
CKSSNarrow3() { | |||
addFrame(Svg::load(asset::plugin(pluginInstance, "res/components/SwitchNarrow_0.svg"))); | |||
@@ -254,9 +308,10 @@ struct DCBlockerT { | |||
} | |||
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: | |||