* 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: | ||||