Release updated modules: Percall, Chopping Kinky, Hexmix VCA, Kickall (v1.1.0 release)tags/v1.1.0
@@ -2,45 +2,4 @@ | |||||
Based on [Befaco](http://www.befaco.org/) Eurorack modules. | Based on [Befaco](http://www.befaco.org/) Eurorack modules. | ||||
### EvenVCO | |||||
Based on [EvenVCO](http://www.befaco.org/even-vco/) | |||||
 | |||||
### Rampage | |||||
Based on [Rampage](http://www.befaco.org/rampage-2/), [Manual PDF](https://befaco.org/docs/Rampage/Rampage_User_Manual.pdf) | |||||
 | |||||
### A\*B+C | |||||
Based on [A\*B+C](http://www.befaco.org/abc/), [Manual PDF](https://befaco.org/docs/AB%2BC/AB%2BC_User_Manual.pdf) | |||||
 | |||||
### Spring Reverb | |||||
Based on [Spring Reverb](http://www.befaco.org/spring-reverb/) | |||||
 | |||||
### Mixer | |||||
Based on [Mixer](http://www.befaco.org/mixer-2/) | |||||
 | |||||
### Slew Limiter | |||||
Based on [Slew Limiter](http://www.befaco.org/slew-limiter/) | |||||
 | |||||
### Dual Atenuverter | |||||
Based on [Dual Atenuverter](http://www.befaco.org/dual-atenuverter/) | |||||
 | |||||
[VCV Library page](https://library.vcvrack.com/Befaco) |
@@ -1,93 +1,146 @@ | |||||
{ | |||||
"slug": "Befaco", | |||||
"version": "1.0.1", | |||||
"license": "GPL-3.0-or-later", | |||||
"name": "Befaco", | |||||
"author": "VCV", | |||||
"authorEmail": "contact@vcvrack.com", | |||||
"pluginUrl": "https://vcvrack.com/Befaco.html", | |||||
"authorUrl": "https://vcvrack.com/", | |||||
"sourceUrl": "https://github.com/VCVRack/Befaco", | |||||
"modules": [ | |||||
{ | |||||
"slug": "EvenVCO", | |||||
"name": "EvenVCO", | |||||
"description": "Oscillator including even-harmonic waveform", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-even-vco-", | |||||
"tags": [ | |||||
"VCO", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Rampage", | |||||
"name": "Rampage", | |||||
"description": "Dual ramp generator", | |||||
"manualUrl": "https://befaco.org/docs/Rampage/Rampage_User_Manual.pdf", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-rampage", | |||||
"tags": [ | |||||
"Function Generator", | |||||
"Logic", | |||||
"Slew Limiter", | |||||
"Envelope Follower", | |||||
"Dual", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "ABC", | |||||
"name": "A*B+C", | |||||
"description": "Dual four-quadrant multiplier with VC offset", | |||||
"manualUrl": "https://befaco.org/docs/AB%2BC/AB%2BC_User_Manual.pdf", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-a-b-c", | |||||
"tags": [ | |||||
"Ring Modulator", | |||||
"Attenuator", | |||||
"Dual", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "SpringReverb", | |||||
"name": "Spring Reverb", | |||||
"description": "Spring reverb tank driver", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-spring-reverb-", | |||||
"tags": [ | |||||
"Reverb", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Mixer", | |||||
"name": "Mixer", | |||||
"description": "Four-channel mixer for audio or CV", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-mixer-", | |||||
"tags": [ | |||||
"Mixer", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "SlewLimiter", | |||||
"name": "Slew Limiter", | |||||
"description": "Voltage controlled slew limiter, AKA lag processor", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-vc-slew-limiter-", | |||||
"tags": [ | |||||
"Slew Limiter", | |||||
"Envelope Follower", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "DualAtenuverter", | |||||
"name": "Dual Atenuverter", | |||||
"description": "Attenuates, inverts, and applies offset to a signal", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-dual-atenuverter-", | |||||
"tags": [ | |||||
"Attenuator", | |||||
"Dual", | |||||
"Hardware clone" | |||||
] | |||||
} | |||||
] | |||||
} | |||||
{ | |||||
"slug": "Befaco", | |||||
"version": "1.1.0", | |||||
"license": "GPL-3.0-or-later", | |||||
"name": "Befaco", | |||||
"author": "VCV", | |||||
"authorEmail": "contact@vcvrack.com", | |||||
"pluginUrl": "https://vcvrack.com/Befaco.html", | |||||
"authorUrl": "https://vcvrack.com/", | |||||
"sourceUrl": "https://github.com/VCVRack/Befaco", | |||||
"modules": [ | |||||
{ | |||||
"slug": "EvenVCO", | |||||
"name": "EvenVCO", | |||||
"description": "Oscillator including even-harmonic waveform", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-even-vco-", | |||||
"tags": [ | |||||
"VCO", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Rampage", | |||||
"name": "Rampage", | |||||
"description": "Dual ramp generator", | |||||
"manualUrl": "https://befaco.org/docs/Rampage/Rampage_User_Manual.pdf", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-rampage", | |||||
"tags": [ | |||||
"Function Generator", | |||||
"Logic", | |||||
"Slew Limiter", | |||||
"Envelope Follower", | |||||
"Dual", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "ABC", | |||||
"name": "A*B+C", | |||||
"description": "Dual four-quadrant multiplier with VC offset", | |||||
"manualUrl": "https://befaco.org/docs/AB%2BC/AB%2BC_User_Manual.pdf", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-a-b-c", | |||||
"tags": [ | |||||
"Ring Modulator", | |||||
"Attenuator", | |||||
"Dual", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "SpringReverb", | |||||
"name": "Spring Reverb", | |||||
"description": "Spring reverb tank driver", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-spring-reverb-", | |||||
"tags": [ | |||||
"Reverb", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Mixer", | |||||
"name": "Mixer", | |||||
"description": "Four-channel mixer for audio or CV", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-mixer-", | |||||
"tags": [ | |||||
"Mixer", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "SlewLimiter", | |||||
"name": "Slew Limiter", | |||||
"description": "Voltage controlled slew limiter, AKA lag processor", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-vc-slew-limiter-", | |||||
"tags": [ | |||||
"Slew Limiter", | |||||
"Envelope Follower", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "DualAtenuverter", | |||||
"name": "Dual Atenuverter", | |||||
"description": "Attenuates, inverts, and applies offset to a signal", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-dual-atenuverter-", | |||||
"tags": [ | |||||
"Attenuator", | |||||
"Dual", | |||||
"Hardware clone", | |||||
"Polyphonic" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Percall", | |||||
"name": "Percall", | |||||
"description": "Percussive Envelope Generator", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-percall", | |||||
"tags": [ | |||||
"Envelope generator", | |||||
"Mixer", | |||||
"Polyphonic", | |||||
"Hardware clone" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "HexmixVCA", | |||||
"name": "HexmixVCA", | |||||
"description": "Six channel VCA with response curve range from logarithmic to linear and to exponential", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-hexmix-vca", | |||||
"tags": [ | |||||
"Mixer", | |||||
"Hardware clone", | |||||
"Polyphonic", | |||||
"VCA" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "ChoppingKinky", | |||||
"name": "ChoppingKinky", | |||||
"description": "Voltage controllable, dual channel wavefolder", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-chopping-kinky", | |||||
"tags": [ | |||||
"Dual", | |||||
"Hardware clone", | |||||
"Voltage-controlled amplifier", | |||||
"Waveshaper" | |||||
] | |||||
}, | |||||
{ | |||||
"slug": "Kickall", | |||||
"name": "Kickall", | |||||
"description": "Bassdrum module, with pitch and volume envelopes", | |||||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-kickall", | |||||
"tags": [ | |||||
"Drum", | |||||
"Hardware clone", | |||||
"Synth voice" | |||||
] | |||||
} | |||||
] | |||||
} |
@@ -0,0 +1,210 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="8.3556204mm" | |||||
height="8.3556299mm" | |||||
viewBox="0 0 8.3556203 8.3556298" | |||||
version="1.1" | |||||
id="svg15246" | |||||
sodipodi:docname="BefacoInputPort.svg" | |||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"> | |||||
<defs | |||||
id="defs15240"> | |||||
<clipPath | |||||
id="clip89"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="18" | |||||
height="19" | |||||
id="rect4864" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip90"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||||
id="path4861" /> | |||||
</clipPath> | |||||
<mask | |||||
id="mask44"> | |||||
<g | |||||
style="filter:url(#alpha)" | |||||
id="g4858" | |||||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||||
<rect | |||||
x="0" | |||||
y="0" | |||||
width="3052.8701" | |||||
height="3351.5" | |||||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||||
id="rect4856" /> | |||||
</g> | |||||
</mask> | |||||
<filter | |||||
id="alpha" | |||||
filterUnits="objectBoundingBox" | |||||
x="0" | |||||
y="0" | |||||
width="1" | |||||
height="1"> | |||||
<feColorMatrix | |||||
type="matrix" | |||||
in="SourceGraphic" | |||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||||
id="feColorMatrix4149" /> | |||||
</filter> | |||||
<clipPath | |||||
id="clipPath17821"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="18" | |||||
height="19" | |||||
id="rect17819" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clipPath17825"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||||
id="path17823" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip87"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="24" | |||||
height="26" | |||||
id="rect4848" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip88"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||||
id="path4845" /> | |||||
</clipPath> | |||||
<mask | |||||
id="mask43"> | |||||
<g | |||||
style="filter:url(#alpha)" | |||||
id="g4842" | |||||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||||
<rect | |||||
x="0" | |||||
y="0" | |||||
width="3052.8701" | |||||
height="3351.5" | |||||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||||
id="rect4840" /> | |||||
</g> | |||||
</mask> | |||||
<filter | |||||
id="filter17836" | |||||
filterUnits="objectBoundingBox" | |||||
x="0" | |||||
y="0" | |||||
width="1" | |||||
height="1"> | |||||
<feColorMatrix | |||||
type="matrix" | |||||
in="SourceGraphic" | |||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||||
id="feColorMatrix17834" /> | |||||
</filter> | |||||
<clipPath | |||||
id="clipPath17840"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="24" | |||||
height="26" | |||||
id="rect17838" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clipPath17844"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||||
id="path17842" /> | |||||
</clipPath> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="11.2" | |||||
inkscape:cx="8.7220802" | |||||
inkscape:cy="-10.120793" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1361" | |||||
inkscape:window-x="2551" | |||||
inkscape:window-y="-9" | |||||
inkscape:window-maximized="1" | |||||
units="px" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:document-rotation="0" /> | |||||
<metadata | |||||
id="metadata15243"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-88.611154,-119.19859)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7255" | |||||
d="m 92.788964,127.42922 c -2.235179,0 -4.05281,-1.81762 -4.05281,-4.05282 0,-2.23516 1.817631,-4.05281 4.05281,-4.05281 2.235176,0 4.05281,1.81765 4.05281,4.05281 0,2.2352 -1.817634,4.05282 -4.05281,4.05282" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<rect | |||||
style="fill:#bfbfbf;fill-opacity:1;stroke:none;stroke-width:0.108444;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
id="rect883" | |||||
width="1.2277683" | |||||
height="8.059844" | |||||
x="122.77332" | |||||
y="-96.818886" | |||||
transform="rotate(90)" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7261" | |||||
d="m 92.788964,126.29511 c -1.609548,0 -2.918685,-1.30916 -2.918685,-2.91871 0,-1.60954 1.309137,-2.91867 2.918685,-2.91867 1.609549,0 2.918682,1.30913 2.918682,2.91867 0,1.60955 -1.309133,2.91871 -2.918682,2.91871" | |||||
style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7265" | |||||
d="m 94.588681,123.3764 c 0,0.99357 -0.806153,1.79974 -1.799717,1.79974 -0.993567,0 -1.79972,-0.80617 -1.79972,-1.79974 0,-0.99356 0.806153,-1.79969 1.79972,-1.79969 0.993564,0 1.799717,0.80613 1.799717,1.79969" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7255-6" | |||||
d="m 92.788964,127.42922 c -2.235179,0 -4.05281,-1.81762 -4.05281,-4.05282 0,-2.23516 1.817631,-4.05281 4.05281,-4.05281 2.235176,0 4.05281,1.81765 4.05281,4.05281 0,2.2352 -1.817634,4.05282 -4.05281,4.05282" | |||||
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,210 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="8.3556204mm" | |||||
height="8.3556299mm" | |||||
viewBox="0 0 8.3556203 8.3556298" | |||||
version="1.1" | |||||
id="svg15246" | |||||
sodipodi:docname="BefacoOutputPort.svg" | |||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"> | |||||
<defs | |||||
id="defs15240"> | |||||
<clipPath | |||||
id="clip89"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="18" | |||||
height="19" | |||||
id="rect4864" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip90"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||||
id="path4861" /> | |||||
</clipPath> | |||||
<mask | |||||
id="mask44"> | |||||
<g | |||||
style="filter:url(#alpha)" | |||||
id="g4858" | |||||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||||
<rect | |||||
x="0" | |||||
y="0" | |||||
width="3052.8701" | |||||
height="3351.5" | |||||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||||
id="rect4856" /> | |||||
</g> | |||||
</mask> | |||||
<filter | |||||
id="alpha" | |||||
filterUnits="objectBoundingBox" | |||||
x="0" | |||||
y="0" | |||||
width="1" | |||||
height="1"> | |||||
<feColorMatrix | |||||
type="matrix" | |||||
in="SourceGraphic" | |||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||||
id="feColorMatrix4149" /> | |||||
</filter> | |||||
<clipPath | |||||
id="clipPath17821"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="18" | |||||
height="19" | |||||
id="rect17819" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clipPath17825"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||||
id="path17823" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip87"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="24" | |||||
height="26" | |||||
id="rect4848" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clip88"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||||
id="path4845" /> | |||||
</clipPath> | |||||
<mask | |||||
id="mask43"> | |||||
<g | |||||
style="filter:url(#alpha)" | |||||
id="g4842" | |||||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||||
<rect | |||||
x="0" | |||||
y="0" | |||||
width="3052.8701" | |||||
height="3351.5" | |||||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||||
id="rect4840" /> | |||||
</g> | |||||
</mask> | |||||
<filter | |||||
id="filter17836" | |||||
filterUnits="objectBoundingBox" | |||||
x="0" | |||||
y="0" | |||||
width="1" | |||||
height="1"> | |||||
<feColorMatrix | |||||
type="matrix" | |||||
in="SourceGraphic" | |||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||||
id="feColorMatrix17834" /> | |||||
</filter> | |||||
<clipPath | |||||
id="clipPath17840"> | |||||
<rect | |||||
y="0" | |||||
x="0" | |||||
width="24" | |||||
height="26" | |||||
id="rect17838" /> | |||||
</clipPath> | |||||
<clipPath | |||||
id="clipPath17844"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||||
id="path17842" /> | |||||
</clipPath> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="13.52797" | |||||
inkscape:cy="9.4899316" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:window-width="1274" | |||||
inkscape:window-height="1393" | |||||
inkscape:window-x="231" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="0" | |||||
units="px" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:document-rotation="0" /> | |||||
<metadata | |||||
id="metadata15243"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-88.611154,-119.19859)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7255" | |||||
d="m 92.788964,127.42922 c -2.235179,0 -4.05281,-1.81762 -4.05281,-4.05282 0,-2.23516 1.817631,-4.05281 4.05281,-4.05281 2.235176,0 4.05281,1.81765 4.05281,4.05281 0,2.2352 -1.817634,4.05282 -4.05281,4.05282" | |||||
style="fill:#f11e1e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<rect | |||||
style="fill:#f8a0a0;fill-opacity:1;stroke:none;stroke-width:0.108454;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
id="rect883" | |||||
width="1.228" | |||||
height="8.059844" | |||||
x="122.7732" | |||||
y="-96.819077" | |||||
transform="rotate(90)" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7261" | |||||
d="m 92.788964,126.29511 c -1.609548,0 -2.918685,-1.30916 -2.918685,-2.91871 0,-1.60954 1.309137,-2.91867 2.918685,-2.91867 1.609549,0 2.918682,1.30913 2.918682,2.91867 0,1.60955 -1.309133,2.91871 -2.918682,2.91871" | |||||
style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7265" | |||||
d="m 94.588681,123.3764 c 0,0.99357 -0.806153,1.79974 -1.799717,1.79974 -0.993567,0 -1.79972,-0.80617 -1.79972,-1.79974 0,-0.99356 0.806153,-1.79969 1.79972,-1.79969 0.993564,0 1.799717,0.80613 1.799717,1.79969" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7255-6" | |||||
d="m 92.788964,127.42922 c -2.235179,0 -4.05281,-1.81762 -4.05281,-4.05282 0,-2.23516 1.817631,-4.05281 4.05281,-4.05281 2.235176,0 4.05281,1.81765 4.05281,4.05281 0,2.2352 -1.817634,4.05282 -4.05281,4.05282" | |||||
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,85 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="9.0000019mm" | |||||
height="9.0000801mm" | |||||
viewBox="0 0 9.0000016 9.00008" | |||||
version="1.1" | |||||
id="svg113936" | |||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)" | |||||
sodipodi:docname="BefacoTinyKnobGrey.svg"> | |||||
<defs | |||||
id="defs113930" /> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="7.919596" | |||||
inkscape:cx="-34.401622" | |||||
inkscape:cy="28.745327" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1393" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="0" | |||||
inkscape:document-rotation="0" /> | |||||
<metadata | |||||
id="metadata113933"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title></dc:title> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-111.86932,-85.795053)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#d4d4d4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 120.10511,92.798549 c 1.38512,-2.062815 0.83525,-4.854005 -1.22757,-6.23803 -2.0617,-1.384026 -4.86062,-0.840773 -6.24463,1.222041 -1.38514,2.062814 -0.83306,4.866126 1.22866,6.251255 2.06282,1.384023 4.8595,0.827547 6.24354,-1.235266" | |||||
id="path109730" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#606060;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 120.02026,91.143448 c -0.46281,2.022043 -2.47716,3.285957 -4.49919,2.823148 -2.02205,-0.462813 -3.28598,-2.47714 -2.82314,-4.499182 0.46281,-2.020944 2.47713,-3.285958 4.49918,-2.82315 2.02093,0.462812 3.28594,2.477141 2.82315,4.499184" | |||||
id="path109732" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:none;stroke:#7f7878;stroke-width:0.11481237;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" | |||||
d="m 120.02026,91.143463 c -0.46281,2.022042 -2.47715,3.285956 -4.49919,2.823145 -2.02204,-0.462811 -3.28595,-2.47714 -2.82314,-4.499183 0.46281,-2.020941 2.47714,-3.285957 4.49918,-2.823145 2.02094,0.46281 3.28596,2.47714 2.82315,4.499183 z m 0,0" | |||||
id="path109734" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 116.20648,88.009558 c -0.11239,-0.07603 -0.14328,-0.229201 -0.0672,-0.342701 0.0761,-0.113495 0.23029,-0.143251 0.34379,-0.06722 0.11356,0.07603 0.14325,0.229201 0.0672,0.342701 -0.076,0.113495 -0.23031,0.14325 -0.34379,0.06722" | |||||
id="path109736" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.46912277;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;fill-opacity:1" | |||||
d="m 116.20647,88.009571 c -0.11239,-0.07603 -0.14326,-0.229202 -0.0672,-0.3427 0.076,-0.113495 0.23032,-0.14325 0.34381,-0.06722 0.11356,0.07602 0.14325,0.229201 0.0672,0.3427 -0.076,0.113495 -0.2303,0.143251 -0.3438,0.06722 z m 0,0" | |||||
id="path109738" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,85 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="9.0000019mm" | |||||
height="9.0000801mm" | |||||
viewBox="0 0 9.0000016 9.00008" | |||||
version="1.1" | |||||
id="svg113936" | |||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)" | |||||
sodipodi:docname="BefacoTinyKnobRed.svg"> | |||||
<defs | |||||
id="defs113930" /> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="-26.636784" | |||||
inkscape:cy="31.87065" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1393" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="0" | |||||
inkscape:document-rotation="0" /> | |||||
<metadata | |||||
id="metadata113933"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-111.86932,-85.795053)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#d4d4d4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 120.10511,92.798549 c 1.38512,-2.062815 0.83525,-4.854005 -1.22757,-6.23803 -2.0617,-1.384026 -4.86062,-0.840773 -6.24463,1.222041 -1.38514,2.062814 -0.83306,4.866126 1.22866,6.251255 2.06282,1.384023 4.8595,0.827547 6.24354,-1.235266" | |||||
id="path109730" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 120.02026,91.143448 c -0.46281,2.022043 -2.47716,3.285957 -4.49919,2.823148 -2.02205,-0.462813 -3.28598,-2.47714 -2.82314,-4.499182 0.46281,-2.020944 2.47713,-3.285958 4.49918,-2.82315 2.02093,0.462812 3.28594,2.477141 2.82315,4.499184" | |||||
id="path109732" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:none;stroke:#7f7878;stroke-width:0.11481237;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" | |||||
d="m 120.02026,91.143463 c -0.46281,2.022042 -2.47715,3.285956 -4.49919,2.823145 -2.02204,-0.462811 -3.28595,-2.47714 -2.82314,-4.499183 0.46281,-2.020941 2.47714,-3.285957 4.49918,-2.823145 2.02094,0.46281 3.28596,2.47714 2.82315,4.499183 z m 0,0" | |||||
id="path109734" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28209424" | |||||
d="m 116.20648,88.009558 c -0.11239,-0.07603 -0.14328,-0.229201 -0.0672,-0.342701 0.0761,-0.113495 0.23029,-0.143251 0.34379,-0.06722 0.11356,0.07603 0.14325,0.229201 0.0672,0.342701 -0.076,0.113495 -0.23031,0.14325 -0.34379,0.06722" | |||||
id="path109736" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.46912277;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;fill-opacity:1" | |||||
d="m 116.20647,88.009571 c -0.11239,-0.07603 -0.14326,-0.229202 -0.0672,-0.3427 0.076,-0.113495 0.23032,-0.14325 0.34381,-0.06722 0.11356,0.07602 0.14325,0.229201 0.0672,0.3427 -0.076,0.113495 -0.2303,0.143251 -0.3438,0.06722 z m 0,0" | |||||
id="path109738" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,105 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="54" | |||||
height="54.002399" | |||||
viewBox="0 0 14.2875 14.288134" | |||||
version="1.1" | |||||
id="svg16908" | |||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)" | |||||
sodipodi:docname="Davies1900hLargeGrey.svg"> | |||||
<defs | |||||
id="defs16902"> | |||||
<clipPath | |||||
clipPathUnits="userSpaceOnUse" | |||||
id="clipPath6367"> | |||||
<path | |||||
d="M 0,3193 H 2089 V 0 H 0 Z" | |||||
id="path6365" | |||||
inkscape:connector-curvature="0" /> | |||||
</clipPath> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="4.7207333" | |||||
inkscape:cy="8.7180405" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1393" | |||||
inkscape:window-x="43" | |||||
inkscape:window-y="161" | |||||
inkscape:window-maximized="0" | |||||
units="px" | |||||
inkscape:document-rotation="0" /> | |||||
<metadata | |||||
id="metadata16905"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-231.61418,-118.28792)"> | |||||
<g | |||||
id="g2004" | |||||
transform="matrix(0.39687059,0,0,-0.39687059,-305.70597,1064.5469)" | |||||
style="stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none"> | |||||
<g | |||||
transform="translate(1389.8926,2366.2998)" | |||||
id="g6455" | |||||
style="stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6457" | |||||
style="fill:#a9a9a9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none" | |||||
d="m 0,0 c 0,-9.941 -8.059,-18 -18,-18 -9.941,0 -18,8.059 -18,18 0,9.941 8.059,18 18,18 C -8.059,18 0,9.941 0,0" /> | |||||
</g> | |||||
<g | |||||
transform="translate(1389.791,2368.2021)" | |||||
id="g6459" | |||||
style="stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6461" | |||||
style="fill:#606060;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none" | |||||
d="m 0,0 c -0.79,1.279 -2.12,2.537 -2.86,4.327 -0.74,1.784 -0.691,3.609 -1.033,5.067 -0.805,0.997 -1.711,1.902 -2.709,2.708 -1.459,0.341 -3.282,0.293 -5.068,1.033 -1.79,0.742 -3.048,2.069 -4.325,2.86 -0.625,0.066 -1.26,0.104 -1.902,0.104 -0.644,0 -1.279,-0.038 -1.903,-0.104 -1.279,-0.791 -2.537,-2.118 -4.327,-2.86 -1.784,-0.74 -3.607,-0.692 -5.067,-1.033 -0.997,-0.806 -1.904,-1.711 -2.708,-2.708 -0.342,-1.458 -0.294,-3.283 -1.035,-5.067 -0.74,-1.79 -2.069,-3.048 -2.858,-4.327 -0.066,-0.625 -0.103,-1.26 -0.103,-1.902 0,-0.643 0.037,-1.278 0.103,-1.905 0.789,-1.277 2.118,-2.535 2.858,-4.325 0.741,-1.784 0.693,-3.609 1.035,-5.066 0.804,-0.998 1.711,-1.904 2.708,-2.709 1.46,-0.342 3.283,-0.293 5.067,-1.032 1.79,-0.743 3.048,-2.071 4.327,-2.861 0.624,-0.065 1.259,-0.103 1.903,-0.103 0.642,0 1.277,0.038 1.902,0.103 1.277,0.79 2.535,2.118 4.325,2.861 1.786,0.739 3.609,0.69 5.068,1.032 0.998,0.805 1.904,1.711 2.709,2.709 0.342,1.457 0.293,3.282 1.033,5.066 0.74,1.79 2.07,3.048 2.86,4.325 0.065,0.627 0.102,1.262 0.102,1.905 C 0.102,-1.26 0.065,-0.625 0,0" /> | |||||
</g> | |||||
<g | |||||
transform="translate(1372.4912,2384.2314)" | |||||
id="g6463" | |||||
style="stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6465" | |||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.8894587;stroke-miterlimit:4;stroke-dasharray:none" | |||||
d="M 0,0 C -0.195,0.045 -0.393,0.069 -0.598,0.069 -0.804,0.069 -1.002,0.045 -1.196,0 V -18.157 H 0 Z" /> | |||||
</g> | |||||
</g> | |||||
</g> | |||||
</svg> |
@@ -1,17 +1,20 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
using simd::float_4; | |||||
inline float clip(float x) { | |||||
template <typename T> | |||||
static T clip4(T x) { | |||||
// Pade approximant of x/(1 + x^12)^(1/12) | // Pade approximant of x/(1 + x^12)^(1/12) | ||||
const float limit = 1.16691853009184f; | |||||
x = clamp(x, -limit, limit); | |||||
return (x + 1.45833f * std::pow(x, 13) + 0.559028f * std::pow(x, 25) + 0.0427035f * std::pow(x, 37)) | |||||
/ (1 + 1.54167f * std::pow(x, 12) + 0.642361f * std::pow(x, 24) + 0.0579909f * std::pow(x, 36)); | |||||
const T limit = 1.16691853009184f; | |||||
x = clamp(x * 0.1f, -limit, limit); | |||||
return 10.0f * (x + 1.45833f * simd::pow(x, 13) + 0.559028f * simd::pow(x, 25) + 0.0427035f * simd::pow(x, 37)) | |||||
/ (1.0f + 1.54167f * simd::pow(x, 12) + 0.642361f * simd::pow(x, 24) + 0.0579909f * simd::pow(x, 36)); | |||||
} | } | ||||
inline float exponentialBipolar80Pade_5_4(float x) { | |||||
return (0.109568f * x + 0.281588f * std::pow(x, 3) + 0.133841f * std::pow(x, 5)) | |||||
/ (1 - 0.630374f * std::pow(x, 2) + 0.166271f * std::pow(x, 4)); | |||||
static float exponentialBipolar80Pade_5_4(float x) { | |||||
return (0.109568 * x + 0.281588 * std::pow(x, 3) + 0.133841 * std::pow(x, 5)) | |||||
/ (1. - 0.630374 * std::pow(x, 2) + 0.166271 * std::pow(x, 4)); | |||||
} | } | ||||
@@ -38,8 +41,8 @@ struct ABC : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
enum LightIds { | enum LightIds { | ||||
ENUMS(OUT1_LIGHT, 2), | |||||
ENUMS(OUT2_LIGHT, 2), | |||||
ENUMS(OUT1_LIGHT, 3), | |||||
ENUMS(OUT2_LIGHT, 3), | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
@@ -51,39 +54,121 @@ struct ABC : Module { | |||||
configParam(C2_LEVEL_PARAM, -1.0, 1.0, 0.0, "C2 Level"); | configParam(C2_LEVEL_PARAM, -1.0, 1.0, 0.0, "C2 Level"); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
float a1 = inputs[A1_INPUT].getVoltage(); | |||||
float b1 = inputs[B1_INPUT].getNormalVoltage(5.f) * 2.f*exponentialBipolar80Pade_5_4(params[B1_LEVEL_PARAM].getValue()); | |||||
float c1 = inputs[C1_INPUT].getNormalVoltage(10.f) * exponentialBipolar80Pade_5_4(params[C1_LEVEL_PARAM].getValue()); | |||||
float out1 = a1 * b1 / 5.f + c1; | |||||
int processSection(simd::float_4* out, InputIds inputA, InputIds inputB, InputIds inputC, ParamIds levelB, ParamIds levelC) { | |||||
float a2 = inputs[A2_INPUT].getVoltage(); | |||||
float b2 = inputs[B2_INPUT].getNormalVoltage(5.f) * 2.f*exponentialBipolar80Pade_5_4(params[B2_LEVEL_PARAM].getValue()); | |||||
float c2 = inputs[C2_INPUT].getNormalVoltage(10.f) * exponentialBipolar80Pade_5_4(params[C2_LEVEL_PARAM].getValue()); | |||||
float out2 = a2 * b2 / 5.f + c2; | |||||
float_4 inA[4] = {}; | |||||
float_4 inB[4] = {}; | |||||
float_4 inC[4] = {}; | |||||
// Set outputs | |||||
if (outputs[OUT1_OUTPUT].isConnected()) { | |||||
outputs[OUT1_OUTPUT].setVoltage(clip(out1 / 10.f) * 10.f); | |||||
int channelsA = inputs[inputA].getChannels(); | |||||
int channelsB = inputs[inputB].getChannels(); | |||||
int channelsC = inputs[inputC].getChannels(); | |||||
// this sets the number of active engines (according to polyphony standard) | |||||
// NOTE: A*B + C has the number of active engines set by any one of the three inputs | |||||
int activeEngines = std::max(1, channelsA); | |||||
activeEngines = std::max(activeEngines, channelsB); | |||||
activeEngines = std::max(activeEngines, channelsC); | |||||
float mult_B = (2.f / 5.f) * exponentialBipolar80Pade_5_4(params[levelB].getValue()); | |||||
float mult_C = exponentialBipolar80Pade_5_4(params[levelC].getValue()); | |||||
if (inputs[inputA].isConnected()) { | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
inA[c / 4] = inputs[inputA].getPolyVoltageSimd<float_4>(c); | |||||
} | |||||
if (inputs[inputB].isConnected()) { | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
inB[c / 4] = inputs[inputB].getPolyVoltageSimd<float_4>(c) * mult_B; | |||||
} | |||||
else { | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
inB[c / 4] = 5.f * mult_B; | |||||
} | |||||
if (inputs[inputC].isConnected()) { | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
inC[c / 4] = inputs[inputC].getPolyVoltageSimd<float_4>(c) * mult_C; | |||||
} | } | ||||
else { | else { | ||||
out2 += out1; | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
inC[c / 4] = 10.f * mult_C; | |||||
} | |||||
for (int c = 0; c < activeEngines; c += 4) | |||||
out[c / 4] = clip4(inA[c / 4] * inB[c / 4] + inC[c / 4]); | |||||
return activeEngines; | |||||
} | |||||
void process(const ProcessArgs& args) override { | |||||
// process upper section | |||||
float_4 out1[4] = {}; | |||||
int activeEngines1 = 1; | |||||
if (outputs[OUT1_OUTPUT].isConnected() || outputs[OUT2_OUTPUT].isConnected()) { | |||||
activeEngines1 = processSection(out1, A1_INPUT, B1_INPUT, C1_INPUT, B1_LEVEL_PARAM, C1_LEVEL_PARAM); | |||||
} | } | ||||
float_4 out2[4] = {}; | |||||
int activeEngines2 = 1; | |||||
// process lower section | |||||
if (outputs[OUT2_OUTPUT].isConnected()) { | if (outputs[OUT2_OUTPUT].isConnected()) { | ||||
outputs[OUT2_OUTPUT].setVoltage(clip(out2 / 10.f) * 10.f); | |||||
activeEngines2 = processSection(out2, A2_INPUT, B2_INPUT, C2_INPUT, B2_LEVEL_PARAM, C2_LEVEL_PARAM); | |||||
} | |||||
// Set outputs | |||||
if (outputs[OUT1_OUTPUT].isConnected()) { | |||||
outputs[OUT1_OUTPUT].setChannels(activeEngines1); | |||||
for (int c = 0; c < activeEngines1; c += 4) | |||||
outputs[OUT1_OUTPUT].setVoltageSimd(out1[c / 4], c); | |||||
} | |||||
else if (outputs[OUT2_OUTPUT].isConnected()) { | |||||
for (int c = 0; c < activeEngines1; c += 4) | |||||
out2[c / 4] += out1[c / 4]; | |||||
activeEngines2 = std::max(activeEngines1, activeEngines2); | |||||
outputs[OUT2_OUTPUT].setChannels(activeEngines2); | |||||
for (int c = 0; c < activeEngines2; c += 4) | |||||
outputs[OUT2_OUTPUT].setVoltageSimd(out2[c / 4], c); | |||||
} | } | ||||
// Lights | // Lights | ||||
lights[OUT1_LIGHT + 0].setSmoothBrightness(out1 / 5.f, args.sampleTime); | |||||
lights[OUT1_LIGHT + 1].setSmoothBrightness(-out1 / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 0].setSmoothBrightness(out2 / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 1].setSmoothBrightness(-out2 / 5.f, args.sampleTime); | |||||
if (activeEngines1 == 1) { | |||||
float b = out1[0].s[0]; | |||||
lights[OUT1_LIGHT + 0].setSmoothBrightness(b / 5.f, args.sampleTime); | |||||
lights[OUT1_LIGHT + 1].setSmoothBrightness(-b / 5.f, args.sampleTime); | |||||
lights[OUT1_LIGHT + 2].setBrightness(0.f); | |||||
} | |||||
else { | |||||
float b = 10.f; | |||||
lights[OUT1_LIGHT + 0].setBrightness(0.0f); | |||||
lights[OUT1_LIGHT + 1].setBrightness(0.0f); | |||||
lights[OUT1_LIGHT + 2].setBrightness(b); | |||||
} | |||||
if (activeEngines2 == 1) { | |||||
float b = out2[0].s[0]; | |||||
lights[OUT2_LIGHT + 0].setSmoothBrightness(b / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 1].setSmoothBrightness(-b / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 2].setBrightness(0.f); | |||||
} | |||||
else { | |||||
float b = 10.f; | |||||
lights[OUT2_LIGHT + 0].setBrightness(0.0f); | |||||
lights[OUT2_LIGHT + 1].setBrightness(0.0f); | |||||
lights[OUT2_LIGHT + 2].setBrightness(b); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct ABCWidget : ModuleWidget { | struct ABCWidget : ModuleWidget { | ||||
ABCWidget(ABC *module) { | |||||
ABCWidget(ABC* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ABC.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ABC.svg"))); | ||||
@@ -95,19 +180,19 @@ struct ABCWidget : ModuleWidget { | |||||
addParam(createParam<Davies1900hRedKnob>(Vec(45, 204), module, ABC::B2_LEVEL_PARAM)); | addParam(createParam<Davies1900hRedKnob>(Vec(45, 204), module, ABC::B2_LEVEL_PARAM)); | ||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(45, 274), module, ABC::C2_LEVEL_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(45, 274), module, ABC::C2_LEVEL_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(7, 28), module, ABC::A1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 70), module, ABC::B1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 112), module, ABC::C1_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(7, 154), module, ABC::OUT1_OUTPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 195), module, ABC::A2_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 237), module, ABC::B2_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 279), module, ABC::C2_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(7, 321), module, ABC::OUT2_OUTPUT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(37, 162), module, ABC::OUT1_LIGHT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(37, 329), module, ABC::OUT2_LIGHT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 28), module, ABC::A1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 70), module, ABC::B1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 112), module, ABC::C1_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(7, 154), module, ABC::OUT1_OUTPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 195), module, ABC::A2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 237), module, ABC::B2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 279), module, ABC::C2_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(7, 321), module, ABC::OUT2_OUTPUT)); | |||||
addChild(createLight<MediumLight<RedGreenBlueLight>>(Vec(37, 162), module, ABC::OUT1_LIGHT)); | |||||
addChild(createLight<MediumLight<RedGreenBlueLight>>(Vec(37, 329), module, ABC::OUT2_LIGHT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelABC = createModel<ABC, ABCWidget>("ABC"); | |||||
Model* modelABC = createModel<ABC, ABCWidget>("ABC"); |
@@ -0,0 +1,362 @@ | |||||
#include "plugin.hpp" | |||||
#include "ChowDSP.hpp" | |||||
struct ChoppingKinky : Module { | |||||
enum ParamIds { | |||||
FOLD_A_PARAM, | |||||
FOLD_B_PARAM, | |||||
CV_A_PARAM, | |||||
CV_B_PARAM, | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
IN_A_INPUT, | |||||
IN_B_INPUT, | |||||
IN_GATE_INPUT, | |||||
CV_A_INPUT, | |||||
VCA_CV_A_INPUT, | |||||
CV_B_INPUT, | |||||
VCA_CV_B_INPUT, | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
OUT_CHOPP_OUTPUT, | |||||
OUT_A_OUTPUT, | |||||
OUT_B_OUTPUT, | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
LED_A_LIGHT, | |||||
LED_B_LIGHT, | |||||
NUM_LIGHTS | |||||
}; | |||||
enum { | |||||
CHANNEL_A, | |||||
CHANNEL_B, | |||||
CHANNEL_CHOPP, | |||||
NUM_CHANNELS | |||||
}; | |||||
static const int WAVESHAPE_CACHE_SIZE = 256; | |||||
float waveshapeA[WAVESHAPE_CACHE_SIZE + 1] = {}; | |||||
float waveshapeBPositive[WAVESHAPE_CACHE_SIZE + 1] = {}; | |||||
float waveshapeBNegative[WAVESHAPE_CACHE_SIZE + 1] = {}; | |||||
dsp::SchmittTrigger trigger; | |||||
bool outputAToChopp = false; | |||||
float previousA = 0.0; | |||||
chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS]; | |||||
int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling | |||||
dsp::BiquadFilter blockDCFilter; | |||||
bool blockDC = false; | |||||
ChoppingKinky() { | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
configParam(FOLD_A_PARAM, 0.f, 2.f, 0.f, "Gain/shape control for channel A"); | |||||
configParam(FOLD_B_PARAM, 0.f, 2.f, 0.f, "Gain/shape control for channel B"); | |||||
configParam(CV_A_PARAM, -1.f, 1.f, 0.f, "Channel A CV control attenuverter"); | |||||
configParam(CV_B_PARAM, -1.f, 1.f, 0.f, "Channel A CV control attenuverter"); | |||||
cacheWaveshaperResponses(); | |||||
// calculate up/downsampling rates | |||||
onSampleRateChange(); | |||||
} | |||||
void onSampleRateChange() override { | |||||
float sampleRate = APP->engine->getSampleRate(); | |||||
blockDCFilter.setParameters(dsp::BiquadFilter::HIGHPASS, 10.3f / sampleRate, M_SQRT1_2, 1.0f); | |||||
for (int channel_idx = 0; channel_idx < NUM_CHANNELS; channel_idx++) { | |||||
oversampler[channel_idx].setOversamplingIndex(oversamplingIndex); | |||||
oversampler[channel_idx].reset(sampleRate); | |||||
} | |||||
} | |||||
void process(const ProcessArgs& args) override { | |||||
float gainA = params[FOLD_A_PARAM].getValue(); | |||||
gainA += params[CV_A_PARAM].getValue() * inputs[CV_A_INPUT].getVoltage() / 10.f; | |||||
gainA += inputs[VCA_CV_A_INPUT].getVoltage() / 10.f; | |||||
gainA = std::max(gainA, 0.f); | |||||
// CV_B_INPUT is normalled to CV_A_INPUT (input with attenuverter) | |||||
float gainB = params[FOLD_B_PARAM].getValue(); | |||||
gainB += params[CV_B_PARAM].getValue() * inputs[CV_B_INPUT].getNormalVoltage(inputs[CV_A_INPUT].getVoltage()) / 10.f; | |||||
gainB += inputs[VCA_CV_B_INPUT].getVoltage() / 10.f; | |||||
gainB = std::max(gainB, 0.f); | |||||
const float inA = inputs[IN_A_INPUT].getVoltage(); | |||||
const float inB = inputs[IN_B_INPUT].getNormalVoltage(inputs[IN_A_INPUT].getVoltage()); | |||||
// if the CHOPP gate is wired in, do chop logic | |||||
if (inputs[IN_GATE_INPUT].isConnected()) { | |||||
// TODO: check rescale? | |||||
trigger.process(rescale(inputs[IN_GATE_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f)); | |||||
outputAToChopp = trigger.isHigh(); | |||||
} | |||||
// else zero-crossing detector on input A switches between A and B | |||||
else { | |||||
if (previousA > 0 && inA < 0) { | |||||
outputAToChopp = false; | |||||
} | |||||
else if (previousA < 0 && inA > 0) { | |||||
outputAToChopp = true; | |||||
} | |||||
} | |||||
previousA = inA; | |||||
const bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected(); | |||||
const bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired; | |||||
const bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired; | |||||
if (aIsRequired) { | |||||
oversampler[CHANNEL_A].upsample(inA * gainA); | |||||
} | |||||
if (bIsRequired) { | |||||
oversampler[CHANNEL_B].upsample(inB * gainB); | |||||
} | |||||
if (choppIsRequired) { | |||||
oversampler[CHANNEL_CHOPP].upsample(outputAToChopp ? 1.f : 0.f); | |||||
} | |||||
float* osBufferA = oversampler[CHANNEL_A].getOSBuffer(); | |||||
float* osBufferB = oversampler[CHANNEL_B].getOSBuffer(); | |||||
float* osBufferChopp = oversampler[CHANNEL_CHOPP].getOSBuffer(); | |||||
for (int i = 0; i < oversampler[0].getOversamplingRatio(); i++) { | |||||
if (aIsRequired) { | |||||
//osBufferA[i] = wavefolderAResponse(osBufferA[i]); | |||||
osBufferA[i] = wavefolderAResponseCached(osBufferA[i]); | |||||
} | |||||
if (bIsRequired) { | |||||
//osBufferB[i] = wavefolderBResponse(osBufferB[i]); | |||||
osBufferB[i] = wavefolderBResponseCached(osBufferB[i]); | |||||
} | |||||
if (choppIsRequired) { | |||||
osBufferChopp[i] = osBufferChopp[i] * osBufferA[i] + (1.f - osBufferChopp[i]) * osBufferB[i]; | |||||
} | |||||
} | |||||
float outA = aIsRequired ? oversampler[CHANNEL_A].downsample() : 0.f; | |||||
float outB = bIsRequired ? oversampler[CHANNEL_B].downsample() : 0.f; | |||||
float outChopp = choppIsRequired ? oversampler[CHANNEL_CHOPP].downsample() : 0.f; | |||||
if (blockDC) { | |||||
outChopp = blockDCFilter.process(outChopp); | |||||
} | |||||
outputs[OUT_A_OUTPUT].setVoltage(outA); | |||||
outputs[OUT_B_OUTPUT].setVoltage(outB); | |||||
outputs[OUT_CHOPP_OUTPUT].setVoltage(outChopp); | |||||
if (inputs[IN_GATE_INPUT].isConnected()) { | |||||
lights[LED_A_LIGHT].setSmoothBrightness((float) outputAToChopp, args.sampleTime); | |||||
lights[LED_B_LIGHT].setSmoothBrightness((float)(!outputAToChopp), args.sampleTime); | |||||
} | |||||
else { | |||||
lights[LED_A_LIGHT].setBrightness(0.f); | |||||
lights[LED_B_LIGHT].setBrightness(0.f); | |||||
} | |||||
} | |||||
float wavefolderAResponseCached(float x) { | |||||
if (x >= 0) { | |||||
float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1); | |||||
return interpolateLinear(waveshapeA, j); | |||||
} | |||||
else { | |||||
return -wavefolderAResponseCached(-x); | |||||
} | |||||
} | |||||
float wavefolderBResponseCached(float x) { | |||||
if (x >= 0) { | |||||
float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1); | |||||
return interpolateLinear(waveshapeBPositive, j); | |||||
} | |||||
else { | |||||
float j = rescale(clamp(-x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1); | |||||
return interpolateLinear(waveshapeBNegative, j); | |||||
} | |||||
} | |||||
static float wavefolderAResponse(float x) { | |||||
if (x < 0) { | |||||
return -wavefolderAResponse(-x); | |||||
} | |||||
float xScaleFactor = 1.f / 20.f; | |||||
float yScaleFactor = 12.5f; | |||||
x = x * xScaleFactor; | |||||
float piecewiseX1 = 0.087; | |||||
float piecewiseX2 = 0.245; | |||||
float piecewiseX3 = 0.3252; | |||||
if (x < piecewiseX1) { | |||||
float x_ = x / piecewiseX1; | |||||
return -0.38 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.8)) + 1.0 / (3 * 1.6) * std::sin(3 * M_PI * std::pow(x_, 0.8))); | |||||
} | |||||
else if (x < piecewiseX2) { | |||||
float x_ = x - piecewiseX1; | |||||
return -yScaleFactor * (-0.2 * std::sin(0.5 * M_PI * 12.69 * x_) - 0.24 * std::sin(1.5 * M_PI * 12.69 * x_)); | |||||
} | |||||
else if (x < piecewiseX3) { | |||||
float x_ = 9.8 * (x - piecewiseX2); | |||||
return -0.33 * yScaleFactor * std::sin(x_ / 0.165) * (1 + 0.9 * std::pow(x_, 3) / (1.0 + 2.0 * std::pow(x_, 6))); | |||||
} | |||||
else { | |||||
float x_ = (x - piecewiseX3) / 0.05; | |||||
return yScaleFactor * ((0.4274 - 0.031) * std::exp(-std::pow(x_, 2.0)) + 0.031); | |||||
} | |||||
} | |||||
static float wavefolderBResponse(float x) { | |||||
float xScaleFactor = 1.f / 20.f; | |||||
float yScaleFactor = 12.5f; | |||||
x = x * xScaleFactor; | |||||
// assymetric response | |||||
if (x > 0) { | |||||
float piecewiseX1 = 0.117; | |||||
float piecewiseX2 = 0.2837; | |||||
if (x < piecewiseX1) { | |||||
float x_ = x / piecewiseX1; | |||||
return -0.3 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.67)) + 1.0 / (3 * 0.8) * std::sin(3 * M_PI * std::pow(x_, 0.67))); | |||||
} | |||||
else if (x < piecewiseX2) { | |||||
float x_ = x - piecewiseX1; | |||||
return 0.35 * yScaleFactor * std::sin(12. * M_PI * x_); | |||||
} | |||||
else { | |||||
float x_ = (x - piecewiseX2); | |||||
return 0.57 * yScaleFactor * std::tanh(x_ / 0.03); | |||||
} | |||||
} | |||||
else { | |||||
float piecewiseX1 = -0.105; | |||||
float piecewiseX2 = -0.20722; | |||||
if (x > piecewiseX1) { | |||||
float x_ = x / piecewiseX1; | |||||
return 0.37 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.65)) + 1.0 / (3 * 1.2) * std::sin(3 * M_PI * std::pow(x_, 0.65))); | |||||
} | |||||
else if (x > piecewiseX2) { | |||||
float x_ = x - piecewiseX1; | |||||
return 0.2 * yScaleFactor * std::sin(15 * M_PI * x_) * (1.0 - 10.f * x_); | |||||
} | |||||
else { | |||||
float x_ = (x - piecewiseX2) / 0.07; | |||||
return yScaleFactor * ((0.4022 - 0.065) * std::exp(-std::pow(x_, 2)) + 0.065); | |||||
} | |||||
} | |||||
} | |||||
// functional form for waveshapers uses a lot of transcendental functions, so we cache | |||||
// the response in a LUT | |||||
void cacheWaveshaperResponses() { | |||||
for (int i = 0; i < WAVESHAPE_CACHE_SIZE; ++i) { | |||||
float x = rescale(i, 0, WAVESHAPE_CACHE_SIZE - 1, 0.0, 10.f); | |||||
waveshapeA[i] = wavefolderAResponse(x); | |||||
waveshapeBPositive[i] = wavefolderBResponse(+x); | |||||
waveshapeBNegative[i] = wavefolderBResponse(-x); | |||||
} | |||||
} | |||||
json_t* dataToJson() override { | |||||
json_t* rootJ = json_object(); | |||||
json_object_set_new(rootJ, "filterDC", json_boolean(blockDC)); | |||||
json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex())); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t* rootJ) override { | |||||
json_t* filterDCJ = json_object_get(rootJ, "filterDC"); | |||||
if (filterDCJ) { | |||||
blockDC = json_boolean_value(filterDCJ); | |||||
} | |||||
json_t* oversamplingIndexJ = json_object_get(rootJ, "oversamplingIndex"); | |||||
if (oversamplingIndexJ) { | |||||
oversamplingIndex = json_integer_value(oversamplingIndexJ); | |||||
onSampleRateChange(); | |||||
} | |||||
} | |||||
}; | |||||
struct ChoppingKinkyWidget : ModuleWidget { | |||||
ChoppingKinkyWidget(ChoppingKinky* module) { | |||||
setModule(module); | |||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ChoppingKinky.svg"))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(createParamCentered<Davies1900hLargeWhiteKnob>(mm2px(Vec(26.051, 21.999)), module, ChoppingKinky::FOLD_A_PARAM)); | |||||
addParam(createParamCentered<Davies1900hLargeWhiteKnob>(mm2px(Vec(26.051, 62.768)), module, ChoppingKinky::FOLD_B_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(10.266, 83.297)), module, ChoppingKinky::CV_A_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(30.277, 83.297)), module, ChoppingKinky::CV_B_PARAM)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.127, 27.843)), module, ChoppingKinky::IN_A_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(26.057, 42.228)), module, ChoppingKinky::IN_GATE_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.104, 56.382)), module, ChoppingKinky::IN_B_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.209, 98.499)), module, ChoppingKinky::CV_A_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.259, 98.499)), module, ChoppingKinky::VCA_CV_A_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.308, 98.499)), module, ChoppingKinky::CV_B_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.358, 98.499)), module, ChoppingKinky::VCA_CV_B_INPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(20.23, 109.669)), module, ChoppingKinky::OUT_CHOPP_OUTPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(31.091, 110.747)), module, ChoppingKinky::OUT_B_OUTPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(9.589, 110.777)), module, ChoppingKinky::OUT_A_OUTPUT)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(26.057, 33.307)), module, ChoppingKinky::LED_A_LIGHT)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(26.057, 51.53)), module, ChoppingKinky::LED_B_LIGHT)); | |||||
} | |||||
void appendContextMenu(Menu* menu) override { | |||||
ChoppingKinky* module = dynamic_cast<ChoppingKinky*>(this->module); | |||||
assert(module); | |||||
menu->addChild(new MenuSeparator()); | |||||
struct DCMenuItem : MenuItem { | |||||
ChoppingKinky* module; | |||||
void onAction(const event::Action& e) override { | |||||
module->blockDC ^= true; | |||||
} | |||||
}; | |||||
DCMenuItem* dcItem = createMenuItem<DCMenuItem>("Block DC on Chopp", CHECKMARK(module->blockDC)); | |||||
dcItem->module = module; | |||||
menu->addChild(dcItem); | |||||
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); | |||||
} | |||||
} | |||||
}; | |||||
Model* modelChoppingKinky = createModel<ChoppingKinky, ChoppingKinkyWidget>("ChoppingKinky"); |
@@ -0,0 +1,422 @@ | |||||
#pragma once | |||||
#include <rack.hpp> | |||||
namespace chowdsp { | |||||
// code taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/, commit 21701fb | |||||
// * AAFilter.hpp | |||||
// * VariableOversampling.hpp | |||||
// * oversampling.hpp | |||||
// * iir.hpp | |||||
template <int ORDER, typename T = float> | |||||
struct IIRFilter { | |||||
/** transfer function numerator coefficients: b_0, b_1, etc.*/ | |||||
T b[ORDER] = {}; | |||||
/** transfer function denominator coefficients: a_0, a_1, etc.*/ | |||||
T a[ORDER] = {}; | |||||
/** filter state */ | |||||
T z[ORDER]; | |||||
IIRFilter() { | |||||
reset(); | |||||
} | |||||
void reset() { | |||||
std::fill(z, &z[ORDER], 0.0f); | |||||
} | |||||
void setCoefficients(const T* b, const T* a) { | |||||
for (int i = 0; i < ORDER; i++) { | |||||
this->b[i] = b[i]; | |||||
} | |||||
for (int i = 1; i < ORDER; i++) { | |||||
this->a[i] = a[i]; | |||||
} | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if <N == 2, T>::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
z[1] = x * b[1] - y * a[1]; | |||||
return y; | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if <N == 3, T>::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
z[1] = z[2] + x * b[1] - y * a[1]; | |||||
z[2] = x * b[2] - y * a[2]; | |||||
return y; | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if < (N > 3), T >::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
for (int i = 1; i < ORDER - 1; ++i) | |||||
z[i] = z[i + 1] + x * b[i] - y * a[i]; | |||||
z[ORDER - 1] = x * b[ORDER - 1] - y * a[ORDER - 1]; | |||||
return y; | |||||
} | |||||
/** Computes the complex transfer function $H(s)$ at a particular frequency | |||||
s: normalized angular frequency equal to $2 \pi f / f_{sr}$ ($\pi$ is the Nyquist frequency) | |||||
*/ | |||||
std::complex<T> getTransferFunction(T s) { | |||||
// Compute sum(a_k z^-k) / sum(b_k z^-k) where z = e^(i s) | |||||
std::complex<T> bSum(b[0], 0); | |||||
std::complex<T> aSum(1, 0); | |||||
for (int i = 1; i < ORDER; i++) { | |||||
T p = -i * s; | |||||
std::complex<T> z(simd::cos(p), simd::sin(p)); | |||||
bSum += b[i] * z; | |||||
aSum += a[i - 1] * z; | |||||
} | |||||
return bSum / aSum; | |||||
} | |||||
T getFrequencyResponse(T f) { | |||||
return simd::abs(getTransferFunction(2 * M_PI * f)); | |||||
} | |||||
T getFrequencyPhase(T f) { | |||||
return simd::arg(getTransferFunction(2 * M_PI * f)); | |||||
} | |||||
}; | |||||
template <typename T = float> | |||||
struct TBiquadFilter : IIRFilter<3, T> { | |||||
enum Type { | |||||
LOWPASS, | |||||
HIGHPASS, | |||||
LOWSHELF, | |||||
HIGHSHELF, | |||||
BANDPASS, | |||||
PEAK, | |||||
NOTCH, | |||||
NUM_TYPES | |||||
}; | |||||
TBiquadFilter() { | |||||
setParameters(LOWPASS, 0.f, 0.f, 1.f); | |||||
} | |||||
/** Calculates and sets the biquad transfer function coefficients. | |||||
f: normalized frequency (cutoff frequency / sample rate), must be less than 0.5 | |||||
Q: quality factor | |||||
V: gain | |||||
*/ | |||||
void setParameters(Type type, float f, float Q, float V) { | |||||
float K = std::tan(M_PI * f); | |||||
switch (type) { | |||||
case LOWPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = K * K * norm; | |||||
this->b[1] = 2.f * this->b[0]; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case HIGHPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = norm; | |||||
this->b[1] = -2.f * this->b[0]; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case LOWSHELF: { | |||||
float sqrtV = std::sqrt(V); | |||||
if (V >= 1.f) { | |||||
float norm = 1.f / (1.f + M_SQRT2 * K + K * K); | |||||
this->b[0] = (1.f + M_SQRT2 * sqrtV * K + V * K * K) * norm; | |||||
this->b[1] = 2.f * (V * K * K - 1.f) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * sqrtV * K + V * K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
} | |||||
else { | |||||
float norm = 1.f / (1.f + M_SQRT2 / sqrtV * K + K * K / V); | |||||
this->b[0] = (1.f + M_SQRT2 * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K / V - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 / sqrtV * K + K * K / V) * norm; | |||||
} | |||||
} break; | |||||
case HIGHSHELF: { | |||||
float sqrtV = std::sqrt(V); | |||||
if (V >= 1.f) { | |||||
float norm = 1.f / (1.f + M_SQRT2 * K + K * K); | |||||
this->b[0] = (V + M_SQRT2 * sqrtV * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - V) * norm; | |||||
this->b[2] = (V - M_SQRT2 * sqrtV * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
} | |||||
else { | |||||
float norm = 1.f / (1.f / V + M_SQRT2 / sqrtV * K + K * K); | |||||
this->b[0] = (1.f + M_SQRT2 * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f / V) * norm; | |||||
this->a[2] = (1.f / V - M_SQRT2 / sqrtV * K + K * K) * norm; | |||||
} | |||||
} break; | |||||
case BANDPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = K / Q * norm; | |||||
this->b[1] = 0.f; | |||||
this->b[2] = -this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case PEAK: { | |||||
float c = 1.0f / K; | |||||
float phi = c * c; | |||||
float Knum = c / Q; | |||||
float Kdenom = Knum; | |||||
if (V > 1.0f) | |||||
Knum *= V; | |||||
else | |||||
Kdenom /= V; | |||||
float norm = phi + Kdenom + 1.0; | |||||
this->b[0] = (phi + Knum + 1.0f) / norm; | |||||
this->b[1] = 2.0f * (1.0f - phi) / norm; | |||||
this->b[2] = (phi - Knum + 1.0f) / norm; | |||||
this->a[1] = 2.0f * (1.0f - phi) / norm; | |||||
this->a[2] = (phi - Kdenom + 1.0f) / norm; | |||||
} break; | |||||
case NOTCH: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = (1.f + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = this->b[1]; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
default: break; | |||||
} | |||||
} | |||||
}; | |||||
typedef TBiquadFilter<> BiquadFilter; | |||||
/** | |||||
High-order filter to be used for anti-aliasing or anti-imaging. | |||||
The template parameter N should be 1/2 the desired filter order. | |||||
Currently uses an 2*N-th order Butterworth filter. | |||||
source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp | |||||
*/ | |||||
template<int N> | |||||
class AAFilter { | |||||
public: | |||||
AAFilter() = default; | |||||
/** Calculate Q values for a Butterworth filter of a given order */ | |||||
static std::vector<float> calculateButterQs(int order) { | |||||
const int lim = int (order / 2); | |||||
std::vector<float> Qs; | |||||
for (int k = 1; k <= lim; ++k) { | |||||
auto b = -2.0f * std::cos((2.0f * k + order - 1) * 3.14159 / (2.0f * order)); | |||||
Qs.push_back(1.0f / b); | |||||
} | |||||
std::reverse(Qs.begin(), Qs.end()); | |||||
return Qs; | |||||
} | |||||
/** | |||||
* Resets the filter to process at a new sample rate. | |||||
* | |||||
* @param sampleRate: The base (i.e. pre-oversampling) sample rate of the audio being processed | |||||
* @param osRatio: The oversampling ratio at which the filter is being used | |||||
*/ | |||||
void reset(float sampleRate, int osRatio) { | |||||
float fc = 0.98f * (sampleRate / 2.0f); | |||||
auto Qs = calculateButterQs(2 * N); | |||||
for (int i = 0; i < N; ++i) | |||||
filters[i].setParameters(BiquadFilter::Type::LOWPASS, fc / (osRatio * sampleRate), Qs[i], 1.0f); | |||||
} | |||||
inline float process(float x) noexcept { | |||||
for (int i = 0; i < N; ++i) | |||||
x = filters[i].process(x); | |||||
return x; | |||||
} | |||||
private: | |||||
BiquadFilter filters[N]; | |||||
}; | |||||
/** | |||||
* Base class for oversampling of any order | |||||
* source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/oversampling.hpp | |||||
*/ | |||||
class BaseOversampling { | |||||
public: | |||||
BaseOversampling() = default; | |||||
virtual ~BaseOversampling() {} | |||||
/** Resets the oversampler for processing at some base sample rate */ | |||||
virtual void reset(float /*baseSampleRate*/) = 0; | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
virtual void upsample(float) noexcept = 0; | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
virtual float downsample() noexcept = 0; | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
virtual float* getOSBuffer() noexcept = 0; | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process. | |||||
To use, create an object and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
for(int k = 0; k < ratio; k++) | |||||
oversample.osBuffer[k] = processSample(oversample.osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
*/ | |||||
template<int ratio, int filtN = 4> | |||||
class Oversampling : public BaseOversampling { | |||||
public: | |||||
Oversampling() = default; | |||||
virtual ~Oversampling() {} | |||||
void reset(float baseSampleRate) override { | |||||
aaFilter.reset(baseSampleRate, ratio); | |||||
aiFilter.reset(baseSampleRate, ratio); | |||||
std::fill(osBuffer, &osBuffer[ratio], 0.0f); | |||||
} | |||||
inline void upsample(float x) noexcept override { | |||||
osBuffer[0] = ratio * x; | |||||
std::fill(&osBuffer[1], &osBuffer[ratio], 0.0f); | |||||
for (int k = 0; k < ratio; k++) | |||||
osBuffer[k] = aiFilter.process(osBuffer[k]); | |||||
} | |||||
inline float downsample() noexcept override { | |||||
float y = 0.0f; | |||||
for (int k = 0; k < ratio; k++) | |||||
y = aaFilter.process(osBuffer[k]); | |||||
return y; | |||||
} | |||||
inline float* getOSBuffer() noexcept override { | |||||
return osBuffer; | |||||
} | |||||
float osBuffer[ratio]; | |||||
private: | |||||
AAFilter<filtN> aaFilter; // anti-aliasing filter | |||||
AAFilter<filtN> aiFilter; // anti-imaging filter | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process, with variable | |||||
oversampling factor. To use, create an object, set the oversampling | |||||
factor using `setOversamplingindex()` and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
float* osBuffer = oversample.getOSBuffer(); | |||||
for(int k = 0; k < ratio; k++) | |||||
osBuffer[k] = processSample(osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
source (modified): https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/VariableOversampling.hpp | |||||
*/ | |||||
template<int filtN = 4> | |||||
class VariableOversampling { | |||||
public: | |||||
VariableOversampling() = default; | |||||
/** Prepare the oversampler to process audio at a given sample rate */ | |||||
void reset(float sampleRate) { | |||||
for (auto* os : oss) | |||||
os->reset(sampleRate); | |||||
} | |||||
/** Sets the oversampling factor as 2^idx */ | |||||
void setOversamplingIndex(int newIdx) { | |||||
osIdx = newIdx; | |||||
} | |||||
/** Returns the oversampling index */ | |||||
int getOversamplingIndex() const noexcept { | |||||
return osIdx; | |||||
} | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
inline void upsample(float x) noexcept { | |||||
oss[osIdx]->upsample(x); | |||||
} | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
inline float downsample() noexcept { | |||||
return oss[osIdx]->downsample(); | |||||
} | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
inline float* getOSBuffer() noexcept { | |||||
return oss[osIdx]->getOSBuffer(); | |||||
} | |||||
/** Returns the current oversampling factor */ | |||||
int getOversamplingRatio() const noexcept { | |||||
return 1 << osIdx; | |||||
} | |||||
private: | |||||
enum { | |||||
NumOS = 5, // number of oversampling options | |||||
}; | |||||
int osIdx = 0; | |||||
Oversampling < 1 << 0, filtN > os0; // 1x | |||||
Oversampling < 1 << 1, filtN > os1; // 2x | |||||
Oversampling < 1 << 2, filtN > os2; // 4x | |||||
Oversampling < 1 << 3, filtN > os3; // 8x | |||||
Oversampling < 1 << 4, filtN > os4; // 16x | |||||
BaseOversampling* oss[NumOS] = { &os0, &os1, &os2, &os3, &os4 }; | |||||
}; | |||||
} // namespace chowdsp |
@@ -1,6 +1,5 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
struct DualAtenuverter : Module { | struct DualAtenuverter : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
ATEN1_PARAM, | ATEN1_PARAM, | ||||
@@ -20,10 +19,8 @@ struct DualAtenuverter : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
enum LightIds { | enum LightIds { | ||||
OUT1_POS_LIGHT, | |||||
OUT1_NEG_LIGHT, | |||||
OUT2_POS_LIGHT, | |||||
OUT2_NEG_LIGHT, | |||||
ENUMS(OUT1_LIGHT, 3), | |||||
ENUMS(OUT2_LIGHT, 3), | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
@@ -35,24 +32,70 @@ struct DualAtenuverter : Module { | |||||
configParam(OFFSET2_PARAM, -10.0, 10.0, 0.0, "Ch 2 offset", " V"); | configParam(OFFSET2_PARAM, -10.0, 10.0, 0.0, "Ch 2 offset", " V"); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
float out1 = inputs[IN1_INPUT].getVoltage() * params[ATEN1_PARAM].getValue() + params[OFFSET1_PARAM].getValue(); | |||||
float out2 = inputs[IN2_INPUT].getVoltage() * params[ATEN2_PARAM].getValue() + params[OFFSET2_PARAM].getValue(); | |||||
out1 = clamp(out1, -10.f, 10.f); | |||||
out2 = clamp(out2, -10.f, 10.f); | |||||
outputs[OUT1_OUTPUT].setVoltage(out1); | |||||
outputs[OUT2_OUTPUT].setVoltage(out2); | |||||
lights[OUT1_POS_LIGHT].setSmoothBrightness(out1 / 5.f, args.sampleTime); | |||||
lights[OUT1_NEG_LIGHT].setSmoothBrightness(-out1 / 5.f, args.sampleTime); | |||||
lights[OUT2_POS_LIGHT].setSmoothBrightness(out2 / 5.f, args.sampleTime); | |||||
lights[OUT2_NEG_LIGHT].setSmoothBrightness(-out2 / 5.f, args.sampleTime); | |||||
void process(const ProcessArgs& args) override { | |||||
using simd::float_4; | |||||
float_4 out1[4] = {}; | |||||
float_4 out2[4] = {}; | |||||
int channels1 = inputs[IN1_INPUT].getChannels(); | |||||
channels1 = channels1 > 0 ? channels1 : 1; | |||||
int channels2 = inputs[IN2_INPUT].getChannels(); | |||||
channels2 = channels2 > 0 ? channels2 : 1; | |||||
float att1 = params[ATEN1_PARAM].getValue(); | |||||
float att2 = params[ATEN2_PARAM].getValue(); | |||||
float offset1 = params[OFFSET1_PARAM].getValue(); | |||||
float offset2 = params[OFFSET2_PARAM].getValue(); | |||||
for (int c = 0; c < channels1; c += 4) { | |||||
out1[c / 4] = clamp(inputs[IN1_INPUT].getVoltageSimd<float_4>(c) * att1 + offset1, -10.f, 10.f); | |||||
} | |||||
for (int c = 0; c < channels2; c += 4) { | |||||
out2[c / 4] = clamp(inputs[IN2_INPUT].getVoltageSimd<float_4>(c) * att2 + offset2, -10.f, 10.f); | |||||
} | |||||
outputs[OUT1_OUTPUT].setChannels(channels1); | |||||
outputs[OUT2_OUTPUT].setChannels(channels2); | |||||
for (int c = 0; c < channels1; c += 4) { | |||||
outputs[OUT1_OUTPUT].setVoltageSimd(out1[c / 4], c); | |||||
} | |||||
for (int c = 0; c < channels2; c += 4) { | |||||
outputs[OUT2_OUTPUT].setVoltageSimd(out2[c / 4], c); | |||||
} | |||||
float light1 = outputs[OUT1_OUTPUT].getVoltageSum() / channels1; | |||||
float light2 = outputs[OUT2_OUTPUT].getVoltageSum() / channels2; | |||||
if (channels1 == 1) { | |||||
lights[OUT1_LIGHT + 0].setSmoothBrightness(light1 / 5.f, args.sampleTime); | |||||
lights[OUT1_LIGHT + 1].setSmoothBrightness(-light1 / 5.f, args.sampleTime); | |||||
lights[OUT1_LIGHT + 2].setBrightness(0.0f); | |||||
} | |||||
else { | |||||
lights[OUT1_LIGHT + 0].setBrightness(0.0f); | |||||
lights[OUT1_LIGHT + 1].setBrightness(0.0f); | |||||
lights[OUT1_LIGHT + 2].setBrightness(10.0f); | |||||
} | |||||
if (channels2 == 1) { | |||||
lights[OUT2_LIGHT + 0].setSmoothBrightness(light2 / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 1].setSmoothBrightness(-light2 / 5.f, args.sampleTime); | |||||
lights[OUT2_LIGHT + 2].setBrightness(0.0f); | |||||
} | |||||
else { | |||||
lights[OUT2_LIGHT + 0].setBrightness(0.0f); | |||||
lights[OUT2_LIGHT + 1].setBrightness(0.0f); | |||||
lights[OUT2_LIGHT + 2].setBrightness(10.0f); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct DualAtenuverterWidget : ModuleWidget { | struct DualAtenuverterWidget : ModuleWidget { | ||||
DualAtenuverterWidget(DualAtenuverter *module) { | |||||
DualAtenuverterWidget(DualAtenuverter* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DualAtenuverter.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DualAtenuverter.svg"))); | ||||
@@ -64,16 +107,16 @@ struct DualAtenuverterWidget : ModuleWidget { | |||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(20, 201), module, DualAtenuverter::ATEN2_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(20, 201), module, DualAtenuverter::ATEN2_PARAM)); | ||||
addParam(createParam<Davies1900hRedKnob>(Vec(20, 260), module, DualAtenuverter::OFFSET2_PARAM)); | addParam(createParam<Davies1900hRedKnob>(Vec(20, 260), module, DualAtenuverter::OFFSET2_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(7, 152), module, DualAtenuverter::IN1_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(43, 152), module, DualAtenuverter::OUT1_OUTPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 152), module, DualAtenuverter::IN1_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(43, 152), module, DualAtenuverter::OUT1_OUTPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 319), module, DualAtenuverter::IN2_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(43, 319), module, DualAtenuverter::OUT2_OUTPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 319), module, DualAtenuverter::IN2_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(43, 319), module, DualAtenuverter::OUT2_OUTPUT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(33, 143), module, DualAtenuverter::OUT1_POS_LIGHT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(33, 311), module, DualAtenuverter::OUT2_POS_LIGHT)); | |||||
addChild(createLight<MediumLight<RedGreenBlueLight>>(Vec(33, 143), module, DualAtenuverter::OUT1_LIGHT)); | |||||
addChild(createLight<MediumLight<RedGreenBlueLight>>(Vec(33, 311), module, DualAtenuverter::OUT2_LIGHT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelDualAtenuverter = createModel<DualAtenuverter, DualAtenuverterWidget>("DualAtenuverter"); | |||||
Model* modelDualAtenuverter = createModel<DualAtenuverter, DualAtenuverterWidget>("DualAtenuverter"); |
@@ -1,5 +1,6 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
using simd::float_4; | |||||
struct EvenVCO : Module { | struct EvenVCO : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
@@ -25,22 +26,21 @@ struct EvenVCO : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
float phase = 0.0; | |||||
float_4 phase[4] = {}; | |||||
float_4 tri[4] = {}; | |||||
/** The value of the last sync input */ | /** The value of the last sync input */ | ||||
float sync = 0.0; | float sync = 0.0; | ||||
/** The outputs */ | /** The outputs */ | ||||
float tri = 0.0; | |||||
/** Whether we are past the pulse width already */ | /** Whether we are past the pulse width already */ | ||||
bool halfPhase = false; | |||||
dsp::MinBlepGenerator<16, 32> triSquareMinBlep; | |||||
dsp::MinBlepGenerator<16, 32> triMinBlep; | |||||
dsp::MinBlepGenerator<16, 32> sineMinBlep; | |||||
dsp::MinBlepGenerator<16, 32> doubleSawMinBlep; | |||||
dsp::MinBlepGenerator<16, 32> sawMinBlep; | |||||
dsp::MinBlepGenerator<16, 32> squareMinBlep; | |||||
bool halfPhase[PORT_MAX_CHANNELS] = {}; | |||||
dsp::RCFilter triFilter; | |||||
dsp::MinBlepGenerator<16, 32> triSquareMinBlep[PORT_MAX_CHANNELS]; | |||||
dsp::MinBlepGenerator<16, 32> triMinBlep[PORT_MAX_CHANNELS]; | |||||
dsp::MinBlepGenerator<16, 32> sineMinBlep[PORT_MAX_CHANNELS]; | |||||
dsp::MinBlepGenerator<16, 32> doubleSawMinBlep[PORT_MAX_CHANNELS]; | |||||
dsp::MinBlepGenerator<16, 32> sawMinBlep[PORT_MAX_CHANNELS]; | |||||
dsp::MinBlepGenerator<16, 32> squareMinBlep[PORT_MAX_CHANNELS]; | |||||
EvenVCO() { | EvenVCO() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); | ||||
@@ -49,102 +49,184 @@ struct EvenVCO : Module { | |||||
configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width"); | configParam(PWM_PARAM, -1.0, 1.0, 0.0, "Pulse width"); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
void process(const ProcessArgs& args) override { | |||||
int channels_pitch1 = inputs[PITCH1_INPUT].getChannels(); | |||||
int channels_pitch2 = inputs[PITCH2_INPUT].getChannels(); | |||||
int channels = 1; | |||||
channels = std::max(channels, channels_pitch1); | |||||
channels = std::max(channels, channels_pitch2); | |||||
float pitch_0 = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; | |||||
// Compute frequency, pitch is 1V/oct | // Compute frequency, pitch is 1V/oct | ||||
float pitch = 1.f + std::round(params[OCTAVE_PARAM].getValue()) + params[TUNE_PARAM].getValue() / 12.f; | |||||
pitch += inputs[PITCH1_INPUT].getVoltage() + inputs[PITCH2_INPUT].getVoltage(); | |||||
pitch += inputs[FM_INPUT].getVoltage() / 4.f; | |||||
float freq = dsp::FREQ_C4 * std::pow(2.f, pitch); | |||||
freq = clamp(freq, 0.f, 20000.f); | |||||
// Pulse width | |||||
float pw = params[PWM_PARAM].getValue() + inputs[PWM_INPUT].getVoltage() / 5.f; | |||||
const float minPw = 0.05; | |||||
pw = rescale(clamp(pw, -1.f, 1.f), -1.f, 1.f, minPw, 1.f - minPw); | |||||
// Advance phase | |||||
float deltaPhase = clamp(freq * args.sampleTime, 1e-6f, 0.5f); | |||||
float oldPhase = phase; | |||||
phase += deltaPhase; | |||||
if (oldPhase < 0.5 && phase >= 0.5) { | |||||
float crossing = -(phase - 0.5) / deltaPhase; | |||||
triSquareMinBlep.insertDiscontinuity(crossing, 2.f); | |||||
doubleSawMinBlep.insertDiscontinuity(crossing, -2.f); | |||||
float_4 pitch[4] = {}; | |||||
for (int c = 0; c < channels; c += 4) | |||||
pitch[c / 4] = pitch_0; | |||||
if (inputs[PITCH1_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels; c += 4) | |||||
pitch[c / 4] += inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c); | |||||
} | } | ||||
if (!halfPhase && phase >= pw) { | |||||
float crossing = -(phase - pw) / deltaPhase; | |||||
squareMinBlep.insertDiscontinuity(crossing, 2.f); | |||||
halfPhase = true; | |||||
if (inputs[PITCH2_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels; c += 4) | |||||
pitch[c / 4] += inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(c); | |||||
} | } | ||||
// Reset phase if at end of cycle | |||||
if (phase >= 1.f) { | |||||
phase -= 1.f; | |||||
float crossing = -phase / deltaPhase; | |||||
triSquareMinBlep.insertDiscontinuity(crossing, -2.f); | |||||
doubleSawMinBlep.insertDiscontinuity(crossing, -2.f); | |||||
squareMinBlep.insertDiscontinuity(crossing, -2.f); | |||||
sawMinBlep.insertDiscontinuity(crossing, -2.f); | |||||
halfPhase = false; | |||||
if (inputs[FM_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels; c += 4) | |||||
pitch[c / 4] += inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) / 4.f; | |||||
} | |||||
float_4 freq[4] = {}; | |||||
for (int c = 0; c < channels; c += 4) { | |||||
freq[c / 4] = dsp::FREQ_C4 * simd::pow(2.f, pitch[c / 4]); | |||||
freq[c / 4] = clamp(freq[c / 4], 0.f, 20000.f); | |||||
} | |||||
// Pulse width | |||||
float_4 pw[4] = {}; | |||||
for (int c = 0; c < channels; c += 4) | |||||
pw[c / 4] = params[PWM_PARAM].getValue(); | |||||
if (inputs[PWM_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels; c += 4) | |||||
pw[c / 4] += inputs[PWM_INPUT].getPolyVoltageSimd<float_4>(c) / 5.f; | |||||
} | |||||
float_4 deltaPhase[4] = {}; | |||||
float_4 oldPhase[4] = {}; | |||||
for (int c = 0; c < channels; c += 4) { | |||||
pw[c / 4] = rescale(clamp(pw[c / 4], -1.0f, 1.0f), -1.0f, 1.0f, 0.05f, 1.0f - 0.05f); | |||||
// Advance phase | |||||
deltaPhase[c / 4] = clamp(freq[c / 4] * args.sampleTime, 1e-6f, 0.5f); | |||||
oldPhase[c / 4] = phase[c / 4]; | |||||
phase[c / 4] += deltaPhase[c / 4]; | |||||
} | |||||
// the next block can't be done with SIMD instructions: | |||||
for (int c = 0; c < channels; c++) { | |||||
if (oldPhase[c / 4].s[c % 4] < 0.5 && phase[c / 4].s[c % 4] >= 0.5) { | |||||
float crossing = -(phase[c / 4].s[c % 4] - 0.5) / deltaPhase[c / 4].s[c % 4]; | |||||
triSquareMinBlep[c].insertDiscontinuity(crossing, 2.f); | |||||
doubleSawMinBlep[c].insertDiscontinuity(crossing, -2.f); | |||||
} | |||||
if (!halfPhase[c] && phase[c / 4].s[c % 4] >= pw[c / 4].s[c % 4]) { | |||||
float crossing = -(phase[c / 4].s[c % 4] - pw[c / 4].s[c % 4]) / deltaPhase[c / 4].s[c % 4]; | |||||
squareMinBlep[c].insertDiscontinuity(crossing, 2.f); | |||||
halfPhase[c] = true; | |||||
} | |||||
// Reset phase if at end of cycle | |||||
if (phase[c / 4].s[c % 4] >= 1.f) { | |||||
phase[c / 4].s[c % 4] -= 1.f; | |||||
float crossing = -phase[c / 4].s[c % 4] / deltaPhase[c / 4].s[c % 4]; | |||||
triSquareMinBlep[c].insertDiscontinuity(crossing, -2.f); | |||||
doubleSawMinBlep[c].insertDiscontinuity(crossing, -2.f); | |||||
squareMinBlep[c].insertDiscontinuity(crossing, -2.f); | |||||
sawMinBlep[c].insertDiscontinuity(crossing, -2.f); | |||||
halfPhase[c] = false; | |||||
} | |||||
} | |||||
float_4 triSquareMinBlepOut[4] = {}; | |||||
float_4 doubleSawMinBlepOut[4] = {}; | |||||
float_4 sawMinBlepOut[4] = {}; | |||||
float_4 squareMinBlepOut[4] = {}; | |||||
float_4 triSquare[4] = {}; | |||||
float_4 sine[4] = {}; | |||||
float_4 doubleSaw[4] = {}; | |||||
float_4 even[4] = {}; | |||||
float_4 saw[4] = {}; | |||||
float_4 square[4] = {}; | |||||
float_4 triOut[4] = {}; | |||||
for (int c = 0; c < channels; c++) { | |||||
triSquareMinBlepOut[c / 4].s[c % 4] = triSquareMinBlep[c].process(); | |||||
doubleSawMinBlepOut[c / 4].s[c % 4] = doubleSawMinBlep[c].process(); | |||||
sawMinBlepOut[c / 4].s[c % 4] = sawMinBlep[c].process(); | |||||
squareMinBlepOut[c / 4].s[c % 4] = squareMinBlep[c].process(); | |||||
} | } | ||||
// Outputs | // Outputs | ||||
float triSquare = (phase < 0.5) ? -1.f : 1.f; | |||||
triSquare += triSquareMinBlep.process(); | |||||
// Integrate square for triangle | |||||
tri += 4.f * triSquare * freq * args.sampleTime; | |||||
tri *= (1.f - 40.f * args.sampleTime); | |||||
float sine = -std::cos(2*M_PI * phase); | |||||
float doubleSaw = (phase < 0.5) ? (-1.f + 4.f*phase) : (-1.f + 4.f*(phase - 0.5)); | |||||
doubleSaw += doubleSawMinBlep.process(); | |||||
float even = 0.55 * (doubleSaw + 1.27 * sine); | |||||
float saw = -1.f + 2.f*phase; | |||||
saw += sawMinBlep.process(); | |||||
float square = (phase < pw) ? -1.f : 1.f; | |||||
square += squareMinBlep.process(); | |||||
// Set outputs | |||||
outputs[TRI_OUTPUT].setVoltage(5.f*tri); | |||||
outputs[SINE_OUTPUT].setVoltage(5.f*sine); | |||||
outputs[EVEN_OUTPUT].setVoltage(5.f*even); | |||||
outputs[SAW_OUTPUT].setVoltage(5.f*saw); | |||||
outputs[SQUARE_OUTPUT].setVoltage(5.f*square); | |||||
outputs[TRI_OUTPUT].setChannels(channels); | |||||
outputs[SINE_OUTPUT].setChannels(channels); | |||||
outputs[EVEN_OUTPUT].setChannels(channels); | |||||
outputs[SAW_OUTPUT].setChannels(channels); | |||||
outputs[SQUARE_OUTPUT].setChannels(channels); | |||||
for (int c = 0; c < channels; c += 4) { | |||||
triSquare[c / 4] = simd::ifelse((phase[c / 4] < 0.5f), -1.f, +1.f); | |||||
triSquare[c / 4] += triSquareMinBlepOut[c / 4]; | |||||
// Integrate square for triangle | |||||
tri[c / 4] += (4.f * triSquare[c / 4]) * (freq[c / 4] * args.sampleTime); | |||||
tri[c / 4] *= (1.f - 40.f * args.sampleTime); | |||||
triOut[c / 4] = 5.f * tri[c / 4]; | |||||
sine[c / 4] = 5.f * simd::cos(2 * M_PI * phase[c / 4]); | |||||
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] *= 5.f; | |||||
even[c / 4] = 0.55 * (doubleSaw[c / 4] + 1.27 * sine[c / 4]); | |||||
saw[c / 4] = -1.f + 2.f * phase[c / 4]; | |||||
saw[c / 4] += sawMinBlepOut[c / 4]; | |||||
saw[c / 4] *= 5.f; | |||||
square[c / 4] = simd::ifelse((phase[c / 4] < pw[c / 4]), -1.f, +1.f); | |||||
square[c / 4] += squareMinBlepOut[c / 4]; | |||||
square[c / 4] *= 5.f; | |||||
// Set outputs | |||||
outputs[TRI_OUTPUT].setVoltageSimd(triOut[c / 4], c); | |||||
outputs[SINE_OUTPUT].setVoltageSimd(sine[c / 4], c); | |||||
outputs[EVEN_OUTPUT].setVoltageSimd(even[c / 4], c); | |||||
outputs[SAW_OUTPUT].setVoltageSimd(saw[c / 4], c); | |||||
outputs[SQUARE_OUTPUT].setVoltageSimd(square[c / 4], c); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct EvenVCOWidget : ModuleWidget { | struct EvenVCOWidget : ModuleWidget { | ||||
EvenVCOWidget(EvenVCO *module) { | |||||
EvenVCOWidget(EvenVCO* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/EvenVCO.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/EvenVCO.svg"))); | ||||
addChild(createWidget<Knurlie>(Vec(15, 0))); | addChild(createWidget<Knurlie>(Vec(15, 0))); | ||||
addChild(createWidget<Knurlie>(Vec(15, 365))); | addChild(createWidget<Knurlie>(Vec(15, 365))); | ||||
addChild(createWidget<Knurlie>(Vec(15*6, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(15*6, 365))); | |||||
addChild(createWidget<Knurlie>(Vec(15 * 6, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(15 * 6, 365))); | |||||
addParam(createParam<BefacoBigSnapKnob>(Vec(22, 32), module, EvenVCO::OCTAVE_PARAM)); | addParam(createParam<BefacoBigSnapKnob>(Vec(22, 32), module, EvenVCO::OCTAVE_PARAM)); | ||||
addParam(createParam<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO::TUNE_PARAM)); | addParam(createParam<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO::TUNE_PARAM)); | ||||
addParam(createParam<Davies1900hRedKnob>(Vec(16, 230), module, EvenVCO::PWM_PARAM)); | addParam(createParam<Davies1900hRedKnob>(Vec(16, 230), module, EvenVCO::PWM_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(8, 120), module, EvenVCO::PITCH1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(19, 157), module, EvenVCO::PITCH2_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(48, 183), module, EvenVCO::FM_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(86, 189), module, EvenVCO::SYNC_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(8, 120), module, EvenVCO::PITCH1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(19, 157), module, EvenVCO::PITCH2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(48, 183), module, EvenVCO::FM_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(86, 189), module, EvenVCO::SYNC_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(72, 236), module, EvenVCO::PWM_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(72, 236), module, EvenVCO::PWM_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(10, 283), module, EvenVCO::TRI_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(87, 283), module, EvenVCO::SINE_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(48, 306), module, EvenVCO::EVEN_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(10, 327), module, EvenVCO::SAW_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(10, 283), module, EvenVCO::TRI_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(87, 283), module, EvenVCO::SINE_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(48, 306), module, EvenVCO::EVEN_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(10, 327), module, EvenVCO::SAW_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(87, 327), module, EvenVCO::SQUARE_OUTPUT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelEvenVCO = createModel<EvenVCO, EvenVCOWidget>("EvenVCO"); | |||||
Model* modelEvenVCO = createModel<EvenVCO, EvenVCOWidget>("EvenVCO"); |
@@ -0,0 +1,212 @@ | |||||
#include "plugin.hpp" | |||||
using simd::float_4; | |||||
static float gainFunction(float x, float shape) { | |||||
float lin = x; | |||||
if (shape > 0.f) { | |||||
float log = 11.f * x / (10.f * x + 1.f); | |||||
return crossfade(lin, log, shape); | |||||
} | |||||
else { | |||||
float exp = std::pow(x, 4); | |||||
return crossfade(lin, exp, -shape); | |||||
} | |||||
} | |||||
struct HexmixVCA : Module { | |||||
enum ParamIds { | |||||
ENUMS(SHAPE_PARAM, 6), | |||||
ENUMS(VOL_PARAM, 6), | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
ENUMS(IN_INPUT, 6), | |||||
ENUMS(CV_INPUT, 6), | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
ENUMS(OUT_OUTPUT, 6), | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
NUM_LIGHTS | |||||
}; | |||||
const static int numRows = 6; | |||||
dsp::ClockDivider cvDivider; | |||||
float outputLevels[numRows] = {}; | |||||
float shapes[numRows] = {}; | |||||
bool finalRowIsMix = true; | |||||
HexmixVCA() { | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
for (int i = 0; i < numRows; ++i) { | |||||
configParam(SHAPE_PARAM + i, -1.f, 1.f, 0.f, string::f("Channel %d VCA response", i)); | |||||
configParam(VOL_PARAM + i, 0.f, 1.f, 1.f, string::f("Channel %d output level", i)); | |||||
} | |||||
cvDivider.setDivision(16); | |||||
for (int row = 0; row < numRows; ++row) { | |||||
outputLevels[row] = 1.f; | |||||
} | |||||
} | |||||
void process(const ProcessArgs& args) override { | |||||
float_4 mix[4] = {}; | |||||
int maxChannels = 1; | |||||
// only calculate gains/shapes every 16 samples | |||||
if (cvDivider.process()) { | |||||
for (int row = 0; row < numRows; ++row) { | |||||
shapes[row] = params[SHAPE_PARAM + row].getValue(); | |||||
outputLevels[row] = params[VOL_PARAM + row].getValue(); | |||||
} | |||||
} | |||||
for (int row = 0; row < numRows; ++row) { | |||||
bool finalRow = (row == numRows - 1); | |||||
int channels = 1; | |||||
float_4 in[4] = {}; | |||||
bool inputIsConnected = inputs[IN_INPUT + row].isConnected(); | |||||
if (inputIsConnected) { | |||||
channels = inputs[row].getChannels(); | |||||
// if we're in "mixer" mode, an input only counts towards the main output polyphony count if it's | |||||
// not taken out of the mix (i.e. patched in). the final row should count towards polyphony calc. | |||||
if (finalRowIsMix && (finalRow || !outputs[OUT_OUTPUT + row].isConnected())) { | |||||
maxChannels = std::max(maxChannels, channels); | |||||
} | |||||
float cvGain = clamp(inputs[CV_INPUT + row].getNormalVoltage(10.f) / 10.f, 0.f, 1.f); | |||||
float gain = gainFunction(cvGain, shapes[row]) * outputLevels[row]; | |||||
for (int c = 0; c < channels; c += 4) { | |||||
in[c / 4] = inputs[row].getVoltageSimd<float_4>(c) * gain; | |||||
} | |||||
} | |||||
if (!finalRow) { | |||||
if (outputs[OUT_OUTPUT + row].isConnected()) { | |||||
// if output is connected, we don't add to mix | |||||
outputs[OUT_OUTPUT + row].setChannels(channels); | |||||
for (int c = 0; c < channels; c += 4) { | |||||
outputs[OUT_OUTPUT + row].setVoltageSimd(in[c / 4], c); | |||||
} | |||||
} | |||||
else if (finalRowIsMix) { | |||||
// else add to mix (if setting enabled) | |||||
for (int c = 0; c < channels; c += 4) { | |||||
mix[c / 4] += in[c / 4]; | |||||
} | |||||
} | |||||
} | |||||
// final row | |||||
else { | |||||
if (outputs[OUT_OUTPUT + row].isConnected()) { | |||||
if (finalRowIsMix) { | |||||
outputs[OUT_OUTPUT + row].setChannels(maxChannels); | |||||
// last channel must always go into mix | |||||
for (int c = 0; c < channels; c += 4) { | |||||
mix[c / 4] += in[c / 4]; | |||||
} | |||||
for (int c = 0; c < maxChannels; c += 4) { | |||||
outputs[OUT_OUTPUT + row].setVoltageSimd(mix[c / 4], c); | |||||
} | |||||
} | |||||
else { | |||||
// same as other rows | |||||
outputs[OUT_OUTPUT + row].setChannels(channels); | |||||
for (int c = 0; c < channels; c += 4) { | |||||
outputs[OUT_OUTPUT + row].setVoltageSimd(in[c / 4], c); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void dataFromJson(json_t* rootJ) override { | |||||
json_t* modeJ = json_object_get(rootJ, "finalRowIsMix"); | |||||
if (modeJ) { | |||||
finalRowIsMix = json_boolean_value(modeJ); | |||||
} | |||||
} | |||||
json_t* dataToJson() override { | |||||
json_t* rootJ = json_object(); | |||||
json_object_set_new(rootJ, "finalRowIsMix", json_boolean(finalRowIsMix)); | |||||
return rootJ; | |||||
} | |||||
}; | |||||
struct HexmixVCAWidget : ModuleWidget { | |||||
HexmixVCAWidget(HexmixVCA* module) { | |||||
setModule(module); | |||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HexmixVCA.svg"))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 15.51)), module, HexmixVCA::SHAPE_PARAM + 0)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 34.115)), module, HexmixVCA::SHAPE_PARAM + 1)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 52.72)), module, HexmixVCA::SHAPE_PARAM + 2)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 71.325)), module, HexmixVCA::SHAPE_PARAM + 3)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 89.93)), module, HexmixVCA::SHAPE_PARAM + 4)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(20.412, 108.536)), module, HexmixVCA::SHAPE_PARAM + 5)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 15.51)), module, HexmixVCA::VOL_PARAM + 0)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 34.115)), module, HexmixVCA::VOL_PARAM + 1)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 52.72)), module, HexmixVCA::VOL_PARAM + 2)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 71.325)), module, HexmixVCA::VOL_PARAM + 3)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 89.93)), module, HexmixVCA::VOL_PARAM + 4)); | |||||
addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(35.458, 108.536)), module, HexmixVCA::VOL_PARAM + 5)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 15.51)), module, HexmixVCA::IN_INPUT + 0)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 34.115)), module, HexmixVCA::IN_INPUT + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 52.72)), module, HexmixVCA::IN_INPUT + 2)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 71.325)), module, HexmixVCA::IN_INPUT + 3)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 89.93)), module, HexmixVCA::IN_INPUT + 4)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(6.581, 108.536)), module, HexmixVCA::IN_INPUT + 5)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 15.51)), module, HexmixVCA::CV_INPUT + 0)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 34.115)), module, HexmixVCA::CV_INPUT + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 52.72)), module, HexmixVCA::CV_INPUT + 2)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 71.325)), module, HexmixVCA::CV_INPUT + 3)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 89.93)), module, HexmixVCA::CV_INPUT + 4)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(52.083, 108.536)), module, HexmixVCA::CV_INPUT + 5)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 15.51)), module, HexmixVCA::OUT_OUTPUT + 0)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 34.115)), module, HexmixVCA::OUT_OUTPUT + 1)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 52.72)), module, HexmixVCA::OUT_OUTPUT + 2)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 71.325)), module, HexmixVCA::OUT_OUTPUT + 3)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 89.93)), module, HexmixVCA::OUT_OUTPUT + 4)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(64.222, 108.536)), module, HexmixVCA::OUT_OUTPUT + 5)); | |||||
} | |||||
void appendContextMenu(Menu* menu) override { | |||||
HexmixVCA* module = dynamic_cast<HexmixVCA*>(this->module); | |||||
assert(module); | |||||
menu->addChild(new MenuSeparator()); | |||||
struct MixMenuItem : MenuItem { | |||||
HexmixVCA* module; | |||||
void onAction(const event::Action& e) override { | |||||
module->finalRowIsMix ^= true; | |||||
} | |||||
}; | |||||
MixMenuItem* mixItem = createMenuItem<MixMenuItem>("Final row is mix", CHECKMARK(module->finalRowIsMix)); | |||||
mixItem->module = module; | |||||
menu->addChild(mixItem); | |||||
} | |||||
}; | |||||
Model* modelHexmixVCA = createModel<HexmixVCA, HexmixVCAWidget>("HexmixVCA"); |
@@ -0,0 +1,146 @@ | |||||
#include "plugin.hpp" | |||||
#include "ChowDSP.hpp" | |||||
struct Kickall : Module { | |||||
enum ParamIds { | |||||
TUNE_PARAM, | |||||
TRIGG_BUTTON_PARAM, | |||||
SHAPE_PARAM, | |||||
DECAY_PARAM, | |||||
TIME_PARAM, | |||||
BEND_PARAM, | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
TRIGG_INPUT, | |||||
VOLUME_INPUT, | |||||
TUNE_INPUT, | |||||
SHAPE_INPUT, | |||||
DECAY_INPUT, | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
OUT_OUTPUT, | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
ENV_LIGHT, | |||||
NUM_LIGHTS | |||||
}; | |||||
static constexpr float FREQ_A0 = 27.5f; | |||||
static constexpr float FREQ_B2 = 123.471f; | |||||
static constexpr float minVolumeDecay = 0.075f; | |||||
static constexpr float maxVolumeDecay = 4.f; | |||||
static constexpr float minPitchDecay = 0.0075f; | |||||
static constexpr float maxPitchDecay = 1.f; | |||||
static constexpr float bendRange = 10000; | |||||
float phase = 0.f; | |||||
ADEnvelope volume; | |||||
ADEnvelope pitch; | |||||
dsp::SchmittTrigger trigger; | |||||
static const int UPSAMPLE = 8; | |||||
chowdsp::Oversampling<UPSAMPLE> oversampler; | |||||
Kickall() { | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
// TODO: review this mapping, using displayBase multiplier seems more normal | |||||
configParam(TUNE_PARAM, FREQ_A0, FREQ_B2, 0.5 * (FREQ_A0 + FREQ_B2), "Tune", "Hz"); | |||||
configParam(TRIGG_BUTTON_PARAM, 0.f, 1.f, 0.f, "Manual trigger"); | |||||
configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Wave shape"); | |||||
configParam(DECAY_PARAM, 0.f, 1.f, 0.01f, "VCA Envelope decay time"); | |||||
configParam(TIME_PARAM, 0.f, 1.0f, 0.f, "Pitch envelope decay time"); | |||||
configParam(BEND_PARAM, 0.f, 1.f, 0.f, "Pitch envelope attenuator"); | |||||
volume.attackTime = 0.01; | |||||
volume.attackShape = 0.5; | |||||
volume.decayShape = 3.0; | |||||
pitch.attackTime = 0.00165; | |||||
pitch.decayShape = 3.0; | |||||
// calculate up/downsampling rates | |||||
onSampleRateChange(); | |||||
} | |||||
void onSampleRateChange() override { | |||||
oversampler.reset(APP->engine->getSampleRate()); | |||||
} | |||||
void process(const ProcessArgs& args) override { | |||||
// TODO: check values | |||||
if (trigger.process(inputs[TRIGG_INPUT].getVoltage() / 2.0f + params[TRIGG_BUTTON_PARAM].getValue() * 10.0)) { | |||||
volume.trigger(); | |||||
pitch.trigger(); | |||||
} | |||||
const float vcaGain = clamp(inputs[VOLUME_INPUT].getNormalVoltage(10.f) / 10.f, 0.f, 1.0f); | |||||
// pitch envelope | |||||
const float bend = bendRange * std::pow(params[BEND_PARAM].getValue(), 3.0); | |||||
pitch.decayTime = rescale(params[TIME_PARAM].getValue(), 0.f, 1.0f, minPitchDecay, maxPitchDecay); | |||||
pitch.process(args.sampleTime); | |||||
// volume envelope | |||||
const float volumeDecay = minVolumeDecay * std::pow(2.f, params[DECAY_PARAM].getValue() * std::log2(maxVolumeDecay / minVolumeDecay)); | |||||
volume.decayTime = clamp(volumeDecay + inputs[DECAY_INPUT].getVoltage() * 0.1f, 0.01, 10.0); | |||||
volume.process(args.sampleTime); | |||||
float freq = params[TUNE_PARAM].getValue(); | |||||
freq *= std::pow(2.f, inputs[TUNE_INPUT].getVoltage()); | |||||
const float kickFrequency = std::max(10.0f, freq + bend * pitch.env); | |||||
const float phaseInc = clamp(args.sampleTime * kickFrequency / UPSAMPLE, 1e-6, 0.35f); | |||||
const float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 10.f + params[SHAPE_PARAM].getValue(), 0.0f, 1.0f) * 0.99f; | |||||
const float shapeB = (1.0f - shape) / (1.0f + shape); | |||||
const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape)); | |||||
float* inputBuf = oversampler.getOSBuffer(); | |||||
for (int i = 0; i < UPSAMPLE; ++i) { | |||||
phase += phaseInc; | |||||
phase -= std::floor(phase); | |||||
inputBuf[i] = sin2pi_pade_05_5_4(phase); | |||||
inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB); | |||||
} | |||||
const float out = volume.env * oversampler.downsample() * 5.0f * vcaGain; | |||||
outputs[OUT_OUTPUT].setVoltage(out); | |||||
lights[ENV_LIGHT].setBrightness(volume.env); | |||||
} | |||||
}; | |||||
struct KickallWidget : ModuleWidget { | |||||
KickallWidget(Kickall* module) { | |||||
setModule(module); | |||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Kickall.svg"))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(createParamCentered<BefacoTinyKnobGrey>(mm2px(Vec(8.472, 28.97)), module, Kickall::TUNE_PARAM)); | |||||
addParam(createParamCentered<BefacoPush>(mm2px(Vec(22.409, 29.159)), module, Kickall::TRIGG_BUTTON_PARAM)); | |||||
addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.526, 49.292)), module, Kickall::SHAPE_PARAM)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(19.667, 63.897)), module, Kickall::DECAY_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 71.803)), module, Kickall::TIME_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 93.517)), module, Kickall::BEND_PARAM)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.501, 14.508)), module, Kickall::VOLUME_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 14.536)), module, Kickall::TRIGG_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.525, 113.191)), module, Kickall::DECAY_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 113.208)), module, Kickall::TUNE_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.485, 113.208)), module, Kickall::SHAPE_INPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.525, 14.52)), module, Kickall::OUT_OUTPUT)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.535, 34.943)), module, Kickall::ENV_LIGHT)); | |||||
} | |||||
}; | |||||
Model* modelKickall = createModel<Kickall, KickallWidget>("Kickall"); |
@@ -1,5 +1,6 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
using simd::float_4; | |||||
struct Mixer : Module { | struct Mixer : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
@@ -24,6 +25,7 @@ struct Mixer : Module { | |||||
enum LightIds { | enum LightIds { | ||||
OUT_POS_LIGHT, | OUT_POS_LIGHT, | ||||
OUT_NEG_LIGHT, | OUT_NEG_LIGHT, | ||||
OUT_BLUE_LIGHT, | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
@@ -35,25 +37,71 @@ struct Mixer : Module { | |||||
configParam(CH4_PARAM, 0.0, 1.0, 0.0, "Ch 4 level", "%", 0, 100); | configParam(CH4_PARAM, 0.0, 1.0, 0.0, "Ch 4 level", "%", 0, 100); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
float in1 = inputs[IN1_INPUT].getVoltage() * params[CH1_PARAM].getValue(); | |||||
float in2 = inputs[IN2_INPUT].getVoltage() * params[CH2_PARAM].getValue(); | |||||
float in3 = inputs[IN3_INPUT].getVoltage() * params[CH3_PARAM].getValue(); | |||||
float in4 = inputs[IN4_INPUT].getVoltage() * params[CH4_PARAM].getValue(); | |||||
float out = in1 + in2 + in3 + in4; | |||||
outputs[OUT1_OUTPUT].setVoltage(out); | |||||
outputs[OUT2_OUTPUT].setVoltage(-out); | |||||
lights[OUT_POS_LIGHT].setSmoothBrightness(out / 5.f, args.sampleTime); | |||||
lights[OUT_NEG_LIGHT].setSmoothBrightness(-out / 5.f, args.sampleTime); | |||||
void process(const ProcessArgs& args) override { | |||||
int channels1 = inputs[IN1_INPUT].getChannels(); | |||||
int channels2 = inputs[IN2_INPUT].getChannels(); | |||||
int channels3 = inputs[IN3_INPUT].getChannels(); | |||||
int channels4 = inputs[IN4_INPUT].getChannels(); | |||||
int out_channels = 1; | |||||
out_channels = std::max(out_channels, channels1); | |||||
out_channels = std::max(out_channels, channels2); | |||||
out_channels = std::max(out_channels, channels3); | |||||
out_channels = std::max(out_channels, channels4); | |||||
float_4 out[4] = {}; | |||||
if (inputs[IN1_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels1; c += 4) | |||||
out[c / 4] += inputs[IN1_INPUT].getVoltageSimd<float_4>(c) * params[CH1_PARAM].getValue(); | |||||
} | |||||
if (inputs[IN2_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels2; c += 4) | |||||
out[c / 4] += inputs[IN2_INPUT].getVoltageSimd<float_4>(c) * params[CH2_PARAM].getValue(); | |||||
} | |||||
if (inputs[IN3_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels3; c += 4) | |||||
out[c / 4] += inputs[IN3_INPUT].getVoltageSimd<float_4>(c) * params[CH3_PARAM].getValue(); | |||||
} | |||||
if (inputs[IN4_INPUT].isConnected()) { | |||||
for (int c = 0; c < channels4; c += 4) | |||||
out[c / 4] += inputs[IN4_INPUT].getVoltageSimd<float_4>(c) * params[CH4_PARAM].getValue(); | |||||
} | |||||
outputs[OUT1_OUTPUT].setChannels(out_channels); | |||||
outputs[OUT2_OUTPUT].setChannels(out_channels); | |||||
for (int c = 0; c < out_channels; c += 4) { | |||||
outputs[OUT1_OUTPUT].setVoltageSimd(out[c / 4], c); | |||||
out[c / 4] *= -1.f; | |||||
outputs[OUT2_OUTPUT].setVoltageSimd(out[c / 4], c); | |||||
} | |||||
if (out_channels == 1) { | |||||
float light = outputs[OUT1_OUTPUT].getVoltage(); | |||||
lights[OUT_POS_LIGHT].setSmoothBrightness(light / 5.f, args.sampleTime); | |||||
lights[OUT_NEG_LIGHT].setSmoothBrightness(-light / 5.f, args.sampleTime); | |||||
} | |||||
else { | |||||
float light = 0.0f; | |||||
for (int c = 0; c < out_channels; c++) { | |||||
float tmp = outputs[OUT1_OUTPUT].getVoltage(c); | |||||
light += tmp * tmp; | |||||
} | |||||
light = std::sqrt(light); | |||||
lights[OUT_POS_LIGHT].setBrightness(0.0f); | |||||
lights[OUT_NEG_LIGHT].setBrightness(0.0f); | |||||
lights[OUT_BLUE_LIGHT].setSmoothBrightness(light / 5.f, args.sampleTime); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct MixerWidget : ModuleWidget { | struct MixerWidget : ModuleWidget { | ||||
MixerWidget(Mixer *module) { | |||||
MixerWidget(Mixer* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Mixer.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Mixer.svg"))); | ||||
@@ -65,18 +113,18 @@ struct MixerWidget : ModuleWidget { | |||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(19, 137), module, Mixer::CH3_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(19, 137), module, Mixer::CH3_PARAM)); | ||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(19, 190), module, Mixer::CH4_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(19, 190), module, Mixer::CH4_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(7, 242), module, Mixer::IN1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(43, 242), module, Mixer::IN2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 242), module, Mixer::IN1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(43, 242), module, Mixer::IN2_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(7, 281), module, Mixer::IN3_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(43, 281), module, Mixer::IN4_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 281), module, Mixer::IN3_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(43, 281), module, Mixer::IN4_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(7, 324), module, Mixer::OUT1_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(43, 324), module, Mixer::OUT2_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(7, 324), module, Mixer::OUT1_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(43, 324), module, Mixer::OUT2_OUTPUT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(32.7, 310), module, Mixer::OUT_POS_LIGHT)); | |||||
addChild(createLight<MediumLight<RedGreenBlueLight>>(Vec(32.7, 310), module, Mixer::OUT_POS_LIGHT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelMixer = createModel<Mixer, MixerWidget>("Mixer"); | |||||
Model* modelMixer = createModel<Mixer, MixerWidget>("Mixer"); |
@@ -0,0 +1,220 @@ | |||||
#include "plugin.hpp" | |||||
using simd::float_4; | |||||
struct Percall : Module { | |||||
enum ParamIds { | |||||
ENUMS(VOL_PARAMS, 4), | |||||
ENUMS(DECAY_PARAMS, 4), | |||||
ENUMS(CHOKE_PARAMS, 2), | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
ENUMS(CH_INPUTS, 4), | |||||
STRENGTH_INPUT, | |||||
ENUMS(TRIG_INPUTS, 4), | |||||
ENUMS(CV_INPUTS, 4), | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
ENUMS(CH_OUTPUTS, 4), | |||||
ENUMS(ENV_OUTPUTS, 4), | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
ENUMS(LEDS, 4), | |||||
NUM_LIGHTS | |||||
}; | |||||
ADEnvelope envs[4]; | |||||
float gains[4] = {}; | |||||
dsp::SchmittTrigger trigger[4]; | |||||
dsp::ClockDivider cvDivider; | |||||
dsp::ClockDivider lightDivider; | |||||
const int LAST_CHANNEL_ID = 3; | |||||
const float attackTime = 1.5e-3; | |||||
const float minDecayTime = 4.5e-3; | |||||
const float maxDecayTime = 4.f; | |||||
Percall() { | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
for (int i = 0; i < 4; i++) { | |||||
configParam(VOL_PARAMS + i, 0.f, 1.f, 1.f, string::f("Channel %d level", i + 1), "%", 0, 100); | |||||
configParam(DECAY_PARAMS + i, 0.f, 1.f, 0.f, string::f("Channel %d decay time", i + 1)); | |||||
envs[i].attackTime = attackTime; | |||||
envs[i].attackShape = 0.5f; | |||||
envs[i].decayShape = 2.0f; | |||||
} | |||||
for (int i = 0; i < 2; i++) { | |||||
configParam(CHOKE_PARAMS + i, 0.f, 1.f, 0.f, string::f("Choke %d to %d", 2 * i + 1, 2 * i + 2)); | |||||
} | |||||
cvDivider.setDivision(16); | |||||
lightDivider.setDivision(128); | |||||
} | |||||
void process(const ProcessArgs& args) override { | |||||
float strength = 1.0f; | |||||
if (inputs[STRENGTH_INPUT].isConnected()) { | |||||
strength = std::sqrt(clamp(inputs[STRENGTH_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f)); | |||||
} | |||||
// only calculate gains/decays every 16 samples | |||||
if (cvDivider.process()) { | |||||
for (int i = 0; i < 4; i++) { | |||||
gains[i] = std::pow(params[VOL_PARAMS + i].getValue(), 2.f) * strength; | |||||
float fallCv = inputs[CV_INPUTS + i].getVoltage() * 0.05f + params[DECAY_PARAMS + i].getValue(); | |||||
envs[i].decayTime = rescale(std::pow(clamp(fallCv, 0.f, 1.0f), 2.f), 0.f, 1.f, minDecayTime, maxDecayTime); | |||||
} | |||||
} | |||||
float_4 mix[4] = {}; | |||||
int maxPolyphonyChannels = 1; | |||||
// Mixer channels | |||||
for (int i = 0; i < 4; i++) { | |||||
if (trigger[i].process(rescale(inputs[TRIG_INPUTS + i].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) { | |||||
envs[i].trigger(); | |||||
} | |||||
// if choke is enabled, and current channel is odd and left channel is in attack | |||||
if ((i % 2) && params[CHOKE_PARAMS + i / 2].getValue() && envs[i - 1].stage == ADEnvelope::STAGE_ATTACK) { | |||||
// TODO: is there a more graceful way to choke, e.g. rapid envelope? | |||||
// TODO: this will just silence it instantly, maybe switch to STAGE_DECAY and modify fall time | |||||
envs[i].stage = ADEnvelope::STAGE_OFF; | |||||
} | |||||
envs[i].process(args.sampleTime); | |||||
int polyphonyChannels = 1; | |||||
float_4 in[4] = {}; | |||||
bool inputIsConnected = inputs[CH_INPUTS + i].isConnected(); | |||||
bool inputIsNormed = !inputIsConnected && (i % 2) && inputs[CH_INPUTS + i - 1].isConnected(); | |||||
if ((inputIsConnected || inputIsNormed)) { | |||||
int channelToReadFrom = inputIsNormed ? CH_INPUTS + i - 1 : CH_INPUTS + i; | |||||
polyphonyChannels = inputs[channelToReadFrom].getChannels(); | |||||
// an input only counts towards the main output polyphony count if it's not taken out of the mix | |||||
// (i.e. an output is patched in). the final input should always count towards polyphony count. | |||||
if (i == CH_INPUTS_LAST || !outputs[CH_OUTPUTS + i].isConnected()) { | |||||
maxPolyphonyChannels = std::max(maxPolyphonyChannels, polyphonyChannels); | |||||
} | |||||
// only process input audio if envelope is active | |||||
if (envs[i].stage != ADEnvelope::STAGE_OFF) { | |||||
float gain = gains[i] * envs[i].env; | |||||
for (int c = 0; c < polyphonyChannels; c += 4) { | |||||
in[c / 4] = inputs[channelToReadFrom].getVoltageSimd<float_4>(c) * gain; | |||||
} | |||||
} | |||||
} | |||||
if (i != LAST_CHANNEL_ID) { | |||||
// if connected, output via the jack (and don't add to mix) | |||||
if (outputs[CH_OUTPUTS + i].isConnected()) { | |||||
outputs[CH_OUTPUTS + i].setChannels(polyphonyChannels); | |||||
for (int c = 0; c < polyphonyChannels; c += 4) { | |||||
outputs[CH_OUTPUTS + i].setVoltageSimd(in[c / 4], c); | |||||
} | |||||
} | |||||
else { | |||||
// else add to mix | |||||
for (int c = 0; c < polyphonyChannels; c += 4) { | |||||
mix[c / 4] += in[c / 4]; | |||||
} | |||||
} | |||||
} | |||||
// otherwise if it is the final channel and it's wired in | |||||
else if (outputs[CH_OUTPUTS + i].isConnected()) { | |||||
outputs[CH_OUTPUTS + i].setChannels(maxPolyphonyChannels); | |||||
// last channel must always go into mix | |||||
for (int c = 0; c < polyphonyChannels; c += 4) { | |||||
mix[c / 4] += in[c / 4]; | |||||
} | |||||
for (int c = 0; c < maxPolyphonyChannels; c += 4) { | |||||
outputs[CH_OUTPUTS + i].setVoltageSimd(mix[c / 4], c); | |||||
} | |||||
} | |||||
// set env output | |||||
if (outputs[ENV_OUTPUTS + i].isConnected()) { | |||||
outputs[ENV_OUTPUTS + i].setVoltage(10.f * strength * envs[i].env); | |||||
} | |||||
} | |||||
if (lightDivider.process()) { | |||||
for (int i = 0; i < 4; i++) { | |||||
lights[LEDS + i].setBrightness(envs[i].env); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
struct PercallWidget : ModuleWidget { | |||||
PercallWidget(Percall* module) { | |||||
setModule(module); | |||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Percall.svg"))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.048, 41.265)), module, Percall::VOL_PARAMS + 0)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(22.879, 41.265)), module, Percall::VOL_PARAMS + 1)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(37.709, 41.265)), module, Percall::VOL_PARAMS + 2)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(52.54, 41.265)), module, Percall::VOL_PARAMS + 3)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(5.385, 53.912)), module, Percall::DECAY_PARAMS + 0)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(20.292, 53.912)), module, Percall::DECAY_PARAMS + 1)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(35.173, 53.912)), module, Percall::DECAY_PARAMS + 2)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(49.987, 53.912)), module, Percall::DECAY_PARAMS + 3)); | |||||
addParam(createParam<CKSS>(mm2px(Vec(13.365, 58.672)), module, Percall::CHOKE_PARAMS + 0)); | |||||
addParam(createParam<CKSS>(mm2px(Vec(42.993, 58.672)), module, Percall::CHOKE_PARAMS + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.15, 12.905)), module, Percall::CH_INPUTS + 0)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(20.298, 12.905)), module, Percall::CH_INPUTS + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(40.266, 12.905)), module, Percall::CH_INPUTS + 2)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.437, 12.905)), module, Percall::CH_INPUTS + 3)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(30.282, 18.221)), module, Percall::STRENGTH_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(7.15, 24.827)), module, Percall::TRIG_INPUTS + 0)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(18.488, 23.941)), module, Percall::TRIG_INPUTS + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(42.171, 23.95)), module, Percall::TRIG_INPUTS + 2)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(53.437, 24.827)), module, Percall::TRIG_INPUTS + 3)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.037, 101.844)), module, Percall::CV_INPUTS + 0)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.159, 101.844)), module, Percall::CV_INPUTS + 1)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.28, 101.844)), module, Percall::CV_INPUTS + 2)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.402, 101.844)), module, Percall::CV_INPUTS + 3)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.524, 101.844)), module, Percall::CH_OUTPUTS + 0)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.645, 101.844)), module, Percall::CH_OUTPUTS + 1)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.524, 113.766)), module, Percall::CH_OUTPUTS + 2)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.645, 113.766)), module, Percall::CH_OUTPUTS + 3)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.037, 113.766)), module, Percall::ENV_OUTPUTS + 0)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(15.159, 113.766)), module, Percall::ENV_OUTPUTS + 1)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.28, 113.766)), module, Percall::ENV_OUTPUTS + 2)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.402, 113.766)), module, Percall::ENV_OUTPUTS + 3)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(8.107, 49.221)), module, Percall::LEDS + 0)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(22.934, 49.221)), module, Percall::LEDS + 1)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.762, 49.221)), module, Percall::LEDS + 2)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(52.589, 49.221)), module, Percall::LEDS + 3)); | |||||
} | |||||
}; | |||||
Model* modelPercall = createModel<Percall, PercallWidget>("Percall"); |
@@ -0,0 +1,28 @@ | |||||
#pragma once | |||||
#include <rack.hpp> | |||||
/** When triggered, holds a high value for a specified time before going low again */ | |||||
struct PulseGenerator_4 { | |||||
simd::float_4 remaining = 0.f; | |||||
/** Immediately disables the pulse */ | |||||
void reset() { | |||||
remaining = 0.f; | |||||
} | |||||
/** Advances the state by `deltaTime`. Returns whether the pulse is in the HIGH state. */ | |||||
simd::float_4 process(float deltaTime) { | |||||
simd::float_4 mask = (remaining > 0.f); | |||||
remaining -= ifelse(mask, deltaTime, 0.f); | |||||
return ifelse(mask, simd::float_4::mask(), 0.f); | |||||
} | |||||
/** Begins a trigger with the given `duration`. */ | |||||
void trigger(simd::float_4 mask, float duration = 1e-3f) { | |||||
// Keep the previous pulse if the existing pulse will be held longer than the currently requested one. | |||||
remaining = ifelse(mask & (duration > remaining), duration, remaining); | |||||
} | |||||
}; |
@@ -1,19 +1,20 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "PulseGenerator_4.hpp" | |||||
using simd::float_4; | |||||
static float shapeDelta(float delta, float tau, float shape) { | |||||
float lin = sgn(delta) * 10.f / tau; | |||||
static float_4 shapeDelta(float_4 delta, float_4 tau, float shape) { | |||||
float_4 lin = simd::sgn(delta) * 10.f / tau; | |||||
if (shape < 0.f) { | if (shape < 0.f) { | ||||
float log = sgn(delta) * 40.f / tau / (std::fabs(delta) + 1.f); | |||||
return crossfade(lin, log, -shape * 0.95f); | |||||
float_4 log = simd::sgn(delta) * 40.f / tau / (simd::fabs(delta) + 1.f); | |||||
return simd::crossfade(lin, log, -shape * 0.95f); | |||||
} | } | ||||
else { | else { | ||||
float exp = M_E * delta / tau; | |||||
return crossfade(lin, exp, shape * 0.90f); | |||||
float_4 exp = M_E * delta / tau; | |||||
return simd::crossfade(lin, exp, shape * 0.90f); | |||||
} | } | ||||
} | } | ||||
struct Rampage : Module { | struct Rampage : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
RANGE_A_PARAM, | RANGE_A_PARAM, | ||||
@@ -61,22 +62,26 @@ struct Rampage : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
enum LightIds { | enum LightIds { | ||||
COMPARATOR_LIGHT, | |||||
MIN_LIGHT, | |||||
MAX_LIGHT, | |||||
OUT_A_LIGHT, | |||||
OUT_B_LIGHT, | |||||
RISING_A_LIGHT, | |||||
RISING_B_LIGHT, | |||||
FALLING_A_LIGHT, | |||||
FALLING_B_LIGHT, | |||||
ENUMS(COMPARATOR_LIGHT, 3), | |||||
ENUMS(MIN_LIGHT, 3), | |||||
ENUMS(MAX_LIGHT, 3), | |||||
ENUMS(OUT_A_LIGHT, 3), | |||||
ENUMS(OUT_B_LIGHT, 3), | |||||
ENUMS(RISING_A_LIGHT, 3), | |||||
ENUMS(RISING_B_LIGHT, 3), | |||||
ENUMS(FALLING_A_LIGHT, 3), | |||||
ENUMS(FALLING_B_LIGHT, 3), | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
float out[2] = {}; | |||||
bool gate[2] = {}; | |||||
dsp::SchmittTrigger trigger[2]; | |||||
dsp::PulseGenerator endOfCyclePulse[2]; | |||||
float_4 out[2][4] = {}; | |||||
float_4 gate[2][4] = {}; // use simd __m128 logic instead of bool | |||||
dsp::TSchmittTrigger<float_4> trigger_4[2][4]; | |||||
PulseGenerator_4 endOfCyclePulse[2][4]; | |||||
// ChannelMask channelMask; | |||||
Rampage() { | Rampage() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
@@ -95,103 +100,230 @@ struct Rampage : Module { | |||||
configParam(BALANCE_PARAM, 0.0, 1.0, 0.5, "Balance"); | configParam(BALANCE_PARAM, 0.0, 1.0, 0.5, "Balance"); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
for (int c = 0; c < 2; c++) { | |||||
float in = inputs[IN_A_INPUT + c].getVoltage(); | |||||
if (trigger[c].process(params[TRIGG_A_PARAM + c].getValue() * 10.0 + inputs[TRIGG_A_INPUT + c].getVoltage() / 2.0)) { | |||||
gate[c] = true; | |||||
void process(const ProcessArgs& args) override { | |||||
int channels_in[2] = {}; | |||||
int channels_trig[2] = {}; | |||||
int channels[2] = {}; // the larger of in or trig (per-part) | |||||
// determine number of channels: | |||||
for (int part = 0; part < 2; part++) { | |||||
channels_in[part] = inputs[IN_A_INPUT + part].getChannels(); | |||||
channels_trig[part] = inputs[TRIGG_A_INPUT + part].getChannels(); | |||||
channels[part] = std::max(channels_in[part], channels_trig[part]); | |||||
channels[part] = std::max(1, channels[part]); | |||||
outputs[OUT_A_OUTPUT + part].setChannels(channels[part]); | |||||
outputs[RISING_A_OUTPUT + part].setChannels(channels[part]); | |||||
outputs[FALLING_A_OUTPUT + part].setChannels(channels[part]); | |||||
outputs[EOC_A_OUTPUT + part].setChannels(channels[part]); | |||||
} | |||||
// total number of active polyphony engines, accounting for both halves | |||||
// (channels[0] / channels[1] are the number of active engines per section) | |||||
const int channels_max = std::max(channels[0], channels[1]); | |||||
outputs[COMPARATOR_OUTPUT].setChannels(channels_max); | |||||
outputs[MIN_OUTPUT].setChannels(channels_max); | |||||
outputs[MAX_OUTPUT].setChannels(channels_max); | |||||
// loop over two parts of Rampage: | |||||
for (int part = 0; part < 2; part++) { | |||||
float_4 in[4] = {}; | |||||
float_4 in_trig[4] = {}; | |||||
float_4 riseCV[4] = {}; | |||||
float_4 fallCV[4] = {}; | |||||
float_4 cycle[4] = {}; | |||||
// get parameters: | |||||
float minTime; | |||||
switch ((int) params[RANGE_A_PARAM + part].getValue()) { | |||||
case 0: | |||||
minTime = 1e-2; | |||||
break; | |||||
case 1: | |||||
minTime = 1e-3; | |||||
break; | |||||
default: | |||||
minTime = 1e-1; | |||||
break; | |||||
} | } | ||||
if (gate[c]) { | |||||
in = 10.0; | |||||
float_4 param_rise = params[RISE_A_PARAM + part].getValue() * 10.0f; | |||||
float_4 param_fall = params[FALL_A_PARAM + part].getValue() * 10.0f; | |||||
float_4 param_trig = params[TRIGG_A_PARAM + part].getValue() * 20.0f; | |||||
float_4 param_cycle = params[CYCLE_A_PARAM + part].getValue() * 10.0f; | |||||
for (int c = 0; c < channels[part]; c += 4) { | |||||
riseCV[c / 4] = param_rise; | |||||
fallCV[c / 4] = param_fall; | |||||
cycle[c / 4] = param_cycle; | |||||
in_trig[c / 4] = param_trig; | |||||
} | } | ||||
float shape = params[SHAPE_A_PARAM + c].getValue(); | |||||
float delta = in - out[c]; | |||||
// read inputs: | |||||
if (inputs[IN_A_INPUT + part].isConnected()) { | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
in[c / 4] = inputs[IN_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
} | |||||
// Integrator | |||||
float minTime; | |||||
switch ((int) params[RANGE_A_PARAM + c].getValue()) { | |||||
case 0: minTime = 1e-2; break; | |||||
case 1: minTime = 1e-3; break; | |||||
default: minTime = 1e-1; break; | |||||
if (inputs[TRIGG_A_INPUT + part].isConnected()) { | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
in_trig[c / 4] += inputs[TRIGG_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
} | } | ||||
bool rising = false; | |||||
bool falling = false; | |||||
if (delta > 0) { | |||||
// Rise | |||||
float riseCv = params[RISE_A_PARAM + c].getValue() - inputs[EXP_CV_A_INPUT + c].getVoltage() / 10.0 + inputs[RISE_CV_A_INPUT + c].getVoltage() / 10.0; | |||||
riseCv = clamp(riseCv, 0.0f, 1.0f); | |||||
float rise = minTime * std::pow(2.0, riseCv * 10.0); | |||||
out[c] += shapeDelta(delta, rise, shape) * args.sampleTime; | |||||
rising = (in - out[c] > 1e-3); | |||||
if (!rising) { | |||||
gate[c] = false; | |||||
if (inputs[EXP_CV_A_INPUT + part].isConnected()) { | |||||
float_4 expCV[4] = {}; | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
expCV[c / 4] = inputs[EXP_CV_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
for (int c = 0; c < channels[part]; c += 4) { | |||||
riseCV[c / 4] -= expCV[c / 4]; | |||||
fallCV[c / 4] -= expCV[c / 4]; | |||||
} | } | ||||
} | } | ||||
else if (delta < 0) { | |||||
// Fall | |||||
float fallCv = params[FALL_A_PARAM + c].getValue() - inputs[EXP_CV_A_INPUT + c].getVoltage() / 10.0 + inputs[FALL_CV_A_INPUT + c].getVoltage() / 10.0; | |||||
fallCv = clamp(fallCv, 0.0f, 1.0f); | |||||
float fall = minTime * std::pow(2.0, fallCv * 10.0); | |||||
out[c] += shapeDelta(delta, fall, shape) * args.sampleTime; | |||||
falling = (in - out[c] < -1e-3); | |||||
if (!falling) { | |||||
// End of cycle, check if we should turn the gate back on (cycle mode) | |||||
endOfCyclePulse[c].trigger(1e-3); | |||||
if (params[CYCLE_A_PARAM + c].getValue() * 10.0 + inputs[CYCLE_A_INPUT + c].getVoltage() >= 4.0) { | |||||
gate[c] = true; | |||||
} | |||||
} | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
riseCV[c / 4] += inputs[RISE_CV_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
fallCV[c / 4] += inputs[FALL_CV_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
for (int c = 0; c < channels[part]; c += 4) | |||||
cycle[c / 4] += inputs[CYCLE_A_INPUT + part].getPolyVoltageSimd<float_4>(c); | |||||
// start processing: | |||||
for (int c = 0; c < channels[part]; c += 4) { | |||||
// process SchmittTriggers | |||||
float_4 trig_mask = trigger_4[part][c / 4].process(in_trig[c / 4] / 2.0); | |||||
gate[part][c / 4] = ifelse(trig_mask, float_4::mask(), gate[part][c / 4]); | |||||
in[c / 4] = ifelse(gate[part][c / 4], 10.0f, in[c / 4]); | |||||
float_4 delta = in[c / 4] - out[part][c / 4]; | |||||
// rise / fall branching | |||||
float_4 delta_gt_0 = delta > 0.f; | |||||
float_4 delta_lt_0 = delta < 0.f; | |||||
float_4 delta_eq_0 = ~(delta_lt_0 | delta_gt_0); | |||||
float_4 rateCV = ifelse(delta_gt_0, riseCV[c / 4], 0.f); | |||||
rateCV = ifelse(delta_lt_0, fallCV[c / 4], rateCV); | |||||
rateCV = clamp(rateCV, 0.f, 10.0f); | |||||
float_4 rate = minTime * simd::pow(2.0f, rateCV); | |||||
float shape = params[SHAPE_A_PARAM + part].getValue(); | |||||
out[part][c / 4] += shapeDelta(delta, rate, shape) * args.sampleTime; | |||||
float_4 rising = (in[c / 4] - out[part][c / 4]) > 1e-3f; | |||||
float_4 falling = (in[c / 4] - out[part][c / 4]) < -1e-3f; | |||||
float_4 end_of_cycle = simd::andnot(falling, delta_lt_0); | |||||
endOfCyclePulse[part][c / 4].trigger(end_of_cycle, 1e-3); | |||||
gate[part][c / 4] = ifelse(simd::andnot(rising, delta_gt_0), 0.f, gate[part][c / 4]); | |||||
gate[part][c / 4] = ifelse(end_of_cycle & (cycle[c / 4] >= 4.0f), float_4::mask(), gate[part][c / 4]); | |||||
gate[part][c / 4] = ifelse(delta_eq_0, 0.f, gate[part][c / 4]); | |||||
out[part][c / 4] = ifelse(rising | falling, out[part][c / 4], in[c / 4]); | |||||
float_4 out_rising = ifelse(rising, 10.0f, 0.f); | |||||
float_4 out_falling = ifelse(falling, 10.0f, 0.f); | |||||
float_4 pulse = endOfCyclePulse[part][c / 4].process(args.sampleTime); | |||||
float_4 out_EOC = ifelse(pulse, 10.f, 0.f); | |||||
outputs[OUT_A_OUTPUT + part].setVoltageSimd(out[part][c / 4], c); | |||||
outputs[RISING_A_OUTPUT + part].setVoltageSimd(out_rising, c); | |||||
outputs[FALLING_A_OUTPUT + part].setVoltageSimd(out_falling, c); | |||||
outputs[EOC_A_OUTPUT + part].setVoltageSimd(out_EOC, c); | |||||
} // for(int c, ...) | |||||
if (channels[part] == 1) { | |||||
lights[RISING_A_LIGHT + 3 * part ].setSmoothBrightness(outputs[RISING_A_OUTPUT + part].getVoltage() / 10.f, args.sampleTime); | |||||
lights[RISING_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[RISING_A_LIGHT + 3 * part + 2].setBrightness(0.0f); | |||||
lights[FALLING_A_LIGHT + 3 * part ].setSmoothBrightness(outputs[FALLING_A_OUTPUT + part].getVoltage() / 10.f, args.sampleTime); | |||||
lights[FALLING_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[FALLING_A_LIGHT + 3 * part + 2].setBrightness(0.0f); | |||||
lights[OUT_A_LIGHT + 3 * part ].setSmoothBrightness(out[part][0].s[0] / 10.0, args.sampleTime); | |||||
lights[OUT_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[OUT_A_LIGHT + 3 * part + 2].setBrightness(0.0f); | |||||
} | } | ||||
else { | else { | ||||
gate[c] = false; | |||||
lights[RISING_A_LIGHT + 3 * part ].setBrightness(0.0f); | |||||
lights[RISING_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[RISING_A_LIGHT + 3 * part + 2].setBrightness(10.0f); | |||||
lights[FALLING_A_LIGHT + 3 * part ].setBrightness(0.0f); | |||||
lights[FALLING_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[FALLING_A_LIGHT + 3 * part + 2].setBrightness(10.0f); | |||||
lights[OUT_A_LIGHT + 3 * part ].setBrightness(0.0f); | |||||
lights[OUT_A_LIGHT + 3 * part + 1].setBrightness(0.0f); | |||||
lights[OUT_A_LIGHT + 3 * part + 2].setBrightness(10.0f); | |||||
} | } | ||||
if (!rising && !falling) { | |||||
out[c] = in; | |||||
} | |||||
} // for (int part, ... ) | |||||
outputs[RISING_A_OUTPUT + c].setVoltage((rising ? 10.0 : 0.0)); | |||||
outputs[FALLING_A_OUTPUT + c].setVoltage((falling ? 10.0 : 0.0)); | |||||
lights[RISING_A_LIGHT + c].setSmoothBrightness(rising ? 1.0 : 0.0, args.sampleTime); | |||||
lights[FALLING_A_LIGHT + c].setSmoothBrightness(falling ? 1.0 : 0.0, args.sampleTime); | |||||
outputs[EOC_A_OUTPUT + c].setVoltage((endOfCyclePulse[c].process(args.sampleTime) ? 10.0 : 0.0)); | |||||
outputs[OUT_A_OUTPUT + c].setVoltage(out[c]); | |||||
lights[OUT_A_LIGHT + c].setSmoothBrightness(out[c] / 10.0, args.sampleTime); | |||||
} | |||||
// Logic | // Logic | ||||
float balance = params[BALANCE_PARAM].getValue(); | float balance = params[BALANCE_PARAM].getValue(); | ||||
float a = out[0]; | |||||
float b = out[1]; | |||||
if (balance < 0.5) | |||||
b *= 2.0 * balance; | |||||
else if (balance > 0.5) | |||||
a *= 2.0 * (1.0 - balance); | |||||
outputs[COMPARATOR_OUTPUT].setVoltage((b > a ? 10.0 : 0.0)); | |||||
outputs[MIN_OUTPUT].setVoltage(std::min(a, b)); | |||||
outputs[MAX_OUTPUT].setVoltage(std::max(a, b)); | |||||
for (int c = 0; c < channels_max; c += 4) { | |||||
float_4 a = out[0][c / 4]; | |||||
float_4 b = out[1][c / 4]; | |||||
if (balance < 0.5) | |||||
b *= 2.0f * balance; | |||||
else if (balance > 0.5) | |||||
a *= 2.0f * (1.0 - balance); | |||||
float_4 comp = ifelse(b > a, 10.0f, 0.f); | |||||
float_4 out_min = simd::fmin(a, b); | |||||
float_4 out_max = simd::fmax(a, b); | |||||
outputs[COMPARATOR_OUTPUT].setVoltageSimd(comp, c); | |||||
outputs[MIN_OUTPUT].setVoltageSimd(out_min, c); | |||||
outputs[MAX_OUTPUT].setVoltageSimd(out_max, c); | |||||
} | |||||
// Lights | // Lights | ||||
lights[COMPARATOR_LIGHT].setSmoothBrightness(outputs[COMPARATOR_OUTPUT].value / 10.0, args.sampleTime); | |||||
lights[MIN_LIGHT].setSmoothBrightness(outputs[MIN_OUTPUT].value / 10.0, args.sampleTime); | |||||
lights[MAX_LIGHT].setSmoothBrightness(outputs[MAX_OUTPUT].value / 10.0, args.sampleTime); | |||||
if (channels_max == 1) { | |||||
lights[COMPARATOR_LIGHT ].setSmoothBrightness(outputs[COMPARATOR_OUTPUT].getVoltage(), args.sampleTime); | |||||
lights[COMPARATOR_LIGHT + 1].setBrightness(0.0f); | |||||
lights[COMPARATOR_LIGHT + 2].setBrightness(0.0f); | |||||
lights[MIN_LIGHT ].setSmoothBrightness(outputs[MIN_OUTPUT].getVoltage(), args.sampleTime); | |||||
lights[MIN_LIGHT + 1].setBrightness(0.0f); | |||||
lights[MIN_LIGHT + 2].setBrightness(0.0f); | |||||
lights[MAX_LIGHT ].setSmoothBrightness(outputs[MAX_OUTPUT].getVoltage(), args.sampleTime); | |||||
lights[MAX_LIGHT + 1].setBrightness(0.0f); | |||||
lights[MAX_LIGHT + 2].setBrightness(0.0f); | |||||
} | |||||
else { | |||||
lights[COMPARATOR_LIGHT ].setBrightness(0.0f); | |||||
lights[COMPARATOR_LIGHT + 1].setBrightness(0.0f); | |||||
lights[COMPARATOR_LIGHT + 2].setBrightness(10.0f); | |||||
lights[MIN_LIGHT ].setBrightness(0.0f); | |||||
lights[MIN_LIGHT + 1].setBrightness(0.0f); | |||||
lights[MIN_LIGHT + 2].setBrightness(10.0f); | |||||
lights[MAX_LIGHT ].setBrightness(0.0f); | |||||
lights[MAX_LIGHT + 1].setBrightness(0.0f); | |||||
lights[MAX_LIGHT + 2].setBrightness(10.0f); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct RampageWidget : ModuleWidget { | struct RampageWidget : ModuleWidget { | ||||
RampageWidget(Rampage *module) { | |||||
RampageWidget(Rampage* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Rampage.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Rampage.svg"))); | ||||
addChild(createWidget<Knurlie>(Vec(15, 0))); | addChild(createWidget<Knurlie>(Vec(15, 0))); | ||||
addChild(createWidget<Knurlie>(Vec(box.size.x-30, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 30, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(15, 365))); | addChild(createWidget<Knurlie>(Vec(15, 365))); | ||||
addChild(createWidget<Knurlie>(Vec(box.size.x-30, 365))); | |||||
addChild(createWidget<Knurlie>(Vec(box.size.x - 30, 365))); | |||||
addParam(createParam<BefacoSwitch>(Vec(94, 32), module, Rampage::RANGE_A_PARAM)); | addParam(createParam<BefacoSwitch>(Vec(94, 32), module, Rampage::RANGE_A_PARAM)); | ||||
addParam(createParam<BefacoTinyKnob>(Vec(27, 90), module, Rampage::SHAPE_A_PARAM)); | addParam(createParam<BefacoTinyKnob>(Vec(27, 90), module, Rampage::SHAPE_A_PARAM)); | ||||
@@ -207,42 +339,42 @@ struct RampageWidget : ModuleWidget { | |||||
addParam(createParam<BefacoSwitch>(Vec(141, 238), module, Rampage::CYCLE_B_PARAM)); | addParam(createParam<BefacoSwitch>(Vec(141, 238), module, Rampage::CYCLE_B_PARAM)); | ||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(117, 76), module, Rampage::BALANCE_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(117, 76), module, Rampage::BALANCE_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(14, 30), module, Rampage::IN_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(52, 37), module, Rampage::TRIGG_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(8, 268), module, Rampage::RISE_CV_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(67, 268), module, Rampage::FALL_CV_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(38, 297), module, Rampage::EXP_CV_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(102, 290), module, Rampage::CYCLE_A_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(229, 30), module, Rampage::IN_B_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(192, 37), module, Rampage::TRIGG_B_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(176, 268), module, Rampage::RISE_CV_B_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(237, 268), module, Rampage::FALL_CV_B_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(207, 297), module, Rampage::EXP_CV_B_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(143, 290), module, Rampage::CYCLE_B_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(8, 326), module, Rampage::RISING_A_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(68, 326), module, Rampage::FALLING_A_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(104, 326), module, Rampage::EOC_A_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(102, 195), module, Rampage::OUT_A_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(177, 326), module, Rampage::RISING_B_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(237, 326), module, Rampage::FALLING_B_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(140, 326), module, Rampage::EOC_B_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(142, 195), module, Rampage::OUT_B_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(122, 133), module, Rampage::COMPARATOR_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(89, 157), module, Rampage::MIN_OUTPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(155, 157), module, Rampage::MAX_OUTPUT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(132, 167), module, Rampage::COMPARATOR_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(123, 174), module, Rampage::MIN_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(141, 174), module, Rampage::MAX_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(126, 185), module, Rampage::OUT_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(138, 185), module, Rampage::OUT_B_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(18, 312), module, Rampage::RISING_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(78, 312), module, Rampage::FALLING_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(187, 312), module, Rampage::RISING_B_LIGHT)); | |||||
addChild(createLight<SmallLight<RedLight>>(Vec(247, 312), module, Rampage::FALLING_B_LIGHT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(14, 30), module, Rampage::IN_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(52, 37), module, Rampage::TRIGG_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(8, 268), module, Rampage::RISE_CV_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(67, 268), module, Rampage::FALL_CV_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(38, 297), module, Rampage::EXP_CV_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(102, 290), module, Rampage::CYCLE_A_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(229, 30), module, Rampage::IN_B_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(192, 37), module, Rampage::TRIGG_B_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(176, 268), module, Rampage::RISE_CV_B_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(237, 268), module, Rampage::FALL_CV_B_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(207, 297), module, Rampage::EXP_CV_B_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(143, 290), module, Rampage::CYCLE_B_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(8, 326), module, Rampage::RISING_A_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(68, 326), module, Rampage::FALLING_A_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(104, 326), module, Rampage::EOC_A_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(102, 195), module, Rampage::OUT_A_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(177, 326), module, Rampage::RISING_B_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(237, 326), module, Rampage::FALLING_B_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(140, 326), module, Rampage::EOC_B_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(142, 195), module, Rampage::OUT_B_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(122, 133), module, Rampage::COMPARATOR_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(89, 157), module, Rampage::MIN_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(155, 157), module, Rampage::MAX_OUTPUT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(132, 167), module, Rampage::COMPARATOR_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(123, 174), module, Rampage::MIN_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(141, 174), module, Rampage::MAX_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(126, 185), module, Rampage::OUT_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(138, 185), module, Rampage::OUT_B_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(18, 312), module, Rampage::RISING_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(78, 312), module, Rampage::FALLING_A_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(187, 312), module, Rampage::RISING_B_LIGHT)); | |||||
addChild(createLight<SmallLight<RedGreenBlueLight>>(Vec(247, 312), module, Rampage::FALLING_B_LIGHT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelRampage = createModel<Rampage, RampageWidget>("Rampage"); | |||||
Model* modelRampage = createModel<Rampage, RampageWidget>("Rampage"); |
@@ -1,5 +1,6 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
using simd::float_4; | |||||
struct SlewLimiter : Module { | struct SlewLimiter : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
@@ -19,7 +20,7 @@ struct SlewLimiter : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
float out = 0.0; | |||||
float_4 out[4] = {}; | |||||
SlewLimiter() { | SlewLimiter() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); | ||||
@@ -28,40 +29,63 @@ struct SlewLimiter : Module { | |||||
configParam(FALL_PARAM, 0.0, 1.0, 0.0, "Fall time"); | configParam(FALL_PARAM, 0.0, 1.0, 0.0, "Fall time"); | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
float in = inputs[IN_INPUT].getVoltage(); | |||||
float shape = params[SHAPE_PARAM].getValue(); | |||||
void process(const ProcessArgs& args) override { | |||||
// minimum and maximum slopes in volts per second | |||||
float_4 in[4] = {}; | |||||
float_4 riseCV[4] = {}; | |||||
float_4 fallCV[4] = {}; | |||||
// this is the number of active polyphony engines, defined by the input | |||||
int numPolyphonyEngines = inputs[IN_INPUT].getChannels(); | |||||
// minimum and std::maximum slopes in volts per second | |||||
const float slewMin = 0.1; | const float slewMin = 0.1; | ||||
const float slewMax = 10000.f; | const float slewMax = 10000.f; | ||||
// Amount of extra slew per voltage difference | // Amount of extra slew per voltage difference | ||||
const float shapeScale = 1/10.f; | |||||
// Rise | |||||
if (in > out) { | |||||
float rise = inputs[RISE_INPUT].getVoltage() / 10.f + params[RISE_PARAM].getValue(); | |||||
float slew = slewMax * std::pow(slewMin / slewMax, rise); | |||||
out += slew * crossfade(1.f, shapeScale * (in - out), shape) * args.sampleTime; | |||||
if (out > in) | |||||
out = in; | |||||
} | |||||
// Fall | |||||
else if (in < out) { | |||||
float fall = inputs[FALL_INPUT].getVoltage() / 10.f + params[FALL_PARAM].getValue(); | |||||
float slew = slewMax * std::pow(slewMin / slewMax, fall); | |||||
out -= slew * crossfade(1.f, shapeScale * (out - in), shape) * args.sampleTime; | |||||
if (out < in) | |||||
out = in; | |||||
} | |||||
const float shapeScale = 1 / 10.f; | |||||
const float_4 param_rise = params[RISE_PARAM].getValue() * 10.f; | |||||
const float_4 param_fall = params[FALL_PARAM].getValue() * 10.f; | |||||
outputs[OUT_OUTPUT].setChannels(numPolyphonyEngines); | |||||
for (int c = 0; c < numPolyphonyEngines; c += 4) { | |||||
in[c / 4] = inputs[IN_INPUT].getVoltageSimd<float_4>(c); | |||||
outputs[OUT_OUTPUT].setVoltage(out); | |||||
if (inputs[RISE_INPUT].isConnected()) { | |||||
riseCV[c / 4] = inputs[RISE_INPUT].getPolyVoltageSimd<float_4>(c); | |||||
} | |||||
if (inputs[FALL_INPUT].isConnected()) { | |||||
fallCV[c / 4] = inputs[FALL_INPUT].getPolyVoltageSimd<float_4>(c); | |||||
} | |||||
riseCV[c / 4] += param_rise; | |||||
fallCV[c / 4] += param_fall; | |||||
float_4 delta = in[c / 4] - out[c / 4]; | |||||
float_4 delta_gt_0 = delta > 0.f; | |||||
float_4 delta_lt_0 = delta < 0.f; | |||||
float_4 rateCV = {}; | |||||
rateCV = ifelse(delta_gt_0, riseCV[c / 4], 0.f); | |||||
rateCV = ifelse(delta_lt_0, fallCV[c / 4], rateCV) * 0.1f; | |||||
float_4 pm_one = simd::sgn(delta); | |||||
float_4 slew = slewMax * simd::pow(slewMin / slewMax, rateCV); | |||||
const float shape = params[SHAPE_PARAM].getValue(); | |||||
out[c / 4] += slew * simd::crossfade(pm_one, shapeScale * delta, shape) * args.sampleTime; | |||||
out[c / 4] = ifelse(delta_gt_0 & (out[c / 4] > in[c / 4]), in[c / 4], out[c / 4]); | |||||
out[c / 4] = ifelse(delta_lt_0 & (out[c / 4] < in[c / 4]), in[c / 4], out[c / 4]); | |||||
outputs[OUT_OUTPUT].setVoltageSimd(out[c / 4], c); | |||||
} | |||||
} | } | ||||
}; | }; | ||||
struct SlewLimiterWidget : ModuleWidget { | struct SlewLimiterWidget : ModuleWidget { | ||||
SlewLimiterWidget(::SlewLimiter *module) { | |||||
SlewLimiterWidget(SlewLimiter* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SlewLimiter.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SlewLimiter.svg"))); | ||||
@@ -73,13 +97,13 @@ struct SlewLimiterWidget : ModuleWidget { | |||||
addParam(createParam<BefacoSlidePot>(Vec(15, 102), module, ::SlewLimiter::RISE_PARAM)); | addParam(createParam<BefacoSlidePot>(Vec(15, 102), module, ::SlewLimiter::RISE_PARAM)); | ||||
addParam(createParam<BefacoSlidePot>(Vec(60, 102), module, ::SlewLimiter::FALL_PARAM)); | addParam(createParam<BefacoSlidePot>(Vec(60, 102), module, ::SlewLimiter::FALL_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(10, 273), module, ::SlewLimiter::RISE_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(55, 273), module, ::SlewLimiter::FALL_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(10, 273), module, ::SlewLimiter::RISE_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(55, 273), module, ::SlewLimiter::FALL_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(10, 323), module, ::SlewLimiter::IN_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(55, 323), module, ::SlewLimiter::OUT_OUTPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(10, 323), module, ::SlewLimiter::IN_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(55, 323), module, ::SlewLimiter::OUT_OUTPUT)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelSlewLimiter = createModel<::SlewLimiter, SlewLimiterWidget>("SlewLimiter"); | |||||
Model* modelSlewLimiter = createModel<::SlewLimiter, SlewLimiterWidget>("SlewLimiter"); |
@@ -1,7 +1,5 @@ | |||||
#include <string.h> | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "pffft.h" | |||||
#include <pffft.h> | |||||
BINARY(src_SpringReverbIR_pcm); | BINARY(src_SpringReverbIR_pcm); | ||||
@@ -32,19 +30,23 @@ struct SpringReverb : Module { | |||||
}; | }; | ||||
enum LightIds { | enum LightIds { | ||||
PEAK_LIGHT, | PEAK_LIGHT, | ||||
VU1_LIGHT, | |||||
NUM_LIGHTS = VU1_LIGHT + 7 | |||||
ENUMS(VU1_LIGHTS, 7), | |||||
NUM_LIGHTS | |||||
}; | }; | ||||
dsp::RealTimeConvolver *convolver = NULL; | |||||
dsp::RealTimeConvolver* convolver = NULL; | |||||
dsp::SampleRateConverter<1> inputSrc; | dsp::SampleRateConverter<1> inputSrc; | ||||
dsp::SampleRateConverter<1> outputSrc; | dsp::SampleRateConverter<1> outputSrc; | ||||
dsp::DoubleRingBuffer<dsp::Frame<1>, 16*BLOCK_SIZE> inputBuffer; | |||||
dsp::DoubleRingBuffer<dsp::Frame<1>, 16*BLOCK_SIZE> outputBuffer; | |||||
dsp::DoubleRingBuffer<dsp::Frame<1>, 16 * BLOCK_SIZE> inputBuffer; | |||||
dsp::DoubleRingBuffer<dsp::Frame<1>, 16 * BLOCK_SIZE> outputBuffer; | |||||
dsp::RCFilter dryFilter; | dsp::RCFilter dryFilter; | ||||
dsp::PeakFilter vuFilter; | |||||
dsp::PeakFilter lightFilter; | |||||
dsp::VuMeter2 vuFilter; | |||||
dsp::VuMeter2 lightFilter; | |||||
dsp::ClockDivider lightRefreshClock; | |||||
const float brightnessIntervals[8] = {17.f, 14.f, 12.f, 9.f, 6.f, 0.f, -6.f, -12.f}; | |||||
SpringReverb() { | SpringReverb() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
@@ -55,18 +57,23 @@ struct SpringReverb : Module { | |||||
convolver = new dsp::RealTimeConvolver(BLOCK_SIZE); | convolver = new dsp::RealTimeConvolver(BLOCK_SIZE); | ||||
const float *kernel = (const float*) BINARY_START(src_SpringReverbIR_pcm); | |||||
const float* kernel = (const float*) BINARY_START(src_SpringReverbIR_pcm); | |||||
size_t kernelLen = BINARY_SIZE(src_SpringReverbIR_pcm) / sizeof(float); | size_t kernelLen = BINARY_SIZE(src_SpringReverbIR_pcm) / sizeof(float); | ||||
convolver->setKernel(kernel, kernelLen); | convolver->setKernel(kernel, kernelLen); | ||||
vuFilter.mode = dsp::VuMeter2::PEAK; | |||||
lightFilter.mode = dsp::VuMeter2::PEAK; | |||||
lightRefreshClock.setDivision(32); | |||||
} | } | ||||
~SpringReverb() { | ~SpringReverb() { | ||||
delete convolver; | delete convolver; | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
float in1 = inputs[IN1_INPUT].getVoltage(); | |||||
float in2 = inputs[IN2_INPUT].getVoltage(); | |||||
void process(const ProcessArgs& args) override { | |||||
float in1 = inputs[IN1_INPUT].getVoltageSum(); | |||||
float in2 = inputs[IN2_INPUT].getVoltageSum(); | |||||
const float levelScale = 0.030; | const float levelScale = 0.030; | ||||
const float levelBase = 25.0; | const float levelBase = 25.0; | ||||
float level1 = levelScale * dsp::exponentialBipolar(levelBase, params[LEVEL1_PARAM].getValue()) * inputs[CV1_INPUT].getNormalVoltage(10.0) / 10.0; | float level1 = levelScale * dsp::exponentialBipolar(levelBase, params[LEVEL1_PARAM].getValue()) * inputs[CV1_INPUT].getNormalVoltage(10.0) / 10.0; | ||||
@@ -114,6 +121,7 @@ struct SpringReverb : Module { | |||||
// Set output | // Set output | ||||
if (outputBuffer.empty()) | if (outputBuffer.empty()) | ||||
return; | return; | ||||
float wet = outputBuffer.shift().samples[0]; | float wet = outputBuffer.shift().samples[0]; | ||||
float balance = clamp(params[WET_PARAM].getValue() + inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); | float balance = clamp(params[WET_PARAM].getValue() + inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); | ||||
float mix = crossfade(in1, wet, balance); | float mix = crossfade(in1, wet, balance); | ||||
@@ -121,32 +129,33 @@ struct SpringReverb : Module { | |||||
outputs[WET_OUTPUT].setVoltage(clamp(wet, -10.0f, 10.0f)); | outputs[WET_OUTPUT].setVoltage(clamp(wet, -10.0f, 10.0f)); | ||||
outputs[MIX_OUTPUT].setVoltage(clamp(mix, -10.0f, 10.0f)); | outputs[MIX_OUTPUT].setVoltage(clamp(mix, -10.0f, 10.0f)); | ||||
// Set lights | |||||
float lightRate = 5.0 * args.sampleTime; | |||||
vuFilter.setRate(lightRate); | |||||
vuFilter.process(std::fabs(wet)); | |||||
lightFilter.setRate(lightRate); | |||||
lightFilter.process(std::fabs(dry*50.0)); | |||||
float vuValue = vuFilter.peak(); | |||||
for (int i = 0; i < 7; i++) { | |||||
float light = std::pow(1.413, i) * vuValue / 10.0 - 1.0; | |||||
lights[VU1_LIGHT + i].value = clamp(light, 0.0f, 1.0f); | |||||
// process VU lights | |||||
vuFilter.process(args.sampleTime, wet); | |||||
// process peak light | |||||
lightFilter.process(args.sampleTime, dry * 50.0); | |||||
if (lightRefreshClock.process()) { | |||||
for (int i = 0; i < 7; i++) { | |||||
float brightness = vuFilter.getBrightness(brightnessIntervals[i + 1], brightnessIntervals[i]); | |||||
lights[VU1_LIGHTS + i].setBrightness(brightness); | |||||
} | |||||
lights[PEAK_LIGHT].value = lightFilter.v; | |||||
} | } | ||||
lights[PEAK_LIGHT].value = lightFilter.peak(); | |||||
} | } | ||||
}; | }; | ||||
struct SpringReverbWidget : ModuleWidget { | struct SpringReverbWidget : ModuleWidget { | ||||
SpringReverbWidget(SpringReverb *module) { | |||||
SpringReverbWidget(SpringReverb* module) { | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SpringReverb.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SpringReverb.svg"))); | ||||
addChild(createWidget<Knurlie>(Vec(15, 0))); | addChild(createWidget<Knurlie>(Vec(15, 0))); | ||||
addChild(createWidget<Knurlie>(Vec(15, 365))); | addChild(createWidget<Knurlie>(Vec(15, 365))); | ||||
addChild(createWidget<Knurlie>(Vec(15*6, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(15*6, 365))); | |||||
addChild(createWidget<Knurlie>(Vec(15 * 6, 0))); | |||||
addChild(createWidget<Knurlie>(Vec(15 * 6, 365))); | |||||
addParam(createParam<BefacoBigKnob>(Vec(22, 29), module, SpringReverb::WET_PARAM)); | addParam(createParam<BefacoBigKnob>(Vec(22, 29), module, SpringReverb::WET_PARAM)); | ||||
@@ -155,25 +164,25 @@ struct SpringReverbWidget : ModuleWidget { | |||||
addParam(createParam<Davies1900hWhiteKnob>(Vec(42, 210), module, SpringReverb::HPF_PARAM)); | addParam(createParam<Davies1900hWhiteKnob>(Vec(42, 210), module, SpringReverb::HPF_PARAM)); | ||||
addInput(createInput<PJ301MPort>(Vec(7, 243), module, SpringReverb::CV1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(88, 243), module, SpringReverb::CV2_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(27, 281), module, SpringReverb::IN1_INPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(67, 281), module, SpringReverb::IN2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(7, 243), module, SpringReverb::CV1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(88, 243), module, SpringReverb::CV2_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(27, 281), module, SpringReverb::IN1_INPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(67, 281), module, SpringReverb::IN2_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(7, 317), module, SpringReverb::MIX_OUTPUT)); | |||||
addInput(createInput<PJ301MPort>(Vec(47, 324), module, SpringReverb::MIX_CV_INPUT)); | |||||
addOutput(createOutput<PJ301MPort>(Vec(88, 317), module, SpringReverb::WET_OUTPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(7, 317), module, SpringReverb::MIX_OUTPUT)); | |||||
addInput(createInput<BefacoInputPort>(Vec(47, 324), module, SpringReverb::MIX_CV_INPUT)); | |||||
addOutput(createOutput<BefacoOutputPort>(Vec(88, 317), module, SpringReverb::WET_OUTPUT)); | |||||
addChild(createLight<MediumLight<GreenRedLight>>(Vec(55, 269), module, SpringReverb::PEAK_LIGHT)); | addChild(createLight<MediumLight<GreenRedLight>>(Vec(55, 269), module, SpringReverb::PEAK_LIGHT)); | ||||
addChild(createLight<MediumLight<RedLight>>(Vec(55, 113), module, SpringReverb::VU1_LIGHT + 0)); | |||||
addChild(createLight<MediumLight<YellowLight>>(Vec(55, 126), module, SpringReverb::VU1_LIGHT + 1)); | |||||
addChild(createLight<MediumLight<YellowLight>>(Vec(55, 138), module, SpringReverb::VU1_LIGHT + 2)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 150), module, SpringReverb::VU1_LIGHT + 3)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 163), module, SpringReverb::VU1_LIGHT + 4)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 175), module, SpringReverb::VU1_LIGHT + 5)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 188), module, SpringReverb::VU1_LIGHT + 6)); | |||||
addChild(createLight<MediumLight<RedLight>>(Vec(55, 113), module, SpringReverb::VU1_LIGHTS + 0)); | |||||
addChild(createLight<MediumLight<YellowLight>>(Vec(55, 126), module, SpringReverb::VU1_LIGHTS + 1)); | |||||
addChild(createLight<MediumLight<YellowLight>>(Vec(55, 138), module, SpringReverb::VU1_LIGHTS + 2)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 150), module, SpringReverb::VU1_LIGHTS + 3)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 163), module, SpringReverb::VU1_LIGHTS + 4)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 175), module, SpringReverb::VU1_LIGHTS + 5)); | |||||
addChild(createLight<MediumLight<GreenLight>>(Vec(55, 188), module, SpringReverb::VU1_LIGHTS + 6)); | |||||
} | } | ||||
}; | }; | ||||
Model *modelSpringReverb = createModel<SpringReverb, SpringReverbWidget>("SpringReverb"); | |||||
Model* modelSpringReverb = createModel<SpringReverb, SpringReverbWidget>("SpringReverb"); |
@@ -13,4 +13,8 @@ void init(rack::Plugin *p) { | |||||
p->addModel(modelMixer); | p->addModel(modelMixer); | ||||
p->addModel(modelSlewLimiter); | p->addModel(modelSlewLimiter); | ||||
p->addModel(modelDualAtenuverter); | p->addModel(modelDualAtenuverter); | ||||
p->addModel(modelPercall); | |||||
p->addModel(modelHexmixVCA); | |||||
p->addModel(modelChoppingKinky); | |||||
p->addModel(modelKickall); | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include <rack.hpp> | |||||
using namespace rack; | using namespace rack; | ||||
@@ -13,12 +13,122 @@ extern Model *modelSpringReverb; | |||||
extern Model *modelMixer; | extern Model *modelMixer; | ||||
extern Model *modelSlewLimiter; | extern Model *modelSlewLimiter; | ||||
extern Model *modelDualAtenuverter; | extern Model *modelDualAtenuverter; | ||||
extern Model *modelPercall; | |||||
extern Model *modelHexmixVCA; | |||||
extern Model *modelChoppingKinky; | |||||
extern Model *modelKickall; | |||||
struct Knurlie : SVGScrew { | |||||
struct Knurlie : SvgScrew { | |||||
Knurlie() { | Knurlie() { | ||||
sw->svg = APP->window->loadSvg(asset::plugin(pluginInstance, "res/Knurlie.svg")); | sw->svg = APP->window->loadSvg(asset::plugin(pluginInstance, "res/Knurlie.svg")); | ||||
sw->wrap(); | sw->wrap(); | ||||
box.size = sw->box.size; | box.size = sw->box.size; | ||||
} | } | ||||
}; | }; | ||||
struct BefacoTinyKnobRed : app::SvgKnob { | |||||
BefacoTinyKnobRed() { | |||||
minAngle = -0.8 * M_PI; | |||||
maxAngle = 0.8 * M_PI; | |||||
setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoTinyKnobRed.svg"))); | |||||
} | |||||
}; | |||||
struct BefacoTinyKnobWhite : app::SvgKnob { | |||||
BefacoTinyKnobWhite() { | |||||
minAngle = -0.8 * M_PI; | |||||
maxAngle = 0.8 * M_PI; | |||||
setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/BefacoTinyKnob.svg"))); | |||||
} | |||||
}; | |||||
struct BefacoTinyKnobGrey : app::SvgKnob { | |||||
BefacoTinyKnobGrey() { | |||||
minAngle = -0.8 * M_PI; | |||||
maxAngle = 0.8 * M_PI; | |||||
setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoTinyKnobGrey.svg"))); | |||||
} | |||||
}; | |||||
struct Davies1900hLargeGreyKnob : Davies1900hKnob { | |||||
Davies1900hLargeGreyKnob() { | |||||
setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Davies1900hLargeGrey.svg"))); | |||||
} | |||||
}; | |||||
struct BefacoOutputPort : app::SvgPort { | |||||
BefacoOutputPort() { | |||||
setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoOutputPort.svg"))); | |||||
} | |||||
}; | |||||
struct BefacoInputPort : app::SvgPort { | |||||
BefacoInputPort() { | |||||
setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BefacoInputPort.svg"))); | |||||
} | |||||
}; | |||||
template <typename T> | |||||
T sin2pi_pade_05_5_4(T x) { | |||||
x -= 0.5f; | |||||
return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5)) | |||||
/ (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4)); | |||||
} | |||||
template <typename T> | |||||
T tanh_pade(T x) { | |||||
T x2 = x * x; | |||||
T q = 12.f + x2; | |||||
return 12.f * x * q / (36.f * x2 + q * q); | |||||
} | |||||
struct ADEnvelope { | |||||
enum Stage { | |||||
STAGE_OFF, | |||||
STAGE_ATTACK, | |||||
STAGE_DECAY | |||||
}; | |||||
Stage stage = STAGE_OFF; | |||||
float env = 0.f; | |||||
float attackTime = 0.1, decayTime = 0.1; | |||||
float attackShape = 1.0, decayShape = 1.0; | |||||
ADEnvelope() { }; | |||||
void process(const float& sampleTime) { | |||||
if (stage == STAGE_OFF) { | |||||
env = envLinear = 0.0f; | |||||
} | |||||
else if (stage == STAGE_ATTACK) { | |||||
envLinear += sampleTime / attackTime; | |||||
env = std::pow(envLinear, attackShape); | |||||
} | |||||
else if (stage == STAGE_DECAY) { | |||||
envLinear -= sampleTime / decayTime; | |||||
env = std::pow(envLinear, decayShape); | |||||
} | |||||
if (envLinear >= 1.0f) { | |||||
stage = STAGE_DECAY; | |||||
env = envLinear = 1.0f; | |||||
} | |||||
else if (envLinear <= 0.0f) { | |||||
stage = STAGE_OFF; | |||||
env = envLinear = 0.0f; | |||||
} | |||||
} | |||||
void trigger() { | |||||
stage = ADEnvelope::STAGE_ATTACK; | |||||
// non-linear envelopes won't retrigger at the correct starting point if | |||||
// attackShape != decayShape, so we advance the linear envelope | |||||
envLinear = std::pow(env, 1.0f / attackShape); | |||||
} | |||||
private: | |||||
float envLinear = 0.f; | |||||
}; |