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. | |||
### 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" | |||
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) | |||
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 | |||
}; | |||
enum LightIds { | |||
ENUMS(OUT1_LIGHT, 2), | |||
ENUMS(OUT2_LIGHT, 2), | |||
ENUMS(OUT1_LIGHT, 3), | |||
ENUMS(OUT2_LIGHT, 3), | |||
NUM_LIGHTS | |||
}; | |||
@@ -51,39 +54,121 @@ struct ABC : Module { | |||
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 { | |||
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()) { | |||
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[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 { | |||
ABCWidget(ABC *module) { | |||
ABCWidget(ABC* module) { | |||
setModule(module); | |||
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<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" | |||
struct DualAtenuverter : Module { | |||
enum ParamIds { | |||
ATEN1_PARAM, | |||
@@ -20,10 +19,8 @@ struct DualAtenuverter : Module { | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
OUT1_POS_LIGHT, | |||
OUT1_NEG_LIGHT, | |||
OUT2_POS_LIGHT, | |||
OUT2_NEG_LIGHT, | |||
ENUMS(OUT1_LIGHT, 3), | |||
ENUMS(OUT2_LIGHT, 3), | |||
NUM_LIGHTS | |||
}; | |||
@@ -35,24 +32,70 @@ struct DualAtenuverter : Module { | |||
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 { | |||
DualAtenuverterWidget(DualAtenuverter *module) { | |||
DualAtenuverterWidget(DualAtenuverter* module) { | |||
setModule(module); | |||
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<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" | |||
using simd::float_4; | |||
struct EvenVCO : Module { | |||
enum ParamIds { | |||
@@ -25,22 +26,21 @@ struct EvenVCO : Module { | |||
NUM_OUTPUTS | |||
}; | |||
float phase = 0.0; | |||
float_4 phase[4] = {}; | |||
float_4 tri[4] = {}; | |||
/** The value of the last sync input */ | |||
float sync = 0.0; | |||
/** The outputs */ | |||
float tri = 0.0; | |||
/** 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() { | |||
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"); | |||
} | |||
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 | |||
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 | |||
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 { | |||
EvenVCOWidget(EvenVCO *module) { | |||
EvenVCOWidget(EvenVCO* module) { | |||
setModule(module); | |||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/EvenVCO.svg"))); | |||
addChild(createWidget<Knurlie>(Vec(15, 0))); | |||
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<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO::TUNE_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" | |||
using simd::float_4; | |||
struct Mixer : Module { | |||
enum ParamIds { | |||
@@ -24,6 +25,7 @@ struct Mixer : Module { | |||
enum LightIds { | |||
OUT_POS_LIGHT, | |||
OUT_NEG_LIGHT, | |||
OUT_BLUE_LIGHT, | |||
NUM_LIGHTS | |||
}; | |||
@@ -35,25 +37,71 @@ struct Mixer : Module { | |||
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 { | |||
MixerWidget(Mixer *module) { | |||
MixerWidget(Mixer* module) { | |||
setModule(module); | |||
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, 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 "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) { | |||
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 { | |||
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 { | |||
enum ParamIds { | |||
RANGE_A_PARAM, | |||
@@ -61,22 +62,26 @@ struct Rampage : Module { | |||
NUM_OUTPUTS | |||
}; | |||
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 | |||
}; | |||
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() { | |||
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"); | |||
} | |||
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 { | |||
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 | |||
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[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 { | |||
RampageWidget(Rampage *module) { | |||
RampageWidget(Rampage* module) { | |||
setModule(module); | |||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Rampage.svg"))); | |||
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(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<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<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" | |||
using simd::float_4; | |||
struct SlewLimiter : Module { | |||
enum ParamIds { | |||
@@ -19,7 +20,7 @@ struct SlewLimiter : Module { | |||
NUM_OUTPUTS | |||
}; | |||
float out = 0.0; | |||
float_4 out[4] = {}; | |||
SlewLimiter() { | |||
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"); | |||
} | |||
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 slewMax = 10000.f; | |||
// 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 { | |||
SlewLimiterWidget(::SlewLimiter *module) { | |||
SlewLimiterWidget(SlewLimiter* module) { | |||
setModule(module); | |||
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(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 "pffft.h" | |||
#include <pffft.h> | |||
BINARY(src_SpringReverbIR_pcm); | |||
@@ -32,19 +30,23 @@ struct SpringReverb : Module { | |||
}; | |||
enum LightIds { | |||
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> 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::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() { | |||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
@@ -55,18 +57,23 @@ struct SpringReverb : Module { | |||
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); | |||
convolver->setKernel(kernel, kernelLen); | |||
vuFilter.mode = dsp::VuMeter2::PEAK; | |||
lightFilter.mode = dsp::VuMeter2::PEAK; | |||
lightRefreshClock.setDivision(32); | |||
} | |||
~SpringReverb() { | |||
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 levelBase = 25.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 | |||
if (outputBuffer.empty()) | |||
return; | |||
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 mix = crossfade(in1, wet, balance); | |||
@@ -121,32 +129,33 @@ struct SpringReverb : Module { | |||
outputs[WET_OUTPUT].setVoltage(clamp(wet, -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 { | |||
SpringReverbWidget(SpringReverb *module) { | |||
SpringReverbWidget(SpringReverb* module) { | |||
setModule(module); | |||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/SpringReverb.svg"))); | |||
addChild(createWidget<Knurlie>(Vec(15, 0))); | |||
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)); | |||
@@ -155,25 +164,25 @@ struct SpringReverbWidget : ModuleWidget { | |||
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<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(modelSlewLimiter); | |||
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; | |||
@@ -13,12 +13,122 @@ extern Model *modelSpringReverb; | |||
extern Model *modelMixer; | |||
extern Model *modelSlewLimiter; | |||
extern Model *modelDualAtenuverter; | |||
extern Model *modelPercall; | |||
extern Model *modelHexmixVCA; | |||
extern Model *modelChoppingKinky; | |||
extern Model *modelKickall; | |||
struct Knurlie : SVGScrew { | |||
struct Knurlie : SvgScrew { | |||
Knurlie() { | |||
sw->svg = APP->window->loadSvg(asset::plugin(pluginInstance, "res/Knurlie.svg")); | |||
sw->wrap(); | |||
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; | |||
}; |