From 73110bbde2f7b170d2a0373123c95667ccae45aa Mon Sep 17 00:00:00 2001 From: Roland Rabien Date: Wed, 20 May 2020 16:44:46 -0700 Subject: [PATCH] Initial Commit --- .github/workflows/build_linux.yaml | 25 + .github/workflows/build_macos.yaml | 29 + .github/workflows/build_windows.yaml | 25 + .gitignore | 35 ++ .gitmodules | 9 + LICENSE | 29 + README.md | 10 + ci/build.sh | 149 +++++ modules/gin | 1 + modules/juce | 1 + modules/plugin_sdk | 1 + plugin/Source/Boxes.h | 420 +++++++++++++ plugin/Source/Cfg.h | 10 + plugin/Source/PluginEditor.cpp | 88 +++ plugin/Source/PluginEditor.h | 43 ++ plugin/Source/PluginProcessor.cpp | 869 +++++++++++++++++++++++++++ plugin/Source/PluginProcessor.h | 282 +++++++++ plugin/Source/WavetableVoice.cpp | 379 ++++++++++++ plugin/Source/WavetableVoice.h | 58 ++ plugin/Wavetable.jucer | 137 +++++ 20 files changed, 2600 insertions(+) create mode 100644 .github/workflows/build_linux.yaml create mode 100644 .github/workflows/build_macos.yaml create mode 100644 .github/workflows/build_windows.yaml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 README.md create mode 100755 ci/build.sh create mode 160000 modules/gin create mode 160000 modules/juce create mode 160000 modules/plugin_sdk create mode 100644 plugin/Source/Boxes.h create mode 100644 plugin/Source/Cfg.h create mode 100644 plugin/Source/PluginEditor.cpp create mode 100644 plugin/Source/PluginEditor.h create mode 100644 plugin/Source/PluginProcessor.cpp create mode 100644 plugin/Source/PluginProcessor.h create mode 100644 plugin/Source/WavetableVoice.cpp create mode 100644 plugin/Source/WavetableVoice.h create mode 100644 plugin/Wavetable.jucer diff --git a/.github/workflows/build_linux.yaml b/.github/workflows/build_linux.yaml new file mode 100644 index 0000000..ecf30f3 --- /dev/null +++ b/.github/workflows/build_linux.yaml @@ -0,0 +1,25 @@ +name: Build Linux +on: [push] +jobs: + build: + name: Build Linux + runs-on: ubuntu-latest + steps: + - name: Fix up git URLs + run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig + shell: bash + - uses: actions/checkout@v1 + with: + token: ${{ secrets.ACCESS_TOKEN }} + submodules: true + - name: "Run script" + run: | + export OS="linux" + ./ci/build.sh + shell: bash + env: + APIKEY: ${{ secrets.APIKEY }} + - uses: actions/upload-artifact@v2 + with: + name: VirtualAnalog_Linux + path: ci/bin/VirtualAnalog_Linux.zip diff --git a/.github/workflows/build_macos.yaml b/.github/workflows/build_macos.yaml new file mode 100644 index 0000000..7a18ed8 --- /dev/null +++ b/.github/workflows/build_macos.yaml @@ -0,0 +1,29 @@ +name: Build macOS +on: [push] +jobs: + build: + name: Build macOS + runs-on: macos-latest + steps: + - name: Fix up git URLs + run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig + shell: bash + - uses: actions/checkout@v1 + with: + token: ${{ secrets.ACCESS_TOKEN }} + submodules: true + - name: "Run script" + run: | + export OS="mac" + ./ci/build.sh + shell: bash + env: + APPLICATION: ${{ secrets.APPLICATION }} + INSTALLER: ${{ secrets.INSTALLER }} + APPLE_PASS: ${{ secrets.APPLE_PASS }} + APPLE_USER: ${{ secrets.APPLE_USER }} + APIKEY: ${{ secrets.APIKEY }} + - uses: actions/upload-artifact@v2 + with: + name: VirtualAnalog_Mac + path: ci/bin/VirtualAnalog_Mac.zip diff --git a/.github/workflows/build_windows.yaml b/.github/workflows/build_windows.yaml new file mode 100644 index 0000000..f52a467 --- /dev/null +++ b/.github/workflows/build_windows.yaml @@ -0,0 +1,25 @@ +name: Build Windows +on: [push] +jobs: + build: + name: Build Windows + runs-on: windows-latest + steps: + - name: Fix up git URLs + run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig + shell: bash + - uses: actions/checkout@v1 + with: + token: ${{ secrets.ACCESS_TOKEN }} + submodules: true + - name: "Run script" + run: | + export OS="win" + ./ci/build.sh + shell: bash + env: + APIKEY: ${{ secrets.APIKEY }} + - uses: actions/upload-artifact@v2 + with: + name: VirtualAnalog_Win + path: ci/bin/VirtualAnalog_Win.zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3bacf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +xcuserdata +Builds +JuceLibraryCode diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..82c95c5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "modules/gin"] + path = modules/gin + url = git@github.com:/FigBug/Gin.git +[submodule "modules/juce"] + path = modules/juce + url = git@github.com:juce-framework/JUCE.git +[submodule "modules/plugin_sdk"] + path = modules/plugin_sdk + url = git@github.com:TurnipHat/plugin_sdk.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f386d22 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, Roland Rabien +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2474ad3 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Wavetable +Wavetable VST / AU plugin + +![Build macOS](https://github.com/FigBug/Wavetable/workflows/Build%20macOS/badge.svg) +![Build Windows](https://github.com/FigBug/Wavetable/workflows/Build%20Windows/badge.svg) +![Build Linux](https://github.com/FigBug/Wavetable/workflows/Build%20Linux/badge.svg) + +A 4 oscillator subtractive synth with 2 filters and flexible modulation options. + +https://socalabs.com/synths/Wavetable/ diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 0000000..77a3044 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,149 @@ +#!/bin/bash -e + +PLUGIN="Wavetable" + +# linux specific stiff +if [ $OS = "linux" ]; then + export GDK_BACKEND=x11 + + sudo apt-get update + sudo apt-get install clang git ladspa-sdk freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev webkit2gtk-4.0 juce-tools xvfb + + Xvfb :99 & + export DISPLAY=:99 + sleep 5 +fi + +# mac specific stuff +if [ $OS = "mac" ]; then + # Create a temp keychain + if [ -n "$GITHUB_ACTIONS" ]; then + echo "Create a keychain" + security create-keychain -p nr4aGPyz Keys.keychain + + echo $APPLICATION | base64 -D -o /tmp/Application.p12 + echo $INSTALLER | base64 -D -o /tmp/Installer.p12 + + security import /tmp/Application.p12 -t agg -k Keys.keychain -P aym9PKWB -A -T /usr/bin/codesign + security import /tmp/Installer.p12 -t agg -k Keys.keychain -P aym9PKWB -A -T /usr/bin/codesign + + security list-keychains -s Keys.keychain + security default-keychain -s Keys.keychain + security unlock-keychain -p nr4aGPyz Keys.keychain + security set-keychain-settings -l -u -t 13600 Keys.keychain + security set-key-partition-list -S apple-tool:,apple: -s -k nr4aGPyz Keys.keychain + fi + DEV_APP_ID="Developer ID Application: Roland Rabien (3FS7DJDG38)" + DEV_INST_ID="Developer ID Installer: Roland Rabien (3FS7DJDG38)" +fi + +ROOT=$(cd "$(dirname "$0")/.."; pwd) +cd "$ROOT" +echo "$ROOT" + +cd "$ROOT/ci" +rm -Rf bin +mkdir bin + +# Get the hash +cd "$ROOT/modules/juce" +HASH=`git rev-parse HEAD` +echo "Hash: $HASH" + +# Get the Projucer +cd "$ROOT/ci/bin" +while true +do + PROJUCER_URL=$(curl -s -S "https://projucer.rabien.com/get_projucer.php?hash=$HASH&os=$OS&key=$APIKEY") + echo "Response: $PROJUCER_URL" + if [[ $PROJUCER_URL == http* ]]; then + curl -s -S $PROJUCER_URL -o "$ROOT/ci/bin/Projucer.zip" + unzip Projucer.zip + break + fi + sleep 15 +done + +# Resave jucer file +if [ "$OS" = "mac" ]; then + "$ROOT/ci/bin/Projucer.app/Contents/MacOS/Projucer" --resave "$ROOT/plugin/$PLUGIN.jucer" +elif [ "$OS" = "linux" ]; then + "$ROOT/ci/bin/Projucer" --resave "$ROOT/plugin/$PLUGIN.jucer" +else + "$ROOT/ci/bin/Projucer.exe" --resave "$ROOT/plugin/$PLUGIN.jucer" +fi + +# Build mac version +if [ "$OS" = "mac" ]; then + cd "$ROOT/plugin/Builds/MacOSX" + xcodebuild -configuration Release || exit 1 + + cp -R ~/Library/Audio/Plug-Ins/VST/$PLUGIN.vst "$ROOT/ci/bin" + cp -R ~/Library/Audio/Plug-Ins/Components/$PLUGIN.component "$ROOT/ci/bin" + + cd "$ROOT/ci/bin" + for filename in ./*.vst; do + codesign -s "$DEV_APP_ID" -v "$filename" --options=runtime --timestamp + done + for filename in ./*.component; do + codesign -s "$DEV_APP_ID" -v "$filename" --options=runtime --timestamp + done + + # Build notarize tool + cd "$ROOT/modules/gin/tools/notarize" + "$ROOT/ci/bin/Projucer.app/Contents/MacOS/Projucer" --set-global-search-path osx defaultJuceModulePath "$ROOT/modules/juce/modules" + "$ROOT/ci/bin/Projucer.app/Contents/MacOS/Projucer" --resave "notarize.jucer" + cd Builds/MacOSX + xcodebuild -configuration Release || exit 1 + cd build/Release + cp notarize "$ROOT/ci/bin" + + # Notarize + cd "$ROOT/ci/bin" + zip -r ${PLUGIN}_Mac.zip $PLUGIN.vst $PLUGIN.component + + "$ROOT/ci/bin/notarize" -ns ${PLUGIN}_Mac.zip $APPLE_USER $APPLE_PASS com.figbug.$PLUGIN.vst + + rm ${PLUGIN}_Mac.zip + xcrun stapler staple $PLUGIN.vst + xcrun stapler staple $PLUGIN.component + zip -r ${PLUGIN}_Mac.zip $PLUGIN.vst $PLUGIN.component + + curl -F "files=@${PLUGIN}_Mac.zip" "https://socalabs.com/files/set.php?key=$APIKEY" +fi + +# Build linux version +if [ "$OS" = "linux" ]; then + cd "$ROOT/plugin/Builds/LinuxMakefile" + make CONFIG=Release + + cd "$ROOT/plugin/Builds/LinuxMakefile" + cp ./build/$PLUGIN.so "$ROOT/ci/bin" + + cd "$ROOT/ci/bin" + rm -Rf ${PLUGIN}_Linux.zip + zip -r ${PLUGIN}_Linux.zip $PLUGIN.so + + curl -F "files=@${PLUGIN}_Linux.zip" "https://socalabs.com/files/set.php?key=$APIKEY" +fi + +# Build Win version +if [ "$OS" = "win" ]; then + VS_WHERE="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" + + MSBUILD_EXE=$("$VS_WHERE" -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe") + echo $MSBUILD_EXE + + cd "$ROOT/plugin/Builds/VisualStudio2019" + "$MSBUILD_EXE" "$PLUGIN.sln" "//p:VisualStudioVersion=16.0" "//m" "//t:Build" "//p:Configuration=Release64" "//p:Platform=x64" "//p:PreferredToolArchitecture=x64" + "$MSBUILD_EXE" "$PLUGIN.sln" "//p:VisualStudioVersion=16.0" "//m" "//t:Build" "//p:Configuration=Release" "//p:PlatformTarget=x86" "//p:PreferredToolArchitecture=x64" + + cd "$ROOT/ci/bin" + + cp "$ROOT/plugin/Builds/VisualStudio2019/x64/Release64/VST/${PLUGIN}.dll" . + cp "$ROOT/plugin/Builds/VisualStudio2019/Win32/Release/VST/${PLUGIN}_32b.dll" . + + 7z a ${PLUGIN}_Win.zip ${PLUGIN}.dll ${PLUGIN}_32b.dll + + curl -F "files=@${PLUGIN}_Win.zip" "https://socalabs.com/files/set.php?key=$APIKEY" +fi \ No newline at end of file diff --git a/modules/gin b/modules/gin new file mode 160000 index 0000000..2aa9e9f --- /dev/null +++ b/modules/gin @@ -0,0 +1 @@ +Subproject commit 2aa9e9fb9208a7e49eef07fe9a65365351b8481a diff --git a/modules/juce b/modules/juce new file mode 160000 index 0000000..83b1436 --- /dev/null +++ b/modules/juce @@ -0,0 +1 @@ +Subproject commit 83b1436c6a21f82ffdc4125592836f21dbd7b1e7 diff --git a/modules/plugin_sdk b/modules/plugin_sdk new file mode 160000 index 0000000..bcbef19 --- /dev/null +++ b/modules/plugin_sdk @@ -0,0 +1 @@ +Subproject commit bcbef199f4dd78b883aebd6477cc104dad850e1b diff --git a/plugin/Source/Boxes.h b/plugin/Source/Boxes.h new file mode 100644 index 0000000..c701fb5 --- /dev/null +++ b/plugin/Source/Boxes.h @@ -0,0 +1,420 @@ +#pragma once + +#include +#include "PluginProcessor.h" +#include "Cfg.h" + +//============================================================================== +class CommonBox : public gin::PagedControlBox +{ +public: + CommonBox (gin::ProcessorEditor& e, WavetableAudioProcessor& proc_) + : gin::PagedControlBox (e), proc (proc_) + { + auto& g = proc.globalParams; + + addPage ("Main", 3, 2); + + addControl (0, new gin::Knob (g.level), 0, 0); + addControl (0, new gin::Switch (g.mono), 1, 0); + addControl (0, new gin::Select (g.glideMode), 2, 0); + + addControl (0, v = new gin::Knob (g.voices), 0, 1); + addControl (0, l = new gin::Switch (g.legato), 1, 1); + addControl (0, s = new gin::Knob (g.glideRate), 2, 1); + + watchParam (g.mono); + watchParam (g.glideMode); + + addPage ("Control", 1, 2); + + addControl (1, new gin::Switch (g.mpe), 0, 0); + } + + void paramChanged() override + { + gin::PagedControlBox::paramChanged(); + + auto& g = proc.globalParams; + v->setEnabled (! g.mono->isOn()); + l->setEnabled (g.glideMode->getProcValue() != 0.0f); + s->setEnabled (g.glideMode->getProcValue() != 0.0f); + } + + WavetableAudioProcessor& proc; + ParamComponentPtr v, l, s; +}; + +//============================================================================== +class OscillatorBox : public gin::PagedControlBox +{ +public: + OscillatorBox (gin::ProcessorEditor& e, WavetableAudioProcessor& proc_) + : gin::PagedControlBox (e), proc (proc_) + { + for ( int i = 0; i < numElementsInArray (proc.wtParams); i++) + { + auto& wt = proc.wtParams[i]; + + addPage ("WT " + String (i + 1), 3, 4); + addPageEnable (i * 2, wt.enable); + + addControl (i * 2, new gin::Knob (wt.tune, true), 1, 0); + addControl (i * 2, new gin::Knob (wt.finetune, true), 2, 0); + addControl (i * 2, new gin::Knob (wt.pan, true), 0, 1); + addControl (i * 2, new gin::Knob (wt.level), 1, 1); + + addPage ("Unison " + String (i + 1), 2, 4); + addControl (i * 2 + 1, new gin::Knob (wt.voices), 0, 0); + addControl (i * 2 + 1, trans[i] = new gin::Knob (wt.voicesTrns, true), 1, 0); + addControl (i * 2 + 1, detune[i] = new gin::Knob (wt.detune), 0, 1); + addControl (i * 2 + 1, spread[i] = new gin::Knob (wt.spread), 1, 1); + + watchParam (wt.voices); + } + + for ( int i = 0; i < numElementsInArray (proc.oscParams); i++) + { + auto& osc = proc.oscParams[i]; + + addPage ("OSC " + String (i + 1), 3, 4); + addPageEnable (i * 2, osc.enable); + + addControl (Cfg::numWTs * 2 + i * 2, new gin::Select (osc.wave), 0, 0); + addControl (Cfg::numWTs * 2 + i * 2, new gin::Knob (osc.tune, true), 1, 0); + addControl (Cfg::numWTs * 2 + i * 2, new gin::Knob (osc.finetune, true), 2, 0); + addControl (Cfg::numWTs * 2 + i * 2, new gin::Knob (osc.pan, true), 0, 1); + addControl (Cfg::numWTs * 2 + i * 2, new gin::Knob (osc.level), 1, 1); + addControl (Cfg::numWTs * 2 + i * 2, pw[Cfg::numWTs + i] = new gin::Knob (osc.pulsewidth), 2, 1); + + watchParam (osc.wave); + + addPage ("Unison " + String (i + 1), 2, 4); + addControl (Cfg::numWTs * 2 + i * 2 + 1, new gin::Knob (osc.voices), 0, 0); + addControl (Cfg::numWTs * 2 + i * 2 + 1, trans[Cfg::numWTs + i] = new gin::Knob (osc.voicesTrns, true), 1, 0); + addControl (Cfg::numWTs * 2 + i * 2 + 1, detune[Cfg::numWTs + i] = new gin::Knob (osc.detune), 0, 1); + addControl (Cfg::numWTs * 2 + i * 2 + 1, spread[Cfg::numWTs + i] = new gin::Knob (osc.spread), 1, 1); + + watchParam (osc.voices); + } + + setPageOpen (0, true); + setPageOpen (2, true); + setPageOpen (4, true); + } + + void paramChanged() override + { + gin::PagedControlBox::paramChanged(); + + for ( int i = 0; i < numElementsInArray (proc.wtParams); i++) + { + auto& wt = proc.oscParams[i]; + + trans[i]->setEnabled (wt.voices->getProcValue() > 1); + detune[i]->setEnabled (wt.voices->getProcValue() > 1); + spread[i]->setEnabled (wt.voices->getProcValue() > 1); + } + + for ( int i = 0; i < numElementsInArray (proc.oscParams); i++) + { + auto& osc = proc.oscParams[i]; + pw[Cfg::numWTs + i]->setEnabled ((gin::Wave) int (osc.wave->getProcValue()) == gin::Wave::pulse); + + trans[Cfg::numWTs + i]->setEnabled (osc.voices->getProcValue() > 1); + detune[Cfg::numWTs + i]->setEnabled (osc.voices->getProcValue() > 1); + spread[Cfg::numWTs + i]->setEnabled (osc.voices->getProcValue() > 1); + } + } + + WavetableAudioProcessor& proc; + ParamComponentPtr pw[Cfg::numOSCs + Cfg::numWTs], trans[Cfg::numOSCs + Cfg::numWTs], + detune[Cfg::numOSCs + Cfg::numWTs], spread[Cfg::numOSCs + Cfg::numWTs]; +}; + +//============================================================================== +class FilterAmpBox : public gin::PagedControlBox +{ +public: + FilterAmpBox (gin::ProcessorEditor& e, WavetableAudioProcessor& proc_) + : gin::PagedControlBox (e), proc (proc_) + { + for (int i = 0; i < numElementsInArray (proc.filterParams); i++) + { + auto& flt = proc.filterParams[i]; + + addPage ("Filter " + String (i + 1), 8, 2); + addBottomButton (i, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcFilter[i], true)); + addPageEnable (i, flt.enable); + + addControl (i, new gin::Select (flt.type), 0, 0); + auto freq = new gin::Knob (flt.frequency); + addControl (i, freq, 1, 0); + addControl (i, new gin::Knob (flt.resonance), 2, 0); + + addControl (i, new gin::Knob (flt.keyTracking), 0, 1); + addControl (i, new gin::Knob (flt.amount, true), 1, 1); + addControl (i, v[i] = new gin::Knob (flt.velocityTracking), 2, 1); + + adsr[i] = new gin::ADSRComponent (); + adsr[i]->setParams (flt.attack, flt.decay, flt.sustain, flt.release); + addControl (i, adsr[i], 3, 0, 3, 2); + + addControl (i, a[i] = new gin::Knob (flt.attack), 6, 0); + addControl (i, d[i] = new gin::Knob (flt.decay), 7, 0); + addControl (i, s[i] = new gin::Knob (flt.sustain), 6, 1); + addControl (i, r[i] = new gin::Knob (flt.release), 7, 1); + + watchParam (flt.amount); + + freq->setLiveValuesCallback ([this, i] () + { + if (proc.filterParams[i].amount->getUserValue() != 0.0f || + proc.filterParams[i].keyTracking->getUserValue() != 0.0f || + proc.modMatrix.isModulated (gin::ModDstId (proc.filterParams[i].frequency->getModIndex()))) + return proc.getLiveFilterCutoff (i); + return Array(); + }); + + int n = numElementsInArray (proc.filterParams); + auto& env = proc.adsrParams; + + addPage ("ADSR", 8, 2); + addControl (n, new gin::Knob (env.attack), 0, 0); + addControl (n, new gin::Knob (env.decay), 1, 0); + addControl (n, new gin::Knob (env.sustain), 0, 1); + addControl (n, new gin::Knob (env.release), 1, 1); + addControl (n, new gin::Knob (env.velocityTracking), 2, 0); + + auto g = new gin::ADSRComponent (); + g->setParams (env.attack, env.decay, env.sustain, env.release); + addControl (n, g, 3, 0, 3, 2); + } + } + + void paramChanged () override + { + gin::PagedControlBox::paramChanged (); + + for ( int i = 0; i < numElementsInArray (proc.filterParams); i++) + { + auto& flt = proc.filterParams[i]; + v[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + a[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + d[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + s[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + r[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + adsr[i]->setEnabled (flt.amount->getUserValue() != 0.0f); + } + } + + WavetableAudioProcessor& proc; + ParamComponentPtr v[Cfg::numFilters], a[Cfg::numFilters], d[Cfg::numFilters], s[Cfg::numFilters], r[Cfg::numFilters]; + gin::ADSRComponent* adsr[Cfg::numFilters]; +}; + +//============================================================================== +class ModulationBox : public gin::PagedControlBox +{ +public: + ModulationBox (gin::ProcessorEditor& e, WavetableAudioProcessor& proc_) + : gin::PagedControlBox (e), proc (proc_) + { + int cnt = 0; + for (int i = 0; i < numElementsInArray (proc.lfoParams); i++, cnt++) + { + auto& lfo = proc.lfoParams[i]; + + addPage ("LFO" + String (i + 1), 5, 2); + addPageEnable (cnt, lfo.enable); + addBottomButton (cnt, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcLFO[i], true)); + addBottomButton (cnt, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcMonoLFO[i], false)); + + addControl (cnt, new gin::Select (lfo.wave), 0, 0); + addControl (cnt, new gin::Switch (lfo.sync), 1, 0); + addControl (cnt, r[i] = new gin::Knob (lfo.rate), 2, 0); + addControl (cnt, b[i] = new gin::Select (lfo.beat), 2, 0); + + addControl (cnt, new gin::Knob (lfo.depth, true), 0, 1); + addControl (cnt, new gin::Knob (lfo.phase, true), 1, 1); + addControl (cnt, new gin::Knob (lfo.offset, true), 2, 1); + addControl (cnt, new gin::Knob (lfo.fade, true), 3, 1); + addControl (cnt, new gin::Knob (lfo.delay), 4, 1); + + auto l = new gin::LFOComponent(); + l->setParams (lfo.wave, lfo.sync, lfo.rate, lfo.beat, lfo.depth, lfo.offset, lfo.phase, lfo.enable); + addControl (cnt, l, 3, 0, 2, 1); + + watchParam (lfo.sync); + } + + for (int i = 0; i < numElementsInArray (proc.envParams); i++, cnt++) + { + auto& env = proc.envParams[i]; + + addPage ("ENV" + String (i + 1), 5, 2); + addPageEnable (cnt, env.enable); + addBottomButton (cnt, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcEnv[i], true)); + + addControl (cnt, new gin::Knob (env.attack), 0, 0); + addControl (cnt, new gin::Knob (env.decay), 1, 0); + addControl (cnt, new gin::Knob (env.sustain), 0, 1); + addControl (cnt, new gin::Knob (env.release), 1, 1); + + auto adsr = new gin::ADSRComponent (); + adsr->setParams (env.attack, env.decay, env.sustain, env.release); + addControl (cnt, adsr, 2, 0, 3, 2); + } + + auto& stp = proc.stepLfoParams; + addPage ("Step", 6, 2); + addPageEnable (cnt, stp.enable); + addBottomButton (cnt, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcStep, true)); + addBottomButton (cnt, new gin::ModulationSourceButton (proc.modMatrix, proc.modSrcMonoStep, false)); + + addControl (cnt, new gin::Select (stp.beat), 0, 0); + addControl (cnt, new gin::Knob (stp.length), 0, 1); + + auto s = new gin::StepLFOComponent(); + s->setParams (stp.beat, stp.length, stp.level, stp.enable); + addControl (cnt, s, 1, 0, 5, 2); + cnt++; + + addPage ("All", 5, 2); + addControl (cnt, new gin::ModSrcListBox (proc.modMatrix), 0, 0, 5, 2); + cnt++; + + addPage ("Mod Matrix", 5, 2); + addControl (cnt, new gin::ModMatrixBox (proc, proc.modMatrix), 0, 0, 5, 2); + cnt++; + } + + void paramChanged () override + { + gin::PagedControlBox::paramChanged (); + + for (int i = 0; i < numElementsInArray (proc.lfoParams); i++) + { + auto& lfo = proc.lfoParams[i]; + r[i]->setVisible (! lfo.sync->isOn()); + b[i]->setVisible (lfo.sync->isOn()); + } + } + + WavetableAudioProcessor& proc; + ParamComponentPtr r[Cfg::numLFOs], b[Cfg::numLFOs]; +}; + +//============================================================================== +class EffectsBox : public gin::PagedControlBox +{ +public: + EffectsBox (gin::ProcessorEditor& e, WavetableAudioProcessor& proc_) + : gin::PagedControlBox (e), proc (proc_) + { + int idx = 0; + + addPage ("Gate", 8, 2); + addPageEnable (idx, proc.gateParams.enable); + addControl (idx, new gin::Select (proc.gateParams.beat), 0, 0); + addControl (idx, new gin::Knob (proc.gateParams.length), 1, 0); + addControl (idx, new gin::Knob (proc.gateParams.attack), 0, 1); + addControl (idx, new gin::Knob (proc.gateParams.release), 1, 1); + auto g = new gin::GateEffectComponent (); + g->setParams (proc.gateParams.length, proc.gateParams.l, proc.gateParams.r, proc.gateParams.enable); + addControl (idx, g, 2, 0, 6, 2); + idx++; + + addPage ("Chorus", 3, 2); + addPageEnable (idx, proc.chorusParams.enable); + addControl (idx, new gin::Knob (proc.chorusParams.delay), 0, 0); + addControl (idx, new gin::Knob (proc.chorusParams.rate), 1, 0); + addControl (idx, new gin::Knob (proc.chorusParams.depth), 2, 0); + addControl (idx, new gin::Knob (proc.chorusParams.width), 0, 1); + addControl (idx, new gin::Knob (proc.chorusParams.mix), 1, 1); + idx++; + + addPage ("Distortion", 2, 2); + addPageEnable (idx, proc.distortionParams.enable); + addControl (idx, new gin::Knob (proc.distortionParams.amount), 0, 0); + addControl (idx, new gin::Knob (proc.distortionParams.highpass), 1, 0); + addControl (idx, new gin::Knob (proc.distortionParams.output), 0, 1); + addControl (idx, new gin::Knob (proc.distortionParams.mix), 1, 1); + idx++; + + addPage ("EQ", 6, 2); + addPageEnable (idx, proc.eqParams.enable); + addControl (idx, new gin::Knob (proc.eqParams.loFreq), 0, 0); + addControl (idx, new gin::Knob (proc.eqParams.loGain), 1, 0); + addControl (idx, new gin::Knob (proc.eqParams.loQ), 2, 0); + addControl (idx, new gin::Knob (proc.eqParams.mid1Freq), 3, 0); + addControl (idx, new gin::Knob (proc.eqParams.mid1Gain), 4, 0); + addControl (idx, new gin::Knob (proc.eqParams.mid1Q), 5, 0); + addControl (idx, new gin::Knob (proc.eqParams.mid2Freq), 0, 1); + addControl (idx, new gin::Knob (proc.eqParams.mid2Gain), 1, 1); + addControl (idx, new gin::Knob (proc.eqParams.mid2Q), 2, 1); + addControl (idx, new gin::Knob (proc.eqParams.hiFreq), 3, 1); + addControl (idx, new gin::Knob (proc.eqParams.hiGain), 4, 1); + addControl (idx, new gin::Knob (proc.eqParams.hiQ), 5, 1); + idx++; + + addPage ("Comp", 3, 2); + addPageEnable (idx, proc.compressorParams.enable); + addControl (idx, new gin::Knob (proc.compressorParams.attack), 0, 0); + addControl (idx, new gin::Knob (proc.compressorParams.release), 1, 0); + addControl (idx, new gin::Knob (proc.compressorParams.ratio), 2, 0); + addControl (idx, new gin::Knob (proc.compressorParams.threshold), 0, 1); + addControl (idx, new gin::Knob (proc.compressorParams.gain), 1, 1); + idx++; + + addPage ("Delay", 3, 2); + addPageEnable (idx, proc.delayParams.enable); + addControl (idx, new gin::Switch (proc.delayParams.sync), 0, 0); + addControl (idx, t = new gin::Knob (proc.delayParams.time), 1, 0); + addControl (idx, b = new gin::Select (proc.delayParams.beat), 1, 0); + addControl (idx, new gin::Knob (proc.delayParams.fb), 2, 0); + addControl (idx, new gin::Knob (proc.delayParams.cf), 1, 1); + addControl (idx, new gin::Knob (proc.delayParams.mix), 2, 1); + idx++; + + watchParam (proc.delayParams.sync); + + addPage ("Reverb", 3, 2); + addPageEnable (idx, proc.reverbParams.enable); + addControl (idx, new gin::Knob (proc.reverbParams.damping), 0, 0); + addControl (idx, new gin::Knob (proc.reverbParams.freezeMode), 1, 0); + addControl (idx, new gin::Knob (proc.reverbParams.roomSize), 2, 0); + addControl (idx, new gin::Knob (proc.reverbParams.width), 0, 1); + addControl (idx, new gin::Knob (proc.reverbParams.mix), 1, 1); + idx++; + + addPage ("Limiter", 2, 2); + addPageEnable (idx, proc.limiterParams.enable); + addControl (idx, new gin::Knob (proc.limiterParams.attack), 0, 0); + addControl (idx, new gin::Knob (proc.limiterParams.release), 1, 0); + addControl (idx, new gin::Knob (proc.limiterParams.threshold), 0, 1); + addControl (idx, new gin::Knob (proc.limiterParams.gain), 1, 1); + idx++; + + addPage ("Scope", 8, 2); + auto scope = new gin::TriggeredScope (proc.fifo); + scope->setNumChannels (2); + scope->setTriggerMode (gin::TriggeredScope::TriggerMode::Up); + scope->setColour (gin::TriggeredScope::lineColourId, Colours::transparentBlack); + addControl (idx, scope, 0, 0, 8, 2); + idx++; + + setPageOpen (0, false); + } + + void paramChanged () override + { + gin::PagedControlBox::paramChanged (); + + t->setVisible (! proc.delayParams.sync->isOn()); + b->setVisible (proc.delayParams.sync->isOn()); + } + + WavetableAudioProcessor& proc; + ParamComponentPtr t, b; +}; diff --git a/plugin/Source/Cfg.h b/plugin/Source/Cfg.h new file mode 100644 index 0000000..d4a413c --- /dev/null +++ b/plugin/Source/Cfg.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Cfg +{ + constexpr static int numWTs = 2; + constexpr static int numOSCs = 1; + constexpr static int numFilters = 1; + constexpr static int numENVs = 3; + constexpr static int numLFOs = 3; +} diff --git a/plugin/Source/PluginEditor.cpp b/plugin/Source/PluginEditor.cpp new file mode 100644 index 0000000..33e4f03 --- /dev/null +++ b/plugin/Source/PluginEditor.cpp @@ -0,0 +1,88 @@ +#include "PluginProcessor.h" +#include "PluginEditor.h" + +using namespace gin; + +//============================================================================== +WavetableAudioProcessorEditor::WavetableAudioProcessorEditor (WavetableAudioProcessor& p) + : ProcessorEditor (p, 50, 50 + 15), proc (p) +{ + oscHeaders.addChildComponent (modOverview); + + gin::addAndMakeVisible (*this, { &commonHeader, &common, &unisonHeader }); + + gin::addAndMakeVisible (*this, { &oscHeaders }); + gin::addAndMakeVisible (*this, { &oscillators }); + + gin::addAndMakeVisible (*this, { &FiltersHeader }); + gin::addAndMakeVisible (*this, { &Filters }); + + gin::addAndMakeVisible (*this, { &modulationHeader, &modulation }); + gin::addAndMakeVisible (*this, { &effectsHeader, &effects }); + + oscHeaders.addAndMakeVisible ( usage ); + + setGridSize (14, 8, 0, 3 * 25); +} + +WavetableAudioProcessorEditor::~WavetableAudioProcessorEditor() +{ +} + +//============================================================================== +void WavetableAudioProcessorEditor::paint (Graphics& g) +{ + ProcessorEditor::paint (g); + + g.setColour (Colours::white.withAlpha (0.2f)); + + auto rc = getFullGridArea(); + g.drawRect (rc.expanded (1)); +} + +void WavetableAudioProcessorEditor::resized() +{ + ProcessorEditor::resized (); + + auto rc = getFullGridArea(); + + int hh = 25; + + int gx = getGridWidth(); + int gy = getGridHeight(); + + // Oscillators + { + auto rHeaders = rc.removeFromTop (hh); + oscHeaders.setBounds (rHeaders.removeFromLeft (gx * 14)); + + auto rOscs = rc.removeFromTop (gy * 4); + oscillators.setBounds (rOscs.removeFromLeft (gx * 14)); + + modOverview.setBounds (4, 4, 200, hh - 8); + + usage.setBounds ( oscHeaders.getLocalBounds().removeFromRight (14 * 5 + 2).withSizeKeepingCentre (14 * 5 + 2, 16)); + } + + // ADSR and mod + { + auto rHeaders = rc.removeFromTop (hh); + ampFiltersHeader.setBounds (rHeaders.removeFromLeft (gx * 6)); + modulationHeader.setBounds (rHeaders.removeFromLeft (gx * 8)); + + auto rFilters = rc.removeFromTop (gy * 2); + ampFilters.setBounds (rFilters.removeFromLeft (gx * 6)); + modulation.setBounds (rFilters.removeFromLeft (gx * 8)); + } + + // Effects & Common + { + auto rHeaders = rc.removeFromTop (hh); + effectsHeader.setBounds (rHeaders.removeFromLeft (gx * 10)); + commonHeader.setBounds (rHeaders.removeFromLeft (gx * 4)); + + auto rControls = rc.removeFromTop (gy * 2); + effects.setBounds (rControls.removeFromLeft (gx * 10)); + common.setBounds (rControls.removeFromLeft (gx * 4)); + } +} diff --git a/plugin/Source/PluginEditor.h b/plugin/Source/PluginEditor.h new file mode 100644 index 0000000..4546bbb --- /dev/null +++ b/plugin/Source/PluginEditor.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include "PluginProcessor.h" +#include "Boxes.h" + +//============================================================================== +class WavetableAudioProcessorEditor : public gin::ProcessorEditor +{ +public: + WavetableAudioProcessorEditor (WavetableAudioProcessor&); + ~WavetableAudioProcessorEditor() override; + + //============================================================================== + void paint (Graphics&) override; + void resized() override; + +private: + WavetableAudioProcessor& proc; + + gin::ModulationOverview modOverview { proc.modMatrix }; + + gin::ControlHeader commonHeader { "Common" }; + gin::ControlHeader unisonHeader { "Unison" }; + + CommonBox common { *this, proc }; + + gin::ControlHeader oscHeaders = { "Oscillators" }; + OscillatorBox oscillators = { *this, proc }; + + gin::ControlHeader ampFiltersHeader { "Filter / ADSR" }; + FilterAmpBox ampFilters { *this, proc }; + + gin::ControlHeader modulationHeader { "Modulation" }; + ModulationBox modulation { *this, proc }; + + gin::ControlHeader effectsHeader { "Effects" }; + EffectsBox effects { *this, proc }; + + gin::SynthesiserUsage usage { proc }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavetableAudioProcessorEditor) +}; diff --git a/plugin/Source/PluginProcessor.cpp b/plugin/Source/PluginProcessor.cpp new file mode 100644 index 0000000..1ae0f15 --- /dev/null +++ b/plugin/Source/PluginProcessor.cpp @@ -0,0 +1,869 @@ +#include "PluginProcessor.h" +#include "PluginEditor.h" +#include "WavetableVoice.h" + +using namespace gin; + +static String waveTextFunction (const Parameter&, float v) +{ + switch ((Wave)int (v)) + { + case Wave::sine: return "Sine"; + case Wave::triangle: return "Triangle"; + case Wave::sawUp: return "Saw (Up)"; + case Wave::sawDown: return "Saw (Down)"; + case Wave::pulse: return "Pulse"; + case Wave::square: return "Square"; + case Wave::noise: return "Noise"; + default: + jassertfalse; + return {}; + } +} + +static String lfoTextFunction (const Parameter&, float v) +{ + switch ((LFO::WaveShape)int (v)) + { + case LFO::WaveShape::none: return "None"; + case LFO::WaveShape::sine: return "Sine"; + case LFO::WaveShape::triangle: return "Triangle"; + case LFO::WaveShape::sawUp: return "Saw Up"; + case LFO::WaveShape::sawDown: return "Saw Down"; + case LFO::WaveShape::square: return "Square"; + case LFO::WaveShape::squarePos: return "Square+"; + case LFO::WaveShape::sampleAndHold: return "S&H"; + case LFO::WaveShape::noise: return "Noise"; + case LFO::WaveShape::stepUp3: return "Step Up 3"; + case LFO::WaveShape::stepUp4: return "Step Up 4"; + case LFO::WaveShape::stepup8: return "Step Up 8"; + case LFO::WaveShape::stepDown3: return "Step Down 3"; + case LFO::WaveShape::stepDown4: return "Step Down 4"; + case LFO::WaveShape::stepDown8: return "Step Down 8"; + case LFO::WaveShape::pyramid3: return "Pyramid 3"; + case LFO::WaveShape::pyramid5: return "Pyramid 5"; + case LFO::WaveShape::pyramid9: return "Pyramid 9"; + default: + jassertfalse; + return {}; + } +} + +static String enableTextFunction (const Parameter&, float v) +{ + return v > 0.0f ? "On" : "Off"; +} + +static String durationTextFunction (const Parameter&, float v) +{ + return NoteDuration::getNoteDurations()[size_t (v)].getName(); +} + +static String distortionAmountTextFunction (const Parameter&, float v) +{ + return String (v * 5.0f - 1.0f, 1); +} + +static String filterTextFunction (const Parameter&, float v) +{ + switch (int (v)) + { + case 0: return "LP 12"; + case 1: return "LP 24"; + case 2: return "HP 12"; + case 3: return "HP 24"; + case 4: return "BP 12"; + case 5: return "BP 24"; + case 6: return "NT 12"; + case 7: return "NT 24"; + default: + jassertfalse; + return {}; + } +} + +static String freqTextFunction (const Parameter&, float v) +{ + return String (int (getMidiNoteInHertz (v))); +} + +static String glideModeTextFunction (const Parameter&, float v) +{ + switch (int (v)) + { + case 0: return "Off"; + case 1: return "Glissando"; + case 2: return "Portamento"; + default: + jassertfalse; + return {}; + } +} + +//============================================================================== +void WavetableAudioProcessor::WTParams::setup (WavetableAudioProcessor& p, int idx) +{ + String id = "wt" + String (idx + 1); + String nm = "WT" + String (idx + 1) + " "; + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, idx == 0 ? 1.0f : 0.0f, 0.0f); + voices = p.addIntParam (id + "unison", nm + "Unison", "Unison", "", { 1.0, 8.0, 1.0, 1.0 }, 1.0, 0.0f); + voicesTrns = p.addExtParam (id + "unisontrns", nm + "Unison Trns", "LTrans", "st", { -36.0, 36.0, 1.0, 1.0 }, 0.0, 0.0f); + tune = p.addExtParam (id + "tune", nm + "Tune", "Tune", "st", { -36.0, 36.0, 1.0, 1.0 }, 0.0, 0.0f); + finetune = p.addExtParam (id + "finetune", nm + "Fine Tune", "Fine", "ct", { -100.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + level = p.addExtParam (id + "level", nm + "Level", "Level", "db", { -100.0, 0.0, 1.0, 4.0 }, 0.0, 0.0f); + detune = p.addExtParam (id + "detune", nm + "Detune", "Detune", "", { 0.0, 0.5, 0.0, 1.0 }, 0.0, 0.0f); + spread = p.addExtParam (id + "spread", nm + "Spread", "Spread", "%", { -100.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + pan = p.addExtParam (id + "pan", nm + "Pan", "Pan", "", { -1.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + + level->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +void WavetableAudioProcessor::OSCParams::setup (WavetableAudioProcessor& p, int idx) +{ + String id = "osc" + String (idx + 1); + String nm = "OSC" + String (idx + 1) + " "; + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, idx == 0 ? 1.0f : 0.0f, 0.0f); + wave = p.addIntParam (id + "wave", nm + "Wave", "Wave", "", { 1.0, 7.0, 1.0, 1.0 }, 1.0, 0.0f, waveTextFunction); + voices = p.addIntParam (id + "unison", nm + "Unison", "Unison", "", { 1.0, 8.0, 1.0, 1.0 }, 1.0, 0.0f); + voicesTrns = p.addExtParam (id + "unisontrns", nm + "Unison Trns", "LTrans", "st", { -36.0, 36.0, 1.0, 1.0 }, 0.0, 0.0f); + tune = p.addExtParam (id + "tune", nm + "Tune", "Tune", "st", { -36.0, 36.0, 1.0, 1.0 }, 0.0, 0.0f); + finetune = p.addExtParam (id + "finetune", nm + "Fine Tune", "Fine", "ct", { -100.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + level = p.addExtParam (id + "level", nm + "Level", "Level", "db", { -100.0, 0.0, 1.0, 4.0 }, 0.0, 0.0f); + pulsewidth = p.addExtParam (id + "pulsewidth", nm + "Pulse Width", "PW", "%", { 1.0, 99.0, 0.0, 1.0 }, 50.0, 0.0f); + detune = p.addExtParam (id + "detune", nm + "Detune", "Detune", "", { 0.0, 0.5, 0.0, 1.0 }, 0.0, 0.0f); + spread = p.addExtParam (id + "spread", nm + "Spread", "Spread", "%", { -100.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + pan = p.addExtParam (id + "pan", nm + "Pan", "Pan", "", { -1.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + + level->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +void WavetableAudioProcessor::FilterParams::setup (WavetableAudioProcessor& p, int idx) +{ + String id = "flt" + String (idx + 1); + String nm = "FLT" + String (idx + 1) + " "; + + float maxFreq = float (getMidiNoteFromHertz (20000.0)); + + enable = p.addIntParam (id + "enable", nm + "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, idx == 0 ? 1.0f : 0.0f, 0.0f); + type = p.addIntParam (id + "type", nm + "Type", "Type", "", { 0.0, 7.0, 1.0, 1.0 }, 0.0, 0.0f, filterTextFunction); + keyTracking = p.addExtParam (id + "key", nm + "Key", "Key", "%", { 0.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + velocityTracking = p.addExtParam (id + "vel", nm + "Vel", "Vel", "%", { 0.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + frequency = p.addExtParam (id + "freq", nm + "Freq", "Freq", "Hz", { 0.0, maxFreq, 0.0, 1.0 }, 64.0, 0.0f, freqTextFunction); + resonance = p.addExtParam (id + "res", nm + "Res", "Res", "", { 0.0, 100.0, 0.0, 1.0 }, 0.0, 0.0f); + amount = p.addExtParam (id + "amount", nm + "Amount", "Amnt", "", { -1.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + attack = p.addExtParam (id + "attack", nm + "Attack", "A", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + decay = p.addExtParam (id + "decay", nm + "Decay", "D", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + sustain = p.addExtParam (id + "sustain", nm + "Sustain", "S", "%", { 0.0, 100.0, 0.0, 1.0 }, 80.0f, 0.0f); + release = p.addExtParam (id + "release", nm + "Release", "R", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + + sustain->conversionFunction = [] (float in) { return in / 100.0f; }; + velocityTracking->conversionFunction = [] (float in) { return in / 100.0f; }; + keyTracking->conversionFunction = [] (float in) { return in / 100.0f; }; +} + +//============================================================================== +void WavetableAudioProcessor::EnvParams::setup (WavetableAudioProcessor& p, int idx) +{ + String id = "env" + String (idx + 1); + String nm = "ENV" + String (idx + 1) + " "; + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0, 0.0f, enableTextFunction); + attack = p.addExtParam (id + "attack", nm + "Attack", "A", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + decay = p.addExtParam (id + "decay", nm + "Decay", "D", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + sustain = p.addExtParam (id + "sustain", nm + "Sustain", "S", "%", { 0.0, 100.0, 0.0, 1.0 }, 80.0f, 0.0f); + release = p.addExtParam (id + "release", nm + "Release", "R", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + + sustain->conversionFunction = [] (float in) { return in / 100.0f; }; +} + +//============================================================================== +void WavetableAudioProcessor::LFOParams::setup (WavetableAudioProcessor& p, int idx) +{ + String id = "lfo" + String (idx + 1); + String nm = "LFO" + String (idx + 1) + " "; + + auto notes = NoteDuration::getNoteDurations(); + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + sync = p.addIntParam (id + "sync", nm + "Sync", "Sync", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0, 0.0f, enableTextFunction); + wave = p.addIntParam (id + "wave", nm + "Wave", "Wave", "", { 1.0, 17.0, 1.0, 1.0 }, 1.0, 0.0f, lfoTextFunction); + rate = p.addExtParam (id + "rate", nm + "Rate", "Rate", "Hz", { 0.0, 50.0, 0.0, 0.3f }, 10.0, 0.0f); + beat = p.addIntParam (id + "beat", nm + "Beat", "Beat", "", { 0.0, float (notes.size() - 1), 1.0, 1.0 }, 13.0, 0.0f, durationTextFunction); + depth = p.addExtParam (id + "depth", nm + "Depth", "Depth", "", { -1.0, 1.0, 0.0, 1.0 }, 1.0, 0.0f); + phase = p.addExtParam (id + "phase", nm + "Phase", "Phase", "", { -1.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + offset = p.addExtParam (id + "offset", nm + "Offset", "Offset", "", { -1.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + fade = p.addExtParam (id + "fade", nm + "Fade", "Fade", "s", { -60.0, 60.0, 0.0, 0.2f, true }, 0.1f, 0.0f); + delay = p.addExtParam (id + "delay", nm + "Delay", "Delay", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); +} + +//============================================================================== +void WavetableAudioProcessor::StepLFOParams::setup (WavetableAudioProcessor& p) +{ + String id = "slfo"; + String nm = "Step LFO"; + + auto notes = NoteDuration::getNoteDurations(); + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + beat = p.addIntParam (id + "beat", nm + "Beat", "Beat", "", { 0.0, float (notes.size() - 1), 1.0, 1.0 }, 13.0, 0.0f, durationTextFunction); + length = p.addIntParam (id + "length", nm + "Length", "Length", "", { 2.0, 32.0, 1.0, 1.0f }, 8.0f, 0.0f); + + for (int i = 0; i < 32; i++) + { + auto num = String (i + 1); + level[i] = p.addIntParam (id + "step" + num, nm + "Step " + num, "", "", { -1.0, 1.0, 0.0, 1.0f }, 0.0f, 0.0f); + } +} + +//============================================================================== +void WavetableAudioProcessor::GateParams::setup (WavetableAudioProcessor& p) +{ + String id = "gate"; + String nm = "Gate"; + + auto notes = NoteDuration::getNoteDurations(); + + enable = p.addIntParam (id + "enable", nm + "Enable", "Enable", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + beat = p.addIntParam (id + "beat", nm + "Beat", "Beat", "", { 0.0, float (notes.size() - 1), 1.0, 1.0 }, 7.0, 0.0f, durationTextFunction); + length = p.addIntParam (id + "length", nm + "Length", "Length", "", { 2.0, 32.0, 1.0, 1.0f }, 8.0f, 0.0f); + attack = p.addExtParam (id + "attack", nm + "Attack", "A", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + release = p.addExtParam (id + "release", nm + "Release", "R", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + + for (int i = 0; i < 32; i++) + { + auto num = String (i + 1); + l[i] = p.addIntParam (id + "l" + num, nm + "L " + num, "", "", { 0.0, 1.0, 1.0, 1.0f }, (i % 2 == 0 || i % 5 == 0) ? 1.0f : 0.0f, 0.0f); + r[i] = p.addIntParam (id + "r" + num, nm + "R " + num, "", "", { 0.0, 1.0, 1.0, 1.0f }, (i % 2 == 0 || i % 5 == 0) ? 1.0f : 0.0f, 0.0f); + } +} + +//============================================================================== +void WavetableAudioProcessor::ADSRParams::setup (WavetableAudioProcessor& p) +{ + velocityTracking = p.addExtParam ("vel", "Vel", "Vel", "", { 0.0, 100.0, 0.0, 1.0 }, 100.0, 0.0f); + attack = p.addExtParam ("attack", "Attack", "A", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + decay = p.addExtParam ("decay", "Decay", "D", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + sustain = p.addExtParam ("sustain", "Sustain", "S", "%", { 0.0, 100.0, 0.0, 1.0 }, 80.0f, 0.0f); + release = p.addExtParam ("release", "Release", "R", "s", { 0.0, 60.0, 0.0, 0.2f }, 0.1f, 0.0f); + + sustain->conversionFunction = [] (float in) { return in / 100.0f; }; + velocityTracking->conversionFunction = [] (float in) { return in / 100.0f; }; +} + +//============================================================================== +void WavetableAudioProcessor::GlobalParams::setup (WavetableAudioProcessor& p) +{ + mono = p.addIntParam ("mono", "Mono", "", "", { 0.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f, enableTextFunction); + glideMode = p.addIntParam ("gMode", "Glide Mode", "Glide", "", { 0.0, 2.0, 0.0, 1.0 }, 0.0f, 0.0f, glideModeTextFunction); + glideRate = p.addExtParam ("gRate", "Glide Rate", "Rate", "s", { 0.001f, 20.0, 0.0, 0.2f }, 0.3f, 0.0f); + legato = p.addIntParam ("legato", "Legato", "", "", { 0.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f, enableTextFunction); + level = p.addExtParam ("level", "Level", "", "db", { -100.0, 0.0, 1.0, 4.0f }, 0.0, 0.0f); + voices = p.addIntParam ("voices", "Voices", "", "", { 2.0, 40.0, 1.0, 1.0 }, 40.0f, 0.0f); + mpe = p.addIntParam ("mpe", "MPE", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + + level->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +void WavetableAudioProcessor::ChorusParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("chEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + delay = p.addExtParam ("chDelay", "Delay", "", "ms", {0.1f, 30.0f, 0.0f, 1.0f}, 1.0f, 0.0f); + depth = p.addExtParam ("chDepth", "Depth", "", "ms", {0.1f, 20.0f, 0.0f, 1.0f}, 1.0f, 0.0f); + rate = p.addExtParam ("chSpeed", "Speed", "", "Hz", {0.1f, 10.0f, 0.0f, 1.0f}, 3.0f, 0.0f); + width = p.addExtParam ("chWidth", "Width", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.5f, 0.0f); + mix = p.addExtParam ("chMix", "Mix", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.5f, 0.0f); + + delay->conversionFunction = [] (float in) { return in / 1000.0f; }; + depth->conversionFunction = [] (float in) { return in / 1000.0f; }; +} + +//============================================================================== +void WavetableAudioProcessor::DistortionParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("dsEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + amount = p.addExtParam ("dsAmount", "Amount", "", "", { 0.0, 1.0, 0.0, 1.0 }, 0.2f, 0.0f, distortionAmountTextFunction); + highpass = p.addExtParam ("dsHighpass", "Highpass", "", "", { 0.0, 1.0, 0.0, 1.0 }, 0.0, 0.0f); + output = p.addExtParam ("dsOutput", "Output", "", "", { 0.0, 1.0, 0.0, 1.0 }, 1.0, 0.0f); + mix = p.addExtParam ("dsMix", "Mix", "", "", { 0.0, 1.0, 0.0, 1.0 }, 1.0, 0.0f); +} + +//============================================================================== +void WavetableAudioProcessor::EQParams::setup (WavetableAudioProcessor& p) +{ + float maxFreq = float (getMidiNoteFromHertz (20000.0)); + float d1 = float (getMidiNoteFromHertz (80.0)); + float d2 = float (getMidiNoteFromHertz (3000.0)); + float d3 = float (getMidiNoteFromHertz (5000.0)); + float d4 = float (getMidiNoteFromHertz (17000.0)); + + enable = p.addIntParam ("eqEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + + loFreq = p.addExtParam ("eqLoFreq", "Lo Freq", "Freq", "Hz", { 0.0, maxFreq, 0.0, 1.0 }, d1, 0.0f, freqTextFunction); + loQ = p.addExtParam ("eqLoQ", "Lo Q", "Q", "", { 0.025f, 40.0f, 0.0, 0.2f }, 1.0f, 0.0f); + loGain = p.addExtParam ("eqLoGain", "Lo Gain", "Gain", "dB", { -20.0f, 20.0f, 0.0, 1.0 }, 0.0f, 0.0f); + + mid1Freq = p.addExtParam ("eqM1Freq", "Min 1 Freq", "Freq", "Hz", { 0.0, maxFreq, 0.0, 1.0 }, d2, 0.0f, freqTextFunction); + mid1Q = p.addExtParam ("eqM1Q", "Min 1 Q", "Q", "", { 0.025f, 40.0f, 0.0, 0.2f }, 1.0f, 0.0f); + mid1Gain = p.addExtParam ("eqM1Gain", "Min 1 Gain", "Gain", "dB", { -20.0f, 20.0f, 0.0, 1.0 }, 0.0f, 0.0f); + + mid2Freq = p.addExtParam ("eqM2Freq", "Mid 2 Freq", "Freq", "Hz", { 0.0, maxFreq, 0.0, 1.0 }, d3, 0.0f, freqTextFunction); + mid2Q = p.addExtParam ("eqM2Q", "Mid 2 Q", "Q", "", { 0.025f, 40.0f, 0.0, 0.2f }, 1.0f, 0.0f); + mid2Gain = p.addExtParam ("eqM2Gain", "Mid 2 Gain", "Gain", "dB", { -20.0f, 20.0f, 0.0, 1.0 }, 0.0f, 0.0f); + + hiFreq = p.addExtParam ("eqHiFreq", "Hi Freq", "Freq", "Hz", { 0.0, maxFreq, 0.0, 1.0 }, d4, 0.0f, freqTextFunction); + hiQ = p.addExtParam ("eqHiQ", "Hi Q", "Q", "", { 0.025f, 40.0f, 0.0, 0.2f }, 1.0f, 0.0f); + hiGain = p.addExtParam ("eqHiGain", "Hi Gain", "Gain", "dB", { -20.0f, 20.0f, 0.0, 1.0 }, 0.0f, 0.0f); + + loFreq->conversionFunction = [] (float in) { return getMidiNoteInHertz (in); }; + mid1Freq->conversionFunction = [] (float in) { return getMidiNoteInHertz (in); }; + mid2Freq->conversionFunction = [] (float in) { return getMidiNoteInHertz (in); }; + hiFreq->conversionFunction = [] (float in) { return getMidiNoteInHertz (in); }; + + loGain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; + mid1Gain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; + mid2Gain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; + hiGain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +void WavetableAudioProcessor::CompressorParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("cpEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + + attack = p.addExtParam ("cpAttack", "Attack", "", "ms", { 1.0f, 200.0f, 0.0f, 0.4f}, 1.0f, 0.1f); + release = p.addExtParam ("cpRelease", "Release", "", "ms", { 1.0f, 2000.0f, 0.0f, 0.4f}, 5.0f, 0.1f); + ratio = p.addExtParam ("cpRatio", "Ratio", "", "", { 1.0f, 30.0f, 0.0f, 0.4f}, 5.0f, 0.1f); + threshold = p.addExtParam ("cpThreshold", "Thresh", "", "dB", { -60.0f, 0.0f, 0.0f, 1.0f}, -30.0f, 0.1f); + gain = p.addExtParam ("cpGain", "Gain", "", "dB", { -30.0f, 30.0f, 0.0f, 1.0f}, 0.0f, 0.1f); + + attack->conversionFunction = [] (float in) { return in / 1000.0f; }; + release->conversionFunction = [] (float in) { return in / 1000.0f; }; + gain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +void WavetableAudioProcessor::DelayParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("dlEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0f, 0.0f, enableTextFunction); + + float mxd = float (NoteDuration::getNoteDurations().size()) - 1.0f; + + sync = p.addExtParam ("dlSync", "Sync", "", "", { 0.0f, 1.0f, 1.0f, 1.0f}, 0.0f, 0.0f, enableTextFunction); + time = p.addExtParam ("dlTime", "Delay", "", "", { 0.0f, 120.0f, 0.0f, 0.3f}, 1.0f, 0.0f); + beat = p.addExtParam ("dlBeat", "Delay", "", "", { 0.0f, mxd, 1.0f, 1.0f}, 13.0f, 0.0f, durationTextFunction); + fb = p.addExtParam ("dlFb", "FB", "", "dB", {-100.0f, 0.0f, 0.0f, 5.0f}, -10.0f, 0.1f); + cf = p.addExtParam ("dlCf", "CF", "", "dB", {-100.0f, 0.0f, 0.0f, 5.0f}, -100.0f, 0.1f); + mix = p.addExtParam ("dlMix", "Mix", "", "%", { 0.0f, 100.0f, 0.0f, 1.0f}, 0.5f, 0.1f); + + delay = p.addIntParam ("dlDelay", "Delay", "", "", { 0.0f, 120.0f, 0.0f, 1.0f}, 1.0f, {0.2f, SmoothingType::eased}); + + fb->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; + cf->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; + mix->conversionFunction = [] (float in) { return in / 100.0f; }; +} + +//============================================================================== +void WavetableAudioProcessor::ReverbParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("rvEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0, 0.0f); + + damping = p.addExtParam ("rvbDamping", "Damping", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.0f, 0.0f); + freezeMode = p.addExtParam ("rvbFreeze", "Freeze", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.0f, 0.0f); + roomSize = p.addExtParam ("rvbSize", "Size", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.0f, 0.0f); + width = p.addExtParam ("rvbWidth", "Width", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.0f, 0.0f); + mix = p.addExtParam ("rvbMix", "Mix", "", "", {0.0f, 1.0f, 0.0f, 1.0f}, 0.0f, 0.0f); +} + +//============================================================================== +void WavetableAudioProcessor::LimiterParams::setup (WavetableAudioProcessor& p) +{ + enable = p.addIntParam ("lmEnable", "Enable", "", "", { 0.0, 1.0, 1.0, 1.0 }, 0.0, 0.0f); + + attack = p.addExtParam ("lmAttack", "Attack", "", "ms", { 1.0f, 5.0f, 0.0f, 0.4f}, 1.0f, 0.1f); + release = p.addExtParam ("lmRelease", "Release", "", "ms", { 1.0f, 100.0f, 0.0f, 0.4f}, 5.0f, 0.1f); + threshold = p.addExtParam ("lmThreshold", "Ceil", "", "dB", { -60.0f, 0.0f, 0.0f, 1.0f}, -30.0f, 0.1f); + gain = p.addExtParam ("lmGain", "Gain", "", "dB", { -30.0f, 30.0f, 0.0f, 1.0f}, 0.0f, 0.1f); + + attack->conversionFunction = [] (float in) { return in / 1000.0f; }; + release->conversionFunction = [] (float in) { return in / 1000.0f; }; + gain->conversionFunction = [] (float in) { return Decibels::decibelsToGain (in); }; +} + +//============================================================================== +WavetableAudioProcessor::WavetableAudioProcessor() +{ + enableLegacyMode(); + setVoiceStealingEnabled (true); + + for (int i = 0; i < numElementsInArray (wtParams); i++) + wtParams[i].setup (*this, i); + + for (int i = 0; i < numElementsInArray (oscParams); i++) + oscParams[i].setup (*this, i); + + for (int i = 0; i < numElementsInArray (filterParams); i++) + filterParams[i].setup (*this, i); + + for (int i = 0; i < numElementsInArray (envParams); i++) + envParams[i].setup (*this, i); + + for (int i = 0; i < numElementsInArray (lfoParams); i++) + lfoParams[i].setup (*this, i); + + stepLfoParams.setup (*this); + adsrParams.setup (*this); + + globalParams.setup (*this); + gateParams.setup (*this); + chorusParams.setup (*this); + distortionParams.setup (*this); + eqParams.setup (*this); + compressorParams.setup (*this); + delayParams.setup (*this); + reverbParams.setup (*this); + limiterParams.setup (*this); + + eq.setNumChannels (2); + compressor.setNumChannels (2); + limiter.setNumChannels (2); + + for (int i = 0; i < 50; i++) + { + auto voice = new WavetableVoice (*this, bandLimitedLookupTables); + modMatrix.addVoice (voice); + addVoice (voice); + } + + setupModMatrix(); +} + +WavetableAudioProcessor::~WavetableAudioProcessor() +{ +} + +//============================================================================== +void WavetableAudioProcessor::stateUpdated() +{ + modMatrix.stateUpdated (state); +} + +void WavetableAudioProcessor::updateState() +{ + modMatrix.updateState (state); +} + +//============================================================================== +void WavetableAudioProcessor::setupModMatrix() +{ + modSrcPressure = modMatrix.addPolyModSource ("mpep", "MPE Pressure", false); + modSrcTimbre = modMatrix.addPolyModSource ("mpet", "MPE Timbre", false); + + modScrPitchBend = modMatrix.addMonoModSource ("pb", "Pitch Bend", true); + + modSrcNote = modMatrix.addPolyModSource ("note", "MIDI Note Number", false); + modSrcVelocity = modMatrix.addPolyModSource ("vel", "MIDI Velocity", false); + + for (int i = 0; i <= 119; i++) + { + String name = MidiMessage::getControllerName (i); + if (name.isEmpty()) + modSrcCC.add (modMatrix.addMonoModSource (String::formatted ("cc%d", i), String::formatted ("CC %d", i), false)); + else + modSrcCC.add (modMatrix.addMonoModSource (String::formatted ("cc%d", i), String::formatted ("CC %d ", i) + name, false)); + } + + for (int i = 0; i < Cfg::numLFOs; i++) + modSrcMonoLFO.add (modMatrix.addMonoModSource (String::formatted ("mlfo%d", i + 1), String::formatted ("LFO %d (Mono)", i + 1), true)); + + for (int i = 0; i < Cfg::numLFOs; i++) + modSrcLFO.add (modMatrix.addPolyModSource (String::formatted ("lfo%d", i + 1), String::formatted ("LFO %d", i + 1), true)); + + modSrcMonoStep = modMatrix.addMonoModSource ("mstep", "Step LFO (Mono)", true); + modSrcStep = modMatrix.addPolyModSource ("step", "Step LFO", true); + + for (int i = 0; i < Cfg::numFilters; i++) + modSrcFilter.add (modMatrix.addPolyModSource (String::formatted ("fenv%d", i + 1), String::formatted ("Filter Envelope %d", i + 1), false)); + + for (int i = 0; i < Cfg::numENVs; i++) + modSrcEnv.add (modMatrix.addPolyModSource (String::formatted ("env%d", i + 1), String::formatted ("Envelope %d", i + 1), false)); + + auto firstMonoParam = globalParams.mono; + bool polyParam = true; + for (auto pp : getPluginParameters()) + { + if (pp == firstMonoParam) + polyParam = false; + + if (! pp->isInternal()) + { + modMatrix.addParameter (pp, polyParam); + } + } + + modMatrix.build(); +} + +void WavetableAudioProcessor::reset() +{ + Processor::reset(); + + gate.reset(); + chorus.reset(); + distortion.reset(); + stereoDelay.reset(); + compressor.reset(); + limiter.reset(); + + eq.reset(); + + reverb.reset(); + + for (auto& l : modLFOs) + l.reset(); + + modStepLFO.reset(); +} + +void WavetableAudioProcessor::prepareToPlay (double newSampleRate, int newSamplesPerBlock) +{ + Processor::prepareToPlay (newSampleRate, newSamplesPerBlock); + + bandLimitedLookupTables.setSampleRate (newSampleRate); + setCurrentPlaybackSampleRate (newSampleRate); + + modMatrix.setSampleRate (newSampleRate); + + gate.setSampleRate (newSampleRate); + chorus.setSampleRate (newSampleRate); + distortion.setSampleRate (newSampleRate); + stereoDelay.setSampleRate (newSampleRate); + compressor.setSampleRate (newSampleRate); + limiter.setSampleRate (newSampleRate); + + eq.setSampleRate (newSampleRate); + + reverb.setSampleRate (newSampleRate); + + for (auto& l : modLFOs) + l.setSampleRate (newSampleRate); + + modStepLFO.setSampleRate (newSampleRate); +} + +void WavetableAudioProcessor::releaseResources() +{ +} + +void WavetableAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midi) +{ + ScopedNoDenormals noDenormals; + + startBlock(); + setMPE (globalParams.mpe->isOn()); + + playhead = getPlayHead(); + + int pos = 0; + int todo = buffer.getNumSamples(); + + buffer.clear(); + + setMono (globalParams.mono->isOn()); + setLegato (globalParams.legato->isOn()); + setGlissando (globalParams.glideMode->getProcValue() == 1.0f); + setPortamento (globalParams.glideMode->getProcValue() == 2.0f); + setGlideRate (globalParams.glideRate->getProcValue()); + setNumVoices (int (globalParams.voices->getProcValue())); + + while (todo > 0) + { + int thisBlock = std::min (todo, 32); + + updateParams (thisBlock); + + renderNextBlock (buffer, midi, pos, thisBlock); + + auto slice = sliceBuffer (buffer, pos, thisBlock); + applyEffects (slice); + + modMatrix.finishBlock (thisBlock); + + pos += thisBlock; + todo -= thisBlock; + } + + playHead = nullptr; + + fifo.write (buffer); + endBlock (buffer.getNumSamples()); +} + +Array WavetableAudioProcessor::getLiveFilterCutoff (int i) +{ + Array values; + + for (auto v : voices) + { + if (v->isActive()) + { + auto vav = dynamic_cast(v); + values.add (vav->getFilterCutoffNormalized (i)); + } + } + return values; +} + +void WavetableAudioProcessor::applyEffects (AudioSampleBuffer& buffer) +{ + // Apply gate + if (gateParams.enable->isOn()) + gate.process (buffer, noteOnIndex, noteOffIndex); + + // Apply Chorus + if (chorusParams.enable->isOn()) + chorus.process (buffer); + + // Apply Distortion + if (distortionParams.enable->isOn()) + distortion.process (buffer); + + // Apply EQ + if (eqParams.enable->isOn()) + eq.process (buffer); + + // Apply Compressor + if (compressorParams.enable->isOn()) + compressor.process (buffer); + + // Apply Delay + if (delayParams.enable->isOn()) + stereoDelay.process (buffer); + + // Apply Reverb + if (reverbParams.enable->isOn()) + reverb.processStereo (buffer.getWritePointer (0), buffer.getWritePointer (1), buffer.getNumSamples ()); + + // Apply Limiter + if (limiterParams.enable->isOn()) + limiter.process (buffer); + + // Output gain + outputGain.process (buffer); +} + +void WavetableAudioProcessor::updateParams (int newBlockSize) +{ + // Update Mono LFOs + for (int i = 0; i < Cfg::numLFOs; i++) + { + if (lfoParams[i].enable->isOn()) + { + LFO::Parameters params; + + float freq = 0; + if (lfoParams[i].sync->getProcValue() > 0.0f) + freq = 1.0f / NoteDuration::getNoteDurations()[size_t (lfoParams[i].beat->getProcValue())].toSeconds (playhead); + else + freq = modMatrix.getValue (lfoParams[i].rate); + + params.waveShape = (LFO::WaveShape) int (lfoParams[i].wave->getProcValue()); + params.frequency = freq; + params.phase = modMatrix.getValue (lfoParams[i].phase); + params.offset = modMatrix.getValue (lfoParams[i].offset); + params.depth = modMatrix.getValue (lfoParams[i].depth); + params.delay = 0; + params.fade = 0; + + modLFOs[i].setParameters (params); + modLFOs[i].process (newBlockSize); + + modMatrix.setMonoValue (modSrcMonoLFO[i], modLFOs[i].getOutput()); + } + else + { + modMatrix.setMonoValue (modSrcMonoLFO[i], 0); + } + } + + // Update Mono Step LFO + if (stepLfoParams.enable->isOn()) + { + float freq = 1.0f / NoteDuration::getNoteDurations()[size_t (stepLfoParams.beat->getProcValue())].toSeconds (playhead); + + modStepLFO.setFreq (freq); + + int n = int (stepLfoParams.length->getProcValue()); + modStepLFO.setNumPoints (n); + for (int i = n; --i >= 0;) + modStepLFO.setPoint (i, stepLfoParams.level[i]->getProcValue()); + + modStepLFO.process (newBlockSize); + + modMatrix.setMonoValue (modSrcMonoStep, modStepLFO.getOutput()); + } + else + { + modMatrix.setMonoValue (modSrcMonoStep, 0); + } + + // Update Gate + if (gateParams.enable->isOn()) + { + float freq = 1.0f / NoteDuration::getNoteDurations()[size_t (gateParams.beat->getProcValue())].toSeconds (playhead); + + int n = int (gateParams.length->getProcValue()); + + gate.setLength (n); + + for (int i = 0; i < n; i++) + gate.setStep (i, gateParams.l[i]->isOn(), gateParams.r[i]->isOn()); + + gate.setFrequency (freq); + gate.setAttack (modMatrix.getValue (gateParams.attack)); + gate.setRelease (modMatrix.getValue (gateParams.release)); + } + + // Update Chorus + if (chorusParams.enable->isOn()) + { + chorus.setParams (modMatrix.getValue (chorusParams.delay), + modMatrix.getValue (chorusParams.rate), + modMatrix.getValue (chorusParams.depth), + modMatrix.getValue (chorusParams.width), + modMatrix.getValue (chorusParams.mix)); + } + + // Update Distortion + if (distortionParams.enable->isOn()) + { + distortion.setParams (modMatrix.getValue (distortionParams.amount), + modMatrix.getValue (distortionParams.highpass), + modMatrix.getValue (distortionParams.output), + modMatrix.getValue (distortionParams.mix)); + } + + + // Update EQ + if (eqParams.enable->isOn()) + { + eq.setParams (0, EQ::lowshelf, + modMatrix.getValue (eqParams.loFreq), + modMatrix.getValue (eqParams.loQ), + modMatrix.getValue (eqParams.loGain)); + + eq.setParams (1, EQ::peak, + modMatrix.getValue (eqParams.mid1Freq), + modMatrix.getValue (eqParams.mid1Q), + modMatrix.getValue (eqParams.mid1Gain)); + + eq.setParams (2, EQ::peak, + modMatrix.getValue (eqParams.mid2Freq), + modMatrix.getValue (eqParams.mid2Q), + modMatrix.getValue (eqParams.mid2Gain)); + + eq.setParams (3, EQ::highshelf, + modMatrix.getValue (eqParams.hiFreq), + modMatrix.getValue (eqParams.hiQ), + modMatrix.getValue (eqParams.hiGain)); + } + + // Update Compressor + if (compressorParams.enable->isOn()) + { + compressor.setInputGain (1.0f); + compressor.setOutputGain (modMatrix.getValue (compressorParams.gain)); + compressor.setParams (modMatrix.getValue (compressorParams.attack), + modMatrix.getValue (compressorParams.release), + modMatrix.getValue (compressorParams.threshold), + modMatrix.getValue (compressorParams.ratio), + 6); + } + + // Update Delay + if (delayParams.enable->isOn()) + { + if (delayParams.sync->isOn()) + { + auto& duration = NoteDuration::getNoteDurations()[(size_t)delayParams.beat->getUserValueInt()]; + delayParams.delay->setUserValue (duration.toSeconds (getPlayHead())); + } + else + { + delayParams.delay->setUserValue (delayParams.time->getUserValue()); + } + + stereoDelay.setParams (delayParams.delay->getUserValue(), + modMatrix.getValue (delayParams.mix), + modMatrix.getValue (delayParams.fb), + modMatrix.getValue (delayParams.cf)); + } + + + // Update Reverb + if (reverbParams.enable->isOn()) + { + Reverb::Parameters p; + + auto mix = modMatrix.getValue (reverbParams.mix); + WetDryMix wetDry (mix); + + p.damping = modMatrix.getValue (reverbParams.damping); + p.freezeMode = modMatrix.getValue (reverbParams.freezeMode); + p.roomSize = modMatrix.getValue (reverbParams.roomSize); + p.width = modMatrix.getValue (reverbParams.width); + p.dryLevel = wetDry.dryGain; + p.wetLevel = wetDry.wetGain; + + reverb.setParameters (p); + } + + // Update Limiter + if (limiterParams.enable->isOn()) + { + limiter.setInputGain (1.0f); + limiter.setOutputGain (modMatrix.getValue (limiterParams.gain)); + limiter.setParams (modMatrix.getValue (limiterParams.attack), + modMatrix.getValue (limiterParams.release), + modMatrix.getValue (limiterParams.threshold), + 100, 6); + } + + // Output gain + outputGain.setGain (modMatrix.getValue (globalParams.level)); +} + +void WavetableAudioProcessor::handleMidiEvent (const MidiMessage& m) +{ + MPESynthesiser::handleMidiEvent (m); + + if (m.isPitchWheel()) + modMatrix.setMonoValue (modScrPitchBend, float (m.getPitchWheelValue()) / 0x2000 - 1.0f); +} + +void WavetableAudioProcessor::handleController ([[maybe_unused]] int ch, int num, int val) +{ + modMatrix.setMonoValue (modSrcCC[num], val / 127.0f); +} + +//============================================================================== +bool WavetableAudioProcessor::hasEditor() const +{ + return true; +} + +AudioProcessorEditor* WavetableAudioProcessor::createEditor() +{ + return new WavetableAudioProcessorEditor (*this); +} + +//============================================================================== +// This creates new instances of the plugin.. +AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new WavetableAudioProcessor(); +} diff --git a/plugin/Source/PluginProcessor.h b/plugin/Source/PluginProcessor.h new file mode 100644 index 0000000..47b5ed0 --- /dev/null +++ b/plugin/Source/PluginProcessor.h @@ -0,0 +1,282 @@ +#pragma once + +#include + +#include "WavetableVoice.h" + +//============================================================================== +class WavetableAudioProcessor : public gin::Processor, + public gin::Synthesiser +{ +public: + //============================================================================== + WavetableAudioProcessor(); + ~WavetableAudioProcessor() override; + + void stateUpdated() override; + void updateState() override; + + //============================================================================== + void reset() override; + void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + + void processBlock (AudioBuffer&, MidiBuffer&) override; + + //============================================================================== + AudioProcessorEditor* createEditor() override; + bool hasEditor() const override; + + void updateParams (int blockSize); + void setupModMatrix(); + + gin::BandLimitedLookupTables bandLimitedLookupTables; + + //============================================================================== + void handleMidiEvent (const MidiMessage& m) override; + void handleController (int ch, int num, int val) override; + //============================================================================== + Array getLiveFilterCutoff (int idx); + + void applyEffects (AudioSampleBuffer& buffer); + + // WT Params + struct WTParams + { + WTParams() = default; + + gin::Parameter::Ptr enable, voices, voicesTrns, tune, finetune, + level, detune, spread, pan; + + void setup (WavetableAudioProcessor& p, int idx); + + JUCE_DECLARE_NON_COPYABLE (WTParams) + }; + + // Voice Params + struct OSCParams + { + OSCParams() = default; + + gin::Parameter::Ptr enable , wave, voices, voicesTrns, tune, finetune, + level, pulsewidth, detune, spread, pan; + + void setup (WavetableAudioProcessor& p, int idx); + + JUCE_DECLARE_NON_COPYABLE (OSCParams) + }; + + struct FilterParams + { + FilterParams() = default; + + gin::Parameter::Ptr enable, type, keyTracking, velocityTracking, + frequency, resonance, amount, + attack, decay, sustain, release; + + void setup (WavetableAudioProcessor& p, int idx); + + JUCE_DECLARE_NON_COPYABLE (FilterParams) + }; + + struct EnvParams + { + EnvParams() = default; + + gin::Parameter::Ptr enable, attack, decay, sustain, release; + + void setup (WavetableAudioProcessor& p, int idx); + + JUCE_DECLARE_NON_COPYABLE (EnvParams) + }; + + struct LFOParams + { + LFOParams() = default; + + gin::Parameter::Ptr enable, sync, wave, rate, beat, depth, phase, offset, fade, delay; + + void setup (WavetableAudioProcessor& p, int idx); + + JUCE_DECLARE_NON_COPYABLE (LFOParams) + }; + + struct StepLFOParams + { + StepLFOParams() = default; + + gin::Parameter::Ptr enable, beat, length; + gin::Parameter::Ptr level[32] = { nullptr }; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (StepLFOParams) + }; + + struct ADSRParams + { + ADSRParams() = default; + + gin::Parameter::Ptr attack, decay, sustain, release, velocityTracking; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (ADSRParams) + }; + + // Global Params + struct GlobalParams + { + GlobalParams() = default; + + gin::Parameter::Ptr mono, glideMode, glideRate, legato, level, voices, mpe; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (GlobalParams) + }; + + struct GateParams + { + GateParams() = default; + + gin::Parameter::Ptr enable, beat, length, attack, release; + gin::Parameter::Ptr l[32] = { nullptr }; + gin::Parameter::Ptr r[32] = { nullptr }; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (GateParams) + }; + + struct ChorusParams + { + ChorusParams() = default; + + gin::Parameter::Ptr enable, delay, rate, depth, width, mix; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (ChorusParams) + }; + + struct DistortionParams + { + DistortionParams() = default; + + gin::Parameter::Ptr enable, amount, highpass, output, mix; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (DistortionParams) + }; + + struct EQParams + { + EQParams() = default; + + gin::Parameter::Ptr enable, + loFreq, loGain, loQ, + mid1Freq, mid1Gain, mid1Q, + mid2Freq, mid2Gain, mid2Q, + hiFreq, hiGain, hiQ; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (EQParams) + }; + + struct CompressorParams + { + CompressorParams() = default; + + gin::Parameter::Ptr enable, attack, release, ratio, threshold, gain; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (CompressorParams) + }; + + struct DelayParams + { + DelayParams() = default; + + gin::Parameter::Ptr enable, sync, time, beat, fb, cf, mix, delay; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (DelayParams) + }; + + struct ReverbParams + { + ReverbParams() = default; + + gin::Parameter::Ptr enable, damping, freezeMode, roomSize, width, mix; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (ReverbParams) + }; + + struct LimiterParams + { + LimiterParams() = default; + + gin::Parameter::Ptr enable, attack, release, threshold, gain; + + void setup (WavetableAudioProcessor& p); + + JUCE_DECLARE_NON_COPYABLE (LimiterParams) + }; + + //============================================================================== + gin::ModSrcId modSrcPressure, modSrcTimbre, modScrPitchBend, + modSrcNote, modSrcVelocity, modSrcStep, modSrcMonoStep; + + Array modSrcCC, modSrcMonoLFO, modSrcLFO, modSrcFilter, modSrcEnv; + + //============================================================================== + + WTParams wtParams[Cfg::numWTs]; + OSCParams oscParams[Cfg::numOSCs]; + FilterParams filterParams[Cfg::numFilters]; + EnvParams envParams[Cfg::numENVs]; + LFOParams lfoParams[Cfg::numLFOs]; + StepLFOParams stepLfoParams; + + ADSRParams adsrParams; + + GlobalParams globalParams; + GateParams gateParams; + ChorusParams chorusParams; + DistortionParams distortionParams; + EQParams eqParams; + CompressorParams compressorParams; + DelayParams delayParams; + ReverbParams reverbParams; + LimiterParams limiterParams; + + //============================================================================== + gin::GateEffect gate; + gin::Modulation chorus { 0.5f }; + gin::Distortion distortion; + gin::StereoDelay stereoDelay { 120.1 }; + gin::Dynamics compressor; + gin::Dynamics limiter; + gin::EQ eq {4}; + Reverb reverb; + gin::GainProcessor outputGain; + gin::AudioFifo fifo { 2, 44100 }; + + //============================================================================== + gin::ModMatrix modMatrix; + + gin::LFO modLFOs[Cfg::numLFOs]; + gin::StepLFO modStepLFO; + + AudioPlayHead* playhead = nullptr; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavetableAudioProcessor) +}; diff --git a/plugin/Source/WavetableVoice.cpp b/plugin/Source/WavetableVoice.cpp new file mode 100644 index 0000000..ed5cb65 --- /dev/null +++ b/plugin/Source/WavetableVoice.cpp @@ -0,0 +1,379 @@ +#include "WavetableVoice.h" +#include "PluginProcessor.h" + +using namespace gin; + +//============================================================================== +WavetableVoice::WavetableVoice (WavetableAudioProcessor& p, BandLimitedLookupTables& bllt) + : proc (p) + , bandLimitedLookupTables (bllt) +{ + for (auto& f : filters) + f.setNumChannels (2); +} + +void WavetableVoice::noteStarted() +{ + fastKill = false; + startVoice(); + + auto note = getCurrentlyPlayingNote(); + if (glideInfo.fromNote != -1 && (glideInfo.glissando || glideInfo.portamento)) + { + noteSmoother.setTime (glideInfo.rate); + noteSmoother.setValueUnsmoothed (glideInfo.fromNote / 127.0f); + noteSmoother.setValue (note.initialNote / 127.0f); + } + else + { + noteSmoother.setValueUnsmoothed (note.initialNote / 127.0f); + } + + proc.modMatrix.setPolyValue (*this, proc.modSrcVelocity, note.noteOnVelocity.asUnsignedFloat()); + proc.modMatrix.setPolyValue (*this, proc.modSrcTimbre, note.initialTimbre.asUnsignedFloat()); + proc.modMatrix.setPolyValue (*this, proc.modSrcPressure, note.pressure.asUnsignedFloat()); + + ScopedValueSetter svs (disableSmoothing, true); + + for (auto& f : filters) + f.reset(); + + for (auto& a : filterADSRs) + a.reset(); + + for (auto& a : modADSRs) + a.noteOn(); + + for (auto& l : modLFOs) + l.reset(); + + updateParams (0); + snapParams(); + updateParams (0); + snapParams(); + + for (auto& osc : oscillators) + osc.noteOn(); + + for (auto& a : filterADSRs) + a.noteOn(); + + for (auto& a : modADSRs) + a.noteOn(); + + for (auto& l : modLFOs) + l.noteOn(); + + modStepLFO.reset(); + modStepLFO.noteOn(); + + adsr.reset(); + adsr.noteOn(); +} + +void WavetableVoice::noteRetriggered() +{ + auto note = getCurrentlyPlayingNote(); + + if (glideInfo.fromNote != -1 && (glideInfo.glissando || glideInfo.portamento)) + { + noteSmoother.setTime (glideInfo.rate); + noteSmoother.setValue (note.initialNote / 127.0f); + } + else + { + noteSmoother.setValueUnsmoothed (note.initialNote / 127.0f); + } + + proc.modMatrix.setPolyValue (*this, proc.modSrcVelocity, note.noteOnVelocity.asUnsignedFloat()); + proc.modMatrix.setPolyValue (*this, proc.modSrcTimbre, note.initialTimbre.asUnsignedFloat()); + proc.modMatrix.setPolyValue (*this, proc.modSrcPressure, note.pressure.asUnsignedFloat()); + + updateParams (0); + + for (auto& osc : oscillators) + osc.noteOn(); + + for (auto& a : filterADSRs) + a.noteOn(); + + for (auto& a : modADSRs) + a.noteOn(); + + modStepLFO.noteOn(); + adsr.noteOn(); +} + +void WavetableVoice::noteStopped (bool allowTailOff) +{ + adsr.noteOff(); + + for (auto& a : filterADSRs) + a.noteOff(); + + for (auto& a : modADSRs) + a.noteOff(); + + if (! allowTailOff) + { + clearCurrentNote(); + stopVoice(); + } +} + +void WavetableVoice::notePressureChanged() +{ + auto note = getCurrentlyPlayingNote(); + proc.modMatrix.setPolyValue (*this, proc.modSrcPressure, note.pressure.asUnsignedFloat()); +} + +void WavetableVoice::noteTimbreChanged() +{ + auto note = getCurrentlyPlayingNote(); + proc.modMatrix.setPolyValue (*this, proc.modSrcTimbre, note.initialTimbre.asUnsignedFloat()); +} + +void WavetableVoice::setCurrentSampleRate (double newRate) +{ + MPESynthesiserVoice::setCurrentSampleRate (newRate); + + for (auto& osc : oscillators) + osc.setSampleRate (newRate); + + for (auto& f : filters) + f.setSampleRate (newRate); + + for (auto& a : filterADSRs) + a.setSampleRate (newRate); + + for (auto& l : modLFOs) + l.setSampleRate (newRate); + + modStepLFO.setSampleRate (newRate); + noteSmoother.setSampleRate (newRate); + adsr.setSampleRate (newRate); +} + +void WavetableVoice::renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) +{ + updateParams (numSamples); + + // Run OSC + ScratchBuffer buffer (2, numSamples); + + for (int i = 0; i < Cfg::numOSCs; i++) + if (proc.oscParams[i].enable->isOn()) + oscillators[i].processAdding (currentMidiNotes[i], oscParams[i], buffer); + + // Apply velocity + float velocity = currentlyPlayingNote.noteOnVelocity.asUnsignedFloat(); + buffer.applyGain (velocityToGain (velocity, ampKeyTrack)); + + // Apply filters + for (int i = 0; i < numElementsInArray (filters); i++) + if (proc.filterParams[i].enable->isOn()) + filters[i].process (buffer); + + // Run ADSR + adsr.processMultiplying (buffer); + + if (adsr.getState() == AnalogADSR::State::idle) + { + clearCurrentNote(); + stopVoice(); + } + + // Copy output to synth + outputBuffer.addFrom (0, startSample, buffer, 0, 0, numSamples); + outputBuffer.addFrom (1, startSample, buffer, 1, 0, numSamples); + + finishBlock (numSamples); +} + +void WavetableVoice::updateParams (int blockSize) +{ + auto note = getCurrentlyPlayingNote(); + + proc.modMatrix.setPolyValue (*this, proc.modSrcNote, note.initialNote / 127.0f); + + for (int i = 0; i < Cfg::numOSCs; i++) + { + if (! proc.oscParams[i].enable->isOn()) continue; + + currentMidiNotes[i] = noteSmoother.getCurrentValue() * 127.0f; + if (glideInfo.glissando) currentMidiNotes[i] = roundToInt (currentMidiNotes[i]); + currentMidiNotes[i] += float (note.totalPitchbendInSemitones); + currentMidiNotes[i] += getValue (proc.oscParams[i].tune) + getValue (proc.oscParams[i].finetune) / 100.0f; + + oscParams[i].wave = (Wave) int (proc.oscParams[i].wave->getProcValue()); + oscParams[i].voices = int (proc.oscParams[i].voices->getProcValue()); + oscParams[i].vcTrns = int (proc.oscParams[i].voicesTrns->getProcValue()); + oscParams[i].pw = getValue (proc.oscParams[i].pulsewidth) / 100.0f; + oscParams[i].pan = getValue (proc.oscParams[i].pan); + oscParams[i].spread = getValue (proc.oscParams[i].spread) / 100.0f; + oscParams[i].detune = getValue (proc.oscParams[i].detune); + oscParams[i].gain = getValue (proc.oscParams[i].level); + } + + ampKeyTrack = getValue (proc.adsrParams.velocityTracking); + + for (int i = 0; i < Cfg::numFilters; i++) + { + if (! proc.filterParams[i].enable->isOn()) + { + proc.modMatrix.setPolyValue (*this, proc.modSrcFilter[i], 0); + continue; + } + + filterADSRs[i].setAttack (getValue (proc.filterParams[i].attack)); + filterADSRs[i].setSustainLevel (getValue (proc.filterParams[i].sustain)); + filterADSRs[i].setDecay (getValue (proc.filterParams[i].decay)); + filterADSRs[i].setRelease (getValue (proc.filterParams[i].release)); + + filterADSRs[i].process (blockSize); + + float filterWidth = float (getMidiNoteFromHertz (20000.0)); + float filterEnv = filterADSRs[i].getOutput(); + float filterSens = getValue (proc.filterParams[i].velocityTracking); + filterSens = currentlyPlayingNote.noteOnVelocity.asUnsignedFloat() * filterSens + 1.0f - filterSens; + + float n = getValue (proc.filterParams[i].frequency); + n += (currentlyPlayingNote.initialNote - 60) * getValue (proc.filterParams[i].keyTracking); + n += filterEnv * filterSens * getValue (proc.filterParams[i].amount) * filterWidth; + + float f = getMidiNoteInHertz (n); + float maxFreq = std::min (20000.0f, float (getSampleRate() / 2)); + f = jlimit (4.0f, maxFreq, f); + + float q = Q / (1.0f - (getValue (proc.filterParams[i].resonance) / 100.0f) * 0.99f); + + switch (int (proc.filterParams[i].type->getProcValue())) + { + case 0: + filters[i].setType (Filter::lowpass); + filters[i].setSlope (Filter::db12); + break; + case 1: + filters[i].setType (Filter::lowpass); + filters[i].setSlope (Filter::db24); + break; + case 2: + filters[i].setType (Filter::highpass); + filters[i].setSlope (Filter::db12); + break; + case 3: + filters[i].setType (Filter::highpass); + filters[i].setSlope (Filter::db24); + break; + case 4: + filters[i].setType (Filter::bandpass); + filters[i].setSlope (Filter::db12); + break; + case 5: + filters[i].setType (Filter::bandpass); + filters[i].setSlope (Filter::db24); + break; + case 6: + filters[i].setType (Filter::notch); + filters[i].setSlope (Filter::db12); + break; + case 7: + filters[i].setType (Filter::notch); + filters[i].setSlope (Filter::db24); + break; + } + + filters[i].setParams (f, q); + + proc.modMatrix.setPolyValue (*this, proc.modSrcFilter[i], filterADSRs[i].getOutput()); + } + + for (int i = 0; i < Cfg::numENVs; i++) + { + if (proc.envParams[i].enable->isOn()) + { + modADSRs[i].setAttack (getValue (proc.envParams[i].attack)); + modADSRs[i].setSustainLevel (getValue (proc.envParams[i].sustain)); + modADSRs[i].setDecay (getValue (proc.envParams[i].decay)); + modADSRs[i].setRelease (getValue (proc.envParams[i].release)); + + proc.modMatrix.setPolyValue (*this, proc.modSrcEnv[i], modADSRs[i].getOutput()); + + modADSRs[i].process (blockSize); + } + else + { + proc.modMatrix.setPolyValue (*this, proc.modSrcEnv[i], 0.0f); + } + } + + for (int i = 0; i < Cfg::numLFOs; i++) + { + if (proc.lfoParams[i].enable->isOn()) + { + LFO::Parameters params; + + float freq = 0; + if (proc.lfoParams[i].sync->getProcValue() > 0.0f) + freq = 1.0f / NoteDuration::getNoteDurations()[size_t (proc.lfoParams[i].beat->getProcValue())].toSeconds (proc.playhead); + else + freq = getValue (proc.lfoParams[i].rate); + + params.waveShape = (LFO::WaveShape) int (proc.lfoParams[i].wave->getProcValue()); + params.frequency = freq; + params.phase = getValue (proc.lfoParams[i].phase); + params.offset = getValue (proc.lfoParams[i].offset); + params.depth = getValue (proc.lfoParams[i].depth); + params.delay = getValue (proc.lfoParams[i].delay); + params.fade = getValue (proc.lfoParams[i].fade); + + modLFOs[i].setParameters (params); + modLFOs[i].process (blockSize); + + proc.modMatrix.setPolyValue (*this, proc.modSrcLFO[i], modLFOs[i].getOutput()); + } + else + { + proc.modMatrix.setPolyValue (*this, proc.modSrcLFO[i], 0); + } + } + + // Update Step LFO + if (proc.stepLfoParams.enable->isOn()) + { + float freq = 1.0f / NoteDuration::getNoteDurations()[size_t (proc.stepLfoParams.beat->getProcValue())].toSeconds (proc.playhead); + + modStepLFO.setFreq (freq); + + int n = int (proc.stepLfoParams.length->getProcValue()); + modStepLFO.setNumPoints (n); + for (int i = n; --i >= 0;) + modStepLFO.setPoint (i, proc.stepLfoParams.level[i]->getProcValue()); + + modStepLFO.process (blockSize); + + proc.modMatrix.setPolyValue (*this, proc.modSrcStep, modStepLFO.getOutput()); + } + else + { + proc.modMatrix.setPolyValue (*this, proc.modSrcStep, 0); + } + + adsr.setAttack (getValue (proc.adsrParams.attack)); + adsr.setDecay (getValue (proc.adsrParams.decay)); + adsr.setSustainLevel (getValue (proc.adsrParams.sustain)); + adsr.setRelease (fastKill ? 0.01f : getValue (proc.adsrParams.release)); + + noteSmoother.process (blockSize); +} + +bool WavetableVoice::isVoiceActive() +{ + return isActive(); +} + +float WavetableVoice::getFilterCutoffNormalized (int idx) +{ + float freq = filters[idx].getFrequency(); + return proc.filterParams[idx].frequency->getUserRange().convertTo0to1 (getMidiNoteFromHertz (freq)); +} diff --git a/plugin/Source/WavetableVoice.h b/plugin/Source/WavetableVoice.h new file mode 100644 index 0000000..1b6a13b --- /dev/null +++ b/plugin/Source/WavetableVoice.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include "Cfg.h" + +class WavetableAudioProcessor; + +//============================================================================== +class WavetableVoice : public gin::SynthesiserVoice, + public gin::ModVoice +{ +public: + WavetableVoice (WavetableAudioProcessor& p, gin::BandLimitedLookupTables& bandLimitedLookupTables); + + void noteStarted() override; + void noteRetriggered() override; + void noteStopped (bool allowTailOff) override; + + void notePressureChanged() override; + void noteTimbreChanged() override; + void notePitchbendChanged() override {} + void noteKeyStateChanged() override {} + + void setCurrentSampleRate (double newRate) override; + + void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override; + + bool isVoiceActive() override; + + float getFilterCutoffNormalized (int idx); + +private: + void updateParams (int blockSize); + + WavetableAudioProcessor& proc; + gin::BandLimitedLookupTables& bandLimitedLookupTables; + + gin::VoicedStereoOscillator oscillators[Cfg::numOSCs] = + { + bandLimitedLookupTables, + }; + + gin::Filter filters[Cfg::numFilters]; + gin::ADSR filterADSRs[Cfg::numFilters]; + + gin::ADSR modADSRs[Cfg::numENVs]; + gin::LFO modLFOs[Cfg::numLFOs]; + gin::StepLFO modStepLFO; + + gin::AnalogADSR adsr; + + float currentMidiNotes[Cfg::numOSCs]; + gin::VoicedStereoOscillator::Params oscParams[Cfg::numOSCs]; + + gin::EasedValueSmoother noteSmoother; + + float ampKeyTrack = 1.0f; +}; diff --git a/plugin/Wavetable.jucer b/plugin/Wavetable.jucer new file mode 100644 index 0000000..5de073b --- /dev/null +++ b/plugin/Wavetable.jucer @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +