* 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 | # 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 | ||||
@@ -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) |
@@ -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" | |||||
] | |||||
} | } | ||||
] | ] | ||||
} | } |
@@ -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; | 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(); | |||||
} | } | ||||
)); | |||||
} | } | ||||
}; | }; | ||||
@@ -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) | ||||
@@ -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)); | |||||
} | |||||
)); | |||||
} | |||||
}; | }; | ||||
@@ -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 | // 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) { | ||||
@@ -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); | |||||
} | } |
@@ -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: | ||||