├── res
├── images
│ ├── mv_panels.jpg
│ ├── chorus_panels.jpg
│ ├── golem_panels.jpg
│ ├── holt_panels.jpg
│ ├── hombre_panels.jpg
│ ├── rasp_panels.jpg
│ ├── reseq_panels.jpg
│ ├── tape_panels.jpg
│ ├── console_panels.jpg
│ ├── distance_panels.jpg
│ ├── dual_bsg_panels.jpg
│ ├── tremolo_panels.jpg
│ ├── vibrato_panels.jpg
│ ├── capacitor_panels.jpg
│ ├── interstage_panels.jpg
│ ├── monitoring_panels.jpg
│ └── rackwindows_panels.jpg
└── components
│ ├── rw_knob_trimpot.svg
│ ├── rw_knob_medium_dark.svg
│ ├── rw_knob_small_dark.svg
│ ├── rw_knob_large_dark.svg
│ ├── rw_switch_three_vert_0.svg
│ ├── rw_switch_three_vert_2.svg
│ ├── rw_switch_three_0.svg
│ ├── rw_switch_three_2.svg
│ ├── rw_switch_three_1.svg
│ ├── rw_switch_three_vert_1.svg
│ ├── rw_PJ301M.svg
│ ├── rw_PJ301M_silver.svg
│ ├── rw_screw_dark.svg
│ ├── rw_CKSS_0.svg
│ ├── rw_CKSS_rot_0.svg
│ ├── rw_CKSS_1.svg
│ └── rw_CKSS_rot_1.svg
├── .gitignore
├── .github
├── workflows
│ ├── buildDevelop.yml
│ └── buildRelease.yml
└── actions
│ ├── build_linux
│ ├── entrypoint.sh
│ └── Dockerfile
│ ├── build_win
│ ├── entrypoint.sh
│ └── Dockerfile
│ ├── build_osx
│ ├── entrypoint.sh
│ └── Dockerfile
│ ├── upload_zip
│ └── script.sh
│ └── combine_zip
│ └── script.sh
├── Makefile
├── changelog.md
├── LICENSE.md
├── src
├── plugin.hpp
├── components.hpp
├── monitoring.cpp
├── tape.cpp
├── interstage.cpp
├── bitshiftgain.cpp
├── hombre.cpp
├── distance.cpp
├── plugin.cpp
├── rasp.cpp
├── tremolo.cpp
├── holt.cpp
└── vibrato.cpp
├── plugin.json
└── readme.md
/res/images/mv_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/mv_panels.jpg
--------------------------------------------------------------------------------
/res/images/chorus_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/chorus_panels.jpg
--------------------------------------------------------------------------------
/res/images/golem_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/golem_panels.jpg
--------------------------------------------------------------------------------
/res/images/holt_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/holt_panels.jpg
--------------------------------------------------------------------------------
/res/images/hombre_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/hombre_panels.jpg
--------------------------------------------------------------------------------
/res/images/rasp_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/rasp_panels.jpg
--------------------------------------------------------------------------------
/res/images/reseq_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/reseq_panels.jpg
--------------------------------------------------------------------------------
/res/images/tape_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/tape_panels.jpg
--------------------------------------------------------------------------------
/res/images/console_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/console_panels.jpg
--------------------------------------------------------------------------------
/res/images/distance_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/distance_panels.jpg
--------------------------------------------------------------------------------
/res/images/dual_bsg_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/dual_bsg_panels.jpg
--------------------------------------------------------------------------------
/res/images/tremolo_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/tremolo_panels.jpg
--------------------------------------------------------------------------------
/res/images/vibrato_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/vibrato_panels.jpg
--------------------------------------------------------------------------------
/res/images/capacitor_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/capacitor_panels.jpg
--------------------------------------------------------------------------------
/res/images/interstage_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/interstage_panels.jpg
--------------------------------------------------------------------------------
/res/images/monitoring_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/monitoring_panels.jpg
--------------------------------------------------------------------------------
/res/images/rackwindows_panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n0jo/rackwindows/HEAD/res/images/rackwindows_panels.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /design
3 | /dist
4 | /plugin.so
5 | /plugin.dylib
6 | /plugin.dll
7 | /todo.md
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/.github/workflows/buildDevelop.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - develop
5 | name: Develop
6 | env:
7 | RACK_SDK_VERSION: 1.1.6
8 | jobs:
9 | buildLinux:
10 | name: Build Linux
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@master
14 | - name: Build Linux
15 | uses: ./.github/actions/build_linux
16 |
--------------------------------------------------------------------------------
/.github/actions/build_linux/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK
6 | export RACK_USER_DIR=${GITHUB_WORKSPACE}
7 |
8 | git submodule update --init --recursive
9 |
10 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip
11 | unzip -o rack-sdk.zip
12 | rm rack-sdk.zip
13 |
14 | make clean
15 | make dist
16 |
--------------------------------------------------------------------------------
/.github/actions/build_win/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK
6 | export RACK_USER_DIR=${GITHUB_WORKSPACE}
7 |
8 | export CC=x86_64-w64-mingw32-gcc-posix
9 | export CXX=x86_64-w64-mingw32-g++-posix
10 | export STRIP=x86_64-w64-mingw32-strip
11 |
12 | git submodule update --init --recursive
13 |
14 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip
15 | unzip -o rack-sdk.zip
16 | rm rack-sdk.zip
17 |
18 | make clean
19 | make dist
20 |
--------------------------------------------------------------------------------
/.github/actions/build_osx/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK
6 | export RACK_USER_DIR=${GITHUB_WORKSPACE}
7 |
8 | export CC=x86_64-apple-darwin15-clang
9 | export CXX=x86_64-apple-darwin15-clang++
10 | export STRIP=x86_64-apple-darwin15-strip
11 |
12 | git submodule update --init --recursive
13 |
14 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip
15 | unzip -o rack-sdk.zip
16 | rm rack-sdk.zip
17 |
18 | make clean
19 | make dist
20 |
--------------------------------------------------------------------------------
/.github/actions/build_linux/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 |
3 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-Linux"
4 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for Linux"
5 | LABEL "com.github.actions.icon"="headphones"
6 | LABEL "com.github.actions.color"="purple"
7 |
8 | LABEL "repository"="TBD"
9 | LABEL "homepage"="TBD"
10 | LABEL "maintainer"="n0jo"
11 |
12 | RUN apt-get update
13 | RUN apt-get install -y build-essential cmake curl gcc g++ git make tar unzip zip libgl1-mesa-dev libglu1-mesa-dev jq
14 |
15 | ADD entrypoint.sh /entrypoint.sh
16 | RUN chmod a+x /entrypoint.sh
17 |
18 | ENTRYPOINT ["/entrypoint.sh"]
19 |
--------------------------------------------------------------------------------
/.github/actions/build_win/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:stretch
2 |
3 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-Windows"
4 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for Windows"
5 | LABEL "com.github.actions.icon"="headphones"
6 | LABEL "com.github.actions.color"="purple"
7 |
8 | LABEL "repository"="TBD"
9 | LABEL "homepage"="TBD"
10 | LABEL "maintainer"="n0jo"
11 |
12 | RUN apt-get update
13 | RUN apt-get install -y build-essential cmake curl gcc g++ git make tar unzip zip libgl1-mesa-dev libglu1-mesa-dev jq g++-mingw-w64-x86-64
14 |
15 | ADD entrypoint.sh /entrypoint.sh
16 | RUN chmod a+x /entrypoint.sh
17 |
18 | ENTRYPOINT ["/entrypoint.sh"]
19 |
--------------------------------------------------------------------------------
/.github/actions/upload_zip/script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | GITHUB_API_URL=https://api.github.com
6 |
7 | GITHUB_TOKEN=$1
8 |
9 | # Get release url
10 | curl -o release.json \
11 | --header "Authorization: token ${GITHUB_TOKEN}" \
12 | --request GET \
13 | ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${GITHUB_REF#"refs/"}
14 |
15 | UPLOAD_URL=$(jq -r .upload_url release.json)
16 |
17 | ASSET_PATH=$(ls dist/*.zip)
18 |
19 | curl -i \
20 | --header "Authorization: token ${GITHUB_TOKEN}" \
21 | --header "Content-Type: application/zip" \
22 | --request POST \
23 | --data-binary @"${ASSET_PATH}" \
24 | ${UPLOAD_URL%"{?name,label\}"}?name=${ASSET_PATH#"dist/"}
25 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above
2 | RACK_DIR ?= ../..
3 |
4 | # FLAGS will be passed to both the C and C++ compiler
5 | FLAGS +=
6 | CFLAGS +=
7 | CXXFLAGS +=
8 |
9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path.
10 | # Static libraries are fine, but they should be added to this plugin's build system.
11 | LDFLAGS +=
12 |
13 | # Add .cpp files to the build
14 | SOURCES += $(wildcard src/*.cpp)
15 |
16 | # Add files to the ZIP package when running `make dist`
17 | # The compiled plugin and "plugin.json" are automatically added.
18 | DISTRIBUTABLES += res
19 | # DISTRIBUTABLES += $(wildcard LICENSE*)
20 |
21 | # Include the Rack plugin Makefile framework
22 | include $(RACK_DIR)/plugin.mk
23 |
--------------------------------------------------------------------------------
/res/components/rw_knob_trimpot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/res/components/rw_knob_medium_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/res/components/rw_knob_small_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/res/components/rw_knob_large_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/.github/actions/combine_zip/script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -eu
4 |
5 | GITHUB_API_URL=https://api.github.com
6 |
7 | GITHUB_TOKEN=$1
8 |
9 | curl -o release.json \
10 | --header "Authorization: token ${GITHUB_TOKEN}" \
11 | --request GET \
12 | ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${GITHUB_REF#"refs/"}
13 |
14 | UPLOAD_URL=$(jq -r .upload_url release.json)
15 |
16 | DOWNLOAD_LIST=$(jq -r ".assets | .[] | .url" release.json)
17 |
18 | rm release.json
19 |
20 | mkdir dist
21 |
22 | echo "$DOWNLOAD_LIST" | while IFS= read -r line
23 | do
24 | curl -L -o $(basename $line).zip --header "Authorization: token ${GITHUB_TOKEN}" --header "Accept: application/octet-stream" --request GET $line
25 | unzip -o $(basename $line).zip -d dist
26 | rm $(basename $line).zip
27 | done
28 |
29 | PLUGIN_BUILD_SLUG=$(jq -r .slug plugin.json)
30 | PLUGIN_BUILD_NAME=${PLUGIN_BUILD_SLUG}-$(jq -r .version plugin.json)
31 | cd dist
32 | zip -q -9 -r ${PLUGIN_BUILD_NAME}.zip ./${PLUGIN_BUILD_SLUG}
33 | cd ..
34 |
35 | ASSET_PATH=$(ls dist/*.zip)
36 |
37 | curl -i \
38 | --header "Authorization: token ${GITHUB_TOKEN}" \
39 | --header "Content-Type: application/zip" \
40 | --request POST \
41 | --data-binary @"${ASSET_PATH}" \
42 | ${UPLOAD_URL%"{?name,label\}"}?name=${ASSET_PATH#"dist/"}
43 |
--------------------------------------------------------------------------------
/.github/workflows/buildRelease.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [published]
4 | name: Release
5 | env:
6 | RACK_SDK_VERSION: 1.1.6
7 | jobs:
8 | buildLinux:
9 | name: Build Linux
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@master
13 | - name: Build Linux
14 | uses: ./.github/actions/build_linux
15 | - name: upload zip
16 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }}
17 | buildWindows:
18 | name: Build Windows
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@master
22 | - name: Build Windows
23 | uses: ./.github/actions/build_win
24 | - name: upload zip
25 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }}
26 | buildOsx:
27 | name: Build OSX
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@master
31 | - name: Build OSX
32 | uses: ./.github/actions/build_osx
33 | - name: upload zip
34 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }}
35 | combineDist:
36 | name: Combine Distributions
37 | runs-on: ubuntu-latest
38 | needs: [buildLinux, buildWindows, buildOsx]
39 | steps:
40 | - uses: actions/checkout@master
41 | - name: combine zip
42 | run: sh ./.github/actions/combine_zip/script.sh ${{ secrets.GITHUB_TOKEN }}
43 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 |
2 | ### 1.1.2 (13-09-2020)
3 | - New module: Console MM
4 | - New module: Monitoring
5 | - New module: Golem
6 | - New module: Rasp
7 | - Tape: Fixed unbalanced output when loaded in a patch
8 |
9 | ### 1.1.1 (29-07-2020)
10 | - New module: Holt
11 | - ResEQ: Fixed parameter scaling and glitches
12 | - Tape: Improved performance
13 | - DualBSG: Minimized clicks on parameter changes (monophonic only)
14 | - Hombre: Removed wrong tag
15 | - Some modules: Fixed a bug, where the last voltage would be output after disconnection of input
16 | - All: Revised initialization and reset behaviour
17 |
18 | ### 1.1.0 (05-07-2020)
19 | - New module: ResEQ
20 | - New module: Interstage
21 | - Added/Updated low CPU mode for Chorus, Console, Distance, Hombre, MV, Tape, Tremolo and Vibrato
22 | - All: minor performance optimizations
23 |
24 | ### 1.0.4 (22-05-2020)
25 | - Now polyphonic: Dual BSG, Capacitor, Capacitor ST, Chorus, Console, Distance, Hombre, Tape, Tremolo and Vibrato
26 | - Capacitor, Capacitor ST: added low cpu mode
27 |
28 | ### 1.0.3 (24-04-2020)
29 |
30 | - New module: Tape
31 | - New module: Console
32 |
33 | ### 1.0.2 (13-04-2020)
34 |
35 | - Dual BSG: fixed switch graphic not rendering properly
36 |
37 | ### 1.0.1 (07-04-2020)
38 |
39 | - New module: Capacitor Stereo
40 | - New module: Distance
41 | - New module: Tremolo
42 |
43 | - All: process audio only when an output is connected
44 | - All: minor fixes and code cleanup
45 |
46 | ### 1.0.0 (27-03-2020)
47 |
48 | - Initial release
--------------------------------------------------------------------------------
/res/components/rw_switch_three_vert_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/res/components/rw_switch_three_vert_2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/res/components/rw_switch_three_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/res/components/rw_switch_three_2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/res/components/rw_switch_three_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/res/components/rw_switch_three_vert_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ### Rackwindows License ###
2 |
3 | MIT License
4 |
5 | Copyright (c) 2020 Jens Robert Janke
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
25 | ### Airwindows License ###
26 |
27 | MIT License
28 |
29 | Copyright (c) 2018 Chris Johnson
30 |
31 | Permission is hereby granted, free of charge, to any person obtaining a copy
32 | of this software and associated documentation files (the "Software"), to deal
33 | in the Software without restriction, including without limitation the rights
34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35 | copies of the Software, and to permit persons to whom the Software is
36 | furnished to do so, subject to the following conditions:
37 |
38 | The above copyright notice and this permission notice shall be included in all
39 | copies or substantial portions of the Software.
40 |
41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47 | SOFTWARE.
--------------------------------------------------------------------------------
/.github/actions/build_osx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian
2 |
3 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-OSX"
4 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for OS X"
5 | LABEL "com.github.actions.icon"="headphones"
6 | LABEL "com.github.actions.color"="purple"
7 |
8 | LABEL "repository"="TBD"
9 | LABEL "homepage"="TBD"
10 | LABEL "maintainer"="n0jo"
11 |
12 | RUN apt-get update && \
13 | apt-get upgrade -yy && \
14 | apt-get install -yy \
15 | automake \
16 | bison \
17 | curl \
18 | file \
19 | flex \
20 | git \
21 | libtool \
22 | pkg-config \
23 | python \
24 | texinfo \
25 | vim \
26 | wget \
27 | zlib1g-dev \
28 | build-essential \
29 | cmake \
30 | make \
31 | tar \
32 | unzip \
33 | zip \
34 | libgl1-mesa-dev \
35 | libglu1-mesa-dev \
36 | jq \
37 | rsync
38 |
39 | # Install osxcross
40 | # NOTE: The Docker Hub's build machines run varying types of CPUs, so an image
41 | # built with `-march=native` on one of those may not run on every machine - I
42 | # ran into this problem when the images wouldn't run on my 2013-era Macbook
43 | # Pro. As such, we remove this flag entirely.
44 | ENV OSXCROSS_SDK_VERSION 10.11
45 | RUN SDK_VERSION=$OSXCROSS_SDK_VERSION \
46 | mkdir /opt/osxcross && \
47 | cd /opt && \
48 | git clone https://github.com/tpoechtrager/osxcross.git && \
49 | cd osxcross && \
50 | git checkout e0a171828a72a0d7ad4409489033536590008ebf && \
51 | sed -i -e 's|-march=native||g' ./build_clang.sh ./wrapper/build.sh && \
52 | ./tools/get_dependencies.sh && \
53 | curl -L -o ./tarballs/MacOSX${OSXCROSS_SDK_VERSION}.sdk.tar.xz \
54 | https://github.com/apriorit/osxcross-sdks/raw/master/MacOSX${OSXCROSS_SDK_VERSION}.sdk.tar.xz && \
55 | yes | PORTABLE=true ./build.sh && \
56 | ./build_compiler_rt.sh
57 |
58 | ENV PATH $PATH:/opt/osxcross/target/bin
59 |
60 | ADD entrypoint.sh /entrypoint.sh
61 | RUN chmod a+x /entrypoint.sh
62 |
63 | ENTRYPOINT ["/entrypoint.sh"]
64 |
--------------------------------------------------------------------------------
/res/components/rw_PJ301M.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/src/plugin.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "components.hpp"
3 | #include
4 | #include
5 | #include "rwlib.h"
6 |
7 | using namespace rack;
8 |
9 | // Declare the Plugin, defined in plugin.cpp
10 | extern Plugin* pluginInstance;
11 |
12 | // Declare each Model, defined in each module source file
13 | // extern Model* modelAcceleration;
14 | extern Model* modelBitshiftgain;
15 | extern Model* modelCapacitor;
16 | extern Model* modelCapacitor_stereo;
17 | extern Model* modelChorus;
18 | extern Model* modelConsole;
19 | extern Model* modelConsole_mm;
20 | extern Model* modelDistance;
21 | extern Model* modelGolem;
22 | extern Model* modelHolt;
23 | extern Model* modelHombre;
24 | extern Model* modelInterstage;
25 | extern Model* modelMonitoring;
26 | extern Model* modelMv;
27 | extern Model* modelRasp;
28 | extern Model* modelReseq;
29 | extern Model* modelTape;
30 | extern Model* modelTremolo;
31 | extern Model* modelVibrato;
32 |
33 | /* Other stuff */
34 |
35 | /* #quality mode
36 | ======================================================================================== */
37 | void saveQuality(bool quality);
38 | bool loadQuality();
39 | void saveHighQualityAsDefault(bool highQualityAsDefault);
40 | bool loadHighQualityAsDefault();
41 |
42 | struct highQualityDefaultItem : MenuItem {
43 | void onAction(const event::Action& e) override
44 | {
45 | saveHighQualityAsDefault(rightText.empty()); // implicitly toggled
46 | }
47 | };
48 |
49 | /* #console type (Console, Console MM)
50 | ======================================================================================== */
51 | void saveConsoleType(int consoleType);
52 | int loadConsoleType();
53 |
54 | /* #direct out mode (Console MM)
55 | ======================================================================================== */
56 | void saveDirectOutMode(int directOutMode);
57 | int loadDirectOutMode();
58 |
59 | /* #slew type (Rasp)
60 | ======================================================================================== */
61 | void saveSlewType(int slewType);
62 | int loadSlewType();
63 |
64 | /* #delay mode (Golem)
65 | ======================================================================================== */
66 | void saveDelayMode(int delayMode);
67 | int loadDelayMode();
68 |
69 | /* #themes
70 | ======================================================================================== */
71 | static const std::string lightPanelID = "Light Panel";
72 | static const std::string darkPanelID = "Dark Panel";
73 |
74 | // https://github.com/MarcBoule/Geodesics/blob/master/src/Geodesics.hpp
75 | void saveDarkAsDefault(bool darkAsDefault);
76 | bool loadDarkAsDefault();
77 |
78 | struct DarkDefaultItem : MenuItem {
79 | void onAction(const event::Action& e) override
80 | {
81 | saveDarkAsDefault(rightText.empty()); // implicitly toggled
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/res/components/rw_PJ301M_silver.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/res/components/rw_screw_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
83 |
--------------------------------------------------------------------------------
/res/components/rw_CKSS_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
116 |
--------------------------------------------------------------------------------
/res/components/rw_CKSS_rot_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
116 |
--------------------------------------------------------------------------------
/res/components/rw_CKSS_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
121 |
--------------------------------------------------------------------------------
/res/components/rw_CKSS_rot_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
121 |
--------------------------------------------------------------------------------
/plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "slug": "rackwindows",
3 | "name": "Rackwindows",
4 | "version": "1.1.2",
5 | "license": "MIT",
6 | "brand": "Rackwindows",
7 | "author": "Jens Robert Janke",
8 | "authorEmail": "jensrobertjanke@gmail.com",
9 | "authorUrl": "https://github.com/n0jo",
10 | "pluginUrl": "https://github.com/n0jo/rackwindows",
11 | "manualUrl": "https://github.com/n0jo/rackwindows/blob/master/readme.md",
12 | "sourceUrl": "https://github.com/n0jo/rackwindows.git",
13 | "donateUrl": "",
14 | "changelogUrl": "https://github.com/n0jo/rackwindows/blob/master/changelog.md",
15 | "modules": [
16 | {
17 | "slug": "bitshiftgain",
18 | "name": "Dual BSG",
19 | "description": "Dual gain shifter",
20 | "tags": [
21 | "Attenuator",
22 | "Dual",
23 | "Polyphonic",
24 | "Utility"
25 | ]
26 | },
27 | {
28 | "slug": "capacitor",
29 | "name": "Capacitor",
30 | "description": "Filters (Mono)",
31 | "tags": [
32 | "Equalizer",
33 | "Filter",
34 | "Polyphonic"
35 | ]
36 | },
37 | {
38 | "slug": "capacitor_stereo",
39 | "name": "Capacitor Stereo",
40 | "description": "Filters (Stereo)",
41 | "tags": [
42 | "Equalizer",
43 | "Filter",
44 | "Polyphonic"
45 | ]
46 | },
47 | {
48 | "slug": "chorus",
49 | "name": "Chorus",
50 | "description": "Stereo chorus with multi-tap option",
51 | "tags": [
52 | "Chorus",
53 | "Delay",
54 | "Effect",
55 | "Polyphonic"
56 | ]
57 | },
58 | {
59 | "slug": "console",
60 | "name": "Console",
61 | "description": "Stereo summing mixer",
62 | "tags": [
63 | "Mixer",
64 | "Polyphonic",
65 | "Utility"
66 | ]
67 | },
68 | {
69 | "slug": "console_mm",
70 | "name": "Console MM",
71 | "description": "Stereo summing mixer to work in conjunction with MindMeld's MixMaster",
72 | "tags": [
73 | "Mixer",
74 | "Utility"
75 | ]
76 | },
77 | {
78 | "slug": "distance",
79 | "name": "Distance",
80 | "description": "Far-away-izer",
81 | "tags": [
82 | "Effect",
83 | "Polyphonic",
84 | "Utility"
85 | ]
86 | },
87 | {
88 | "slug": "golem",
89 | "name": "Golem",
90 | "description": "Micro-delayable crossfader",
91 | "tags": [
92 | "Utility"
93 | ]
94 | },
95 | {
96 | "slug": "holt",
97 | "name": "Holt",
98 | "description": "Resonant lowpass filter focussed on low frequencies",
99 | "tags": [
100 | "Filter",
101 | "Polyphonic"
102 | ]
103 | },
104 | {
105 | "slug": "hombre",
106 | "name": "Hombre",
107 | "description": "Texas tone and texture",
108 | "tags": [
109 | "Effect",
110 | "Polyphonic"
111 | ]
112 | },
113 | {
114 | "slug": "interstage",
115 | "name": "Interstage",
116 | "description": "Subtle analogifier",
117 | "tags": [
118 | "Effect",
119 | "Filter",
120 | "Polyphonic"
121 | ]
122 | },
123 | {
124 | "slug": "monitoring",
125 | "name": "Monitoring",
126 | "description": "Mix checker",
127 | "tags": [
128 | "Utility"
129 | ]
130 | },
131 | {
132 | "slug": "mv",
133 | "name": "MV",
134 | "description": "Dual-mono reverb",
135 | "tags": [
136 | "Effect",
137 | "Reverb"
138 | ]
139 | },
140 | {
141 | "slug": "rasp",
142 | "name": "Rasp",
143 | "description": "De-Edger, high frequency tamer, acceleration limiter",
144 | "tags": [
145 | "Equalizer",
146 | "EQ",
147 | "Filter",
148 | "Slew limiter",
149 | "Polyphonic"
150 | ]
151 | },
152 | {
153 | "slug": "reseq",
154 | "name": "ResEQ",
155 | "description": "Resonance equalizer",
156 | "tags": [
157 | "Equalizer",
158 | "Filter",
159 | "Polyphonic"
160 | ]
161 | },
162 | {
163 | "slug": "tape",
164 | "name": "Tape",
165 | "description": "All-purpose tape mojo",
166 | "tags": [
167 | "Distortion",
168 | "Effect",
169 | "Polyphonic"
170 | ]
171 | },
172 | {
173 | "slug": "tremolo",
174 | "name": "Tremolo",
175 | "description": "Fluctuating saturation curves",
176 | "tags": [
177 | "Effect",
178 | "Polyphonic",
179 | "Utility"
180 | ]
181 | },
182 | {
183 | "slug": "vibrato",
184 | "name": "Vibrato",
185 | "description": "FM vibrator o_O",
186 | "tags": [
187 | "Chorus",
188 | "Clock generator",
189 | "Effect",
190 | "Flanger",
191 | "Polyphonic",
192 | "Utility"
193 | ]
194 | }
195 | ]
196 | }
--------------------------------------------------------------------------------
/src/components.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "rack.hpp"
3 |
4 | using namespace rack;
5 | extern Plugin* pluginInstance;
6 |
7 | // knobs
8 | struct RwKnobLarge : app::SvgKnob {
9 | RwKnobLarge()
10 | {
11 | minAngle = -0.76 * M_PI;
12 | maxAngle = 0.76 * M_PI;
13 | shadow->opacity = 0;
14 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_large.svg")));
15 | }
16 | };
17 | struct RwKnobLargeDark : app::SvgKnob {
18 | RwKnobLargeDark()
19 | {
20 | minAngle = -0.76 * M_PI;
21 | maxAngle = 0.76 * M_PI;
22 | shadow->opacity = 0.1;
23 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_large_dark.svg")));
24 | }
25 | };
26 | struct RwKnobMedium : app::SvgKnob {
27 | RwKnobMedium()
28 | {
29 | minAngle = -0.76 * M_PI;
30 | maxAngle = 0.76 * M_PI;
31 | shadow->opacity = 0;
32 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_medium.svg")));
33 | }
34 | };
35 | struct RwKnobMediumDark : app::SvgKnob {
36 | RwKnobMediumDark()
37 | {
38 | minAngle = -0.76 * M_PI;
39 | maxAngle = 0.76 * M_PI;
40 | shadow->opacity = 0.1;
41 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_medium_dark.svg")));
42 | }
43 | };
44 | struct RwKnobSmall : app::SvgKnob {
45 | RwKnobSmall()
46 | {
47 | minAngle = -0.76 * M_PI;
48 | maxAngle = 0.76 * M_PI;
49 | shadow->opacity = 0;
50 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_small.svg")));
51 | }
52 | };
53 | struct RwKnobSmallDark : app::SvgKnob {
54 | RwKnobSmallDark()
55 | {
56 | minAngle = -0.76 * M_PI;
57 | maxAngle = 0.76 * M_PI;
58 | shadow->opacity = 0;
59 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_small_dark.svg")));
60 | }
61 | };
62 | struct RwKnobTrimpot : app::SvgKnob {
63 | RwKnobTrimpot()
64 | {
65 | minAngle = -0.76 * M_PI;
66 | maxAngle = 0.76 * M_PI;
67 | shadow->opacity = 0.05;
68 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_knob_trimpot.svg")));
69 | }
70 | };
71 |
72 | // snap knobs
73 | struct RwSwitchKnobLarge : RwKnobLarge {
74 | RwSwitchKnobLarge()
75 | {
76 | snap = true;
77 | }
78 | };
79 | struct RwSwitchKnobMedium : RwKnobMedium {
80 | RwSwitchKnobMedium()
81 | {
82 | snap = true;
83 | }
84 | };
85 | struct RwSwitchKnobMediumDark : RwKnobMediumDark {
86 | RwSwitchKnobMediumDark()
87 | {
88 | minAngle = -0.8 * M_PI;
89 | maxAngle = 0.8 * M_PI;
90 | snap = true;
91 | }
92 | };
93 | struct RwSwitchKnobMediumDarkOneThird : RwKnobMediumDark {
94 | RwSwitchKnobMediumDarkOneThird()
95 | {
96 | minAngle = -0.33 * M_PI;
97 | maxAngle = 0.33 * M_PI;
98 | snap = true;
99 | }
100 | };
101 | struct RwSwitchKnobMediumDarkTwoThirds : RwKnobMediumDark {
102 | RwSwitchKnobMediumDarkTwoThirds()
103 | {
104 | minAngle = -0.74 * M_PI;
105 | maxAngle = 0.74 * M_PI;
106 | snap = true;
107 | }
108 | };
109 | struct RwSwitchKnobMediumSix : RwKnobMediumDark {
110 | RwSwitchKnobMediumSix()
111 | {
112 | minAngle = -0.72 * M_PI;
113 | maxAngle = 0.72 * M_PI;
114 | snap = true;
115 | }
116 | };
117 | struct RwSwitchKnobSmall : RwKnobSmall {
118 | RwSwitchKnobSmall()
119 | {
120 | snap = true;
121 | }
122 | };
123 | struct RwSwitchKnobSmallDark : RwKnobSmallDark {
124 | RwSwitchKnobSmallDark()
125 | {
126 | snap = true;
127 | }
128 | };
129 |
130 | // switches
131 | struct RwSwitchThree : SvgSwitch {
132 | RwSwitchThree()
133 | {
134 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_1.svg")));
135 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_0.svg")));
136 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_2.svg")));
137 | }
138 | };
139 | struct RwSwitchThreeVert : SvgSwitch {
140 | RwSwitchThreeVert()
141 | {
142 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_vert_1.svg")));
143 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_vert_0.svg")));
144 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_switch_three_vert_2.svg")));
145 | }
146 | };
147 | struct RwCKSS : SvgSwitch {
148 | RwCKSS()
149 | {
150 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_CKSS_0.svg")));
151 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_CKSS_1.svg")));
152 | }
153 | };
154 | struct RwCKSSRot : SvgSwitch {
155 | RwCKSSRot()
156 | {
157 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_CKSS_rot_0.svg")));
158 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_CKSS_rot_1.svg")));
159 | }
160 | };
161 |
162 | // ports
163 | struct RwPJ301MPort : app::SvgPort {
164 | RwPJ301MPort()
165 | {
166 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_PJ301M.svg")));
167 | }
168 | };
169 | struct RwPJ301MPortSilver : app::SvgPort {
170 | RwPJ301MPortSilver()
171 | {
172 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/rw_PJ301M_silver.svg")));
173 | }
174 | };
--------------------------------------------------------------------------------
/src/monitoring.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Monitoring
3 | ----------
4 | VCV Rack module based on Monitoring by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - separate controls for processing modes and cans
10 | - no monolat, monorat
11 |
12 | See ./LICENSE.md for all licenses
13 | ************************************************************************************************/
14 |
15 | #include "plugin.hpp"
16 |
17 | using namespace rwlib;
18 |
19 | struct Monitoring : Module {
20 | enum ParamIds {
21 | MODE_PARAM,
22 | CANS_PARAM,
23 | DITHER_PARAM,
24 | NUM_PARAMS
25 | };
26 | enum InputIds {
27 | IN_L_INPUT,
28 | IN_R_INPUT,
29 | NUM_INPUTS
30 | };
31 | enum OutputIds {
32 | OUT_L_OUTPUT,
33 | OUT_R_OUTPUT,
34 | NUM_OUTPUTS
35 | };
36 | enum LightIds {
37 | DITHER_16_LIGHT,
38 | DITHER_24_LIGHT,
39 | NUM_LIGHTS
40 | };
41 |
42 | // module variables
43 | const double gainFactor = 10.0;
44 | enum processingModes {
45 | OFF,
46 | SUBS,
47 | SLEW,
48 | PEAKS,
49 | MID,
50 | SIDE,
51 | VINYL,
52 | AURAT,
53 | PHONE
54 | };
55 | enum cansModes {
56 | CANS_OFF,
57 | CANS_A,
58 | CANS_B,
59 | CANS_C,
60 | CANS_D
61 | };
62 | enum ditherModes {
63 | DITHER_OFF,
64 | DITHER_24,
65 | DITHER_16
66 | };
67 |
68 | // control parameters
69 | int processingMode;
70 | int lastProcessingMode;
71 | int cansMode;
72 | int ditherMode;
73 |
74 | // state variables
75 | SubsOnly subsL, subsR;
76 | SlewOnly slewL, slewR;
77 | PeaksOnly peaksL, peaksR;
78 | BiquadBandpass bandpassL, bandpassR;
79 | Cans cans;
80 | Dark darkL, darkR;
81 | uint32_t fpd;
82 |
83 | //other
84 | double overallscale;
85 |
86 | Monitoring()
87 | {
88 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
89 | configParam(MODE_PARAM, 0.f, 8.f, 0.f, "Mode");
90 | configParam(CANS_PARAM, 0.f, 4.f, 0.f, "Cans");
91 | configParam(DITHER_PARAM, 0.f, 2.f, 0.f, "Dither");
92 |
93 | onReset();
94 | }
95 |
96 | void onReset() override
97 | {
98 | onSampleRateChange();
99 |
100 | subsL = SubsOnly();
101 | subsR = SubsOnly();
102 | slewL = SlewOnly();
103 | slewR = SlewOnly();
104 | peaksL = PeaksOnly();
105 | peaksR = PeaksOnly();
106 | bandpassL = BiquadBandpass();
107 | bandpassR = BiquadBandpass();
108 | cans = Cans();
109 | darkL = Dark();
110 | darkR = Dark();
111 |
112 | processingMode = 0;
113 | cansMode = 0;
114 | lastProcessingMode = 0;
115 | ditherMode = 0;
116 |
117 | fpd = 17;
118 | }
119 |
120 | void onSampleRateChange() override
121 | {
122 | float sampleRate = APP->engine->getSampleRate();
123 |
124 | overallscale = 1.0;
125 | overallscale /= 44100.0;
126 | overallscale *= sampleRate;
127 | }
128 |
129 | void process(const ProcessArgs& args) override
130 | {
131 | // dither light
132 | ditherMode = params[DITHER_PARAM].getValue();
133 | lights[DITHER_24_LIGHT].setBrightness(ditherMode == DITHER_24 ? 1.f : 0.f);
134 | lights[DITHER_16_LIGHT].setBrightness(ditherMode == DITHER_16 ? 1.f : 0.f);
135 |
136 | if (outputs[OUT_L_OUTPUT].isConnected() || outputs[OUT_R_OUTPUT].isConnected()) {
137 |
138 | // get params
139 | processingMode = params[MODE_PARAM].getValue();
140 | cansMode = params[CANS_PARAM].getValue();
141 |
142 | if (processingMode != lastProcessingMode) {
143 | // set up bandpass for vinyl, aurat and phone
144 | if (processingMode == VINYL) {
145 | bandpassL.set(0.0385 / overallscale, 0.0825);
146 | bandpassR.set(0.0385 / overallscale, 0.0825);
147 | }
148 | if (processingMode == AURAT) {
149 | bandpassL.set(0.0375 / overallscale, 0.1575);
150 | bandpassR.set(0.0375 / overallscale, 0.1575);
151 | }
152 | if (processingMode == PHONE) {
153 | bandpassL.set(0.1245 / overallscale, 0.46);
154 | bandpassR.set(0.1245 / overallscale, 0.46);
155 | }
156 | lastProcessingMode = processingMode;
157 | }
158 |
159 | // get input
160 | long double inputSampleL = inputs[IN_L_INPUT].getVoltage();
161 | long double inputSampleR = inputs[IN_R_INPUT].getVoltage();
162 |
163 | // pad gain
164 | inputSampleL /= gainFactor;
165 | inputSampleR /= gainFactor;
166 |
167 | // prepare mid and side
168 | long double mid = inputSampleL + inputSampleR;
169 | long double side = inputSampleL - inputSampleR;
170 |
171 | // processing modes
172 | switch (processingMode) {
173 | case OFF:
174 | break;
175 |
176 | case SUBS:
177 | inputSampleL = subsL.process(inputSampleL, overallscale);
178 | inputSampleR = subsR.process(inputSampleR, overallscale);
179 | break;
180 |
181 | case SLEW:
182 | inputSampleL = slewL.process(inputSampleL);
183 | inputSampleR = slewR.process(inputSampleR);
184 | break;
185 |
186 | case PEAKS:
187 | inputSampleL = peaksL.process(inputSampleL, overallscale);
188 | inputSampleR = peaksR.process(inputSampleR, overallscale);
189 | break;
190 |
191 | case MID:
192 | inputSampleL = mid * 0.5;
193 | inputSampleR = mid * 0.5;
194 | break;
195 |
196 | case SIDE:
197 | inputSampleL = side * 0.5;
198 | inputSampleR = -side * 0.5;
199 | break;
200 |
201 | case VINYL:
202 | inputSampleL = bandpassL.process(inputSampleL);
203 | inputSampleR = bandpassR.process(inputSampleR);
204 | break;
205 |
206 | case AURAT:
207 | inputSampleL = bandpassL.process(inputSampleL);
208 | inputSampleR = bandpassR.process(inputSampleR);
209 | break;
210 |
211 | case PHONE:
212 | inputSampleL = bandpassL.process(mid * 0.5);
213 | inputSampleR = bandpassR.process(mid * 0.5);
214 | break;
215 | }
216 |
217 | // cans
218 | if (cansMode) {
219 | cans.setMode(cansMode);
220 | cans.process(inputSampleL, inputSampleR, overallscale);
221 | }
222 |
223 | // dither
224 | switch (ditherMode) {
225 | case DITHER_OFF:
226 | break;
227 | case DITHER_16:
228 | inputSampleL = darkL.process(inputSampleL, overallscale, false);
229 | inputSampleR = darkR.process(inputSampleR, overallscale, false);
230 | break;
231 | case DITHER_24:
232 | inputSampleL = darkL.process(inputSampleL, overallscale, true);
233 | inputSampleR = darkR.process(inputSampleR, overallscale, true);
234 | break;
235 | }
236 |
237 | // bring gain back up
238 | inputSampleL *= gainFactor;
239 | inputSampleR *= gainFactor;
240 |
241 | // output
242 | outputs[OUT_L_OUTPUT].setVoltage(inputSampleL);
243 | outputs[OUT_R_OUTPUT].setVoltage(inputSampleR);
244 | }
245 | }
246 | };
247 |
248 | struct MonitoringWidget : ModuleWidget {
249 |
250 | MonitoringWidget(Monitoring* module)
251 | {
252 | setModule(module);
253 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/monitoring_dark.svg")));
254 |
255 | //screws
256 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
257 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
258 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
259 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
260 |
261 | // knobs
262 | addParam(createParamCentered(Vec(52.5, 85.0), module, Monitoring::MODE_PARAM));
263 | addParam(createParamCentered(Vec(52.5, 165.0), module, Monitoring::CANS_PARAM));
264 |
265 | // switch
266 | addParam(createParamCentered(Vec(52.5, 235.0), module, Monitoring::DITHER_PARAM));
267 |
268 | // lights
269 | addChild(createLightCentered>(Vec(18.8, 235.0), module, Monitoring::DITHER_24_LIGHT));
270 | addChild(createLightCentered>(Vec(86.3, 235.0), module, Monitoring::DITHER_16_LIGHT));
271 |
272 | // inputs
273 | addInput(createInputCentered(Vec(33.75, 285.0), module, Monitoring::IN_L_INPUT));
274 | addInput(createInputCentered(Vec(71.25, 285.0), module, Monitoring::IN_R_INPUT));
275 |
276 | // outputs
277 | addOutput(createOutputCentered(Vec(33.75, 325.0), module, Monitoring::OUT_L_OUTPUT));
278 | addOutput(createOutputCentered(Vec(71.25, 325.0), module, Monitoring::OUT_R_OUTPUT));
279 | }
280 | };
281 |
282 | Model* modelMonitoring = createModel("monitoring");
--------------------------------------------------------------------------------
/src/tape.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Tape
3 | ----
4 | VCV Rack module based on Tape by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - cv inputs for slam and bump
10 | - polyphonic
11 |
12 | See ./LICENSE.md for all licenses
13 | ************************************************************************************************/
14 |
15 | #include "plugin.hpp"
16 |
17 | // quality options
18 | #define ECO 0
19 | #define HIGH 1
20 |
21 | // polyphony
22 | #define MAX_POLY_CHANNELS 16
23 |
24 | struct Tape : Module {
25 | enum ParamIds {
26 | SLAM_PARAM,
27 | BUMP_PARAM,
28 | NUM_PARAMS
29 | };
30 | enum InputIds {
31 | SLAM_CV_INPUT,
32 | BUMP_CV_INPUT,
33 | IN_L_INPUT,
34 | IN_R_INPUT,
35 | NUM_INPUTS
36 | };
37 | enum OutputIds {
38 | OUT_L_OUTPUT,
39 | OUT_R_OUTPUT,
40 | NUM_OUTPUTS
41 | };
42 | enum LightIds {
43 | NUM_LIGHTS
44 | };
45 |
46 | // module variables
47 | const double gainCut = 0.1;
48 | const double gainBoost = 10.0;
49 | int quality;
50 |
51 | // control parameters
52 | float slamParam;
53 | float bumpParam;
54 |
55 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
56 | rwlib::Tape tapeL[MAX_POLY_CHANNELS];
57 | rwlib::Tape tapeR[MAX_POLY_CHANNELS];
58 | uint32_t fpdL[MAX_POLY_CHANNELS];
59 | uint32_t fpdR[MAX_POLY_CHANNELS];
60 |
61 | // other
62 | double overallscale;
63 |
64 | Tape()
65 | {
66 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
67 | configParam(SLAM_PARAM, 0.f, 1.f, 0.5f, "Slam", "%", 0, 100);
68 | configParam(BUMP_PARAM, 0.f, 1.f, 0.5f, "Bump", "%", 0, 100);
69 |
70 | quality = loadQuality();
71 | onReset();
72 | }
73 |
74 | void onReset() override
75 | {
76 | onSampleRateChange();
77 |
78 | for (int i = 0; i < MAX_POLY_CHANNELS; i++) {
79 | tapeL[i] = rwlib::Tape();
80 | tapeR[i] = rwlib::Tape();
81 |
82 | fpdL[i] = fpdR[i] = 17;
83 | }
84 | }
85 |
86 | void onSampleRateChange() override
87 | {
88 | float sampleRate = APP->engine->getSampleRate();
89 |
90 | overallscale = 1.0;
91 | overallscale /= 44100.0;
92 | overallscale *= sampleRate;
93 |
94 | for (int i = 0; i < MAX_POLY_CHANNELS; i++) {
95 | tapeL[i].onSampleRateChange(overallscale);
96 | tapeR[i].onSampleRateChange(overallscale);
97 | }
98 | }
99 |
100 | json_t* dataToJson() override
101 | {
102 | json_t* rootJ = json_object();
103 |
104 | // quality
105 | json_object_set_new(rootJ, "quality", json_integer(quality));
106 |
107 | return rootJ;
108 | }
109 |
110 | void dataFromJson(json_t* rootJ) override
111 | {
112 | // quality
113 | json_t* qualityJ = json_object_get(rootJ, "quality");
114 | if (qualityJ)
115 | quality = json_integer_value(qualityJ);
116 | }
117 |
118 | void process(const ProcessArgs& args) override
119 | {
120 | slamParam = params[SLAM_PARAM].getValue();
121 | slamParam += inputs[SLAM_CV_INPUT].getVoltage() / 10;
122 | slamParam = clamp(slamParam, 0.01f, 0.99f);
123 |
124 | bumpParam = params[BUMP_PARAM].getValue();
125 | bumpParam += inputs[BUMP_CV_INPUT].getVoltage() / 10;
126 | bumpParam = clamp(bumpParam, 0.01f, 0.99f);
127 |
128 | // number of polyphonic channels
129 | int numChannelsL = std::max(1, inputs[IN_L_INPUT].getChannels());
130 | int numChannelsR = std::max(1, inputs[IN_R_INPUT].getChannels());
131 |
132 | // process left channel
133 | if (outputs[OUT_L_OUTPUT].isConnected()) {
134 |
135 | // for each poly channel left
136 | for (int i = 0; i < numChannelsL; i++) {
137 |
138 | // input
139 | long double inputSampleL = inputs[IN_L_INPUT].getPolyVoltage(i);
140 |
141 | // pad gain
142 | inputSampleL *= gainCut;
143 |
144 | if (quality == HIGH) {
145 | if (fabs(inputSampleL) < 1.18e-37)
146 | inputSampleL = fpdL[i] * 1.18e-37;
147 | }
148 |
149 | // work the magic
150 | inputSampleL = tapeL[i].process(inputSampleL, slamParam, bumpParam, overallscale);
151 |
152 | if (quality == HIGH) {
153 | //32 bit stereo floating point dither
154 | int expon;
155 | frexpf((float)inputSampleL, &expon);
156 | fpdL[i] ^= fpdL[i] << 13;
157 | fpdL[i] ^= fpdL[i] >> 17;
158 | fpdL[i] ^= fpdL[i] << 5;
159 | inputSampleL += ((double(fpdL[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
160 | }
161 |
162 | // bring gain back up
163 | inputSampleL *= gainBoost;
164 |
165 | // output
166 | outputs[OUT_L_OUTPUT].setChannels(numChannelsL);
167 | outputs[OUT_L_OUTPUT].setVoltage(inputSampleL, i);
168 | }
169 | }
170 |
171 | // process right channel
172 | if (outputs[OUT_R_OUTPUT].isConnected()) {
173 |
174 | // for each poly channel right
175 | for (int i = 0; i < numChannelsR; i++) {
176 |
177 | // input
178 | long double inputSampleR = inputs[IN_R_INPUT].getPolyVoltage(i);
179 |
180 | // pad gain
181 | inputSampleR *= gainCut;
182 |
183 | if (quality == HIGH) {
184 | if (fabs(inputSampleR) < 1.18e-37)
185 | inputSampleR = fpdR[i] * 1.18e-37;
186 | }
187 |
188 | // work the magic
189 | inputSampleR = tapeR[i].process(inputSampleR, slamParam, bumpParam, overallscale);
190 |
191 | if (quality == HIGH) {
192 | //32 bit stereo floating point dither
193 | int expon;
194 | frexpf((float)inputSampleR, &expon);
195 | fpdR[i] ^= fpdR[i] << 13;
196 | fpdR[i] ^= fpdR[i] >> 17;
197 | fpdR[i] ^= fpdR[i] << 5;
198 | inputSampleR += ((double(fpdR[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
199 | }
200 |
201 | // bring gain back up
202 | inputSampleR *= gainBoost;
203 |
204 | // output
205 | outputs[OUT_R_OUTPUT].setChannels(numChannelsR);
206 | outputs[OUT_R_OUTPUT].setVoltage(inputSampleR, i);
207 | }
208 | }
209 | }
210 | };
211 |
212 | struct TapeWidget : ModuleWidget {
213 |
214 | // quality item
215 | struct QualityItem : MenuItem {
216 | Tape* module;
217 | int quality;
218 |
219 | void onAction(const event::Action& e) override
220 | {
221 | module->quality = quality;
222 | }
223 |
224 | void step() override
225 | {
226 | rightText = (module->quality == quality) ? "✔" : "";
227 | }
228 | };
229 |
230 | void appendContextMenu(Menu* menu) override
231 | {
232 | Tape* module = dynamic_cast(this->module);
233 | assert(module);
234 |
235 | menu->addChild(new MenuSeparator()); // separator
236 |
237 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
238 | qualityLabel->text = "Quality";
239 | menu->addChild(qualityLabel);
240 |
241 | QualityItem* low = new QualityItem(); // low quality
242 | low->text = "Eco";
243 | low->module = module;
244 | low->quality = 0;
245 | menu->addChild(low);
246 |
247 | QualityItem* high = new QualityItem(); // high quality
248 | high->text = "High";
249 | high->module = module;
250 | high->quality = 1;
251 | menu->addChild(high);
252 | }
253 |
254 | TapeWidget(Tape* module)
255 | {
256 | setModule(module);
257 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/tape_dark.svg")));
258 |
259 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
260 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
261 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
262 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
263 |
264 | // knobs
265 | addParam(createParamCentered(Vec(45.0, 75.0), module, Tape::SLAM_PARAM));
266 | addParam(createParamCentered(Vec(45.0, 145.0), module, Tape::BUMP_PARAM));
267 |
268 | // inputs
269 | addInput(createInputCentered(Vec(26.25, 245.0), module, Tape::SLAM_CV_INPUT));
270 | addInput(createInputCentered(Vec(63.75, 245.0), module, Tape::BUMP_CV_INPUT));
271 | addInput(createInputCentered(Vec(26.25, 285.0), module, Tape::IN_L_INPUT));
272 | addInput(createInputCentered(Vec(63.75, 285.0), module, Tape::IN_R_INPUT));
273 |
274 | // outputs
275 | addOutput(createOutputCentered(Vec(26.25, 325.0), module, Tape::OUT_L_OUTPUT));
276 | addOutput(createOutputCentered(Vec(63.75, 325.0), module, Tape::OUT_R_OUTPUT));
277 | }
278 | };
279 |
280 | Model* modelTape = createModel("tape");
--------------------------------------------------------------------------------
/src/interstage.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Interstage
3 | --------
4 | VCV Rack module based on Interstage by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - polyphonic
10 |
11 | See ./LICENSE.md for all licenses
12 | ************************************************************************************************/
13 |
14 | #include "plugin.hpp"
15 |
16 | // quality options
17 | #define ECO 0
18 | #define HIGH 1
19 |
20 | struct Interstage : Module {
21 | enum ParamIds {
22 | NUM_PARAMS
23 | };
24 | enum InputIds {
25 | IN_L_INPUT,
26 | IN_R_INPUT,
27 | NUM_INPUTS
28 | };
29 | enum OutputIds {
30 | OUT_L_OUTPUT,
31 | OUT_R_OUTPUT,
32 | NUM_OUTPUTS
33 | };
34 | enum LightIds {
35 | NUM_LIGHTS
36 | };
37 |
38 | // module variables
39 | const double gainCut = 0.03125;
40 | const double gainBoost = 32.0;
41 | int quality;
42 |
43 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
44 | double iirSampleAL[16];
45 | double iirSampleBL[16];
46 | double iirSampleCL[16];
47 | double iirSampleDL[16];
48 | double iirSampleEL[16];
49 | double iirSampleFL[16];
50 | long double lastSampleL[16];
51 | bool flipL[16];
52 | uint32_t fpdL[16];
53 | double iirSampleAR[16];
54 | double iirSampleBR[16];
55 | double iirSampleCR[16];
56 | double iirSampleDR[16];
57 | double iirSampleER[16];
58 | double iirSampleFR[16];
59 | long double lastSampleR[16];
60 | bool flipR[16];
61 | uint32_t fpdR[16];
62 |
63 | // other variables, which do not need to be updated every cycle
64 | double overallscale;
65 | double firstStage;
66 | double iirAmount;
67 |
68 | // constants
69 | const double threshold = 0.381966011250105;
70 |
71 | Interstage()
72 | {
73 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
74 |
75 | quality = loadQuality();
76 | onReset();
77 | }
78 |
79 | void onReset() override
80 | {
81 | onSampleRateChange();
82 |
83 | for (int i = 0; i < 16; i++) {
84 | iirSampleAL[i] = iirSampleBL[i] = iirSampleCL[i] = iirSampleDL[i] = iirSampleEL[i] = iirSampleFL[i] = lastSampleL[i] = 0.0;
85 | flipL[i] = true;
86 | fpdR[i] = 17;
87 | iirSampleAR[i] = iirSampleBR[i] = iirSampleCR[i] = iirSampleDR[i] = iirSampleER[i] = iirSampleFR[i] = lastSampleR[i] = 0.0;
88 | flipR[i] = true;
89 | fpdR[i] = 17;
90 | }
91 | }
92 |
93 | void onSampleRateChange() override
94 | {
95 | float sampleRate = APP->engine->getSampleRate();
96 |
97 | overallscale = 1.0;
98 | overallscale /= 44100.0;
99 | overallscale *= sampleRate;
100 |
101 | firstStage = 0.381966011250105 / overallscale;
102 | iirAmount = 0.00295 / overallscale;
103 | }
104 |
105 | json_t* dataToJson() override
106 | {
107 | json_t* rootJ = json_object();
108 |
109 | // quality
110 | json_object_set_new(rootJ, "quality", json_integer(quality));
111 |
112 | return rootJ;
113 | }
114 |
115 | void dataFromJson(json_t* rootJ) override
116 | {
117 | // quality
118 | json_t* qualityJ = json_object_get(rootJ, "quality");
119 | if (qualityJ)
120 | quality = json_integer_value(qualityJ);
121 | }
122 |
123 | void processChannel(Input& input, Output& output, double iirSampleA[], double iirSampleB[], double iirSampleC[], double iirSampleD[], double iirSampleE[], double iirSampleF[], long double lastSample[], bool flip[], uint32_t fpd[])
124 | {
125 | if (output.isConnected()) {
126 |
127 | long double inputSample;
128 | long double drySample;
129 |
130 | // number of polyphonic channels
131 | int numChannels = std::max(1, input.getChannels());
132 |
133 | // for each poly channel
134 | for (int i = 0; i < numChannels; i++) {
135 |
136 | // input
137 | inputSample = input.getPolyVoltage(i);
138 |
139 | // pad gain
140 | inputSample *= gainCut;
141 |
142 | if (quality == HIGH) {
143 | if (fabs(inputSample) < 1.18e-37)
144 | inputSample = fpd[i] * 1.18e-37;
145 | }
146 |
147 | drySample = inputSample;
148 |
149 | inputSample = (inputSample + lastSample[i]) * 0.5; //start the lowpassing with an average
150 |
151 | if (flip[i]) {
152 | //make highpass
153 | iirSampleA[i] = (iirSampleA[i] * (1 - firstStage)) + (inputSample * firstStage);
154 | inputSample = iirSampleA[i];
155 | iirSampleC[i] = (iirSampleC[i] * (1 - iirAmount)) + (inputSample * iirAmount);
156 | inputSample = iirSampleC[i];
157 | iirSampleE[i] = (iirSampleE[i] * (1 - iirAmount)) + (inputSample * iirAmount);
158 | inputSample = iirSampleE[i];
159 | inputSample = drySample - inputSample;
160 |
161 | //slew limit against lowpassed reference point
162 | if (inputSample - iirSampleA[i] > threshold)
163 | inputSample = iirSampleA[i] + threshold;
164 | if (inputSample - iirSampleA[i] < -threshold)
165 | inputSample = iirSampleA[i] - threshold;
166 |
167 | } else {
168 | //make highpass
169 | iirSampleB[i] = (iirSampleB[i] * (1 - firstStage)) + (inputSample * firstStage);
170 | inputSample = iirSampleB[i];
171 | iirSampleD[i] = (iirSampleD[i] * (1 - iirAmount)) + (inputSample * iirAmount);
172 | inputSample = iirSampleD[i];
173 | iirSampleF[i] = (iirSampleF[i] * (1 - iirAmount)) + (inputSample * iirAmount);
174 | inputSample = iirSampleF[i];
175 | inputSample = drySample - inputSample;
176 |
177 | //slew limit against lowpassed reference point
178 | if (inputSample - iirSampleB[i] > threshold)
179 | inputSample = iirSampleB[i] + threshold;
180 | if (inputSample - iirSampleB[i] < -threshold)
181 | inputSample = iirSampleB[i] - threshold;
182 | }
183 |
184 | flip[i] = !flip[i];
185 |
186 | lastSample[i] = inputSample;
187 |
188 | if (quality == HIGH) {
189 | //begin 32 bit stereo floating point dither
190 | int expon;
191 | frexpf((float)inputSample, &expon);
192 | fpd[i] ^= fpd[i] << 13;
193 | fpd[i] ^= fpd[i] >> 17;
194 | fpd[i] ^= fpd[i] << 5;
195 | inputSample += ((double(fpd[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
196 | //end 32 bit stereo floating point dither
197 | }
198 |
199 | // bring gain back up
200 | inputSample *= gainBoost;
201 |
202 | // output
203 | output.setChannels(numChannels);
204 | output.setVoltage(inputSample, i);
205 | }
206 | }
207 | }
208 |
209 | void process(const ProcessArgs& args) override
210 | {
211 | processChannel(inputs[IN_L_INPUT], outputs[OUT_L_OUTPUT], iirSampleAL, iirSampleBL, iirSampleCL, iirSampleDL, iirSampleEL, iirSampleFL, lastSampleL, flipL, fpdL);
212 | processChannel(inputs[IN_R_INPUT], outputs[OUT_R_OUTPUT], iirSampleAR, iirSampleBR, iirSampleCR, iirSampleDR, iirSampleER, iirSampleFR, lastSampleR, flipR, fpdR);
213 | }
214 | };
215 |
216 | struct InterstageWidget : ModuleWidget {
217 |
218 | // quality item
219 | struct QualityItem : MenuItem {
220 | Interstage* module;
221 | int quality;
222 |
223 | void onAction(const event::Action& e) override
224 | {
225 | module->quality = quality;
226 | }
227 |
228 | void step() override
229 | {
230 | rightText = (module->quality == quality) ? "✔" : "";
231 | }
232 | };
233 |
234 | void appendContextMenu(Menu* menu) override
235 | {
236 | Interstage* module = dynamic_cast(this->module);
237 | assert(module);
238 |
239 | menu->addChild(new MenuSeparator()); // separator
240 |
241 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
242 | qualityLabel->text = "Quality";
243 | menu->addChild(qualityLabel);
244 |
245 | QualityItem* low = new QualityItem(); // low quality
246 | low->text = "Eco";
247 | low->module = module;
248 | low->quality = 0;
249 | menu->addChild(low);
250 |
251 | QualityItem* high = new QualityItem(); // high quality
252 | high->text = "High";
253 | high->module = module;
254 | high->quality = 1;
255 | menu->addChild(high);
256 | }
257 |
258 | InterstageWidget(Interstage* module)
259 | {
260 | setModule(module);
261 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/interstage_dark.svg")));
262 |
263 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
264 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
265 |
266 | addInput(createInputCentered(Vec(30.0, 205.0), module, Interstage::IN_L_INPUT));
267 | addInput(createInputCentered(Vec(30.0, 245.0), module, Interstage::IN_R_INPUT));
268 |
269 | addOutput(createOutputCentered(Vec(30.0, 285.0), module, Interstage::OUT_L_OUTPUT));
270 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Interstage::OUT_R_OUTPUT));
271 | }
272 | };
273 |
274 | Model* modelInterstage = createModel("interstage");
--------------------------------------------------------------------------------
/src/bitshiftgain.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Dual BSG
3 | --------
4 | VCV Rack module based on BitshiftGain by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - 2 BSG units
10 | - if no input is connected, the respective output will provide constant voltage selectable in 1V steps from -8V to +8V
11 | - option to link bottom BSG to top BSG -> gain shifts at top BSG are automatically compensated for by the bottom BSG
12 | - if linked, bottom knob acts as an offset
13 | - polyphonic
14 |
15 | See ./LICENSE.md for all licenses
16 | ************************************************************************************************/
17 |
18 | // CAUTION: the output is not limited in any way, positive values can produce very high volumes
19 |
20 | #include "plugin.hpp"
21 |
22 | struct Bitshiftgain : Module {
23 | enum ParamIds {
24 | SHIFT_A_PARAM,
25 | SHIFT_B_PARAM,
26 | LINK_PARAM,
27 | NUM_PARAMS
28 | };
29 | enum InputIds {
30 | IN_A_INPUT,
31 | IN_B_INPUT,
32 | NUM_INPUTS
33 | };
34 | enum OutputIds {
35 | OUT_A_OUTPUT,
36 | OUT_B_OUTPUT,
37 | NUM_OUTPUTS
38 | };
39 | enum LightIds {
40 | LINK_LIGHT,
41 | NUM_LIGHTS
42 | };
43 |
44 | int shiftA;
45 | int shiftB;
46 | bool isLinked;
47 | double lastSampleA; // for zero crossing detection
48 | double lastSampleB; // for zero crossing detection
49 |
50 | Bitshiftgain()
51 | {
52 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
53 | configParam(SHIFT_A_PARAM, -8.0, 8.0, 0.0, "Shift");
54 | configParam(SHIFT_B_PARAM, -8.0, 8.0, 0.0, "Shift/Offset");
55 | configParam(LINK_PARAM, 0.f, 1.f, 0.0, "Link");
56 |
57 | onReset();
58 | }
59 |
60 | void onReset() override
61 | {
62 | shiftA = 0;
63 | shiftB = 0;
64 | isLinked = false;
65 | lastSampleA = 0.0;
66 | lastSampleB = 0.0;
67 | }
68 |
69 | double bitShift(int bitshiftGain)
70 | {
71 | double gain = 1.0;
72 |
73 | //we are directly punching in the gain values rather than calculating them
74 | switch (bitshiftGain) {
75 | case -16:
76 | gain = 0.0000152587890625;
77 | break;
78 | case -15:
79 | gain = 0.000030517578125;
80 | break;
81 | case -14:
82 | gain = 0.00006103515625;
83 | break;
84 | case -13:
85 | gain = 0.0001220703125;
86 | break;
87 | case -12:
88 | gain = 0.000244140625;
89 | break;
90 | case -11:
91 | gain = 0.00048828125;
92 | break;
93 | case -10:
94 | gain = 0.0009765625;
95 | break;
96 | case -9:
97 | gain = 0.001953125;
98 | break;
99 | case -8:
100 | gain = 0.00390625;
101 | break;
102 | case -7:
103 | gain = 0.0078125;
104 | break;
105 | case -6:
106 | gain = 0.015625;
107 | break;
108 | case -5:
109 | gain = 0.03125;
110 | break;
111 | case -4:
112 | gain = 0.0625;
113 | break;
114 | case -3:
115 | gain = 0.125;
116 | break;
117 | case -2:
118 | gain = 0.25;
119 | break;
120 | case -1:
121 | gain = 0.5;
122 | break;
123 | case 0:
124 | gain = 1.0;
125 | break;
126 | case 1:
127 | gain = 2.0;
128 | break;
129 | case 2:
130 | gain = 4.0;
131 | break;
132 | case 3:
133 | gain = 8.0;
134 | break;
135 | case 4:
136 | gain = 16.0;
137 | break;
138 | case 5:
139 | gain = 32.0;
140 | break;
141 | case 6:
142 | gain = 64.0;
143 | break;
144 | case 7:
145 | gain = 128.0;
146 | break;
147 | case 8:
148 | gain = 256.0;
149 | break;
150 | case 9:
151 | gain = 512.0;
152 | break;
153 | case 10:
154 | gain = 1024.0;
155 | break;
156 | case 11:
157 | gain = 2048.0;
158 | break;
159 | case 12:
160 | gain = 4096.0;
161 | break;
162 | case 13:
163 | gain = 8192.0;
164 | break;
165 | case 14:
166 | gain = 16384.0;
167 | break;
168 | case 15:
169 | gain = 32768.0;
170 | break;
171 | case 16:
172 | gain = 65536.0;
173 | break;
174 | }
175 |
176 | return gain;
177 | }
178 |
179 | void process(const ProcessArgs& args) override
180 | {
181 | // link
182 | isLinked = params[LINK_PARAM].getValue() ? true : false;
183 | lights[LINK_LIGHT].setBrightness(isLinked);
184 |
185 | /* section A
186 | =============================================================================== */
187 |
188 | if (inputs[IN_A_INPUT].isConnected()) {
189 |
190 | // get number of polyphonic channels
191 | int numChannelsA = inputs[IN_A_INPUT].getChannels();
192 |
193 | // set number of output channels
194 | outputs[OUT_A_OUTPUT].setChannels(numChannelsA);
195 |
196 | // update shiftA only at zero crossings (of first channel) to reduce clicks on parameter changes
197 | // reasonably effective on most sources, but will not happen across multiple channels, therefore monophonic only
198 | bool isZero = (inputs[IN_A_INPUT].getVoltage() * lastSampleA < 0.0);
199 | shiftA = isZero ? params[SHIFT_A_PARAM].getValue() : shiftA;
200 | lastSampleA = inputs[IN_A_INPUT].getVoltage();
201 |
202 | // for each poly channel
203 | for (int i = 0; i < numChannelsA; i++) {
204 | // shift signal in 6db steps
205 | outputs[OUT_A_OUTPUT].setVoltage(inputs[IN_A_INPUT].getPolyVoltage(i) * bitShift(shiftA), i);
206 | }
207 | } else {
208 | // output -8 to 8 in 1V steps if no input is connected
209 | outputs[OUT_A_OUTPUT].setVoltage(params[SHIFT_A_PARAM].getValue());
210 | }
211 |
212 | /* section B
213 | =============================================================================== */
214 |
215 | if (inputs[IN_B_INPUT].isConnected()) {
216 |
217 | // get number of polyphonic channels
218 | int numChannelsB = inputs[IN_B_INPUT].getChannels();
219 |
220 | // set number of output channels
221 | outputs[OUT_B_OUTPUT].setChannels(numChannelsB);
222 |
223 | // update shiftB only at zero crossings (of first channel) to reduce clicks on parameter changes
224 | // reasonably effective on most sources, but will not happen across multiple channels, therefore monophonic only
225 | bool isZero = (inputs[IN_B_INPUT].getVoltage() * lastSampleB < 0.0);
226 | shiftB = isZero ? params[SHIFT_B_PARAM].getValue() : shiftB;
227 | lastSampleB = inputs[IN_B_INPUT].getVoltage();
228 |
229 | // for each poly channel
230 | for (int i = 0; i < numChannelsB; i++) {
231 | if (isLinked) {
232 | if (inputs[IN_A_INPUT].isConnected()) {
233 | // offset signal in 6db steps
234 | outputs[OUT_B_OUTPUT].setVoltage(inputs[IN_B_INPUT].getPolyVoltage(i) * bitShift(-shiftA + shiftB), i);
235 | } else {
236 | // offset signal in 1V steps
237 | outputs[OUT_B_OUTPUT].setVoltage(inputs[IN_B_INPUT].getPolyVoltage(i) + params[SHIFT_B_PARAM].getValue(), i);
238 | }
239 | } else {
240 | // shift signal in 6db steps
241 | outputs[OUT_B_OUTPUT].setVoltage(inputs[IN_B_INPUT].getPolyVoltage(i) * bitShift(shiftB), i);
242 | }
243 | }
244 | } else {
245 | // output -8 to 8 in 1V steps if no input is connected
246 | outputs[OUT_B_OUTPUT].setVoltage(params[SHIFT_B_PARAM].getValue());
247 | }
248 | }
249 | };
250 |
251 | struct BitshiftgainWidget : ModuleWidget {
252 | BitshiftgainWidget(Bitshiftgain* module)
253 | {
254 | setModule(module);
255 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/bitshiftgain_dark.svg")));
256 |
257 | // screws
258 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
259 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
260 |
261 | // knobs
262 | addParam(createParamCentered(Vec(30.0, 65.0), module, Bitshiftgain::SHIFT_A_PARAM));
263 | addParam(createParamCentered(Vec(30.0, 235.0), module, Bitshiftgain::SHIFT_B_PARAM));
264 |
265 | // switches
266 | addParam(createParamCentered(Vec(30.0, 195.0), module, Bitshiftgain::LINK_PARAM));
267 |
268 | // lights
269 | addChild(createLightCentered>(Vec(48, 195), module, Bitshiftgain::LINK_LIGHT));
270 |
271 | // inputs
272 | addInput(createInputCentered(Vec(30.0, 115.0), module, Bitshiftgain::IN_A_INPUT));
273 | addInput(createInputCentered(Vec(30.0, 285.0), module, Bitshiftgain::IN_B_INPUT));
274 |
275 | // outputs
276 | addOutput(createOutputCentered(Vec(30.0, 155.0), module, Bitshiftgain::OUT_A_OUTPUT));
277 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Bitshiftgain::OUT_B_OUTPUT));
278 | }
279 | };
280 |
281 | Model* modelBitshiftgain = createModel("bitshiftgain");
--------------------------------------------------------------------------------
/src/hombre.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Hombre
3 | ------
4 | VCV Rack module based on Hombre by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - mono
10 | - CV inputs for voicing and intensity
11 | - polyphonic
12 |
13 | See ./LICENSE.md for all licenses
14 | ************************************************************************************************/
15 |
16 | #include "plugin.hpp"
17 |
18 | // quality options
19 | #define ECO 0
20 | #define HIGH 1
21 |
22 | struct Hombre : Module {
23 | enum ParamIds {
24 | VOICING_PARAM,
25 | INTENSITY_PARAM,
26 | NUM_PARAMS
27 | };
28 | enum InputIds {
29 | VOICING_CV_INPUT,
30 | INTENSITY_CV_INPUT,
31 | IN_INPUT,
32 | NUM_INPUTS
33 | };
34 | enum OutputIds {
35 | OUT_OUTPUT,
36 | NUM_OUTPUTS
37 | };
38 | enum LightIds {
39 | NUM_LIGHTS
40 | };
41 |
42 | // module variables
43 | const double gainCut = 0.03125;
44 | const double gainBoost = 32.0;
45 | int quality;
46 |
47 | // control parameters
48 | float voicingParam;
49 | float intensityParam;
50 |
51 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
52 | double p[16][4001];
53 | double slide[16];
54 | int gcount[16];
55 | long double fpNShape[16];
56 |
57 | // other variables, which do not need to be updated every cycle
58 | double overallscale;
59 | double target;
60 | int widthA;
61 | int widthB;
62 | double wet;
63 | double dry;
64 |
65 | Hombre()
66 | {
67 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
68 | configParam(VOICING_PARAM, 0.f, 1.f, 0.5f, "Voicing");
69 | configParam(INTENSITY_PARAM, 0.f, 1.f, 0.5f, "Intensity");
70 |
71 | quality = loadQuality();
72 | onReset();
73 | }
74 |
75 | void onReset() override
76 | {
77 | onSampleRateChange();
78 |
79 | for (int i = 0; i < 16; i++) {
80 | for (int count = 0; count < 4000; count++) {
81 | p[i][count] = 0.0;
82 | }
83 | gcount[i] = 0;
84 | slide[i] = 0.5;
85 | fpNShape[i] = 0.0;
86 | }
87 | }
88 |
89 | void onSampleRateChange() override
90 | {
91 | float sampleRate = APP->engine->getSampleRate();
92 |
93 | overallscale = 1.0;
94 | overallscale /= 44100.0;
95 | overallscale *= sampleRate;
96 |
97 | widthA = (int)(1.0 * overallscale);
98 | widthB = (int)(7.0 * overallscale); //max 364 at 44.1, 792 at 96K
99 | }
100 |
101 | json_t* dataToJson() override
102 | {
103 | json_t* rootJ = json_object();
104 |
105 | // quality
106 | json_object_set_new(rootJ, "quality", json_integer(quality));
107 |
108 | return rootJ;
109 | }
110 |
111 | void dataFromJson(json_t* rootJ) override
112 | {
113 | // quality
114 | json_t* qualityJ = json_object_get(rootJ, "quality");
115 | if (qualityJ)
116 | quality = json_integer_value(qualityJ);
117 | }
118 |
119 | void process(const ProcessArgs& args) override
120 | {
121 | if (outputs[OUT_OUTPUT].isConnected()) {
122 |
123 | voicingParam = params[VOICING_PARAM].getValue();
124 | voicingParam += inputs[VOICING_CV_INPUT].getVoltage() / 5;
125 | voicingParam = clamp(voicingParam, 0.01f, 0.99f);
126 |
127 | intensityParam = params[INTENSITY_PARAM].getValue();
128 | intensityParam += inputs[INTENSITY_CV_INPUT].getVoltage() / 5;
129 | intensityParam = clamp(intensityParam, 0.01f, 0.99f);
130 |
131 | target = voicingParam;
132 | wet = intensityParam;
133 | dry = 1.0 - wet;
134 |
135 | double offsetA;
136 | double offsetB;
137 | double total;
138 | int count;
139 | long double inputSample;
140 | double drySample;
141 |
142 | // input
143 | int numChannels = std::max(1, inputs[IN_INPUT].getChannels());
144 |
145 | // for each poly channel
146 | for (int i = 0; i < numChannels; i++) {
147 |
148 | // input
149 | inputSample = inputs[IN_INPUT].getPolyVoltage(i);
150 |
151 | // pad gain
152 | inputSample *= gainCut;
153 |
154 | if (quality == HIGH) {
155 | if (inputSample < 1.2e-38 && -inputSample < 1.2e-38) {
156 | static int noisesource = 0;
157 | //this declares a variable before anything else is compiled. It won't keep assigning
158 | //it to 0 for every sample, it's as if the declaration doesn't exist in this context,
159 | //but it lets me add this denormalization fix in a single place rather than updating
160 | //it in three different locations. The variable isn't thread-safe but this is only
161 | //a random seed and we can share it with whatever.
162 | noisesource = noisesource % 1700021;
163 | noisesource++;
164 | int residue = noisesource * noisesource;
165 | residue = residue % 170003;
166 | residue *= residue;
167 | residue = residue % 17011;
168 | residue *= residue;
169 | residue = residue % 1709;
170 | residue *= residue;
171 | residue = residue % 173;
172 | residue *= residue;
173 | residue = residue % 17;
174 | double applyresidue = residue;
175 | applyresidue *= 0.00000001;
176 | applyresidue *= 0.00000001;
177 | inputSample = applyresidue;
178 | }
179 | }
180 |
181 | drySample = inputSample;
182 |
183 | slide[i] = (slide[i] * 0.9997) + (target * 0.0003);
184 |
185 | //adjust for sample rate
186 | offsetA = ((pow(slide[i], 2)) * 77) + 3.2;
187 | offsetB = (3.85 * offsetA) + 41;
188 | offsetA *= overallscale;
189 | offsetB *= overallscale;
190 |
191 | if (gcount[i] < 1 || gcount[i] > 2000) {
192 | gcount[i] = 2000;
193 | }
194 | count = gcount[i];
195 |
196 | //double buffer
197 | p[i][count + 2000] = p[i][count] = inputSample;
198 |
199 | count = (int)(gcount[i] + floor(offsetA));
200 |
201 | total = p[i][count] * 0.391; //less as value moves away from .0
202 | total += p[i][count + widthA]; //we can assume always using this in one way or another?
203 | total += p[i][count + widthA + widthA] * 0.391; //greater as value moves away from .0
204 |
205 | inputSample += ((total * 0.274));
206 |
207 | count = (int)(gcount[i] + floor(offsetB));
208 |
209 | total = p[i][count] * 0.918; //less as value moves away from .0
210 | total += p[i][count + widthB]; //we can assume always using this in one way or another?
211 | total += p[i][count + widthB + widthB] * 0.918; //greater as value moves away from .0
212 |
213 | inputSample -= ((total * 0.629));
214 |
215 | inputSample /= 4;
216 |
217 | //still scrolling through the samples, remember
218 | gcount[i]--;
219 |
220 | if (wet != 1.0) {
221 | inputSample = (inputSample * wet) + (drySample * dry);
222 | }
223 |
224 | if (quality == HIGH) {
225 | //stereo 32 bit dither, made small and tidy.
226 | int expon;
227 | frexpf((float)inputSample, &expon);
228 | long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
229 | inputSample += (dither - fpNShape[i]);
230 | fpNShape[i] = dither;
231 | //end 32 bit dither
232 | }
233 |
234 | // bring gain back up
235 | inputSample *= gainBoost;
236 |
237 | // output
238 | outputs[OUT_OUTPUT].setChannels(numChannels);
239 | outputs[OUT_OUTPUT].setVoltage(inputSample, i);
240 | }
241 | }
242 | }
243 | };
244 |
245 | struct HombreWidget : ModuleWidget {
246 |
247 | // quality item
248 | struct QualityItem : MenuItem {
249 | Hombre* module;
250 | int quality;
251 |
252 | void onAction(const event::Action& e) override
253 | {
254 | module->quality = quality;
255 | }
256 |
257 | void step() override
258 | {
259 | rightText = (module->quality == quality) ? "✔" : "";
260 | }
261 | };
262 |
263 | void appendContextMenu(Menu* menu) override
264 | {
265 | Hombre* module = dynamic_cast(this->module);
266 | assert(module);
267 |
268 | menu->addChild(new MenuSeparator()); // separator
269 |
270 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
271 | qualityLabel->text = "Quality";
272 | menu->addChild(qualityLabel);
273 |
274 | QualityItem* low = new QualityItem(); // low quality
275 | low->text = "Eco";
276 | low->module = module;
277 | low->quality = 0;
278 | menu->addChild(low);
279 |
280 | QualityItem* high = new QualityItem(); // high quality
281 | high->text = "High";
282 | high->module = module;
283 | high->quality = 1;
284 | menu->addChild(high);
285 | }
286 |
287 | HombreWidget(Hombre* module)
288 | {
289 | setModule(module);
290 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/hombre_dark.svg")));
291 |
292 | // screws
293 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
294 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
295 |
296 | // knobs
297 | addParam(createParamCentered(Vec(30.0, 65.0), module, Hombre::VOICING_PARAM));
298 | addParam(createParamCentered(Vec(30.0, 125.0), module, Hombre::INTENSITY_PARAM));
299 |
300 | // inputs
301 | addInput(createInputCentered(Vec(30.0, 205.0), module, Hombre::VOICING_CV_INPUT));
302 | addInput(createInputCentered(Vec(30.0, 245.0), module, Hombre::INTENSITY_CV_INPUT));
303 | addInput(createInputCentered(Vec(30.0, 285.0), module, Hombre::IN_INPUT));
304 |
305 | // outputs
306 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Hombre::OUT_OUTPUT));
307 | }
308 | };
309 |
310 | Model* modelHombre = createModel("hombre");
--------------------------------------------------------------------------------
/src/distance.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Distance
3 | --------
4 | VCV Rack module based on Distance by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - mono
10 | - CV inputs for distance and dry/wet
11 | - polyphonic
12 |
13 | See ./LICENSE.md for all licenses
14 | ************************************************************************************************/
15 |
16 | #include "plugin.hpp"
17 |
18 | // quality options
19 | #define ECO 0
20 | #define HIGH 1
21 |
22 | struct Distance : Module {
23 | enum ParamIds {
24 | DISTANCE_PARAM,
25 | DRYWET_PARAM,
26 | NUM_PARAMS
27 | };
28 | enum InputIds {
29 | DISTANCE_CV_INPUT,
30 | DRYWET_CV_INPUT,
31 | IN_INPUT,
32 | NUM_INPUTS
33 | };
34 | enum OutputIds {
35 | OUT_OUTPUT,
36 | NUM_OUTPUTS
37 | };
38 | enum LightIds {
39 | NUM_LIGHTS
40 | };
41 |
42 | // module variables
43 | const double gainCut = 0.03125;
44 | const double gainBoost = 32.0;
45 | int quality;
46 |
47 | // control parameters
48 | float distanceParam;
49 | float drywetParam;
50 |
51 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
52 | double lastclamp[16];
53 | double clasp[16];
54 | double change[16];
55 | double thirdresult[16];
56 | double prevresult[16];
57 | double last[16];
58 | long double fpNShape[16];
59 |
60 | // other variables, which do not need to be updated every cycle
61 | double overallscale;
62 | double softslew;
63 | double filtercorrect;
64 | double thirdfilter;
65 | double levelcorrect;
66 | double wet;
67 | double dry;
68 | float lastDistanceParam;
69 | float lastDrywetParam;
70 |
71 | Distance()
72 | {
73 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
74 | configParam(DISTANCE_PARAM, 0.f, 1.f, 0.f, "Distance");
75 | configParam(DRYWET_PARAM, 0.f, 1.f, 1.f, "Dry/Wet");
76 |
77 | quality = loadQuality();
78 | onReset();
79 | }
80 |
81 | void onReset() override
82 | {
83 | onSampleRateChange();
84 |
85 | for (int i = 0; i < 16; i++) {
86 | thirdresult[i] = prevresult[i] = lastclamp[i] = clasp[i] = change[i] = last[i] = 0.0;
87 | fpNShape[i] = 0.0;
88 | }
89 |
90 | softslew = 0.0;
91 | filtercorrect = 0.0;
92 | thirdfilter = 0.0;
93 | levelcorrect = 0.0;
94 | wet = 0.0;
95 | dry = 0.0;
96 | lastDistanceParam = 0.0;
97 | lastDrywetParam = 0.0;
98 | }
99 |
100 | void onSampleRateChange() override
101 | {
102 | float sampleRate = APP->engine->getSampleRate();
103 |
104 | overallscale = 1.0;
105 | overallscale /= 44100.0;
106 | overallscale *= sampleRate;
107 | }
108 |
109 | json_t* dataToJson() override
110 | {
111 | json_t* rootJ = json_object();
112 |
113 | // quality
114 | json_object_set_new(rootJ, "quality", json_integer(quality));
115 |
116 | return rootJ;
117 | }
118 |
119 | void dataFromJson(json_t* rootJ) override
120 | {
121 | // quality
122 | json_t* qualityJ = json_object_get(rootJ, "quality");
123 | if (qualityJ)
124 | quality = json_integer_value(qualityJ);
125 | }
126 |
127 | void process(const ProcessArgs& args) override
128 | {
129 | if (outputs[OUT_OUTPUT].isConnected()) {
130 |
131 | distanceParam = params[DISTANCE_PARAM].getValue();
132 | distanceParam += inputs[DISTANCE_CV_INPUT].getVoltage() / 5;
133 | distanceParam = clamp(distanceParam, 0.01f, 0.99f);
134 |
135 | drywetParam = params[DRYWET_PARAM].getValue();
136 | drywetParam += inputs[DRYWET_CV_INPUT].getVoltage() / 5;
137 | drywetParam = clamp(drywetParam, 0.01f, 0.99f);
138 |
139 | // update only if distanceParam has changed
140 | if (distanceParam != lastDistanceParam) {
141 | softslew = (pow(distanceParam * 2.0, 3.0) * 12.0) + 0.6;
142 | softslew *= overallscale;
143 | filtercorrect = softslew / 2.0;
144 | thirdfilter = softslew / 3.0;
145 | levelcorrect = 1.0 + (softslew / 6.0);
146 |
147 | lastDistanceParam = distanceParam;
148 | }
149 |
150 | // update only if distanceParam has changed
151 | if (drywetParam != lastDrywetParam) {
152 | wet = drywetParam;
153 | dry = 1.0 - wet;
154 |
155 | lastDrywetParam = drywetParam;
156 | }
157 |
158 | double postfilter;
159 | double bridgerectifier;
160 | long double inputSample;
161 | long double drySample;
162 |
163 | // number of polyphonic channels
164 | int numChannels = std::max(1, inputs[IN_INPUT].getChannels());
165 |
166 | // for each poly channel
167 | for (int i = 0; i < numChannels; i++) {
168 |
169 | // input
170 | inputSample = inputs[IN_INPUT].getPolyVoltage(i);
171 |
172 | // pad gain
173 | inputSample *= gainCut;
174 |
175 | if (quality == HIGH) {
176 | if (inputSample < 1.2e-38 && -inputSample < 1.2e-38) {
177 | static int noisesource = 0;
178 | //this declares a variable before anything else is compiled. It won't keep assigning
179 | //it to 0 for every sample, it's as if the declaration doesn't exist in this context,
180 | //but it lets me add this denormalization fix in a single place rather than updating
181 | //it in three different locations. The variable isn't thread-safe but this is only
182 | //a random seed and we can share it with whatever.
183 | noisesource = noisesource % 1700021;
184 | noisesource++;
185 | int residue = noisesource * noisesource;
186 | residue = residue % 170003;
187 | residue *= residue;
188 | residue = residue % 17011;
189 | residue *= residue;
190 | residue = residue % 1709;
191 | residue *= residue;
192 | residue = residue % 173;
193 | residue *= residue;
194 | residue = residue % 17;
195 | double applyresidue = residue;
196 | applyresidue *= 0.00000001;
197 | applyresidue *= 0.00000001;
198 | inputSample = applyresidue;
199 | }
200 | }
201 |
202 | drySample = inputSample;
203 |
204 | inputSample *= softslew;
205 | lastclamp[i] = clasp[i];
206 | clasp[i] = inputSample - last[i];
207 | postfilter = change[i] = fabs(clasp[i] - lastclamp[i]);
208 | postfilter += filtercorrect;
209 | if (change[i] > 1.5707963267949)
210 | change[i] = 1.5707963267949;
211 | bridgerectifier = (1.0 - sin(change[i]));
212 | if (bridgerectifier < 0.0)
213 | bridgerectifier = 0.0;
214 | inputSample = last[i] + (clasp[i] * bridgerectifier);
215 | last[i] = inputSample;
216 | inputSample /= softslew;
217 | inputSample += (thirdresult[i] * thirdfilter);
218 | inputSample /= (thirdfilter + 1.0);
219 | inputSample += (prevresult[i] * postfilter);
220 | inputSample /= (postfilter + 1.0);
221 | //do an IIR like thing to further squish superdistant stuff
222 | thirdresult[i] = prevresult[i];
223 | prevresult[i] = inputSample;
224 | inputSample *= levelcorrect;
225 |
226 | if (wet < 1.0) {
227 | inputSample = (drySample * dry) + (inputSample * wet);
228 | }
229 |
230 | if (quality == HIGH) {
231 | //stereo 32 bit dither, made small and tidy.
232 | int expon;
233 | frexpf((float)inputSample, &expon);
234 | long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
235 | inputSample += (dither - fpNShape[i]);
236 | fpNShape[i] = dither;
237 | //end 32 bit dither
238 | }
239 |
240 | // bring gain back up
241 | inputSample *= gainBoost;
242 |
243 | // output
244 | outputs[OUT_OUTPUT].setChannels(numChannels);
245 | outputs[OUT_OUTPUT].setVoltage(inputSample, i);
246 | }
247 | }
248 | }
249 | };
250 |
251 | struct DistanceWidget : ModuleWidget {
252 |
253 | // quality item
254 | struct QualityItem : MenuItem {
255 | Distance* module;
256 | int quality;
257 |
258 | void onAction(const event::Action& e) override
259 | {
260 | module->quality = quality;
261 | }
262 |
263 | void step() override
264 | {
265 | rightText = (module->quality == quality) ? "✔" : "";
266 | }
267 | };
268 |
269 | void appendContextMenu(Menu* menu) override
270 | {
271 | Distance* module = dynamic_cast(this->module);
272 | assert(module);
273 |
274 | menu->addChild(new MenuSeparator()); // separator
275 |
276 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
277 | qualityLabel->text = "Quality";
278 | menu->addChild(qualityLabel);
279 |
280 | QualityItem* low = new QualityItem(); // low quality
281 | low->text = "Eco";
282 | low->module = module;
283 | low->quality = 0;
284 | menu->addChild(low);
285 |
286 | QualityItem* high = new QualityItem(); // high quality
287 | high->text = "High";
288 | high->module = module;
289 | high->quality = 1;
290 | menu->addChild(high);
291 | }
292 |
293 | DistanceWidget(Distance* module)
294 | {
295 | setModule(module);
296 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/distance_dark.svg")));
297 |
298 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
299 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
300 |
301 | addParam(createParamCentered(Vec(30.0, 65.0), module, Distance::DISTANCE_PARAM));
302 | addParam(createParamCentered(Vec(30.0, 125.0), module, Distance::DRYWET_PARAM));
303 |
304 | addInput(createInputCentered(Vec(30.0, 205.0), module, Distance::DISTANCE_CV_INPUT));
305 | addInput(createInputCentered(Vec(30.0, 245.0), module, Distance::DRYWET_CV_INPUT));
306 | addInput(createInputCentered(Vec(30.0, 285.0), module, Distance::IN_INPUT));
307 |
308 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Distance::OUT_OUTPUT));
309 | }
310 | };
311 |
312 | Model* modelDistance = createModel("distance");
--------------------------------------------------------------------------------
/src/plugin.cpp:
--------------------------------------------------------------------------------
1 | #include "plugin.hpp"
2 |
3 | Plugin* pluginInstance;
4 |
5 | void init(Plugin* p)
6 | {
7 | pluginInstance = p;
8 |
9 | // Add modules here
10 | // p->addModel(modelAcceleration);
11 | p->addModel(modelBitshiftgain);
12 | p->addModel(modelCapacitor);
13 | p->addModel(modelCapacitor_stereo);
14 | p->addModel(modelChorus);
15 | p->addModel(modelConsole);
16 | p->addModel(modelConsole_mm);
17 | p->addModel(modelDistance);
18 | p->addModel(modelGolem);
19 | p->addModel(modelHolt);
20 | p->addModel(modelHombre);
21 | p->addModel(modelInterstage);
22 | p->addModel(modelMonitoring);
23 | p->addModel(modelMv);
24 | p->addModel(modelRasp);
25 | p->addModel(modelReseq);
26 | p->addModel(modelTape);
27 | p->addModel(modelTremolo);
28 | p->addModel(modelVibrato);
29 |
30 | // Any other plugin initialization may go here.
31 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack.
32 | }
33 |
34 | /* Other stuff */
35 |
36 | /* #quality mode
37 | ======================================================================================== */
38 | void saveQuality(bool quality)
39 | {
40 | json_t* settingsJ = json_object();
41 | json_object_set_new(settingsJ, "quality", json_boolean(quality));
42 | std::string settingsFilename = asset::user("Rackwindows.json");
43 | FILE* file = fopen(settingsFilename.c_str(), "w");
44 | if (file) {
45 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
46 | fclose(file);
47 | }
48 | json_decref(settingsJ);
49 | }
50 |
51 | bool loadQuality()
52 | {
53 | bool ret = false;
54 | std::string settingsFilename = asset::user("Rackwindows.json");
55 | FILE* file = fopen(settingsFilename.c_str(), "r");
56 | if (!file) {
57 | saveQuality(false);
58 | return ret;
59 | }
60 | json_error_t error;
61 | json_t* settingsJ = json_loadf(file, 0, &error);
62 | if (!settingsJ) {
63 | // invalid setting json file
64 | fclose(file);
65 | saveQuality(false);
66 | return ret;
67 | }
68 | json_t* qualityJ = json_object_get(settingsJ, "quality");
69 | if (qualityJ)
70 | ret = json_boolean_value(qualityJ);
71 |
72 | fclose(file);
73 | json_decref(settingsJ);
74 | return ret;
75 | }
76 |
77 | void saveHighQualityAsDefault(bool highQualityAsDefault)
78 | {
79 | json_t* settingsJ = json_object();
80 | json_object_set_new(settingsJ, "highQualityAsDefault", json_boolean(highQualityAsDefault));
81 | std::string settingsFilename = asset::user("Rackwindows.json");
82 | FILE* file = fopen(settingsFilename.c_str(), "w");
83 | if (file) {
84 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
85 | fclose(file);
86 | }
87 | json_decref(settingsJ);
88 | }
89 |
90 | // https://github.com/MarcBoule/Geodesics/blob/master/src/Geodesics.cpp
91 | bool loadHighQualityAsDefault()
92 | {
93 | bool ret = false;
94 | std::string settingsFilename = asset::user("Rackwindows.json");
95 | FILE* file = fopen(settingsFilename.c_str(), "r");
96 | if (!file) {
97 | saveHighQualityAsDefault(false);
98 | return ret;
99 | }
100 | json_error_t error;
101 | json_t* settingsJ = json_loadf(file, 0, &error);
102 | if (!settingsJ) {
103 | // invalid setting json file
104 | fclose(file);
105 | saveHighQualityAsDefault(false);
106 | return ret;
107 | }
108 | json_t* highQualityAsDefaultJ = json_object_get(settingsJ, "highQualityAsDefault");
109 | if (highQualityAsDefaultJ)
110 | ret = json_boolean_value(highQualityAsDefaultJ);
111 |
112 | fclose(file);
113 | json_decref(settingsJ);
114 | return ret;
115 | }
116 |
117 | /* #console type (Console, Console MM)
118 | ======================================================================================== */
119 | void saveConsoleType(int consoleType)
120 | {
121 | json_t* settingsJ = json_object();
122 | json_object_set_new(settingsJ, "consoleType", json_boolean(consoleType));
123 | std::string settingsFilename = asset::user("Rackwindows.json");
124 | FILE* file = fopen(settingsFilename.c_str(), "w");
125 | if (file) {
126 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
127 | fclose(file);
128 | }
129 | json_decref(settingsJ);
130 | }
131 |
132 | int loadConsoleType()
133 | {
134 | bool ret = false;
135 | std::string settingsFilename = asset::user("Rackwindows.json");
136 | FILE* file = fopen(settingsFilename.c_str(), "r");
137 | if (!file) {
138 | saveConsoleType(false);
139 | return ret;
140 | }
141 | json_error_t error;
142 | json_t* settingsJ = json_loadf(file, 0, &error);
143 | if (!settingsJ) {
144 | // invalid setting json file
145 | fclose(file);
146 | saveConsoleType(false);
147 | return ret;
148 | }
149 | json_t* consoleTypeJ = json_object_get(settingsJ, "consoleType");
150 | if (consoleTypeJ)
151 | ret = json_boolean_value(consoleTypeJ);
152 |
153 | fclose(file);
154 | json_decref(settingsJ);
155 | return ret;
156 | }
157 |
158 | /* #slew type (Rasp)
159 | ======================================================================================== */
160 | void saveSlewType(int slewType)
161 | {
162 | json_t* settingsJ = json_object();
163 | json_object_set_new(settingsJ, "slewType", json_integer(slewType));
164 | std::string settingsFilename = asset::user("Rackwindows.json");
165 | FILE* file = fopen(settingsFilename.c_str(), "w");
166 | if (file) {
167 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
168 | fclose(file);
169 | }
170 | json_decref(settingsJ);
171 | }
172 |
173 | int loadSlewType()
174 | {
175 | bool ret = false;
176 | std::string settingsFilename = asset::user("Rackwindows.json");
177 | FILE* file = fopen(settingsFilename.c_str(), "r");
178 | if (!file) {
179 | saveSlewType(false);
180 | return ret;
181 | }
182 | json_error_t error;
183 | json_t* settingsJ = json_loadf(file, 0, &error);
184 | if (!settingsJ) {
185 | // invalid setting json file
186 | fclose(file);
187 | saveSlewType(false);
188 | return ret;
189 | }
190 | json_t* slewTypeJ = json_object_get(settingsJ, "slewType");
191 | if (slewTypeJ)
192 | ret = json_integer_value(slewTypeJ);
193 |
194 | fclose(file);
195 | json_decref(settingsJ);
196 | return ret;
197 | }
198 |
199 | /* #direct output mode (Console MM)
200 | ======================================================================================== */
201 | void saveDirectOutMode(bool directOutMode)
202 | {
203 | json_t* settingsJ = json_object();
204 | json_object_set_new(settingsJ, "directOutMode", json_boolean(directOutMode));
205 | std::string settingsFilename = asset::user("Rackwindows.json");
206 | FILE* file = fopen(settingsFilename.c_str(), "w");
207 | if (file) {
208 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
209 | fclose(file);
210 | }
211 | json_decref(settingsJ);
212 | }
213 |
214 | int loadDirectOutMode()
215 | {
216 | bool ret = false;
217 | std::string settingsFilename = asset::user("Rackwindows.json");
218 | FILE* file = fopen(settingsFilename.c_str(), "r");
219 | if (!file) {
220 | saveDirectOutMode(false);
221 | return ret;
222 | }
223 | json_error_t error;
224 | json_t* settingsJ = json_loadf(file, 0, &error);
225 | if (!settingsJ) {
226 | // invalid setting json file
227 | fclose(file);
228 | saveDirectOutMode(false);
229 | return ret;
230 | }
231 | json_t* directOutModeJ = json_object_get(settingsJ, "directOutMode");
232 | if (directOutModeJ)
233 | ret = json_boolean_value(directOutModeJ);
234 |
235 | fclose(file);
236 | json_decref(settingsJ);
237 | return ret;
238 | }
239 |
240 | /* #delay mode (Golem)
241 | ======================================================================================== */
242 | void saveDelayMode(int delayMode)
243 | {
244 | json_t* settingsJ = json_object();
245 | json_object_set_new(settingsJ, "delayMode", json_boolean(delayMode));
246 | std::string settingsFilename = asset::user("Rackwindows.json");
247 | FILE* file = fopen(settingsFilename.c_str(), "w");
248 | if (file) {
249 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
250 | fclose(file);
251 | }
252 | json_decref(settingsJ);
253 | }
254 |
255 | int loadDelayMode()
256 | {
257 | bool ret = false;
258 | std::string settingsFilename = asset::user("Rackwindows.json");
259 | FILE* file = fopen(settingsFilename.c_str(), "r");
260 | if (!file) {
261 | saveDelayMode(false);
262 | return ret;
263 | }
264 | json_error_t error;
265 | json_t* settingsJ = json_loadf(file, 0, &error);
266 | if (!settingsJ) {
267 | // invalid setting json file
268 | fclose(file);
269 | saveDelayMode(false);
270 | return ret;
271 | }
272 | json_t* delayModeJ = json_object_get(settingsJ, "delayMode");
273 | if (delayModeJ)
274 | ret = json_boolean_value(delayModeJ);
275 |
276 | fclose(file);
277 | json_decref(settingsJ);
278 | return ret;
279 | }
280 |
281 | /* #themes
282 | ======================================================================================== */
283 | // https://github.com/MarcBoule/Geodesics/blob/master/src/Geodesics.cpp
284 | void saveDarkAsDefault(bool darkAsDefault)
285 | {
286 | json_t* settingsJ = json_object();
287 | json_object_set_new(settingsJ, "darkAsDefault", json_boolean(darkAsDefault));
288 | std::string settingsFilename = asset::user("Rackwindows.json");
289 | FILE* file = fopen(settingsFilename.c_str(), "w");
290 | if (file) {
291 | json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
292 | fclose(file);
293 | }
294 | json_decref(settingsJ);
295 | }
296 |
297 | // https://github.com/MarcBoule/Geodesics/blob/master/src/Geodesics.cpp
298 | bool loadDarkAsDefault()
299 | {
300 | bool ret = false;
301 | std::string settingsFilename = asset::user("Rackwindows.json");
302 | FILE* file = fopen(settingsFilename.c_str(), "r");
303 | if (!file) {
304 | saveDarkAsDefault(false);
305 | return ret;
306 | }
307 | json_error_t error;
308 | json_t* settingsJ = json_loadf(file, 0, &error);
309 | if (!settingsJ) {
310 | // invalid setting json file
311 | fclose(file);
312 | saveDarkAsDefault(false);
313 | return ret;
314 | }
315 | json_t* darkAsDefaultJ = json_object_get(settingsJ, "darkAsDefault");
316 | if (darkAsDefaultJ)
317 | ret = json_boolean_value(darkAsDefaultJ);
318 |
319 | fclose(file);
320 | json_decref(settingsJ);
321 | return ret;
322 | }
323 |
324 | // https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/NonLinear.hpp
325 | inline float tanhDriveSignal(float x, float drive)
326 | {
327 | x *= drive;
328 |
329 | if (x < -1.3f) {
330 | return -1.f;
331 | } else if (x < -0.75f) {
332 | return (x * x + 2.6f * x + 1.69f) * 0.833333f - 1.f;
333 | } else if (x > 1.3f) {
334 | return 1.f;
335 | } else if (x > 0.75f) {
336 | return 1.f - (x * x - 2.6f * x + 1.69f) * 0.833333f;
337 | }
338 | return x;
339 | }
340 |
--------------------------------------------------------------------------------
/src/rasp.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Rasp
3 | ----
4 | VCV Rack module based on Slew/Slew2/Slew3 and Acceleration by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - mono
10 | - cv inputs for clamp and limit
11 | - clamp and limit outputs normalled to each other
12 | - slew algorithm selectable via menu
13 | - polyphonic
14 |
15 | See ./LICENSE.md for all licenses
16 | ************************************************************************************************/
17 |
18 | #include "plugin.hpp"
19 |
20 | // quality options
21 | #define ECO 0
22 | #define HIGH 1
23 |
24 | // slew types
25 | #define SLEW2 0
26 | #define SLEW 1
27 | #define SLEW3 2
28 |
29 | // polyphony
30 | #define MAX_POLY_CHANNELS 16
31 |
32 | struct Rasp : Module {
33 | enum ParamIds {
34 | CLAMP_PARAM,
35 | LIMIT_PARAM,
36 | NUM_PARAMS
37 | };
38 | enum InputIds {
39 | CLAMP_CV_INPUT,
40 | LIMIT_CV_INPUT,
41 | IN_INPUT,
42 | NUM_INPUTS
43 | };
44 | enum OutputIds {
45 | CLAMP_OUTPUT,
46 | LIMIT_OUTPUT,
47 | NUM_OUTPUTS
48 | };
49 | enum LightIds {
50 | NUM_LIGHTS
51 | };
52 |
53 | // module variables
54 | const double gainCut = 0.1;
55 | const double gainBoost = 10.0;
56 | bool quality;
57 | int slewType;
58 |
59 | // control parameters
60 | float clampParam;
61 | float limitParam;
62 |
63 | // state variables
64 | rwlib::Slew slew[MAX_POLY_CHANNELS];
65 | rwlib::Slew2 slew2[MAX_POLY_CHANNELS];
66 | rwlib::Slew3 slew3[MAX_POLY_CHANNELS];
67 | rwlib::Acceleration acceleration[MAX_POLY_CHANNELS];
68 | long double fpNShapeClamp[MAX_POLY_CHANNELS];
69 | long double fpNShapeLimit[MAX_POLY_CHANNELS];
70 |
71 | // other
72 | double overallscale;
73 |
74 | Rasp()
75 | {
76 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
77 | configParam(CLAMP_PARAM, 0.f, 1.f, 0.f, "Clamp", " %", 0.f, 100.f);
78 | configParam(LIMIT_PARAM, 0.f, 1.f, 0.f, "Limit", " %", 0.f, 100.f);
79 |
80 | quality = ECO;
81 | slewType = SLEW2;
82 | onReset();
83 | }
84 |
85 | void onReset() override
86 | {
87 | onSampleRateChange();
88 |
89 | clampParam = 0.f;
90 | limitParam = 0.f;
91 |
92 | for (int i = 0; i < MAX_POLY_CHANNELS; i++) {
93 | {
94 | slew[i] = rwlib::Slew();
95 | slew2[i] = rwlib::Slew2();
96 | slew3[i] = rwlib::Slew3();
97 | acceleration[i] = rwlib::Acceleration();
98 | fpNShapeClamp[i] = 0.0;
99 | fpNShapeLimit[i] = 0.0;
100 | }
101 | }
102 | }
103 |
104 | void onSampleRateChange() override
105 | {
106 | float sampleRate = APP->engine->getSampleRate();
107 |
108 | overallscale = 1.0;
109 | overallscale /= 44100.0;
110 | overallscale *= sampleRate;
111 | }
112 |
113 | json_t* dataToJson() override
114 | {
115 | json_t* rootJ = json_object();
116 |
117 | // quality
118 | json_object_set_new(rootJ, "quality", json_integer(quality));
119 |
120 | // slew type
121 | json_object_set_new(rootJ, "slewType", json_integer(slewType));
122 |
123 | return rootJ;
124 | }
125 |
126 | void dataFromJson(json_t* rootJ) override
127 | {
128 | // quality
129 | json_t* qualityJ = json_object_get(rootJ, "quality");
130 | if (qualityJ)
131 | quality = json_integer_value(qualityJ);
132 |
133 | // slew type
134 | json_t* slewTypeJ = json_object_get(rootJ, "slewType");
135 | if (slewTypeJ)
136 | slewType = json_integer_value(slewTypeJ);
137 | }
138 |
139 | void process(const ProcessArgs& args) override
140 | {
141 | // get params
142 | limitParam = params[LIMIT_PARAM].getValue();
143 | limitParam += inputs[LIMIT_CV_INPUT].getVoltage() / 5;
144 | limitParam = clamp(limitParam, 0.f, 1.f);
145 |
146 | clampParam = params[CLAMP_PARAM].getValue();
147 | clampParam += inputs[CLAMP_CV_INPUT].getVoltage() / 5;
148 | clampParam = clamp(clampParam, 0.f, 1.f);
149 |
150 | long double inputSample;
151 | long double clampSample = 0.0;
152 | long double limitSample = 0.0;
153 |
154 | // for each poly channel
155 | for (int i = 0, numChannels = std::max(1, inputs[IN_INPUT].getChannels()); i < numChannels; ++i) {
156 |
157 | // get input
158 | inputSample = inputs[IN_INPUT].getPolyVoltage(i);
159 |
160 | // pad gain
161 | inputSample *= gainCut;
162 |
163 | if (quality == HIGH) {
164 | inputSample = rwlib::denormalize(inputSample);
165 | }
166 |
167 | // work the magic
168 | if (outputs[CLAMP_OUTPUT].isConnected()) {
169 | if (outputs[LIMIT_OUTPUT].isConnected()) {
170 | switch (slewType) {
171 | case SLEW:
172 | clampSample = slew[i].process(inputSample, clampParam, overallscale);
173 | break;
174 | case SLEW2:
175 | clampSample = slew2[i].process(inputSample, clampParam, overallscale);
176 | break;
177 | case SLEW3:
178 | clampSample = slew3[i].process(inputSample, clampParam, overallscale);
179 | }
180 | } else {
181 | limitSample = acceleration[i].process(inputSample, limitParam, 1.f, overallscale);
182 | switch (slewType) {
183 | case SLEW:
184 | clampSample = slew[i].process(limitSample, clampParam, overallscale);
185 | break;
186 | case SLEW2:
187 | clampSample = slew2[i].process(limitSample, clampParam, overallscale);
188 | break;
189 | case SLEW3:
190 | clampSample = slew3[i].process(limitSample, clampParam, overallscale);
191 | }
192 | }
193 | }
194 | if (outputs[LIMIT_OUTPUT].isConnected()) {
195 | if (outputs[CLAMP_OUTPUT].isConnected()) {
196 | limitSample = acceleration[i].process(inputSample, limitParam, 1.f, overallscale);
197 | } else {
198 | switch (slewType) {
199 | case SLEW:
200 | clampSample = slew[i].process(inputSample, clampParam, overallscale);
201 | break;
202 | case SLEW2:
203 | clampSample = slew2[i].process(inputSample, clampParam, overallscale);
204 | break;
205 | case SLEW3:
206 | clampSample = slew3[i].process(inputSample, clampParam, overallscale);
207 | }
208 | limitSample = acceleration[i].process(clampSample, limitParam, 1.f, overallscale);
209 | }
210 | }
211 |
212 | if (quality == HIGH) {
213 | // 32 bit dither, made small and tidy.
214 | int expon;
215 | frexpf((float)clampSample, &expon);
216 | long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
217 | clampSample += (dither - fpNShapeClamp[i]);
218 | fpNShapeClamp[i] = dither;
219 | frexpf((float)limitSample, &expon);
220 | dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
221 | limitSample += (dither - fpNShapeLimit[i]);
222 | fpNShapeLimit[i] = dither;
223 | }
224 |
225 | // bring levels back up
226 | clampSample *= gainBoost;
227 | limitSample *= gainBoost;
228 |
229 | // output
230 | outputs[CLAMP_OUTPUT].setChannels(numChannels);
231 | outputs[CLAMP_OUTPUT].setVoltage(clampSample, i);
232 | outputs[LIMIT_OUTPUT].setChannels(numChannels);
233 | outputs[LIMIT_OUTPUT].setVoltage(limitSample, i);
234 | }
235 | }
236 | };
237 |
238 | struct RaspWidget : ModuleWidget {
239 |
240 | // quality item
241 | struct QualityItem : MenuItem {
242 | Rasp* module;
243 | int quality;
244 |
245 | void onAction(const event::Action& e) override
246 | {
247 | module->quality = quality;
248 | }
249 |
250 | void step() override
251 | {
252 | rightText = (module->quality == quality) ? "✔" : "";
253 | }
254 | };
255 |
256 | // slew type item
257 | struct SlewTypeItem : MenuItem {
258 | Rasp* module;
259 | int slewType;
260 |
261 | void onAction(const event::Action& e) override
262 | {
263 | module->slewType = slewType;
264 | }
265 |
266 | void step() override
267 | {
268 | rightText = (module->slewType == slewType) ? "✔" : "";
269 | }
270 | };
271 |
272 | void appendContextMenu(Menu* menu) override
273 | {
274 | Rasp* module = dynamic_cast(this->module);
275 | assert(module);
276 |
277 | menu->addChild(new MenuSeparator()); // separator
278 |
279 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
280 | qualityLabel->text = "Quality";
281 | menu->addChild(qualityLabel);
282 |
283 | QualityItem* low = new QualityItem(); // low quality
284 | low->text = "Eco";
285 | low->module = module;
286 | low->quality = 0;
287 | menu->addChild(low);
288 |
289 | QualityItem* high = new QualityItem(); // high quality
290 | high->text = "High";
291 | high->module = module;
292 | high->quality = 1;
293 | menu->addChild(high);
294 |
295 | menu->addChild(new MenuSeparator()); // separator
296 |
297 | MenuLabel* slewLabel = new MenuLabel(); // menu label
298 | slewLabel->text = "Clamp Type";
299 | menu->addChild(slewLabel);
300 |
301 | SlewTypeItem* slew = new SlewTypeItem(); // Slew
302 | slew->text = "Slew";
303 | slew->module = module;
304 | slew->slewType = SLEW;
305 | menu->addChild(slew);
306 |
307 | SlewTypeItem* slew2 = new SlewTypeItem(); // Slew2
308 | slew2->text = "Slew2";
309 | slew2->module = module;
310 | slew2->slewType = SLEW2;
311 | menu->addChild(slew2);
312 |
313 | SlewTypeItem* slew3 = new SlewTypeItem(); // Slew3
314 | slew3->text = "Slew3";
315 | slew3->module = module;
316 | slew3->slewType = SLEW3;
317 | menu->addChild(slew3);
318 | }
319 |
320 | RaspWidget(Rasp* module)
321 | {
322 | setModule(module);
323 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/rasp_dark.svg")));
324 |
325 | // screws
326 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
327 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
328 |
329 | //params
330 | addParam(createParamCentered(Vec(30.0, 65.0), module, Rasp::CLAMP_PARAM));
331 | addParam(createParamCentered(Vec(30.0, 115.0), module, Rasp::LIMIT_PARAM));
332 |
333 | // inputs
334 | addInput(createInputCentered(Vec(30.0, 165.0), module, Rasp::CLAMP_CV_INPUT));
335 | addInput(createInputCentered(Vec(30.0, 205.0), module, Rasp::LIMIT_CV_INPUT));
336 | addInput(createInputCentered(Vec(30.0, 245.0), module, Rasp::IN_INPUT));
337 |
338 | // outputs
339 | addOutput(createOutputCentered(Vec(30.0, 285.0), module, Rasp::CLAMP_OUTPUT));
340 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Rasp::LIMIT_OUTPUT));
341 | }
342 | };
343 |
344 | Model* modelRasp = createModel("rasp");
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # A collection of VCV Rack modules based on audio plugins by Chris Johnson from Airwindows
2 |
3 | 
4 |
5 | Most of these modules are straight-up ports of [Airwindows](http://www.airwindows.com) plugins with the addition of a panel (designed to be potentially buildable in hardware) and the occasional tweak or enhancement to take advantage of the modular environment.
6 |
7 | Airwindows plugins are known for exceptional sound quality, innovative approaches and minimalist interfaces. They often focus on and excel at one specific task, which is why I think they are a natural fit for Rack.
8 |
9 | For more in-depth information about the inner workings of a particular Airwindows plugin please check Chris' [website](http://www.airwindows.com). There is a video on every plugin along with a description and I encourage everyone to explore his body of work. Chances are you stumble upon other gems here and there. He's got a [Patreon](https://www.patreon.com/airwindows) in case you appreciate and want to support what he does.
10 |
11 | **NOTE:** Most modules act a bit differently depending on the selected sample rate, e.g. varying center frequencies on filters. Keep in mind that a number of Airwindows plugins are actually sample rate dependent, therefore in some cases it might be a feature, not a bug.
12 |
13 | ## Licence
14 |
15 | See [LICENSE.md](LICENSE.md) for all licenses
16 |
17 | ## Modules
18 |
19 | - [Capacitor Mono/Stereo](#capacitor): Filters
20 | - [Chorus](#chorus): Chorus with multi-tap option (Ensemble)
21 | - [Console](#console): Stereo summing mixer
22 | - [Console MM](#consolemm): Stereo summing mixer to work in conjunction with MindMeld's MixMaster
23 | - [Distance](#distance): Designed to mimic through-air high frequency attenuation
24 | - [Dual BSG](#dual-bsg): Dual gain shifter
25 | - [Golem](#golem): Micro-delayable crossfader
26 | - [Holt](#holt): Resonant lowpass filter focussed on low frequencies
27 | - [Hombre](#hombre): Texas tone and texture
28 | - [Interstage](#interstage): Subtle analogifier
29 | - [Monitoring](#monitoring): Mix checker
30 | - [MV](#mv): Dual-mono reverb
31 | - [Rasp](#rasp): De-Edger, slew and acceleration limiter
32 | - [ResEQ](#reseq): Resonance equalizer
33 | - [Tape](#tape): All-purpose tape mojo
34 | - [Tremolo](#tremolo): Organic pulsating thing
35 | - [Vibrato](#vibrato): FM Vibrator o_O
36 |
37 | ## Capacitor
38 |
39 | Filters
40 |
41 | 
42 |
43 | High/Lowpass filters that come in both mono and stereo, with the stereo version featuring an additional dry/wet control.
44 |
45 | [More information](http://www.airwindows.com/capacitor)
46 |
47 | ## Chorus
48 |
49 | Chorus with multi-tap option (Ensemble)
50 |
51 | 
52 |
53 | This surprisingly versatile module is a combination of Airwindows Chorus and ChorusEnsemble. The switch lets you toggle between the two flavours.
54 |
55 | [More information](http://www.airwindows.com/chorus-vst)
56 |
57 | ## Console
58 |
59 | Stereo summing mixer
60 |
61 | 
62 |
63 | Airwindows Console systems sum signals in a way where the resulting soundstage appears less flat, more spacious and more in line with what we are used to from analog mixers. There are a number of variations/flavours of the console concept and this module currently implements the most recent one, Console6, and PurestConsole, which can be selected in the context menu.
64 |
65 | Please check the link below for further information on how exactly this effect is achieved.
66 |
67 | **NOTE**: Due to how the encoding/decoding works, spaciousness and definition will increase with each additional channel. There will be **no effect on a single channel** at all.
68 |
69 | [More information](http://www.airwindows.com/console2)
70 |
71 | ## Console MM
72 |
73 | This is a special version of Console designed to work alongside and interconnected with MindMeld's MixMaster module. It takes MM's polyphonic direct outs and sums the individual channels according to the selected console type. The stereo outputs can then be routed back into the chain inputs of MM to complete the roundtrip (make sure to check "Solo Chain Input" in MM, rightclick on Master). This setup conveniently provides "analogish" summing while making use of all of MM's fantastic mixing capabilities and goodies.
74 |
75 | For added flexibility and in order to honour Chris' spirit of allowing for settings beyond obvious sweetspots, the input gain can be adjusted, or - as a friend put it - taken from "can`t hear shit happening" to overdriven.
76 |
77 | The 3 direct outputs pass on the respective input signal either unprocessed (polyphonic) or summed (mono).
78 |
79 | ## Distance
80 |
81 | Designed to mimic through-air high frequency attenuation
82 |
83 | 
84 |
85 | Pushes things back. Also quite nice on reverb returns.
86 |
87 | [More information](http://www.airwindows.com/distance-vst)
88 |
89 | ## Dual BSG
90 |
91 | Dual gain shifter
92 |
93 | 
94 |
95 | Scales a signal up or down by increments of exactly 6 dB. If no input is connected, the respective output will provide constant voltage selectable in 1V steps from -8V to +8V. The lower section can be linked to the upper one to automatically compensate for values set by the upper 'Shift' knob. If linked the lower 'Shift' knob can be used to offset the signal in 6db steps (input connected) or 1V steps (input not connected).
96 |
97 | [More information](http://www.airwindows.com/bitshiftgain)
98 |
99 | ## Golem
100 |
101 | Micro-delayable crossfader
102 |
103 | 
104 |
105 | [More information](https://www.airwindows.com/golem-vst/)
106 |
107 | ## Holt
108 |
109 | Resonant lowpass filter focussed on low frequencies
110 |
111 | 
112 |
113 | Interestingly, Holt's algorithm is based on an Excel method for predicting sales figures based on trends. The result is a lowpass filter that's polite in the highs and increasingly mean towards the low-end. It also allows for seamless morphing between no poles (dry) and 4-poles (24db per octave). An additional output saturation stage helps keeping the possibly massive resonances in check (it can still get nasty though, you have been warned).
114 |
115 | [More information](http://www.airwindows.com/holt)
116 |
117 | ## Hombre
118 |
119 | Texas tone and texture
120 |
121 | 
122 |
123 | [More information](http://www.airwindows.com/hombre-vst)
124 |
125 | ## Interstage
126 |
127 | Subtle analogifier
128 |
129 | 
130 |
131 | Well, I won't attempt to describe or even explain what this one does. Please head straight over to the Airwindows site for details. But I will say this: If I had to choose just one Airwindows tool, this would be it (or maybe Console?). It's simple, it's subtle and to my ears pure class. Put it anywhere in your patch, you can do no wrong. It will most likely be for the better :-)
132 |
133 | [More information](https://www.airwindows.com/interstage/)
134 |
135 | ## Monitoring
136 |
137 | Mix checker
138 |
139 | 
140 |
141 | Monitoring is meant to be the last module before the output. It offers a number of tools to help investigate your patch and check for problems. It is calibrated in such a way that modes like *Subs* or *Peaks* will tell you unmistakably when you've got too much (or not enough) energy in a particular area.
142 | Additionally, it features crossfeed for headphones and optional 24/16 bit dither.
143 |
144 | ### Processing modes:
145 |
146 | - **Subs**: This is [SubsOnly](https://www.airwindows.com/slewonly-subsonly/). Useful for checking sub-bass elements.
147 | - **Slew**: This is [SlewOnly](https://www.airwindows.com/slewonly-subsonly/). Similar to *Subs*, but for high frequency content.
148 | - **Peaks**: This is [PeaksOnly](https://www.airwindows.com/peaksonly/). Exposes bursts of excessive energy by transforming them into recognizable signals.
149 | - **Mono**: Mid or sum of the stereo channels.
150 | - **Side**: Just the side information or difference of the stereo channels.
151 | - **Vinyl**: Rolls off the extreme ends of the frequency spectrum.
152 | - **Aurat**: Same as Vinyl, but slightly narrower band.
153 | - **Phone**: Same as above, but significantly less bass and mono.
154 |
155 | ### Can modes:
156 |
157 | These are somewhat sophisticated crossfeed modes for headphones. They go from relatively subtle (A) to quite obvious (D).
158 |
159 | ### Dither:
160 |
161 | You can choose between 24 and 16 bit dithering. The algorithm is [Dark](https://www.airwindows.com/dark/).
162 |
163 | [More information](https://www.airwindows.com/monitoring/)
164 |
165 | ## MV
166 |
167 | Dual-mono reverb
168 |
169 | 
170 |
171 | A reverb based on Bitshiftgain and old Alesis Midiverbs. Capable of turning everything into a pad or sustaining a 'bloom' forever. Watch your volume when using small amounts of depth with full on regeneration.
172 |
173 | [More information](http://www.airwindows.com/mv)
174 |
175 | ## Rasp
176 |
177 | De-edger, high frequency tamer, acceleration limiter
178 |
179 | 
180 |
181 | Rasp combines Slew/Slew2/Slew3 and Acceleration in a single module for versatile high frequency conditioning. The Slew algorithms are significantly different in character and can be selected in the menu.
182 |
183 | **Routing:** If only the *Limit* output is connected, Slew is routed into Acceleration. If only the *Clamp* output is connected, Acceleration runs into Slew. If both outputs are connected, *Clamp* outputs Slew and *Limit* outputs Acceleration.
184 |
185 | [More information on Acceleration](https://www.airwindows.com/acceleration/)
186 | [Slew](https://www.airwindows.com/slew-2/), [Slew2](https://www.airwindows.com/slew2-2/), [Slew3](https://www.airwindows.com/slew3/)
187 |
188 | ## ResEQ
189 |
190 | Resonance equalizer
191 |
192 | 
193 |
194 | ResEQ passes audio through up to four adjustable frequency bands - and only those bands. All bands are similar and with identical range, but interact with each other in a particular way. They can be stacked for massive boosts or thin the signal out if set slightly apart. What sounds artificial in isolation, can be useful to highlight certain characteristics of a sound and then blend to taste with the dry/wet for more natural results.
195 |
196 | [More information](https://www.airwindows.com/reseq-vst/)
197 |
198 | ## Tape
199 |
200 | All-purpose tape mojo
201 |
202 | 
203 |
204 | Tape is Airwindows' attempt to capture the very essence of what running audio through tape machines does to the signal. Driving it not only alters the volume, but also applies certain characteristics which are often associated with tape. The bump control allows to adjust the low end.
205 |
206 | [More information](http://www.airwindows.com/tape)
207 |
208 | ## Tremolo
209 |
210 | Organic pulsating thing
211 |
212 | 
213 |
214 | Instead of volume animation Tremolo uses fluctuating saturation and antisaturation curves to create a more organic, somewhat tubey tremolo effect.
215 |
216 | [More information](http://www.airwindows.com/tremolo-vst)
217 |
218 | ## Vibrato
219 |
220 | FM Vibrator o_O
221 |
222 | 
223 |
224 | Make sure to also play with the 'Inverse/Wet' knob for chorusing and flange effects. Two trigger outputs have been added for fun.
225 |
226 | [More information](http://www.airwindows.com/vibrato-vst)
227 |
228 | ## A word on processing quality
229 |
230 | Most modules feature an **Eco** mode in order to reduce CPU usage on weaker systems. The actual algorithms remain untouched, but any noise shaping/dithering is skipped. This can result in speed improvements of roughly 10% to 50% depending on the module.
231 |
232 | ## Building from Source
233 |
234 | To compile the modules from source, see the official [VCV Rack documentation](https://vcvrack.com/manual/Building.html).
235 |
236 | ## Colophon
237 |
238 | The typeface used on the panels is [Barlow](https://github.com/jpt/barlow) by Jeremy Tribby.
--------------------------------------------------------------------------------
/src/tremolo.cpp:
--------------------------------------------------------------------------------
1 |
2 | /***********************************************************************************************
3 | Tremolo
4 | -------
5 | VCV Rack module based on Tremolo by Chris Johnson from Airwindows
6 |
7 | Ported and designed by Jens Robert Janke
8 |
9 | Changes/Additions:
10 | - CV inputs for speed and depth
11 | - polyphonic
12 |
13 | See ./LICENSE.md for all licenses
14 | ************************************************************************************************/
15 |
16 | #include "plugin.hpp"
17 |
18 | // quality options
19 | #define ECO 0
20 | #define HIGH 1
21 |
22 | struct Tremolo : Module {
23 | enum ParamIds {
24 | SPEED_PARAM,
25 | DEPTH_PARAM,
26 | NUM_PARAMS
27 | };
28 | enum InputIds {
29 | CLOCK_CV_INPUT,
30 | SPEED_CV_INPUT,
31 | DEPTH_CV_INPUT,
32 | IN_INPUT,
33 | NUM_INPUTS
34 | };
35 | enum OutputIds {
36 | OUT_OUTPUT,
37 | NUM_OUTPUTS
38 | };
39 | enum LightIds {
40 | SPEED_LIGHT,
41 | NUM_LIGHTS
42 | };
43 |
44 | // module variables
45 | const double gainCut = 0.03125;
46 | const double gainBoost = 32.0;
47 | int quality;
48 |
49 | // control parameters
50 | float speedParam;
51 | float depthParam;
52 |
53 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
54 | double sweep[16];
55 | double speedChase[16];
56 | double depthChase[16];
57 | double speedAmount[16];
58 | double depthAmount[16];
59 | double lastSpeed[16];
60 | double lastDepth[16];
61 | long double fpNShape[16];
62 |
63 | // other variables, which do not need to be updated every cycle
64 | double overallscale;
65 | double speedSpeed;
66 | double depthSpeed;
67 | float lastSpeedParam;
68 | float lastDepthParam;
69 |
70 | // constants
71 | const double tupi = 3.141592653589793238;
72 |
73 | Tremolo()
74 | {
75 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
76 | configParam(SPEED_PARAM, 0.f, 1.f, 0.f, "Speed");
77 | configParam(DEPTH_PARAM, 0.f, 1.f, 0.f, "Depth");
78 |
79 | quality = loadQuality();
80 | onReset();
81 | }
82 |
83 | void onReset() override
84 | {
85 | onSampleRateChange();
86 |
87 | speedSpeed = 0.0;
88 | depthSpeed = 0.0;
89 | lastSpeedParam = 0.0;
90 | lastDepthParam = 0.0;
91 |
92 | for (int i = 0; i < 16; i++) {
93 | sweep[i] = 3.141592653589793238 / 2.0;
94 | speedChase[i] = 0.0;
95 | depthChase[i] = 0.0;
96 | speedAmount[i] = 1.0;
97 | depthAmount[i] = 0.0;
98 | lastSpeed[i] = 1000.0;
99 | lastDepth[i] = 1000.0;
100 | fpNShape[i] = 0.0;
101 | }
102 | }
103 |
104 | void onSampleRateChange() override
105 | {
106 | float sampleRate = APP->engine->getSampleRate();
107 |
108 | overallscale = 1.0;
109 | overallscale /= 44100.0;
110 | overallscale *= sampleRate;
111 | }
112 |
113 | json_t* dataToJson() override
114 | {
115 | json_t* rootJ = json_object();
116 |
117 | // quality
118 | json_object_set_new(rootJ, "quality", json_integer(quality));
119 |
120 | return rootJ;
121 | }
122 |
123 | void dataFromJson(json_t* rootJ) override
124 | {
125 | // quality
126 | json_t* qualityJ = json_object_get(rootJ, "quality");
127 | if (qualityJ)
128 | quality = json_integer_value(qualityJ);
129 | }
130 |
131 | void process(const ProcessArgs& args) override
132 | {
133 | if (outputs[OUT_OUTPUT].isConnected()) {
134 |
135 | speedParam = params[SPEED_PARAM].getValue();
136 | speedParam += inputs[SPEED_CV_INPUT].getVoltage() / 5;
137 | speedParam = clamp(speedParam, 0.01f, 0.99f);
138 |
139 | depthParam = params[DEPTH_PARAM].getValue();
140 | depthParam += inputs[DEPTH_CV_INPUT].getVoltage() / 5;
141 | depthParam = clamp(depthParam, 0.01f, 0.99f);
142 |
143 | double speed;
144 | double depth;
145 | double skew;
146 | double density;
147 | double control;
148 | double tempcontrol;
149 | double thickness;
150 | double out;
151 | double bridgerectifier;
152 | double offset;
153 | long double inputSample;
154 | long double drySample;
155 |
156 | // number of polyphonic channels
157 | int numChannels = std::max(1, inputs[IN_INPUT].getChannels());
158 |
159 | // for each poly channel
160 | for (int i = 0; i < numChannels; i++) {
161 |
162 | if (speedParam != lastSpeedParam) {
163 | speedChase[i] = pow(speedParam, 4);
164 | speedSpeed = 300 / (fabs(lastSpeed[i] - speedChase[i]) + 1.0);
165 | lastSpeed[i] = speedChase[i];
166 | }
167 |
168 | if (depthParam != lastDepthParam) {
169 | depthChase[i] = depthParam;
170 | depthSpeed = 300 / (fabs(lastDepth[i] - depthChase[i]) + 1.0);
171 | lastDepth[i] = depthChase[i];
172 | }
173 |
174 | // input
175 | inputSample = inputs[IN_INPUT].getPolyVoltage(i);
176 |
177 | // pad gain
178 | inputSample *= gainCut;
179 |
180 | if (quality == HIGH) {
181 | if (inputSample < 1.2e-38 && -inputSample < 1.2e-38) {
182 | static int noisesource = 0;
183 | //this declares a variable before anything else is compiled. It won't keep assigning
184 | //it to 0 for every sample, it's as if the declaration doesn't exist in this context,
185 | //but it lets me add this denormalization fix in a single place rather than updating
186 | //it in three different locations. The variable isn't thread-safe but this is only
187 | //a random seed and we can share it with whatever.
188 | noisesource = noisesource % 1700021;
189 | noisesource++;
190 | int residue = noisesource * noisesource;
191 | residue = residue % 170003;
192 | residue *= residue;
193 | residue = residue % 17011;
194 | residue *= residue;
195 | residue = residue % 1709;
196 | residue *= residue;
197 | residue = residue % 173;
198 | residue *= residue;
199 | residue = residue % 17;
200 | double applyresidue = residue;
201 | applyresidue *= 0.00000001;
202 | applyresidue *= 0.00000001;
203 | inputSample = applyresidue;
204 | }
205 | }
206 |
207 | drySample = inputSample;
208 |
209 | speedAmount[i] = (((speedAmount[i] * speedSpeed) + speedChase[i]) / (speedSpeed + 1.0));
210 | depthAmount[i] = (((depthAmount[i] * depthSpeed) + depthChase[i]) / (depthSpeed + 1.0));
211 | speed = 0.0001 + (speedAmount[i] / 1000.0);
212 | speed /= overallscale;
213 | depth = 1.0 - pow(1.0 - depthAmount[i], 5);
214 | skew = 1.0 + pow(depthAmount[i], 9);
215 | density = ((1.0 - depthAmount[i]) * 2.0) - 1.0;
216 |
217 | offset = sin(sweep[i]);
218 | sweep[i] += speed;
219 | if (sweep[i] > tupi) {
220 | sweep[i] -= tupi;
221 | }
222 | control = fabs(offset);
223 | if (density > 0) {
224 | tempcontrol = sin(control);
225 | control = (control * (1.0 - density)) + (tempcontrol * density);
226 | } else {
227 | tempcontrol = 1 - cos(control);
228 | control = (control * (1.0 + density)) + (tempcontrol * -density);
229 | }
230 | //produce either boosted or starved version of control signal
231 | //will go from 0 to 1
232 |
233 | thickness = ((control * 2.0) - 1.0) * skew;
234 | out = fabs(thickness);
235 |
236 | //max value for sine function
237 | bridgerectifier = fabs(inputSample);
238 | if (bridgerectifier > 1.57079633)
239 | bridgerectifier = 1.57079633;
240 |
241 | //produce either boosted or starved version
242 | if (thickness > 0)
243 | bridgerectifier = sin(bridgerectifier);
244 | else
245 | bridgerectifier = 1 - cos(bridgerectifier);
246 |
247 | if (inputSample > 0)
248 | inputSample = (inputSample * (1 - out)) + (bridgerectifier * out);
249 | else
250 | inputSample = (inputSample * (1 - out)) - (bridgerectifier * out);
251 |
252 | //blend according to density control
253 | inputSample *= (1.0 - control);
254 | inputSample *= 2.0;
255 | //apply tremolo, apply gain boost to compensate for volume loss
256 | inputSample = (drySample * (1 - depth)) + (inputSample * depth);
257 |
258 | if (quality == HIGH) {
259 | //stereo 32 bit dither, made small and tidy.
260 | int expon;
261 | frexpf((float)inputSample, &expon);
262 | long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
263 | inputSample += (dither - fpNShape[i]);
264 | fpNShape[i] = dither;
265 | }
266 |
267 | // bring gain back up
268 | inputSample *= gainBoost;
269 |
270 | // output
271 | outputs[OUT_OUTPUT].setChannels(numChannels);
272 | outputs[OUT_OUTPUT].setVoltage(inputSample, i);
273 | }
274 |
275 | // lights
276 | lights[SPEED_LIGHT].setSmoothBrightness(fmaxf(0.0, (-sweep[0]) + 1), args.sampleTime);
277 | }
278 | }
279 | };
280 |
281 | struct TremoloWidget : ModuleWidget {
282 |
283 | // quality item
284 | struct QualityItem : MenuItem {
285 | Tremolo* module;
286 | int quality;
287 |
288 | void onAction(const event::Action& e) override
289 | {
290 | module->quality = quality;
291 | }
292 |
293 | void step() override
294 | {
295 | rightText = (module->quality == quality) ? "✔" : "";
296 | }
297 | };
298 |
299 | void appendContextMenu(Menu* menu) override
300 | {
301 | Tremolo* module = dynamic_cast(this->module);
302 | assert(module);
303 |
304 | menu->addChild(new MenuSeparator()); // separator
305 |
306 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
307 | qualityLabel->text = "Quality";
308 | menu->addChild(qualityLabel);
309 |
310 | QualityItem* low = new QualityItem(); // low quality
311 | low->text = "Eco";
312 | low->module = module;
313 | low->quality = 0;
314 | menu->addChild(low);
315 |
316 | QualityItem* high = new QualityItem(); // high quality
317 | high->text = "High";
318 | high->module = module;
319 | high->quality = 1;
320 | menu->addChild(high);
321 | }
322 |
323 | TremoloWidget(Tremolo* module)
324 | {
325 | setModule(module);
326 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/tremolo_dark.svg")));
327 |
328 | // screws
329 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, 0)));
330 | addChild(createWidget(Vec(RACK_GRID_WIDTH * 1.5, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
331 |
332 | // knobs
333 | addParam(createParamCentered(Vec(30.0, 65.0), module, Tremolo::SPEED_PARAM));
334 | addParam(createParamCentered(Vec(30.0, 120.0), module, Tremolo::DEPTH_PARAM));
335 |
336 | // lights
337 | addChild(createLightCentered>(Vec(11, 103), module, Tremolo::SPEED_LIGHT));
338 |
339 | // inputs
340 | // addInput(createInputCentered(Vec(30.0, 165.0), module, Tremolo::CLOCK_CV_INPUT));
341 | addInput(createInputCentered(Vec(30.0, 205.0), module, Tremolo::SPEED_CV_INPUT));
342 | addInput(createInputCentered(Vec(30.0, 245.0), module, Tremolo::DEPTH_CV_INPUT));
343 | addInput(createInputCentered(Vec(30.0, 285.0), module, Tremolo::IN_INPUT));
344 |
345 | // outputs
346 | addOutput(createOutputCentered(Vec(30.0, 325.0), module, Tremolo::OUT_OUTPUT));
347 | }
348 | };
349 |
350 | Model* modelTremolo = createModel("tremolo");
--------------------------------------------------------------------------------
/src/holt.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Holt
3 | ------
4 | VCV Rack module based on Holt by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - mono
10 | - CV inputs for frequency, resonance and poles
11 | - no output control
12 | - no dry/wet
13 | - polyphonic
14 | - added output saturation
15 |
16 | See ./LICENSE.md for all licenses
17 | ************************************************************************************************/
18 |
19 | /*
20 | Note: for the sake of porting variety, this one encapsulates the entire audio plugin as its own entity
21 | Advantages: cleaner module logic, way easier and less messy handling of polyphony
22 | Drawbacks: possibly sliiightly less speedy
23 | */
24 |
25 | #include "plugin.hpp"
26 |
27 | // quality options
28 | #define ECO 0
29 | #define HIGH 1
30 |
31 | /* Engine (the audio plugin code, single channel)
32 | ======================================================================================== */
33 | struct HoltEngine {
34 |
35 | long double previousSampleA;
36 | long double previousTrendA;
37 | long double previousSampleB;
38 | long double previousTrendB;
39 | long double previousSampleC;
40 | long double previousTrendC;
41 | long double previousSampleD;
42 | long double previousTrendD;
43 |
44 | double alpha;
45 | double beta;
46 | float lastFrequencyParam;
47 | float lastResonanceParam;
48 |
49 | HoltEngine()
50 | {
51 | previousSampleA = 0.0;
52 | previousTrendA = 0.0;
53 | previousSampleB = 0.0;
54 | previousTrendB = 0.0;
55 | previousSampleC = 0.0;
56 | previousTrendC = 0.0;
57 | previousSampleD = 0.0;
58 | previousTrendD = 0.0;
59 |
60 | alpha = 0.0;
61 | beta = 0.0;
62 | lastFrequencyParam = 0.0f;
63 | lastResonanceParam = 0.0f;
64 | }
65 |
66 | long double process(long double inputSample, float frequencyParam = 1.0, float resonanceParam = 0.0, float polesParam = 1.0, float outputParam = 1.0, float drywetParam = 1.0)
67 | {
68 | if ((frequencyParam != lastFrequencyParam) || (resonanceParam != lastResonanceParam)) {
69 | alpha = pow(frequencyParam, 4) + 0.00001;
70 | if (alpha > 1.0) {
71 | alpha = 1.0;
72 | }
73 |
74 | beta = (alpha * pow(resonanceParam, 2)) + 0.00001;
75 | alpha += ((1.0 - beta) * pow(frequencyParam, 3)); //correct for droop in frequency
76 | if (alpha > 1.0) {
77 | alpha = 1.0;
78 | }
79 |
80 | lastFrequencyParam = frequencyParam;
81 | lastResonanceParam = resonanceParam;
82 | }
83 |
84 | long double trend;
85 | long double forecast; //defining these here because we're copying the routine four times
86 |
87 | //four-stage wet/dry control using progressive stages that bypass when not engaged
88 | double aWet = 1.0;
89 | double bWet = 1.0;
90 | double cWet = 1.0;
91 | double dWet = polesParam * 4.0;
92 |
93 | if (dWet < 1.0) {
94 | aWet = dWet;
95 | bWet = 0.0;
96 | cWet = 0.0;
97 | dWet = 0.0;
98 | } else if (dWet < 2.0) {
99 | bWet = dWet - 1.0;
100 | cWet = 0.0;
101 | dWet = 0.0;
102 | } else if (dWet < 3.0) {
103 | cWet = dWet - 2.0;
104 | dWet = 0.0;
105 | } else {
106 | dWet -= 3.0;
107 | }
108 | //this is one way to make a little set of dry/wet stages that are successively added to the
109 | //output as the control is turned up. Each one independently goes from 0-1 and stays at 1
110 | //beyond that point: this is a way to progressively add a 'black box' sound processing
111 | //which lets you fall through to simpler processing at lower settings.
112 |
113 | double gain = outputParam;
114 | double wet = drywetParam;
115 |
116 | long double drySample = inputSample;
117 |
118 | if (aWet > 0.0) {
119 | trend = (beta * (inputSample - previousSampleA) + ((0.999 - beta) * previousTrendA));
120 | forecast = previousSampleA + previousTrendA;
121 | inputSample = (alpha * inputSample) + ((0.999 - alpha) * forecast);
122 | previousSampleA = inputSample;
123 | previousTrendA = trend;
124 | inputSample = (inputSample * aWet) + (drySample * (1.0 - aWet));
125 | }
126 |
127 | if (bWet > 0.0) {
128 | trend = (beta * (inputSample - previousSampleB) + ((0.999 - beta) * previousTrendB));
129 | forecast = previousSampleB + previousTrendB;
130 | inputSample = (alpha * inputSample) + ((0.999 - alpha) * forecast);
131 | previousSampleB = inputSample;
132 | previousTrendB = trend;
133 | inputSample = (inputSample * bWet) + (previousSampleA * (1.0 - bWet));
134 | }
135 |
136 | if (cWet > 0.0) {
137 | trend = (beta * (inputSample - previousSampleC) + ((0.999 - beta) * previousTrendC));
138 | forecast = previousSampleC + previousTrendC;
139 | inputSample = (alpha * inputSample) + ((0.999 - alpha) * forecast);
140 | previousSampleC = inputSample;
141 | previousTrendC = trend;
142 | inputSample = (inputSample * cWet) + (previousSampleB * (1.0 - cWet));
143 | }
144 |
145 | if (dWet > 0.0) {
146 | trend = (beta * (inputSample - previousSampleD) + ((0.999 - beta) * previousTrendD));
147 | forecast = previousSampleD + previousTrendD;
148 | inputSample = (alpha * inputSample) + ((0.999 - alpha) * forecast);
149 | previousSampleD = inputSample;
150 | previousTrendD = trend;
151 | inputSample = (inputSample * dWet) + (previousSampleC * (1.0 - dWet));
152 | }
153 |
154 | if (gain < 1.0) {
155 | inputSample *= gain;
156 | }
157 |
158 | //clip to 1.2533141373155 to reach maximum output
159 | if (inputSample > 1.2533141373155)
160 | inputSample = 1.2533141373155;
161 | if (inputSample < -1.2533141373155)
162 | inputSample = -1.2533141373155;
163 | inputSample = sin(inputSample * fabs(inputSample)) / ((inputSample == 0.0) ? 1 : fabs(inputSample));
164 |
165 | if (wet < 1.0) {
166 | inputSample = (inputSample * wet) + (drySample * (1.0 - wet));
167 | }
168 |
169 | return inputSample;
170 | }
171 | };
172 |
173 | /* Dither Noise
174 | ======================================================================================== */
175 | inline long double ditherNoise(long double in)
176 | {
177 | //for live air, we always apply the dither noise. Then, if our result is
178 | //effectively digital black, we'll subtract it again. We want a 'air' hiss
179 |
180 | static int noisesource = 0;
181 | int residue;
182 | double applyresidue;
183 |
184 | noisesource = noisesource % 1700021;
185 | noisesource++;
186 | residue = noisesource * noisesource;
187 | residue = residue % 170003;
188 | residue *= residue;
189 | residue = residue % 17011;
190 | residue *= residue;
191 | residue = residue % 1709;
192 | residue *= residue;
193 | residue = residue % 173;
194 | residue *= residue;
195 | residue = residue % 17;
196 | applyresidue = residue;
197 | applyresidue *= 0.00000001;
198 | applyresidue *= 0.00000001;
199 | in += applyresidue;
200 | if (in < 1.2e-38 && -in < 1.2e-38) {
201 | in -= applyresidue;
202 | }
203 |
204 | return in;
205 | }
206 |
207 | /* Mojo (for output saturation)
208 | ======================================================================================== */
209 | inline long double mojo(long double in)
210 | {
211 | long double mojo = pow(fabs(in), 0.25);
212 | if (mojo > 0.0) {
213 | in = (sin(in * mojo * M_PI * 0.5) / mojo) * 0.987654321;
214 | in *= 0.65; // dial back a bit to keep levels roughly the same
215 | }
216 | return in;
217 | }
218 |
219 | /* Module
220 | ======================================================================================== */
221 | struct Holt : Module {
222 | enum ParamIds {
223 | FREQUENCY_PARAM,
224 | RESONANCE_PARAM,
225 | POLES_PARAM,
226 | NUM_PARAMS
227 | };
228 | enum InputIds {
229 | FREQUENCY_CV_INPUT,
230 | RESONANCE_CV_INPUT,
231 | POLES_CV_INPUT,
232 | IN_INPUT,
233 | NUM_INPUTS
234 | };
235 | enum OutputIds {
236 | OUT_OUTPUT,
237 | NUM_OUTPUTS
238 | };
239 | enum LightIds {
240 | NUM_LIGHTS
241 | };
242 |
243 | // module variables
244 | const double gainCut = 0.03125;
245 | const double gainBoost = 32.0;
246 | int quality;
247 | HoltEngine holt[16];
248 |
249 | // control parameter
250 | float frequencyParam;
251 | float resonanceParam;
252 | float polesParam;
253 |
254 | // other
255 | double overallscale;
256 | long double fpNShape;
257 |
258 | Holt()
259 | {
260 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
261 | configParam(FREQUENCY_PARAM, 0.f, 1.f, 1.f, "Frequency");
262 | configParam(RESONANCE_PARAM, 0.f, 1.f, 0.f, "Resonance");
263 | configParam(POLES_PARAM, 0.f, 1.f, 1.f, "Poles");
264 |
265 | quality = loadQuality();
266 | }
267 |
268 | void onSampleRateChange() override
269 | {
270 | float sampleRate = APP->engine->getSampleRate();
271 |
272 | overallscale = 1.0;
273 | overallscale /= 44100.0;
274 | overallscale *= sampleRate;
275 | }
276 |
277 | void onReset() override
278 | {
279 | for (int i = 0; i < 16; i++) {
280 | holt[i] = HoltEngine();
281 | }
282 |
283 | fpNShape = 0.0;
284 |
285 | onSampleRateChange();
286 | updateParams();
287 | }
288 |
289 | json_t* dataToJson() override
290 | {
291 | json_t* rootJ = json_object();
292 |
293 | // quality
294 | json_object_set_new(rootJ, "quality", json_integer(quality));
295 |
296 | return rootJ;
297 | }
298 |
299 | void dataFromJson(json_t* rootJ) override
300 | {
301 | // quality
302 | json_t* qualityJ = json_object_get(rootJ, "quality");
303 | if (qualityJ)
304 | quality = json_integer_value(qualityJ);
305 | }
306 |
307 | void updateParams()
308 | {
309 | frequencyParam = params[FREQUENCY_PARAM].getValue();
310 | frequencyParam += inputs[FREQUENCY_CV_INPUT].getVoltage() / 9;
311 | frequencyParam = clamp(frequencyParam, 0.01f, 0.99f);
312 |
313 | resonanceParam = params[RESONANCE_PARAM].getValue();
314 | resonanceParam += inputs[RESONANCE_CV_INPUT].getVoltage() / 9;
315 | resonanceParam = clamp(resonanceParam, 0.01f, 0.99f);
316 |
317 | polesParam = params[POLES_PARAM].getValue();
318 | polesParam += inputs[POLES_CV_INPUT].getVoltage() / 10;
319 | polesParam = clamp(polesParam, 0.01f, 0.99f);
320 | }
321 |
322 | void process(const ProcessArgs& args) override
323 | {
324 | updateParams();
325 |
326 | long double in;
327 |
328 | // for each poly channel
329 | for (int i = 0, numChannels = std::max(1, inputs[IN_INPUT].getChannels()); i < numChannels; ++i) {
330 |
331 | // input
332 | in = inputs[IN_INPUT].getPolyVoltage(i) * gainCut;
333 |
334 | if (quality == HIGH) {
335 | in = ditherNoise(in);
336 | }
337 |
338 | // holt
339 | in = holt[i].process(in, frequencyParam, resonanceParam, polesParam);
340 |
341 | // mojo for swallowing excessive resonance
342 | in = mojo(in);
343 |
344 | if (quality == HIGH) {
345 | //stereo 32 bit dither, made small and tidy.
346 | int expon;
347 | frexpf((float)in, &expon);
348 | long double dither = (rand() / (RAND_MAX * 7.737125245533627e+25)) * pow(2, expon + 62);
349 | in += (dither - fpNShape);
350 | fpNShape = dither;
351 | }
352 |
353 | // output
354 | outputs[OUT_OUTPUT].setChannels(numChannels);
355 | outputs[OUT_OUTPUT].setVoltage(in * gainBoost, i);
356 | }
357 | }
358 | };
359 |
360 | /* Widget
361 | ======================================================================================== */
362 | struct HoltWidget : ModuleWidget {
363 |
364 | // quality item
365 | struct QualityItem : MenuItem {
366 | Holt* module;
367 | int quality;
368 |
369 | void onAction(const event::Action& e) override
370 | {
371 | module->quality = quality;
372 | }
373 |
374 | void step() override
375 | {
376 | rightText = (module->quality == quality) ? "✔" : "";
377 | }
378 | };
379 |
380 | void appendContextMenu(Menu* menu) override
381 | {
382 | Holt* module = dynamic_cast(this->module);
383 | assert(module);
384 |
385 | menu->addChild(new MenuSeparator()); // separator
386 |
387 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
388 | qualityLabel->text = "Quality";
389 | menu->addChild(qualityLabel);
390 |
391 | QualityItem* low = new QualityItem(); // low quality
392 | low->text = "Eco";
393 | low->module = module;
394 | low->quality = 0;
395 | menu->addChild(low);
396 |
397 | QualityItem* high = new QualityItem(); // high quality
398 | high->text = "High";
399 | high->module = module;
400 | high->quality = 1;
401 | menu->addChild(high);
402 | }
403 |
404 | HoltWidget(Holt* module)
405 | {
406 | setModule(module);
407 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/holt_dark.svg")));
408 |
409 | // screws
410 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
411 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
412 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
413 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
414 |
415 | // params
416 | addParam(createParamCentered(Vec(45.0, 65.0), module, Holt::FREQUENCY_PARAM));
417 | addParam(createParamCentered(Vec(45.0, 125.0), module, Holt::RESONANCE_PARAM));
418 | addParam(createParamCentered(Vec(45.0, 185.0), module, Holt::POLES_PARAM));
419 |
420 | // inputs
421 | addInput(createInputCentered(Vec(26.25, 245.0), module, Holt::FREQUENCY_CV_INPUT));
422 | addInput(createInputCentered(Vec(63.75, 245.0), module, Holt::RESONANCE_CV_INPUT));
423 | addInput(createInputCentered(Vec(45.0, 285.0), module, Holt::POLES_CV_INPUT));
424 | addInput(createInputCentered(Vec(26.25, 325.0), module, Holt::IN_INPUT));
425 |
426 | // output
427 | addOutput(createOutputCentered(Vec(63.75, 325.0), module, Holt::OUT_OUTPUT));
428 | }
429 | };
430 |
431 | Model* modelHolt = createModel("holt");
--------------------------------------------------------------------------------
/src/vibrato.cpp:
--------------------------------------------------------------------------------
1 | /***********************************************************************************************
2 | Vibrato
3 | -------
4 | VCV Rack module based on Vibrato by Chris Johnson from Airwindows
5 |
6 | Ported and designed by Jens Robert Janke
7 |
8 | Changes/Additions:
9 | - CV inputs for speed, depth, fmspeed, fmdepth and inv/wet
10 | - trigger outputs (EOC) for speed and fmspeed
11 | - polyphonic
12 |
13 | See ./LICENSE.md for all licenses
14 | ************************************************************************************************/
15 |
16 | #include "plugin.hpp"
17 |
18 | // quality options
19 | #define ECO 0
20 | #define HIGH 1
21 |
22 | struct Vibrato : Module {
23 |
24 | enum ParamIds {
25 | SPEED_PARAM,
26 | FMSPEED_PARAM,
27 | DEPTH_PARAM,
28 | FMDEPTH_PARAM,
29 | INVWET_PARAM,
30 | NUM_PARAMS
31 | };
32 | enum InputIds {
33 | SPEED_CV_INPUT,
34 | DEPTH_CV_INPUT,
35 | FMSPEED_CV_INPUT,
36 | FMDEPTH_CV_INPUT,
37 | INVWET_CV_INPUT,
38 | IN_INPUT,
39 | NUM_INPUTS
40 | };
41 | enum OutputIds {
42 | EOC_OUTPUT,
43 | OUT_OUTPUT,
44 | EOC_FM_OUTPUT,
45 | NUM_OUTPUTS
46 | };
47 | enum LightIds {
48 | SPEED_LIGHT,
49 | SPEED_FM_LIGHT,
50 | NUM_LIGHTS
51 | };
52 |
53 | // module variables
54 | const double gainCut = 0.03125;
55 | const double gainBoost = 32.0;
56 | int quality;
57 | dsp::PulseGenerator eocPulse, eocFmPulse;
58 |
59 | // control parameters
60 | float speedParam;
61 | float depthParam;
62 | float fmSpeedParam;
63 | float fmDepthParam;
64 | float invwetParam;
65 |
66 | // state variables (as arrays in order to handle up to 16 polyphonic channels)
67 | double p[16][16386]; //this is processed, not raw incoming samples
68 | double sweep[16];
69 | double sweepB[16];
70 | int gcount[16];
71 | double airPrev[16];
72 | double airEven[16];
73 | double airOdd[16];
74 | double airFactor[16];
75 | bool flip[16];
76 | uint32_t fpd[16];
77 |
78 | // other variables, which do not need to be updated every cycle
79 | double overallscale;
80 | double speed;
81 | double depth;
82 | double speedB;
83 | double depthB;
84 | double wet;
85 | float lastSpeedParam;
86 | float lastDepthParam;
87 | float lastFmSpeedParam;
88 | float lastFmDepthParam;
89 | float lastInvwetParam;
90 |
91 | // constants
92 | const double tupi = 3.141592653589793238 * 2.0;
93 |
94 | Vibrato()
95 | {
96 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
97 | configParam(SPEED_PARAM, 0.f, 1.f, 0.f, "Speed");
98 | configParam(FMSPEED_PARAM, 0.f, 1.f, 0.f, "FM Speed");
99 | configParam(DEPTH_PARAM, 0.f, 1.f, 0.f, "Depth");
100 | configParam(FMDEPTH_PARAM, 0.f, 1.f, 0.f, "FM Depth");
101 | configParam(INVWET_PARAM, 0.f, 1.f, 0.5f, "Inv/Wet");
102 |
103 | quality = loadQuality();
104 | onReset();
105 | }
106 |
107 | void onReset() override
108 | {
109 | onSampleRateChange();
110 |
111 | lastSpeedParam = 0.0;
112 | lastDepthParam = 0.0;
113 | lastFmSpeedParam = 0.0;
114 | lastFmDepthParam = 0.0;
115 | lastInvwetParam = 0.0;
116 |
117 | for (int i = 0; i < 16; i++) {
118 | for (int count = 0; count < 16385; count++) {
119 | p[i][count] = 0.0;
120 | }
121 | sweep[i] = sweepB[i] = 3.141592653589793238 / 2.0;
122 | gcount[i] = 0;
123 |
124 | airPrev[i] = 0.0;
125 | airEven[i] = 0.0;
126 | airOdd[i] = 0.0;
127 | airFactor[i] = 0.0;
128 |
129 | flip[i] = false;
130 |
131 | fpd[i] = 17;
132 | }
133 | }
134 |
135 | void onSampleRateChange() override
136 | {
137 | float sampleRate = APP->engine->getSampleRate();
138 |
139 | overallscale = 1.0;
140 | overallscale /= 44100.0;
141 | overallscale *= sampleRate;
142 | }
143 |
144 | json_t* dataToJson() override
145 | {
146 | json_t* rootJ = json_object();
147 |
148 | // quality
149 | json_object_set_new(rootJ, "quality", json_integer(quality));
150 |
151 | return rootJ;
152 | }
153 |
154 | void dataFromJson(json_t* rootJ) override
155 | {
156 | // quality
157 | json_t* qualityJ = json_object_get(rootJ, "quality");
158 | if (qualityJ)
159 | quality = json_integer_value(qualityJ);
160 | }
161 |
162 | void process(const ProcessArgs& args) override
163 | {
164 | if (outputs[OUT_OUTPUT].isConnected() || outputs[EOC_OUTPUT].isConnected() || outputs[EOC_FM_OUTPUT].isConnected()) {
165 |
166 | speedParam = params[SPEED_PARAM].getValue();
167 | speedParam += inputs[SPEED_CV_INPUT].getVoltage() / 5;
168 | speedParam = clamp(speedParam, 0.01f, 0.99f);
169 |
170 | depthParam = params[DEPTH_PARAM].getValue();
171 | depthParam += inputs[DEPTH_CV_INPUT].getVoltage() / 5;
172 | depthParam = clamp(depthParam, 0.01f, 0.99f);
173 |
174 | fmSpeedParam = params[FMSPEED_PARAM].getValue();
175 | fmSpeedParam += inputs[FMSPEED_CV_INPUT].getVoltage() / 5;
176 | fmSpeedParam = clamp(fmSpeedParam, 0.01f, 0.99f);
177 |
178 | fmDepthParam = params[FMDEPTH_PARAM].getValue();
179 | fmDepthParam += inputs[FMDEPTH_CV_INPUT].getVoltage() / 5;
180 | fmDepthParam = clamp(fmDepthParam, 0.01f, 0.99f);
181 |
182 | invwetParam = params[INVWET_PARAM].getValue();
183 | invwetParam += inputs[INVWET_CV_INPUT].getVoltage() / 5;
184 | invwetParam = clamp(invwetParam, 0.01f, 0.99f);
185 |
186 | if (speedParam != lastSpeedParam) {
187 | speed = pow(0.1 + speedParam, 6);
188 | }
189 |
190 | if (depthParam != lastDepthParam) {
191 | depth = (pow(depthParam, 3) / sqrt(speed)) * 4.0;
192 | }
193 |
194 | if (fmSpeedParam != lastFmSpeedParam) {
195 | speedB = pow(0.1 + fmSpeedParam, 6);
196 | }
197 |
198 | if (fmDepthParam != lastFmDepthParam) {
199 | depthB = pow(fmDepthParam, 3) / sqrt(speedB);
200 | }
201 |
202 | if (invwetParam != lastInvwetParam) {
203 | wet = (invwetParam * 2.0) - 1.0; //note: inv/dry/wet
204 | }
205 |
206 | // number of polyphonic channels
207 | int numChannels = std::max(1, inputs[IN_INPUT].getChannels());
208 |
209 | // for each poly channel
210 | for (int i = 0; i < numChannels; i++) {
211 |
212 | // input
213 | long double inputSample = inputs[IN_INPUT].getPolyVoltage(i);
214 |
215 | // pad gain
216 | inputSample *= gainCut;
217 |
218 | if (quality == HIGH) {
219 | if (fabs(inputSample) < 1.18e-37)
220 | inputSample = fpd[i] * 1.18e-37;
221 | }
222 |
223 | double drySample = inputSample;
224 |
225 | airFactor[i] = airPrev[i] - inputSample;
226 |
227 | if (flip[i]) {
228 | airEven[i] += airFactor[i];
229 | airOdd[i] -= airFactor[i];
230 | airFactor[i] = airEven[i];
231 | } else {
232 | airOdd[i] += airFactor[i];
233 | airEven[i] -= airFactor[i];
234 | airFactor[i] = airOdd[i];
235 | }
236 |
237 | //air, compensates for loss of highs in the interpolation
238 | airOdd[i] = (airOdd[i] - ((airOdd[i] - airEven[i]) / 256.0)) / 1.0001;
239 | airEven[i] = (airEven[i] - ((airEven[i] - airOdd[i]) / 256.0)) / 1.0001;
240 | airPrev[i] = inputSample;
241 | inputSample += airFactor[i];
242 |
243 | flip[i] = !flip[i];
244 |
245 | if (gcount[i] < 1 || gcount[i] > 8192) {
246 | gcount[i] = 8192;
247 | }
248 | int count = gcount[i];
249 | p[i][count + 8192] = p[i][count] = inputSample;
250 |
251 | double offset = depth + (depth * sin(sweep[i]));
252 | count += (int)floor(offset);
253 |
254 | inputSample = p[i][count] * (1.0 - (offset - floor(offset))); //less as value moves away from .0
255 | inputSample += p[i][count + 1]; //we can assume always using this in one way or another?
256 | inputSample += p[i][count + 2] * (offset - floor(offset)); //greater as value moves away from .0
257 | inputSample -= ((p[i][count] - p[i][count + 1]) - (p[i][count + 1] - p[i][count + 2])) / 50.0; //interpolation hacks 'r us
258 | inputSample *= 0.5; // gain trim
259 |
260 | //still scrolling through the samples, remember
261 | sweep[i] += (speed + (speedB * sin(sweepB[i]) * depthB));
262 | sweepB[i] += speedB;
263 | if (sweep[i] > tupi) {
264 | sweep[i] -= tupi;
265 | }
266 | if (sweep[i] < 0.0) {
267 | sweep[i] += tupi;
268 | } //through zero FM
269 | if (sweepB[i] > tupi) {
270 | sweepB[i] -= tupi;
271 | }
272 | gcount[i]--;
273 |
274 | //Inv/Dry/Wet control
275 | if (wet != 1.0) {
276 | inputSample = (inputSample * wet) + (drySample * (1.0 - fabs(wet)));
277 | }
278 |
279 | if (quality == HIGH) {
280 | //begin 32 bit stereo floating point dither
281 | int expon;
282 | frexpf((float)inputSample, &expon);
283 | fpd[i] ^= fpd[i] << 13;
284 | fpd[i] ^= fpd[i] >> 17;
285 | fpd[i] ^= fpd[i] << 5;
286 | inputSample += ((double(fpd[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
287 | //end 32 bit stereo floating point dither
288 | }
289 |
290 | // bring gain back up
291 | inputSample *= gainBoost;
292 |
293 | // audio output
294 | outputs[OUT_OUTPUT].setChannels(numChannels);
295 | outputs[OUT_OUTPUT].setVoltage(inputSample, i);
296 | }
297 |
298 | // triggers
299 | if (sweep[0] < 0.1) {
300 | eocPulse.trigger(1e-3);
301 | }
302 | if (sweepB[0] < 0.1) {
303 | eocFmPulse.trigger(1e-3);
304 | }
305 |
306 | // lights
307 | lights[SPEED_LIGHT].setSmoothBrightness(fmaxf(0.0, (-sweep[0] / 5) + 1), args.sampleTime);
308 | lights[SPEED_FM_LIGHT].setSmoothBrightness(fmaxf(0.0, (-sweepB[0] / 5) + 1), args.sampleTime);
309 |
310 | // trigger outputs
311 | outputs[EOC_OUTPUT].setVoltage((eocPulse.process(args.sampleTime) ? 10.0 : 0.0));
312 | outputs[EOC_FM_OUTPUT].setVoltage((eocFmPulse.process(args.sampleTime) ? 10.0 : 0.0));
313 | }
314 | }
315 | };
316 |
317 | struct VibratoWidget : ModuleWidget {
318 |
319 | // quality item
320 | struct QualityItem : MenuItem {
321 | Vibrato* module;
322 | int quality;
323 |
324 | void onAction(const event::Action& e) override
325 | {
326 | module->quality = quality;
327 | }
328 |
329 | void step() override
330 | {
331 | rightText = (module->quality == quality) ? "✔" : "";
332 | }
333 | };
334 |
335 | void appendContextMenu(Menu* menu) override
336 | {
337 | Vibrato* module = dynamic_cast(this->module);
338 | assert(module);
339 |
340 | menu->addChild(new MenuSeparator()); // separator
341 |
342 | MenuLabel* qualityLabel = new MenuLabel(); // menu label
343 | qualityLabel->text = "Quality";
344 | menu->addChild(qualityLabel);
345 |
346 | QualityItem* low = new QualityItem(); // low quality
347 | low->text = "Eco";
348 | low->module = module;
349 | low->quality = 0;
350 | menu->addChild(low);
351 |
352 | QualityItem* high = new QualityItem(); // high quality
353 | high->text = "High";
354 | high->module = module;
355 | high->quality = 1;
356 | menu->addChild(high);
357 | }
358 |
359 | VibratoWidget(Vibrato* module)
360 | {
361 | setModule(module);
362 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/vibrato_dark.svg")));
363 |
364 | // screws
365 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
366 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
367 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
368 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
369 |
370 | // knobs
371 | addParam(createParamCentered(Vec(30.0, 65.0), module, Vibrato::SPEED_PARAM));
372 | addParam(createParamCentered(Vec(90.0, 65.0), module, Vibrato::FMSPEED_PARAM));
373 | addParam(createParamCentered(Vec(30.0, 125.0), module, Vibrato::DEPTH_PARAM));
374 | addParam(createParamCentered(Vec(90.0, 125.0), module, Vibrato::FMDEPTH_PARAM));
375 | addParam(createParamCentered(Vec(60.0, 190.0), module, Vibrato::INVWET_PARAM));
376 |
377 | // lights
378 | addChild(createLightCentered>(Vec(13, 37), module, Vibrato::SPEED_LIGHT));
379 | addChild(createLightCentered>(Vec(107, 37), module, Vibrato::SPEED_FM_LIGHT));
380 |
381 | // inputs
382 | addInput(createInputCentered(Vec(22.5, 245.0), module, Vibrato::SPEED_CV_INPUT));
383 | addInput(createInputCentered(Vec(22.5, 285.0), module, Vibrato::DEPTH_CV_INPUT));
384 | addInput(createInputCentered(Vec(97.5, 245.0), module, Vibrato::FMSPEED_CV_INPUT));
385 | addInput(createInputCentered(Vec(97.5, 285.0), module, Vibrato::FMDEPTH_CV_INPUT));
386 | addInput(createInputCentered(Vec(60.0, 245.0), module, Vibrato::INVWET_CV_INPUT));
387 | addInput(createInputCentered(Vec(60.0, 285.0), module, Vibrato::IN_INPUT));
388 |
389 | // outputs
390 | addOutput(createOutputCentered(Vec(22.5, 325.0), module, Vibrato::EOC_OUTPUT));
391 | addOutput(createOutputCentered(Vec(60.0, 325.0), module, Vibrato::OUT_OUTPUT));
392 | addOutput(createOutputCentered(Vec(97.5, 325.0), module, Vibrato::EOC_FM_OUTPUT));
393 | }
394 | };
395 |
396 | Model* modelVibrato = createModel("vibrato");
--------------------------------------------------------------------------------