├── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── pr-comment.yml ├── .gitignore ├── BUILDING.md ├── JuceLibraryCode ├── JuceHeader.h ├── JucePluginDefines.h ├── ReadMe.txt ├── include_juce_audio_basics.cpp ├── include_juce_audio_basics.mm ├── include_juce_audio_devices.cpp ├── include_juce_audio_devices.mm ├── include_juce_audio_formats.cpp ├── include_juce_audio_formats.mm ├── include_juce_audio_plugin_client_AAX.cpp ├── include_juce_audio_plugin_client_AAX.mm ├── include_juce_audio_plugin_client_ARA.cpp ├── include_juce_audio_plugin_client_AU_1.mm ├── include_juce_audio_plugin_client_AU_2.mm ├── include_juce_audio_plugin_client_AUv3.mm ├── include_juce_audio_plugin_client_LV2.cpp ├── include_juce_audio_plugin_client_LV2.mm ├── include_juce_audio_plugin_client_Standalone.cpp ├── include_juce_audio_plugin_client_Unity.cpp ├── include_juce_audio_plugin_client_VST2.cpp ├── include_juce_audio_plugin_client_VST3.cpp ├── include_juce_audio_plugin_client_VST_utils.mm ├── include_juce_audio_plugin_client_utils.cpp ├── include_juce_audio_processors.cpp ├── include_juce_audio_processors.mm ├── include_juce_audio_processors_ara.cpp ├── include_juce_audio_processors_lv2_libs.cpp ├── include_juce_audio_utils.cpp ├── include_juce_audio_utils.mm ├── include_juce_core.cpp ├── include_juce_core.mm ├── include_juce_data_structures.cpp ├── include_juce_data_structures.mm ├── include_juce_events.cpp ├── include_juce_events.mm ├── include_juce_graphics.cpp ├── include_juce_graphics.mm ├── include_juce_gui_basics.cpp ├── include_juce_gui_basics.mm ├── include_juce_gui_extra.cpp └── include_juce_gui_extra.mm ├── MultiDexed.jucer ├── README.md ├── Source ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp └── PluginProcessor.h └── build ├── build-linux.sh ├── build-osx.sh ├── build-win.sh └── download-projucer.sh /.clang-format: -------------------------------------------------------------------------------- 1 | # https://code.qt.io/cgit/qt/qt5.git/plain/_clang-format 2 | # Copyright (C) 2016 Olivier Goffart 3 | # 4 | # You may use this file under the terms of the 3-clause BSD license. 5 | # See the file LICENSE from this package for details. 6 | 7 | # This is the clang-format configuration style to be used by Qt, 8 | # based on the rules from https://wiki.qt.io/Qt_Coding_Style and 9 | # https://wiki.qt.io/Coding_Conventions 10 | 11 | --- 12 | # Webkit style was loosely based on the Qt style 13 | BasedOnStyle: WebKit 14 | 15 | Standard: c++17 16 | 17 | # Column width is limited to 100 in accordance with Qt Coding Style. 18 | # https://wiki.qt.io/Qt_Coding_Style 19 | # Note that this may be changed at some point in the future. 20 | ColumnLimit: 100 21 | # How much weight do extra characters after the line length limit have. 22 | # PenaltyExcessCharacter: 4 23 | 24 | # Disable reflow of some specific comments 25 | # qdoc comments: indentation rules are different. 26 | # Translation comments and SPDX license identifiers are also excluded. 27 | CommentPragmas: "^!|^:|^ SPDX-License-Identifier:" 28 | 29 | # We want a space between the type and the star for pointer types. 30 | PointerBindsToType: false 31 | 32 | # We use template< without space. 33 | SpaceAfterTemplateKeyword: false 34 | 35 | # We want to break before the operators, but not before a '='. 36 | BreakBeforeBinaryOperators: NonAssignment 37 | 38 | # Braces are usually attached, but not after functions or class declarations. 39 | BreakBeforeBraces: Custom 40 | BraceWrapping: 41 | AfterClass: true 42 | AfterControlStatement: false 43 | AfterEnum: false 44 | AfterFunction: true 45 | AfterNamespace: false 46 | AfterObjCDeclaration: false 47 | AfterStruct: true 48 | AfterUnion: false 49 | BeforeCatch: false 50 | BeforeElse: false 51 | IndentBraces: false 52 | 53 | # When constructor initializers do not fit on one line, put them each on a new line. 54 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 55 | # Indent initializers by 4 spaces 56 | ConstructorInitializerIndentWidth: 4 57 | 58 | # Indent width for line continuations. 59 | ContinuationIndentWidth: 8 60 | 61 | # No indentation for namespaces. 62 | NamespaceIndentation: None 63 | 64 | # Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125 65 | IndentPPDirectives: AfterHash 66 | # We only indent with 2 spaces for preprocessor directives 67 | PPIndentWidth: 2 68 | 69 | # Horizontally align arguments after an open bracket. 70 | # The coding style does not specify the following, but this is what gives 71 | # results closest to the existing code. 72 | AlignAfterOpenBracket: true 73 | AlwaysBreakTemplateDeclarations: true 74 | 75 | # Ideally we should also allow less short function in a single line, but 76 | # clang-format does not handle that. 77 | AllowShortFunctionsOnASingleLine: Inline 78 | 79 | # The coding style specifies some include order categories, but also tells to 80 | # separate categories with an empty line. It does not specify the order within 81 | # the categories. Since the SortInclude feature of clang-format does not 82 | # re-order includes separated by empty lines, the feature is not used. 83 | SortIncludes: false 84 | 85 | # macros for which the opening brace stays attached. 86 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] 87 | 88 | # Break constructor initializers before the colon and after the commas. 89 | BreakConstructorInitializers: BeforeColon 90 | 91 | # Add "// namespace " comments on closing brace for a namespace 92 | # Ignored for namespaces that qualify as a short namespace, 93 | # see 'ShortNamespaceLines' 94 | FixNamespaceComments: true 95 | 96 | # Definition of how short a short namespace is, default 1 97 | ShortNamespaceLines: 1 98 | 99 | # When escaping newlines in a macro attach the '\' as far left as possible, e.g. 100 | ##define a \ 101 | # something; \ 102 | # other; \ 103 | # thelastlineislong; 104 | AlignEscapedNewlines: Left 105 | 106 | # Avoids the addition of a space between an identifier and the 107 | # initializer list in list-initialization. 108 | SpaceBeforeCpp11BracedList: false 109 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | target-branch: "master" 10 | labels: 11 | - "dependencies" 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/OwlPlug/owlplug-scanner/blob/master/.github/workflows/main.yml 2 | name: MultiDexed 3 | 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | 9 | jobs: 10 | build-win: 11 | runs-on: windows-2019 12 | steps: 13 | - uses: actions/checkout@v3.3.0 14 | with: 15 | fetch-depth: 0 16 | submodules: recursive 17 | token: ${{ secrets.REPO_ACCESS_TOKEN }} 18 | - name: "Download Projucer" 19 | run: | 20 | git clone -b 7.0.5 --depth 1 https://github.com/juce-framework/JUCE JUCE 21 | bash -ex ./build/download-projucer.sh 22 | shell: bash 23 | env: 24 | OS: windows 25 | - name: Get MultiDexed Version 26 | run: echo "version=$((Select-Xml -Path MultiDexed.jucer -XPath '//JUCERPROJECT/@version').Node.Value)" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 27 | id: get-version 28 | - name: "Build MultiDexed" 29 | run: sh -ex ./build/build-win.sh 30 | shell: bash 31 | - name: Upload Artifact 32 | uses: actions/upload-artifact@v3.1.2 33 | with: 34 | name: MultiDexed-win 35 | path: | 36 | ./Builds/VisualStudio2019/x64/Release/VST3/MultiDexed.vst3 37 | ./Builds/VisualStudio2019/x64/Release/Standalone Plugin/MultiDexed.exe 38 | build-osx: 39 | if: false # Skip this job because it is slow 40 | runs-on: macos-11 41 | steps: 42 | - uses: actions/checkout@v3.3.0 43 | with: 44 | fetch-depth: 0 45 | submodules: recursive 46 | token: ${{ secrets.REPO_ACCESS_TOKEN }} 47 | - name: "Download Projucer" 48 | run: | 49 | git clone -b 7.0.5 --depth 1 https://github.com/juce-framework/JUCE JUCE 50 | bash -ex ./build/download-projucer.sh 51 | shell: bash 52 | env: 53 | OS: osx 54 | - name: Install XmlStarlet 55 | run: brew install xmlstarlet 56 | - name: Get MultiDexed Version 57 | run: echo "version=$(xmlstarlet sel -t -v 'string(//JUCERPROJECT/@version)' MultiDexed.jucer)" >> $GITHUB_ENV 58 | id: get-version 59 | - name: "Build macOS" 60 | run: sh -ex ./build/build-osx.sh 61 | shell: bash 62 | - name: "zip macOS" 63 | run: | 64 | cd ./Builds/MacOSX/build/Release/ 65 | zip -r MultiDexed.vst3.macOS.zip MultiDexed.vst3 66 | zip -r MultiDexed.app.macOS.zip MultiDexed.app 67 | cd - 68 | shell: bash 69 | - name: Upload Artifact 70 | uses: actions/upload-artifact@v3.1.2 71 | with: 72 | name: MultiDexed-osx 73 | path: | 74 | ./Builds/MacOSX/build/Release/MultiDexed.vst3.macOS.zip 75 | ./Builds/MacOSX/build/Release/MultiDexed.app.macOS.zip 76 | build-linux: 77 | runs-on: ubuntu-20.04 78 | steps: 79 | - uses: actions/checkout@v3.3.0 80 | with: 81 | fetch-depth: 0 82 | submodules: recursive 83 | token: ${{ secrets.REPO_ACCESS_TOKEN }} 84 | - name: "Download Projucer" 85 | run: | 86 | git clone -b 7.0.5 --depth 1 https://github.com/juce-framework/JUCE JUCE 87 | bash -ex ./build/download-projucer.sh 88 | shell: bash 89 | env: 90 | OS: linux 91 | - name : Update packages 92 | run: sudo apt update 93 | - name : Install Juce dev dependencies 94 | run: sudo apt install libasound2-dev libjack-jackd2-dev ladspa-sdk libcurl4-openssl-dev libfreetype6-dev libx11-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev libwebkit2gtk-4.0-dev libglu1-mesa-dev mesa-common-dev 95 | - name: Install XmlStarlet 96 | run: sudo apt install xmlstarlet 97 | - name: Get MultiDexed Version 98 | run: echo "version=$(xmlstarlet sel -t -v 'string(//JUCERPROJECT/@version)' MultiDexed.jucer)" >> $GITHUB_ENV 99 | id: get-version 100 | - name: "Build Linux" 101 | run: sh -ex ./build/build-linux.sh 102 | shell: bash 103 | - name: Upload Artifact 104 | uses: actions/upload-artifact@v3.1.2 105 | with: 106 | name: MultiDexed-linux 107 | path: | 108 | ./Builds/LinuxMakefile/build/MultiDexed.vst3 109 | ./Builds/LinuxMakefile/build/MultiDexed 110 | release: 111 | name: "Release" 112 | needs: [build-win, build-osx, build-linux] 113 | if: github.ref == 'refs/heads/main' 114 | runs-on: "ubuntu-latest" 115 | steps: 116 | - uses: actions/checkout@v3.3.0 117 | - name: Retrieve artifacts 118 | uses: actions/download-artifact@v3 119 | with: 120 | path: ./build 121 | - uses: "marvinpinto/action-automatic-releases@latest" 122 | with: 123 | repo_token: "${{ secrets.REPO_ACCESS_TOKEN }}" 124 | prerelease: true 125 | automatic_release_tag: latest 126 | title: MultiDexed Latest 127 | files: | 128 | build/MultiDexed-linux 129 | build/MultiDexed-osx 130 | build/MultiDexed-win 131 | -------------------------------------------------------------------------------- /.github/workflows/pr-comment.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/subsurface/subsurface/blob/master/.github/workflows/artifact-links.yml 2 | 3 | name: Add artifact links to pull request 4 | 5 | on: 6 | workflow_run: 7 | workflows: ["MultiDexed"] 8 | types: [completed] 9 | 10 | jobs: 11 | artifacts-url-comments: 12 | name: Add artifact links to PR and issues 13 | runs-on: ubuntu-22.04 14 | 15 | # Restrict permissions for the GITHUB_TOKEN, https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | actions: read 20 | 21 | steps: 22 | - name: Add artifact links to PR and issues 23 | if: github.event.workflow_run.event == 'pull_request' 24 | uses: tonyhallett/artifacts-url-comments@0965ff1a7ae03c5c1644d3c30f956effea4e05ef # v1.1.0 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | with: 28 | prefix: "Build for testing:" 29 | suffix: "Use at your own risk." 30 | format: name 31 | addTo: pull 32 | errorNoArtifacts: false 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | master.zip 2 | vst3sdk/ 3 | vst3sdk-master/ 4 | vst_sdk2_4_rev2.zip 5 | vstsdk2.4/ 6 | build/ 7 | pre_build/ 8 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | Builds are produced using on GitHub Actions. This page describes how to set up local development environments. 4 | 5 | To see the general build steps and dependencies, have a look at [the GitHub Actions workflows](../../tree/main/.github/workflows). 6 | 7 | ## Windows 8 | 9 | Since I normally don't run Windows, here is a way to do it on FreeBSD using VirtualBox. 10 | Using this approach requires a powerful recent computer with "lots" (16 GB) of RAM and even more fast SSD space. 11 | 12 | * Download https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/ (22 GB), it is free, requires no login, comes with Visual Studio Community 2022 which includes a C++ compiler 13 | * Unzip (another 22 GB) 14 | * Import into VirtualBox (another 22 GB) 15 | * Set RAM to 12 GB and increase CPU cores 16 | * First thing before booting, make a snapshot in VirtualBox 17 | * Add an optical drive to the VM so that you can install Guest Extensions 18 | * Boot Windows (first boot takes ~20 minutes) 19 | * Install Guest Extensions (6.1.36 is what comes with the VirtualBox on FreeBSD 13.1-RELEASE with 2023Q1 packages) 20 | * Reboot 21 | * **Why can't I resize the screen?** (see below for workaround) 22 | * Shut down 23 | * Make another snapshot 24 | * Download and install JUCE to `C:\JUCE` 25 | * Open Visual Studio and clone this repository 26 | * Open Projucer 27 | * Create a VisualStudio build configuration 28 | * Double click the respective `*.vcxproj` and open with Visual Studio Community 2022 29 | * Press F7 to build 30 | * It compiles straight away without a hitch 31 | * Running the standalone version seems to crash, as a window is never shown 32 | * Running it with the Visual Studio Community 2022 debugger to see in the code crashes occur 33 | 34 | As for the screen resultion, I had to run 35 | 36 | ``` 37 | sudo vboxmanage list vms # Get the name of the VM 38 | sudo VBoxManage setextradata global GUI/MaxGuestResolution any 39 | sudo VBoxManage setextradata "WinDev2302Eval" "CustomVideoMode1" "1920x1080x32" 40 | sudo VBoxManage controlvm "WinDev2302Eval" setvideomodehint 1920 1080 32 41 | ``` 42 | 43 | ## Linux 44 | 45 | Section to be written 46 | 47 | ## macOS 48 | 49 | Section to be written 50 | -------------------------------------------------------------------------------- /JuceLibraryCode/JuceHeader.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | This is the header file that your files should include in order to get all the 7 | JUCE library headers. You should avoid including the JUCE headers directly in 8 | your own source files, because that wouldn't pick up the correct configuration 9 | options for your app. 10 | 11 | */ 12 | 13 | #pragma once 14 | 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | 30 | #if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION 31 | /** If you've hit this error then the version of the Projucer that was used to generate this project is 32 | older than the version of the JUCE modules being included. To fix this error, re-save your project 33 | using the latest version of the Projucer or, if you aren't using the Projucer to manage your project, 34 | remove the JUCE_PROJUCER_VERSION define. 35 | */ 36 | #error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error." 37 | #endif 38 | 39 | 40 | #if ! JUCE_DONT_DECLARE_PROJECTINFO 41 | namespace ProjectInfo 42 | { 43 | const char* const projectName = "MultiDexed"; 44 | const char* const companyName = ""; 45 | const char* const versionString = "1.0.0"; 46 | const int versionNumber = 0x10000; 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /JuceLibraryCode/JucePluginDefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #pragma once 9 | 10 | //============================================================================== 11 | // Audio plugin settings.. 12 | 13 | #ifndef JucePlugin_Build_VST 14 | #define JucePlugin_Build_VST 0 15 | #endif 16 | #ifndef JucePlugin_Build_VST3 17 | #define JucePlugin_Build_VST3 1 18 | #endif 19 | #ifndef JucePlugin_Build_AU 20 | #define JucePlugin_Build_AU 0 21 | #endif 22 | #ifndef JucePlugin_Build_AUv3 23 | #define JucePlugin_Build_AUv3 0 24 | #endif 25 | #ifndef JucePlugin_Build_AAX 26 | #define JucePlugin_Build_AAX 0 27 | #endif 28 | #ifndef JucePlugin_Build_Standalone 29 | #define JucePlugin_Build_Standalone 1 30 | #endif 31 | #ifndef JucePlugin_Build_Unity 32 | #define JucePlugin_Build_Unity 0 33 | #endif 34 | #ifndef JucePlugin_Build_LV2 35 | #define JucePlugin_Build_LV2 0 36 | #endif 37 | #ifndef JucePlugin_Enable_IAA 38 | #define JucePlugin_Enable_IAA 0 39 | #endif 40 | #ifndef JucePlugin_Enable_ARA 41 | #define JucePlugin_Enable_ARA 0 42 | #endif 43 | #ifndef JucePlugin_Name 44 | #define JucePlugin_Name "MultiDexed" 45 | #endif 46 | #ifndef JucePlugin_Desc 47 | #define JucePlugin_Desc "MultiDexed" 48 | #endif 49 | #ifndef JucePlugin_Manufacturer 50 | #define JucePlugin_Manufacturer "yourcompany" 51 | #endif 52 | #ifndef JucePlugin_ManufacturerWebsite 53 | #define JucePlugin_ManufacturerWebsite "www.yourcompany.com" 54 | #endif 55 | #ifndef JucePlugin_ManufacturerEmail 56 | #define JucePlugin_ManufacturerEmail "" 57 | #endif 58 | #ifndef JucePlugin_ManufacturerCode 59 | #define JucePlugin_ManufacturerCode 0x4d616e75 60 | #endif 61 | #ifndef JucePlugin_PluginCode 62 | #define JucePlugin_PluginCode 0x426f3564 63 | #endif 64 | #ifndef JucePlugin_IsSynth 65 | #define JucePlugin_IsSynth 0 66 | #endif 67 | #ifndef JucePlugin_WantsMidiInput 68 | #define JucePlugin_WantsMidiInput 1 69 | #endif 70 | #ifndef JucePlugin_ProducesMidiOutput 71 | #define JucePlugin_ProducesMidiOutput 0 72 | #endif 73 | #ifndef JucePlugin_IsMidiEffect 74 | #define JucePlugin_IsMidiEffect 0 75 | #endif 76 | #ifndef JucePlugin_EditorRequiresKeyboardFocus 77 | #define JucePlugin_EditorRequiresKeyboardFocus 0 78 | #endif 79 | #ifndef JucePlugin_Version 80 | #define JucePlugin_Version 1.0.0 81 | #endif 82 | #ifndef JucePlugin_VersionCode 83 | #define JucePlugin_VersionCode 0x10000 84 | #endif 85 | #ifndef JucePlugin_VersionString 86 | #define JucePlugin_VersionString "1.0.0" 87 | #endif 88 | #ifndef JucePlugin_VSTUniqueID 89 | #define JucePlugin_VSTUniqueID JucePlugin_PluginCode 90 | #endif 91 | #ifndef JucePlugin_VSTCategory 92 | #define JucePlugin_VSTCategory kPlugCategEffect 93 | #endif 94 | #ifndef JucePlugin_Vst3Category 95 | #define JucePlugin_Vst3Category "Fx" 96 | #endif 97 | #ifndef JucePlugin_AUMainType 98 | #define JucePlugin_AUMainType 'aumf' 99 | #endif 100 | #ifndef JucePlugin_AUSubType 101 | #define JucePlugin_AUSubType JucePlugin_PluginCode 102 | #endif 103 | #ifndef JucePlugin_AUExportPrefix 104 | #define JucePlugin_AUExportPrefix MultiDexedAU 105 | #endif 106 | #ifndef JucePlugin_AUExportPrefixQuoted 107 | #define JucePlugin_AUExportPrefixQuoted "MultiDexedAU" 108 | #endif 109 | #ifndef JucePlugin_AUManufacturerCode 110 | #define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode 111 | #endif 112 | #ifndef JucePlugin_CFBundleIdentifier 113 | #define JucePlugin_CFBundleIdentifier com.yourcompany.MultiDexed 114 | #endif 115 | #ifndef JucePlugin_AAXIdentifier 116 | #define JucePlugin_AAXIdentifier com.yourcompany.MultiDexed 117 | #endif 118 | #ifndef JucePlugin_AAXManufacturerCode 119 | #define JucePlugin_AAXManufacturerCode JucePlugin_ManufacturerCode 120 | #endif 121 | #ifndef JucePlugin_AAXProductId 122 | #define JucePlugin_AAXProductId JucePlugin_PluginCode 123 | #endif 124 | #ifndef JucePlugin_AAXCategory 125 | #define JucePlugin_AAXCategory 0 126 | #endif 127 | #ifndef JucePlugin_AAXDisableBypass 128 | #define JucePlugin_AAXDisableBypass 0 129 | #endif 130 | #ifndef JucePlugin_AAXDisableMultiMono 131 | #define JucePlugin_AAXDisableMultiMono 0 132 | #endif 133 | #ifndef JucePlugin_IAAType 134 | #define JucePlugin_IAAType 0x6175726d 135 | #endif 136 | #ifndef JucePlugin_IAASubType 137 | #define JucePlugin_IAASubType JucePlugin_PluginCode 138 | #endif 139 | #ifndef JucePlugin_IAAName 140 | #define JucePlugin_IAAName "yourcompany: MultiDexed" 141 | #endif 142 | #ifndef JucePlugin_VSTNumMidiInputs 143 | #define JucePlugin_VSTNumMidiInputs 1 144 | #endif 145 | #ifndef JucePlugin_VSTNumMidiOutputs 146 | #define JucePlugin_VSTNumMidiOutputs 16 147 | #endif 148 | #ifndef JucePlugin_ARAContentTypes 149 | #define JucePlugin_ARAContentTypes 0 150 | #endif 151 | #ifndef JucePlugin_ARATransformationFlags 152 | #define JucePlugin_ARATransformationFlags 0 153 | #endif 154 | #ifndef JucePlugin_ARAFactoryID 155 | #define JucePlugin_ARAFactoryID "com.yourcompany.MultiDexed.factory" 156 | #endif 157 | #ifndef JucePlugin_ARADocumentArchiveID 158 | #define JucePlugin_ARADocumentArchiveID "com.yourcompany.MultiDexed.aradocumentarchive.1.0.0" 159 | #endif 160 | #ifndef JucePlugin_ARACompatibleArchiveIDs 161 | #define JucePlugin_ARACompatibleArchiveIDs "" 162 | #endif 163 | -------------------------------------------------------------------------------- /JuceLibraryCode/ReadMe.txt: -------------------------------------------------------------------------------- 1 | 2 | Important Note!! 3 | ================ 4 | 5 | The purpose of this folder is to contain files that are auto-generated by the Projucer, 6 | and ALL files in this folder will be mercilessly DELETED and completely re-written whenever 7 | the Projucer saves your project. 8 | 9 | Therefore, it's a bad idea to make any manual changes to the files in here, or to 10 | put any of your own files in here if you don't want to lose them. (Of course you may choose 11 | to add the folder's contents to your version-control system so that you can re-merge your own 12 | modifications after the Projucer has saved its changes). 13 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_basics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_basics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_devices.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_devices.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_formats.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_formats.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AAX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AAX.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_ARA.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AU_1.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AU_2.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AUv3.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_LV2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_LV2.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_Standalone.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_Unity.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors_ara.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_utils.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_core.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_core.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_data_structures.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_data_structures.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_events.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_events.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_graphics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_graphics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_basics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_basics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_extra.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_extra.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /MultiDexed.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 12 | 14 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiDexed ![](https://github.com/probonopd/MultiDexed/actions/workflows/main.yml/badge.svg) 2 | 3 | ![MultiDexed](https://user-images.githubusercontent.com/2480569/222845457-eff2f74f-9699-4c49-bbec-8e7f58b7d14b.jpg) 4 | 5 | MultiDexed is a standalone application and a VST plugin for Windows, macOS, and Linux that runs multiple instances of [Dexed](https://github.com/asb2m10/dexed) to create the unison effect explained [here](https://www.youtube.com/watch?v=Hzwvd8aZUUU) and showcased [here](https://youtu.be/TutoLkJ_bks?t=718) by Anders Enger Jensen. 6 | 7 | The instances can be detuned and stereo panned. 8 | 9 | MultiDexed is especially useful in DAWs with a limited number of tracks, such as Ableton Live Lite. 10 | 11 | __This is work in progress.__ Any help is greatly appreciated. 12 | 13 | - [x] Make it build on GitHub Actions for Windows, macOS, and Linux 14 | - [x] Do not crash when loaded into REAPER for Windows (running on FreeBSD with Proton WINE) 15 | - [x] Load Dexed VST and create multiple instances of it 16 | - [x] Make the instances produce sound (thanks [__@getdunne__](https://github.com/getdunne)) 17 | - [x] Each instance is slightly detuned 18 | - [x] Each instance is stereo shifted (panned) 19 | - [x] Add GUI for selecting amount of detune, and amount of stereo panning 20 | - [ ] Add GUI for selecting number of instances 21 | - [ ] Save and restore plugin state 22 | - [ ] Make it build for Linux on Raspberry Pi (aarch64) 23 | - [ ] Make it build on CirrusCI for FreeBSD 24 | - [ ] Stretch goal: Make it read, write, and use [MiniDexed](https://github.com/probonopd/MultiDexed) performance files and/or TX816, TX802 performances 25 | 26 | __NOTE:__ A Dexed version newer than 0.9.6 needs to be installed (e.g., the NIGHTLY version from the Dexed GitHub page). Dexed 0.9.6 and earlier are based on JUCE 6 which seemingly leads to crashes when being hosted in the MultiDexed vst3. 27 | -------------------------------------------------------------------------------- /Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | PluginAudioProcessorEditor::PluginAudioProcessorEditor(PluginAudioProcessor &p) 14 | : AudioProcessorEditor(&p), audioProcessor(p) 15 | { 16 | // Get our PluginAudioProcessor instance that is defined in PluginProcessor.h 17 | auto pluginAudioProcessor = dynamic_cast(getAudioProcessor()); 18 | 19 | // Create a tabbed component 20 | tabbedComponent = std::make_unique(juce::TabbedButtonBar::TabsAtTop); 21 | addAndMakeVisible(*tabbedComponent); 22 | 23 | // Get the background color of the window 24 | auto backgroundColor = getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId); 25 | 26 | // Create a tab for each instance of Dexed 27 | for (int i = 0; i < pluginAudioProcessor->numberOfInstances; i++) { 28 | dexedComponents[i] = std::make_unique(); 29 | pluginAudioProcessor->dexedPluginInstances[i]->createEditorIfNeeded(); 30 | dexedComponents[i]->addAndMakeVisible(pluginAudioProcessor->dexedPluginInstances[i]->getActiveEditor()); 31 | // Name the first tab "Master", and the rest "Dexed 1", "Dexed 2", etc. 32 | if (i == 0) { 33 | tabbedComponent->addTab(juce::String("Master"), backgroundColor, dexedComponents[i].get(), true); 34 | } 35 | else { 36 | tabbedComponent->addTab(juce::String("Dexed ") + juce::String(i), backgroundColor, dexedComponents[i].get(), true); 37 | } 38 | dexedEditors[i] = pluginAudioProcessor->dexedPluginInstances[i]->getActiveEditor(); 39 | dexedComponents[i]->setSize(dexedEditors[i]->getWidth(), dexedEditors[i]->getHeight()); 40 | tabbedComponent->setSize(dexedComponents[i]->getWidth(), dexedComponents[i]->getHeight() + tabbedComponent->getTabBarDepth()); 41 | } 42 | 43 | // Make the tabbed component visible 44 | tabbedComponent->setVisible(true); 45 | 46 | // Set the size of the editor window 47 | setSize(tabbedComponent->getWidth(), tabbedComponent->getHeight() + 100); 48 | 49 | // Sliders for the MultiDexed parameters 50 | addAndMakeVisible(detuneSlider); 51 | detuneSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 52 | detuneSlider.setTextBoxStyle(juce::Slider::TextBoxAbove, true, 50, 20); 53 | 54 | detuneSliderAttachment = std::make_unique(pluginAudioProcessor->apvts, "detuneSpread", detuneSlider); 55 | 56 | addAndMakeVisible(panSlider); 57 | panSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 58 | panSlider.setTextBoxStyle(juce::Slider::TextBoxAbove, true, 50, 20); 59 | panSliderAttachment = std::make_unique(pluginAudioProcessor->apvts, "panSpread", panSlider); 60 | addAndMakeVisible(panLabel); 61 | panLabel.setText("Pan", juce::dontSendNotification); 62 | panLabel.attachToComponent(&panSlider, false); 63 | 64 | } 65 | 66 | PluginAudioProcessorEditor::~PluginAudioProcessorEditor() { 67 | // Clean up Dexed components and detach slider attachments 68 | for (int i = 0; i < audioProcessor.numberOfInstances; i++) { 69 | dexedEditors[i] = nullptr; 70 | dexedComponents[i] = nullptr; 71 | } 72 | 73 | tabbedComponent = nullptr; 74 | detuneSliderAttachment = nullptr; 75 | panSliderAttachment = nullptr; 76 | } 77 | 78 | //============================================================================== 79 | void PluginAudioProcessorEditor::paint(juce::Graphics &g) 80 | { 81 | 82 | } 83 | 84 | void PluginAudioProcessorEditor::resized() 85 | { 86 | // This is generally where you'll want to lay out the positions of any 87 | // subcomponents in your editor.. 88 | 89 | panSlider.setBounds(0, 0, 100, 100); 90 | detuneSlider.setBounds(100, 0, 100, 100); 91 | 92 | 93 | // Add tabbed component to hold the Dexed editors 94 | tabbedComponent->setBounds(0, 100, getWidth(), getHeight() - 100); 95 | } 96 | -------------------------------------------------------------------------------- /Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class PluginAudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | PluginAudioProcessorEditor (PluginAudioProcessor&); 21 | ~PluginAudioProcessorEditor() override; 22 | 23 | //============================================================================== 24 | void paint (juce::Graphics&) override; 25 | void resized() override; 26 | 27 | private: 28 | // This reference is provided as a quick way for your editor to 29 | // access the processor object that created it. 30 | PluginAudioProcessor& audioProcessor; 31 | 32 | // Pointer to our button 33 | juce::TextButton button; 34 | 35 | // Pointer to our tabbed component 36 | std::unique_ptr tabbedComponent; 37 | 38 | // Array with 8 pointers to our Dexed components 39 | std::unique_ptr dexedComponents[8]; 40 | 41 | // Array with 8 pointers to our Dexed editors 42 | juce::AudioProcessorEditor* dexedEditors[8]; 43 | 44 | // Sliders for the MultiDexed parameters 45 | juce::Slider detuneSlider; 46 | juce::Slider panSlider; 47 | 48 | // Attach the sliders to the parameters 49 | std::unique_ptr detuneSliderAttachment; 50 | std::unique_ptr panSliderAttachment; 51 | 52 | // Labels for the sliders 53 | juce::Label detuneLabel; 54 | juce::Label panLabel; 55 | 56 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginAudioProcessorEditor) 57 | }; 58 | -------------------------------------------------------------------------------- /Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | #include 4 | #include 5 | #include 6 | 7 | // Build on FreeBSD with: 8 | // sed -i '' -e 's|stat64|stat|g' make_helpers/juce_SimpleBinaryBuilder.cpp 9 | // gmake CONFIG=Release 10 | // or 11 | // gmake CONFIG=Debug 12 | 13 | PluginAudioProcessor::PluginAudioProcessor() 14 | : apvts(*this, nullptr, "Parameters", createParameterLayout()), 15 | // juce::AudioProcessor(BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true) 16 | juce::AudioProcessor(BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo(), true)) 17 | { 18 | juce::OwnedArray pluginDescriptions; 19 | juce::KnownPluginList pluginList; 20 | juce::AudioPluginFormatManager pluginFormatManager; 21 | 22 | juce::VST3PluginFormat *vst3 = new juce::VST3PluginFormat(); 23 | pluginFormatManager.addFormat(vst3); 24 | 25 | juce::String pluginPath; 26 | 27 | // Check which operating system we are running on 28 | // and set the plugin path accordingly 29 | 30 | // Linux 31 | if (juce::SystemStats::getOperatingSystemType() & juce::SystemStats::OperatingSystemType::Linux) { 32 | pluginPath = "/usr/lib/vst3/Dexed.vst3"; 33 | } 34 | 35 | // Windows 36 | if (juce::SystemStats::getOperatingSystemType() & juce::SystemStats::OperatingSystemType::Windows) { 37 | pluginPath = "C:\\Program Files\\Common Files\\VST3\\Dexed.vst3\\Contents\\x86_64-win\\Dexed.vst3"; 38 | } 39 | 40 | // MacOS 41 | if (juce::SystemStats::getOperatingSystemType() & juce::SystemStats::OperatingSystemType::MacOSX) { 42 | pluginPath = "/Library/Audio/Plug-Ins/VST3/Dexed.vst3"; 43 | } 44 | 45 | // FreeBSD 46 | if (juce::SystemStats::getOperatingSystemType() == juce::SystemStats::OperatingSystemType::UnknownOS) { 47 | pluginPath = "/usr/local/lib/vst3/Dexed.vst3"; 48 | } 49 | 50 | // Print the plugin path or error if not found 51 | if (pluginPath.isEmpty()) { 52 | std::cout << "Error: Plugin not found" << std::endl; 53 | 54 | } else { 55 | std::cout << "Plugin Path: " << pluginPath.toStdString() << std::endl; 56 | } 57 | 58 | pluginList.scanAndAddFile(pluginPath, true, pluginDescriptions, 59 | *pluginFormatManager.getFormat(0)); 60 | 61 | // If no plugin was found, print an error 62 | if (pluginDescriptions.size() == 0) { 63 | std::cout << "Error: Dexed plugin not found" << std::endl; 64 | return; 65 | } 66 | 67 | juce::String msg("Error Loading Plugin: "); 68 | 69 | // Create a AudioPluginInstances from the pluginDescriptions 70 | // and put them in the dexedPluginInstances array 71 | for (int i = 0; i < numberOfInstances; i++) { 72 | dexedPluginInstances[i] = pluginFormatManager.createPluginInstance( 73 | *pluginDescriptions[0], getSampleRate(), getBlockSize(), msg); 74 | } 75 | 76 | // Check that the AudioPluginInstances were created, if not print an error 77 | for (int i = 0; i < numberOfInstances; i++) { 78 | if (dexedPluginInstances[i] == nullptr) { 79 | std::cout << msg.toStdString() << std::endl; 80 | return; 81 | } 82 | } 83 | 84 | for (int i = 0; i < numberOfInstances; i++) { 85 | // jassert the existence of the AudioPluginInstance 86 | jassert(dexedPluginInstances[i] != nullptr); 87 | } 88 | 89 | // Print the plugin name and vendor for each plugin instance 90 | for (int i = 0; i < numberOfInstances; i++) { 91 | std::cout << "Plugin Name: " << dexedPluginInstances[i]->getName().toStdString() << std::endl; 92 | } 93 | } 94 | 95 | PluginAudioProcessor::~PluginAudioProcessor() 96 | { 97 | // Release the plugins 98 | for (int i = 0; i < numberOfInstances; i++) { 99 | if (dexedPluginInstances[i] != nullptr) { 100 | dexedPluginInstances[i]->releaseResources(); 101 | } 102 | } 103 | } 104 | 105 | void PluginAudioProcessor::detune() 106 | { 107 | 108 | // Return if any of the plugin instances are null 109 | for (int i = 0; i < numberOfInstances; i++) { 110 | if (dexedPluginInstances[i] == nullptr) { 111 | return; 112 | } 113 | } 114 | 115 | // Detune the plugin instances in the range determined by the detuneSpread parameter 116 | float range = apvts.getRawParameterValue("detuneSpread")->load(); 117 | std::cout << "Using Detune Spread: " << range << std::endl; 118 | for (int i = 1; i < numberOfInstances; i++) { 119 | double detune = 0.5 - range/2.0 + i * range/numberOfInstances; 120 | std::cout << "Setting instance " << i << " to detune " << detune << std::endl; 121 | dexedPluginInstances[i]->getParameters()[3]->setValueNotifyingHost(detune); 122 | } 123 | 124 | } 125 | 126 | void PluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) 127 | { 128 | // Return if any of the plugin instances are null 129 | for (int i = 0; i < numberOfInstances; i++) { 130 | if (dexedPluginInstances[i] == nullptr) { 131 | return; 132 | } 133 | } 134 | 135 | int maximumExpectedSamplesPerBlock = samplesPerBlock; 136 | 137 | 138 | for (int i = 0; i < numberOfInstances; i++) { 139 | 140 | if (dexedPluginInstances[i] == nullptr) { 141 | return; 142 | } 143 | 144 | dexedPluginInstances[i]->releaseResources(); 145 | dexedPluginInstances[i]->setRateAndBufferSizeDetails(sampleRate, maximumExpectedSamplesPerBlock); 146 | 147 | // sync number of buses 148 | 149 | // TODO: Do we need nuberIfInstances instead of the hardcoded 2? 150 | for (int dir = 0; dir < 2; ++dir) { 151 | const bool isInput = (dir == 0); 152 | int expectedNumBuses = getBusCount(isInput); 153 | int requiredNumBuses1 = dexedPluginInstances[i]->getBusCount(isInput); 154 | 155 | for (; expectedNumBuses < requiredNumBuses1; expectedNumBuses++) 156 | dexedPluginInstances[i]->addBus(isInput); 157 | 158 | for (; requiredNumBuses1 < expectedNumBuses; requiredNumBuses1++) 159 | dexedPluginInstances[i]->removeBus(isInput); 160 | 161 | } 162 | 163 | dexedPluginInstances[i]->setBusesLayout(getBusesLayout()); 164 | dexedPluginInstances[i]->prepareToPlay(sampleRate, maximumExpectedSamplesPerBlock); 165 | 166 | // Set the program for each plugin instance 167 | dexedPluginInstances[i]->setCurrentProgram(5); 168 | 169 | } 170 | 171 | // Get the plugin state from the first plugin instance 172 | // and apply it to all plugin instances 173 | juce::MemoryBlock state; 174 | dexedPluginInstances[0]->getStateInformation(state); 175 | for (int i = 1; i < numberOfInstances; i++) { 176 | dexedPluginInstances[i]->setStateInformation(state.getData(), static_cast(state.getSize())); 177 | } 178 | 179 | // Configure the plugin instances to our liking 180 | 181 | detune(); 182 | 183 | for (int i = 0; i < dexedPluginInstances[0]->getParameters().size(); i++) { 184 | // Print the names of the parameters and their values 185 | // if (!dexedPluginInstances[0]->getParameterName(i).contains("MIDI CC")) { 186 | // std::cout << "Parameter " << i << ": " << dexedPluginInstances[0]->getParameterName(i).toStdString() << " = " << dexedPluginInstances[0]->getParameter(i) << std::endl; 187 | // } 188 | // Add listener to each parameter 189 | juce::AudioProcessorParameter* parameter = dexedPluginInstances[0]->getParameters()[i]; 190 | 191 | parameter->addListener(this); 192 | } 193 | 194 | // Add apvts listener for detuneSpread in order to call detune() when it changes 195 | apvts.addParameterListener("detuneSpread", this); 196 | 197 | // Add apvts listener for panSpread in order to print a message when it changes 198 | apvts.addParameterListener("panSpread", this); 199 | 200 | // Add apvts listener for all parameters to update the other instances when they change 201 | // for (int i = 0; i < getParameters().size(); i++) { 202 | // juce::AudioProcessorParameter* parameter = getParameters()[i]; 203 | // apvts.addParameterListener(parameter->getName(0), this); 204 | // } 205 | 206 | // Add aptvs listener for all parameters in instance 0 in order to update the other instances when they change 207 | // for (int i = 0; i < dexedPluginInstances[0]->getParameters().size(); i++) { 208 | // juce::AudioProcessorParameter* parameter = dexedPluginInstances[0]->getParameters()[i]; 209 | // apvts.addParameterListener(parameter->getName(0), this); 210 | // } 211 | 212 | } 213 | 214 | void PluginAudioProcessor::releaseResources() 215 | { 216 | // Release the plugins 217 | for (int i = 0; i < numberOfInstances; i++) { 218 | if (dexedPluginInstances[i] != nullptr) { 219 | dexedPluginInstances[i]->releaseResources(); 220 | } 221 | } 222 | } 223 | 224 | void PluginAudioProcessor::processBlock(juce::AudioBuffer &buffer, 225 | juce::MidiBuffer &midiMessages) 226 | { 227 | int numberOfUnmutedInstances = 0; 228 | for (int i = 1; i < numberOfInstances; i++) { 229 | if (dexedPluginInstances[i]->getParameters()[2]->getValue()>0) { 230 | numberOfUnmutedInstances++; 231 | } 232 | } 233 | for (int i = 0; i < numberOfInstances; i++) { 234 | // NOTE: Even though we don't use the sound of plugin instance 0, we still need to process it for the GUI to work 235 | // Make empty initialized buffer for each plugin instance in dexedPluginBuffers 236 | dexedPluginBuffers[i] = juce::AudioBuffer(buffer.getNumChannels(), buffer.getNumSamples()); 237 | // Process the audio through each plugin instance 238 | if (dexedPluginInstances[i]) { 239 | dexedPluginInstances[i]->processBlock(dexedPluginBuffers[i], midiMessages); 240 | } 241 | } 242 | 243 | // TODO: If we don't want artifacts when panSpread is automated, 244 | // we need to make sure that the panSpread value gets smoothed between its old and new value? 245 | float panAmountFactor = apvts.getRawParameterValue("panSpread")->load(); 246 | // std::cout << "Using Pan Spread: " << panAmountFactor << std::endl; 247 | // Combine the sound of all the plugin instances 248 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 249 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 250 | double sum = 0; 251 | int i; 252 | for (i = 1; i < numberOfInstances; i++) { 253 | // if numberOfInstances is 9, pan for instance 1 is 0.0, for instance 2 is 0.14, for instance 3 is 0.28, for instance 4 is 0.42, for instance 5 is 0.57, for instance 6 is 0.71, for instance 7 is 0.85, for instance 8 is 1.0 254 | // if numberOfInstances is 8, pan for instance 1 is 0.0, for instance 2 is 0.17, for instance 3 is 0.33, for instance 4 is 0.5, for instance 5 is 0.67, for instance 6 is 0.83, for instance 7 is 1.0 255 | // if numberOfInstances is 7, pan for instance 1 is 0.0, for instance 2 is 0.2, for instance 3 is 0.4, for instance 4 is 0.6, for instance 5 is 0.8, for instance 6 is 1.0 256 | // if numberOfInstances is 6, pan for instance 1 is 0.0, for instance 2 is 0.25, for instance 3 is 0.5, for instance 4 is 0.75, for instance 5 is 1.0 257 | // if numberOfInstances is 5, pan for instance 1 is 0.0, for instance 2 is 0.33, for instance 3 is 0.66, for instance 4 is 1.0 258 | // if numberOfInstances is 4, pan for instance 1 is 0.0, for instance 2 is 0.5, for instance 3 is 1.0 259 | // if numberOfInstances is 3, pan for instance 1 is 0.0, for instance 2 is 1.0 260 | // if numberOfInstances is 2, pan for instance 1 is 0.0 261 | // if numberOfInstances is 1, pan for instance 1 is 0.0 262 | // Considering the above, the pan for instance i is (i-1)/(numberOfInstances-1) 263 | double pan = (i-1.0)/(numberOfInstances-1.0); 264 | 265 | // if (channel == 0) { 266 | // // Left channel 267 | // sum += dexedPluginBuffers[i].getSample(channel, sample) * (1.0 - pan); 268 | // } 269 | // else { 270 | // // Right channel 271 | // sum += dexedPluginBuffers[i].getSample(channel, sample) * pan; 272 | // } 273 | 274 | // Do the same but don't apply panning fully, only apply it by panSpread % 275 | 276 | 277 | 278 | // if (channel == 0) { 279 | // // Left channel 280 | // sum += dexedPluginBuffers[i].getSample(channel, sample) * (1.0 - panAmountFactor * pan); 281 | // } 282 | // else { 283 | // // Right channel 284 | // sum += dexedPluginBuffers[i].getSample(channel, sample) * (panAmountFactor * pan + (1.0 - panAmountFactor)); 285 | // } 286 | 287 | // Normalization factor, taking into account the number of unmuted instances and the pan amount factor 288 | double normalizationFactor = 1.0 / (numberOfUnmutedInstances * (1.0 - panAmountFactor * pan) + numberOfUnmutedInstances * (panAmountFactor * pan + (1.0 - panAmountFactor))); 289 | 290 | // Do the above but also apply normalization 291 | if (channel == 0) { 292 | // Left channel 293 | sum += dexedPluginBuffers[i].getSample(channel, sample) * (1.0 - panAmountFactor * pan) * normalizationFactor; 294 | } 295 | else { 296 | // Right channel 297 | sum += dexedPluginBuffers[i].getSample(channel, sample) * (panAmountFactor * pan + (1.0 - panAmountFactor)) * normalizationFactor; 298 | } 299 | 300 | // FIXME: Stereo not centered when panSpread is > 0.0 301 | // Something must be wrong because when one increases the spread, the stereo is no longer balanced 302 | // Maybe something is not linear? 303 | // What do we need to change so that the stereo is balanced when the spread is 0.0 and when the spread is 1.0 304 | // and for all values in between? 305 | 306 | } 307 | 308 | 309 | 310 | // buffer.setSample(channel, sample, sum / numberOfUnmutedstances); 311 | buffer.setSample(channel, sample, sum); 312 | 313 | } 314 | } 315 | } 316 | 317 | juce::AudioProcessorEditor *PluginAudioProcessor::createEditor() 318 | { 319 | 320 | // Return if any of the plugin instances are null 321 | for (int i = 0; i < numberOfInstances; i++) { 322 | if (dexedPluginInstances[i] == nullptr) { 323 | return nullptr; 324 | } 325 | } 326 | 327 | return new PluginAudioProcessorEditor(*this); 328 | } 329 | 330 | void PluginAudioProcessor::getStateInformation(juce::MemoryBlock &destData) { 331 | // Return state of instance 0 332 | if (dexedPluginInstances[0] != nullptr) { 333 | return dexedPluginInstances[0]->getStateInformation(destData); 334 | } 335 | // TODO: Should probably save the state of all instances individually; 336 | // how do other plugin hosts save the state of multiple plugin instances? 337 | } 338 | 339 | void PluginAudioProcessor::setStateInformation(const void *data, int sizeInBytes) { 340 | // Set state of all instances, but prevent infinite loop 341 | if (dexedPluginInstances[0] != nullptr) { 342 | shouldSynchronize = false; 343 | for (int i = 0; i < numberOfInstances; i++) { 344 | dexedPluginInstances[i]->setStateInformation(data, sizeInBytes); 345 | } 346 | detune(); 347 | shouldSynchronize = true; 348 | } 349 | // TODO: Should probably load the state of all instances from the saved state individually; 350 | // how do other plugin hosts save the state of multiple plugin instances? 351 | } 352 | 353 | //============================================================================== 354 | // This creates new instances of the plugin.. 355 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() 356 | { 357 | return new PluginAudioProcessor(); 358 | } 359 | 360 | const juce::String PluginAudioProcessor::getName() const 361 | { 362 | return JucePlugin_Name; 363 | } 364 | 365 | bool PluginAudioProcessor::acceptsMidi() const 366 | { 367 | #if JucePlugin_WantsMidiInput 368 | return true; 369 | #else 370 | return false; 371 | #endif 372 | } 373 | 374 | bool PluginAudioProcessor::producesMidi() const 375 | { 376 | #if JucePlugin_ProducesMidiOutput 377 | return true; 378 | #else 379 | return false; 380 | #endif 381 | } 382 | 383 | bool PluginAudioProcessor::isMidiEffect() const 384 | { 385 | #if JucePlugin_IsMidiEffect 386 | return true; 387 | #else 388 | return false; 389 | #endif 390 | } 391 | 392 | double PluginAudioProcessor::getTailLengthSeconds() const 393 | { 394 | return dexedPluginInstances[0]->getTailLengthSeconds(); 395 | } 396 | 397 | int PluginAudioProcessor::getNumPrograms() 398 | { 399 | return dexedPluginInstances[0]->getNumPrograms(); 400 | } 401 | 402 | int PluginAudioProcessor::getCurrentProgram() 403 | { 404 | return dexedPluginInstances[0]->getCurrentProgram(); 405 | } 406 | 407 | // setCurrentProgram() is called when the user changes the program in the host 408 | void PluginAudioProcessor::setCurrentProgram(int index) 409 | { 410 | // Return if any of the plugin instances are null 411 | for (int i = 0; i < numberOfInstances; i++) { 412 | if (dexedPluginInstances[i] == nullptr) { 413 | return; 414 | } 415 | } 416 | 417 | // Update the program in instance 0, the other instances will follow 418 | dexedPluginInstances[0]->setCurrentProgram(index); 419 | } 420 | 421 | const juce::String PluginAudioProcessor::getProgramName(int index) 422 | { 423 | // Return if any of the plugin instances are null 424 | for (int i = 0; i < numberOfInstances; i++) { 425 | if (dexedPluginInstances[i] == nullptr) { 426 | return ""; 427 | } 428 | } 429 | 430 | return dexedPluginInstances[0]->getProgramName(index); 431 | } 432 | 433 | void PluginAudioProcessor::changeProgramName(int index, const juce::String &newName) { 434 | // Return if any of the plugin instances are null 435 | for (int i = 0; i < numberOfInstances; i++) { 436 | if (dexedPluginInstances[i] == nullptr) { 437 | return; 438 | } 439 | } 440 | 441 | dexedPluginInstances[0]->changeProgramName(index, newName); 442 | } 443 | 444 | bool PluginAudioProcessor::hasEditor() const 445 | { 446 | // Only permit editor to open if plugins instantiated properly 447 | for (int i = 0; i < numberOfInstances; i++) { 448 | if (dexedPluginInstances[i] == nullptr) { 449 | return false; 450 | } 451 | } 452 | return true; 453 | } 454 | 455 | bool PluginAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const 456 | { 457 | // Return if any of the plugin instances are null 458 | for (int i = 0; i < numberOfInstances; i++) { 459 | if (dexedPluginInstances[i] == nullptr) { 460 | return false; 461 | } 462 | } 463 | 464 | return (layouts.getMainOutputChannelSet() == juce::AudioChannelSet::stereo()); 465 | } 466 | 467 | // // Because we inherit from juce::AudioProcessorValueTreeState::Listener, we need to implement this method 468 | void PluginAudioProcessor::parameterChanged(const juce::String ¶meterID, float newValue) 469 | { 470 | DBG("parameterChanged() called with parameterID = " + parameterID + " and newValue = " + juce::String(newValue)); 471 | // If the parameterID is "detuneSpread", then we need to call detune() 472 | if (parameterID == "detuneSpread") { 473 | shouldSynchronize = false; 474 | detune(); 475 | shouldSynchronize = true; 476 | } 477 | } 478 | 479 | // Because we inherit from juce::AudioProcessorParameter::Listener, we need to implement this method 480 | void PluginAudioProcessor::parameterValueChanged(int parameterIndex, float newValue) // We can't know which set of parameters the index refers to; FIXME 481 | { 482 | // Return if any of the plugin instances are null 483 | for (int i = 0; i < numberOfInstances; i++) { 484 | if (dexedPluginInstances[i] == nullptr) { 485 | return; 486 | } 487 | } 488 | 489 | // Get the parameter that changed 490 | juce::AudioProcessorParameter* parameter = nullptr; 491 | for (int i = 0; i < dexedPluginInstances[0]->getParameters().size(); i++) { 492 | if (dexedPluginInstances[0]->getParameters()[i]->getParameterIndex() == parameterIndex) { 493 | parameter = dexedPluginInstances[0]->getParameters()[i]; 494 | break; 495 | } 496 | } 497 | 498 | if (parameter == nullptr) { 499 | // Parameter not found, return 500 | return; 501 | } 502 | 503 | // Get the name of the parameter 504 | juce::String parameterName = parameter->getName(100); 505 | 506 | // We cannot distinguish between changes in the plugin instance and changes in the host, 507 | // so for now we just call detune() whenever the parameter changes, even if the user 508 | // changed the parameter with the same index in plugin instance 0 rather than the host 509 | 510 | std::cout << "Parameter " << parameterIndex << ": " << parameterName.toStdString() << " = " << newValue << std::endl; 511 | 512 | // Update the value of the parameter in all other plugin instances 513 | for (int i = 1; i < numberOfInstances; i++) { 514 | if (shouldSynchronize) { 515 | for (int j = 0; j < dexedPluginInstances[i]->getParameters().size(); j++) { 516 | if (dexedPluginInstances[i]->getParameters()[j]->getParameterIndex() == parameterIndex) { 517 | dexedPluginInstances[i]->getParameters()[j]->setValueNotifyingHost(newValue); 518 | break; 519 | } 520 | } 521 | } 522 | // FIXME: Why does the above work for some parameters but not others (e.g. "OP1 F COARSE")? 523 | } 524 | 525 | // When a cartridge is loaded, update the parameters of all instances 526 | // TODO: Find a better trigger for this, e.g. when the user clicks "Load Cartridge" 527 | if (parameterIndex == 2236) { 528 | // Synchronize the plugin state from instance 0 to all other instances 529 | // Get the state of instance 0 530 | juce::MemoryBlock state; 531 | dexedPluginInstances[0]->getStateInformation(state); 532 | for (int i = 1; i < numberOfInstances; i++) { 533 | dexedPluginInstances[i]->setStateInformation(state.getData(), static_cast(state.getSize())); 534 | } 535 | detune(); 536 | // Update the names of all programs exposed by the plugin to the host 537 | updateHostDisplay(); // TODO: Why does this not work? How can we update the menu containing the progams in the host? 538 | // dexedPluginInstances[0]->updateHostDisplay(); // Does not work either 539 | 540 | // Change the program to the one selected in instance 0 541 | setCurrentProgram(dexedPluginInstances[0]->getCurrentProgram()); 542 | } 543 | } 544 | 545 | // Because we inherit from juce::AudioProcessorParameter::Listener, we need to implement this method 546 | void PluginAudioProcessor::parameterGestureChanged(int parameterIndex, bool gestureIsStarting) 547 | { 548 | // Not used 549 | } 550 | 551 | juce::AudioProcessorValueTreeState::ParameterLayout PluginAudioProcessor::createParameterLayout(){ 552 | std::vector> parameters; 553 | parameters.push_back(std::make_unique("detuneSpread", // parameterID 554 | "Detune Spread", // parameter name 555 | 0.0f, // minimum value 556 | 0.4f, // maximum value 557 | 0.1f)); // default value 558 | parameters.push_back(std::make_unique("panSpread", // parameterID 559 | "Pan Spread", // parameter name 560 | 0.0f, // minimum value 561 | 1.0f, // maximum value 562 | 1.0f)); // default value 563 | return { parameters.begin(), parameters.end() }; 564 | } 565 | -------------------------------------------------------------------------------- /Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class PluginAudioProcessor : public juce::AudioProcessor, 18 | juce::AudioProcessorParameter::Listener, 19 | juce::AudioProcessorValueTreeState::Listener 20 | // https://www.youtube.com/watch?v=Bw_OkHNpj1M&t=1990s 21 | #if JucePlugin_Enable_ARA 22 | , 23 | public juce::AudioProcessorARAExtension 24 | #endif 25 | { 26 | public: 27 | //============================================================================== 28 | PluginAudioProcessor(); 29 | ~PluginAudioProcessor() override; 30 | 31 | //============================================================================== 32 | void prepareToPlay(double sampleRate, int samplesPerBlock) override; 33 | void releaseResources() override; 34 | 35 | #ifndef JucePlugin_PreferredChannelConfigurations 36 | bool isBusesLayoutSupported(const BusesLayout &layouts) const override; 37 | #endif 38 | 39 | void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; 40 | 41 | //============================================================================== 42 | juce::AudioProcessorEditor *createEditor() override; 43 | bool hasEditor() const override; 44 | 45 | //============================================================================== 46 | const juce::String getName() const override; 47 | 48 | bool acceptsMidi() const override; 49 | bool producesMidi() const override; 50 | bool isMidiEffect() const override; 51 | double getTailLengthSeconds() const override; 52 | 53 | //============================================================================== 54 | int getNumPrograms() override; 55 | int getCurrentProgram() override; 56 | void setCurrentProgram(int index) override; 57 | const juce::String getProgramName(int index) override; 58 | void changeProgramName(int index, const juce::String &newName) override; 59 | 60 | //============================================================================== 61 | void getStateInformation(juce::MemoryBlock &destData) override; 62 | void setStateInformation(const void *data, int sizeInBytes) override; 63 | 64 | const int numberOfInstances = 5; 65 | 66 | bool shouldSynchronize = true; 67 | 68 | // Make an array that can hold numberOfInstances juce::AudioProcessor instances 69 | std::array, 5> dexedPluginInstances; 70 | 71 | // Buffers for the plugin instances 72 | std::array, 5> dexedPluginBuffers; 73 | 74 | // Because we inherit from juce::AudioProcessorValueTreeState::Listener, we need to implement this method 75 | void parameterChanged(const juce::String ¶meterID, float newValue) override; 76 | 77 | // Because we inherit from juce::AudioProcessorParameter::Listener, we need to implement these methods 78 | void parameterValueChanged(int parameterIndex, float newValue) override; 79 | void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override; 80 | 81 | // Method to detune the plugin instances 82 | void detune(); 83 | 84 | juce::AudioProcessorValueTreeState apvts; 85 | 86 | private: 87 | //============================================================================== 88 | // Declare parameterListener to be a juce::AudioProcessorParameter::Listener 89 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 90 | 91 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginAudioProcessor) 92 | }; 93 | -------------------------------------------------------------------------------- /build/build-linux.sh: -------------------------------------------------------------------------------- 1 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 2 | 3 | "$ROOT/build/bin/JUCE/Projucer" --resave "$ROOT/MultiDexed.jucer" 4 | 5 | cd "$ROOT/Builds/LinuxMakefile" 6 | make CONFIG=Release 7 | -------------------------------------------------------------------------------- /build/build-osx.sh: -------------------------------------------------------------------------------- 1 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 2 | 3 | # Resave jucer files 4 | "$ROOT/build/bin/JUCE/Projucer.app/Contents/MacOS/Projucer" --resave "$ROOT/MultiDexed.jucer" 5 | 6 | cd "$ROOT/Builds/MacOSX" 7 | xcodebuild -configuration Release || exit 1 8 | -------------------------------------------------------------------------------- /build/build-win.sh: -------------------------------------------------------------------------------- 1 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 2 | 3 | # Resave jucer files 4 | "$ROOT/build/bin/JUCE/Projucer.exe" --resave "$ROOT/MultiDexed.jucer" 5 | 6 | VS_WHERE="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" 7 | 8 | MSBUILD_EXE=$("$VS_WHERE" -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe") 9 | echo $MSBUILD_EXE 10 | 11 | cd "$ROOT/Builds/VisualStudio2019" 12 | "$MSBUILD_EXE" "MultiDexed.sln" "//p:VisualStudioVersion=19.0" "//m" "//t:Build" "//p:Configuration=Release" "//p:PlatformTarget=x64" "//p:PreferredToolArchitecture=x64" 13 | -------------------------------------------------------------------------------- /build/download-projucer.sh: -------------------------------------------------------------------------------- 1 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 2 | cd "$ROOT" 3 | echo "$ROOT" 4 | 5 | # Get the Projucer version 6 | cd "$ROOT/JUCE" 7 | JUCE_HASH=`git rev-parse HEAD` 8 | JUCE_VERSION=`git tag --points-at HEAD` 9 | echo "Juce Hash: $JUCE_HASH, Juce Version: $JUCE_VERSION" 10 | 11 | # Download the Projucer 12 | mkdir -p "$ROOT/build/bin" 13 | cd "$ROOT/build/bin" 14 | 15 | curl -v -L "https://github.com/juce-framework/JUCE/releases/download/$JUCE_VERSION/juce-$JUCE_VERSION-$OS.zip" --output "$ROOT/build/bin/JUCE.zip" 16 | unzip JUCE.zip 17 | --------------------------------------------------------------------------------