Browse Source

Merge pull request #20 from hemmer/v1

Release updated modules: Percall, Chopping Kinky, Hexmix VCA, Kickall (v1.1.0 release)
tags/v1.1.0
Andrew Belt GitHub 3 years ago
parent
commit
3b80c5cdd3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 8087 additions and 505 deletions
  1. +1
    -42
      README.md
  2. +146
    -93
      plugin.json
  3. +210
    -0
      res/BefacoInputPort.svg
  4. +210
    -0
      res/BefacoOutputPort.svg
  5. +85
    -0
      res/BefacoTinyKnobGrey.svg
  6. +85
    -0
      res/BefacoTinyKnobRed.svg
  7. +1064
    -0
      res/ChoppingKinky.svg
  8. +105
    -0
      res/Davies1900hLargeGrey.svg
  9. +1371
    -0
      res/HexmixVCA.svg
  10. +1016
    -0
      res/Kickall.svg
  11. +1497
    -0
      res/Percall.svg
  12. +126
    -41
      src/ABC.cpp
  13. +362
    -0
      src/ChoppingKinky.cpp
  14. +422
    -0
      src/ChowDSP.hpp
  15. +68
    -25
      src/DualAtenuverter.cpp
  16. +163
    -81
      src/EvenVCO.cpp
  17. +212
    -0
      src/HexmixVCA.cpp
  18. +146
    -0
      src/Kickall.cpp
  19. +70
    -22
      src/Mixer.cpp
  20. +220
    -0
      src/Percall.cpp
  21. +28
    -0
      src/PulseGenerator_4.hpp
  22. +257
    -125
      src/Rampage.cpp
  23. +54
    -30
      src/SlewLimiter.cpp
  24. +53
    -44
      src/SpringReverb.cpp
  25. +4
    -0
      src/plugin.cpp
  26. +112
    -2
      src/plugin.hpp

+ 1
- 42
README.md View File

@@ -2,45 +2,4 @@

Based on [Befaco](http://www.befaco.org/) Eurorack modules.

### EvenVCO

Based on [EvenVCO](http://www.befaco.org/even-vco/)

![EvenVCO](https://library.vcvrack.com/screenshots/Befaco/EvenVCO.m.png)

### Rampage

Based on [Rampage](http://www.befaco.org/rampage-2/), [Manual PDF](https://befaco.org/docs/Rampage/Rampage_User_Manual.pdf)

![Rampage](https://library.vcvrack.com/screenshots/Befaco/Rampage.m.png)

### 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)

![A\*B+C](https://library.vcvrack.com/screenshots/Befaco/ABC.m.png)

### Spring Reverb

Based on [Spring Reverb](http://www.befaco.org/spring-reverb/)

![Spring Reverb](https://library.vcvrack.com/screenshots/Befaco/SpringReverb.m.png)

### Mixer

Based on [Mixer](http://www.befaco.org/mixer-2/)

![Mixer](https://library.vcvrack.com/screenshots/Befaco/Mixer.m.png)

### Slew Limiter

Based on [Slew Limiter](http://www.befaco.org/slew-limiter/)

![Slew Limiter](https://library.vcvrack.com/screenshots/Befaco/SlewLimiter.m.png)


### Dual Atenuverter

Based on [Dual Atenuverter](http://www.befaco.org/dual-atenuverter/)

![Dual Atenuverter](https://library.vcvrack.com/screenshots/Befaco/DualAtenuverter.m.png)
[VCV Library page](https://library.vcvrack.com/Befaco)

+ 146
- 93
plugin.json View File

@@ -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"
]
}
]
}

+ 210
- 0
res/BefacoInputPort.svg View File

@@ -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>

+ 210
- 0
res/BefacoOutputPort.svg View File

@@ -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>

+ 85
- 0
res/BefacoTinyKnobGrey.svg View File

@@ -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>

+ 85
- 0
res/BefacoTinyKnobRed.svg View File

@@ -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>

+ 1064
- 0
res/ChoppingKinky.svg
File diff suppressed because it is too large
View File


+ 105
- 0
res/Davies1900hLargeGrey.svg View File

@@ -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>

+ 1371
- 0
res/HexmixVCA.svg
File diff suppressed because it is too large
View File


+ 1016
- 0
res/Kickall.svg
File diff suppressed because it is too large
View File


+ 1497
- 0
res/Percall.svg
File diff suppressed because it is too large
View File


+ 126
- 41
src/ABC.cpp View File

@@ -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");

+ 362
- 0
src/ChoppingKinky.cpp View File

@@ -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");

+ 422
- 0
src/ChowDSP.hpp View File

@@ -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

+ 68
- 25
src/DualAtenuverter.cpp View File

@@ -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");

+ 163
- 81
src/EvenVCO.cpp View File

@@ -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");

+ 212
- 0
src/HexmixVCA.cpp View File

@@ -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");

+ 146
- 0
src/Kickall.cpp View File

@@ -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");

+ 70
- 22
src/Mixer.cpp View File

@@ -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");

+ 220
- 0
src/Percall.cpp View File

@@ -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");

+ 28
- 0
src/PulseGenerator_4.hpp View File

@@ -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);
}
};

+ 257
- 125
src/Rampage.cpp View File

@@ -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");

+ 54
- 30
src/SlewLimiter.cpp View File

@@ -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");

+ 53
- 44
src/SpringReverb.cpp View File

@@ -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");

+ 4
- 0
src/plugin.cpp View File

@@ -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);
}

+ 112
- 2
src/plugin.hpp View File

@@ -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;
};

Loading…
Cancel
Save