├── .clang-format ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── labeler.yml └── workflows │ ├── auto-format.yml │ ├── cmake.yml │ ├── ios.yml │ ├── labeler.yml │ └── manual.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── installers ├── mac │ ├── ChowMatrix.pkgproj │ ├── Intro.txt │ └── build_mac_installer.sh └── windows │ ├── ChowMatrix_Install_Script.iss │ ├── build_win_installer.sh │ └── chowmatrix.ico ├── manual ├── .gitignore ├── ChowMatrixManual.pdf ├── ChowMatrixManual.tex ├── Makefile ├── manual.cls └── screenshots │ ├── BottomBar.png │ ├── DetailsView.png │ ├── GraphView.png │ └── full_gui.png ├── modules ├── CMakeLists.txt ├── cmake │ ├── SubprojectVersion.cmake │ ├── WarningFlags.cmake │ └── pluginval.cmake └── polylogarithm │ ├── CMakeLists.txt │ ├── Li2.cpp │ └── Li2.hpp ├── res ├── CMakeLists.txt ├── Diceroll.png ├── LeftArrow.svg ├── Panic.png ├── RightArrow.svg ├── cog-solid.svg ├── ellipsis-h-solid.png ├── gui.xml ├── gui_ios.xml ├── home.svg ├── logo.png ├── logo.svg ├── presets │ ├── Chord.chowpreset │ ├── Crazy.chowpreset │ ├── Default.chowpreset │ ├── Lush.chowpreset │ ├── Rhythmic.chowpreset │ ├── Wobble.chowpreset │ └── Wonky.chowpreset └── undo-solid.png ├── scripts ├── aax_builds.sh ├── ios_build.sh ├── mac_builds.sh └── win_builds.sh └── src ├── CMakeLists.txt ├── ChowMatrix.cpp ├── ChowMatrix.h ├── NodeManager.cpp ├── NodeManager.h ├── dsp ├── BaseNode.cpp ├── BaseNode.h ├── Delay │ ├── DelayProc.cpp │ ├── DelayProc.h │ ├── DelayStore.h │ ├── Diffusion.cpp │ ├── Diffusion.h │ ├── PitchShiftWrapper.cpp │ ├── PitchShiftWrapper.h │ ├── Reverser.cpp │ ├── Reverser.h │ ├── TempoSyncUtils.h │ ├── VariableDelay.cpp │ └── VariableDelay.h ├── DelayNode.cpp ├── DelayNode.h ├── Distortion │ ├── Distortion.cpp │ ├── Distortion.h │ ├── LookupTables.cpp │ └── LookupTables.h ├── InputNode.cpp ├── InputNode.h ├── Parameters │ ├── BaseController.cpp │ ├── BaseController.h │ ├── DelayTypeControl.cpp │ ├── DelayTypeControl.h │ ├── HostParamControl.cpp │ ├── HostParamControl.h │ ├── InsanityControl.cpp │ ├── InsanityControl.h │ ├── InsanityLockHelper.cpp │ ├── InsanityLockHelper.h │ ├── NodeParamControl.cpp │ ├── NodeParamControl.h │ ├── ParamHelpers.cpp │ ├── ParamHelpers.h │ ├── RandomiseLockHelper.cpp │ ├── RandomiseLockHelper.h │ ├── SyncControl.cpp │ └── SyncControl.h ├── ProcessorBase.h └── ProcessorChain.h ├── gui ├── AutoUpdating.cpp ├── AutoUpdating.h ├── BottomBar │ ├── ABComp.cpp │ ├── ABComp.h │ ├── BottomBarLNF.cpp │ ├── BottomBarLNF.h │ ├── HostControlMenu.cpp │ ├── HostControlMenu.h │ ├── HostControlMenuComp.cpp │ ├── HostControlMenuComp.h │ ├── TextSlider.h │ ├── TextSliderItem.h │ └── WetGainSlider.h ├── DetailsView │ ├── NodeDetails.cpp │ ├── NodeDetails.h │ ├── NodeDetailsComponent.cpp │ ├── NodeDetailsComponent.h │ ├── NodeDetailsGUI.cpp │ ├── NodeDetailsGUI.h │ ├── NodeDetailsViewport.cpp │ └── NodeDetailsViewport.h ├── IOSUtils │ ├── LongPressActionHelper.cpp │ ├── LongPressActionHelper.h │ ├── PopupMenuOptionsHelpers.h │ ├── TipJar.cpp │ ├── TipJar.h │ └── TwoFingerDragToScrollListener.h ├── LookAndFeel │ ├── InsanityLNF.h │ └── PresetsLNF.h ├── MatrixView │ ├── DelayNodeComponent.cpp │ ├── DelayNodeComponent.h │ ├── GraphView.cpp │ ├── GraphView.h │ ├── GraphViewItem.h │ ├── GraphViewport.cpp │ ├── GraphViewport.h │ ├── InputNodeComponent.h │ ├── MatrixAurora.cpp │ ├── MatrixAurora.h │ ├── NodeCompManager.cpp │ ├── NodeCompManager.h │ ├── NodeComponent.cpp │ └── NodeComponent.h ├── NodeInfo.h ├── ParamSlider.cpp └── ParamSlider.h ├── headless ├── CMakeLists.txt ├── PresetResaver.cpp ├── PresetResaver.h ├── ScreenshotGenerator.cpp ├── ScreenshotGenerator.h ├── UnitTests │ ├── DelayClearTest.cpp │ ├── HostControlTest.cpp │ ├── PerformanceTest.cpp │ ├── SaveLoadSpeedTest.cpp │ ├── UnitTests.cpp │ └── UnitTests.h └── main.cpp ├── pch.h └── state ├── PresetManager.cpp ├── PresetManager.h ├── StateManager.cpp └── StateManager.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Left 7 | AlignOperands: Align 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Never 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: Yes 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | BreakAfterJavaFieldAnnotations: false 22 | BreakBeforeBinaryOperators: NonAssignment 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializersBeforeComma: false 26 | BreakStringLiterals: false 27 | ColumnLimit: 0 28 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 29 | ConstructorInitializerIndentWidth: 4 30 | ContinuationIndentWidth: 4 31 | Cpp11BracedListStyle: false 32 | DerivePointerAlignment: false 33 | DisableFormat: false 34 | ExperimentalAutoDetectBinPacking: false 35 | ForEachMacros: ['forEachXmlChildElement'] 36 | IndentCaseLabels: true 37 | IndentWidth: 4 38 | IndentWrappedFunctionNames: true 39 | KeepEmptyLinesAtTheStartOfBlocks: false 40 | Language: Cpp 41 | MaxEmptyLinesToKeep: 1 42 | NamespaceIndentation: Inner 43 | PointerAlignment: Left 44 | ReflowComments: false 45 | SortIncludes: true 46 | SpaceAfterCStyleCast: true 47 | SpaceAfterLogicalNot: true 48 | SpaceBeforeAssignmentOperators: true 49 | SpaceBeforeCpp11BracedList: true 50 | SpaceBeforeParens: NonEmptyParentheses 51 | SpaceInEmptyParentheses: false 52 | SpaceBeforeInheritanceColon: true 53 | SpacesInAngles: false 54 | SpacesInCStyleCastParentheses: false 55 | SpacesInContainerLiterals: true 56 | SpacesInParentheses: false 57 | SpacesInSquareBrackets: false 58 | Standard: "c++17" 59 | TabWidth: 4 60 | UseTab: Never 61 | --- 62 | Language: ObjC 63 | BasedOnStyle: Chromium 64 | AlignTrailingComments: true 65 | BreakBeforeBraces: Allman 66 | ColumnLimit: 0 67 | IndentWidth: 4 68 | KeepEmptyLinesAtTheStartOfBlocks: false 69 | ObjCSpaceAfterProperty: true 70 | ObjCSpaceBeforeProtocolList: true 71 | PointerAlignment: Left 72 | SpacesBeforeTrailingComments: 1 73 | TabWidth: 4 74 | UseTab: Never 75 | ... 76 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # repo: Chowdhury-DSP/ChowMatrix 2 | # filename: FUNDING.YML 3 | 4 | patreon: chowdsp 5 | custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=93S67ZSKMBG68&source=url 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: jatinchowdhury18 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Windows 10, MacOS] 28 | - DAW [e.g. Ableton Live, FL Studio, Audacity] 29 | - Version [e.g. 2.3] 30 | - 32/64 bit (Windows only) 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: jatinchowdhury18 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # labeler.yml: 2 | # Assign labels to pull requests that change various files 3 | # For more information, see https://github.com/actions/labeler 4 | 5 | dsp: 6 | - src/dsp/**/* 7 | 8 | gui: 9 | - src/gui/**/* 10 | 11 | presets: 12 | - src/state/presets/* 13 | - res/presets/* 14 | 15 | documentation: 16 | - manual/**/* 17 | - README.md 18 | 19 | ci-pipeline: 20 | - .github/workflows/* 21 | 22 | installers: 23 | - installers/**/* 24 | -------------------------------------------------------------------------------- /.github/workflows/auto-format.yml: -------------------------------------------------------------------------------- 1 | name: Auto-formatter 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - develop 8 | paths: 9 | - '**.cpp' 10 | - '**.h' 11 | 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build_and_test: 16 | if: contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false 17 | name: Apply clang-format to pull request 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Install Linux Deps 22 | run: | 23 | wget https://apt.llvm.org/llvm.sh 24 | chmod +x llvm.sh 25 | sudo ./llvm.sh 11 26 | sudo apt-get install clang-format-11 27 | clang-format-11 --version 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | with: 32 | persist-credentials: false 33 | fetch-depth: 0 34 | 35 | - name: Run clang-format 36 | shell: bash 37 | run: find src/ -iname *.h -o -iname *.cpp | xargs clang-format-11 -style=file -verbose -i 38 | 39 | - name: Commit & Push changes 40 | uses: actions-js/push@master 41 | with: 42 | message: "Apply clang-format" 43 | branch: ${{ github.head_ref }} 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | paths-ignore: 9 | - 'bin/**' 10 | - 'manual/**' 11 | - '*.sh' 12 | - '*.md' 13 | pull_request: 14 | branches: 15 | - main 16 | - develop 17 | paths-ignore: 18 | - 'bin/**' 19 | - 'manual/**' 20 | - '*.sh' 21 | - '*.md' 22 | 23 | workflow_dispatch: 24 | 25 | jobs: 26 | build_and_test: 27 | if: contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false 28 | name: Test ChowMatrix on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | strategy: 31 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 32 | matrix: 33 | include: 34 | - os: ubuntu-latest 35 | cmake_args: "-DBUILD_HEADLESS=ON" 36 | build_args: "" 37 | - os: windows-latest 38 | cmake_args: "-DRUN_PLUGINVAL=ON" 39 | build_args: "--target pluginval-all" 40 | - os: macos-latest 41 | cmake_args: "-DRUN_PLUGINVAL=ON" 42 | build_args: "--target pluginval-all" 43 | 44 | steps: 45 | - name: Install Linux Deps 46 | if: runner.os == 'Linux' 47 | run: | 48 | sudo apt-get update 49 | sudo apt install libasound2-dev libcurl4-openssl-dev libx11-dev libxinerama-dev libxext-dev libfreetype6-dev libwebkit2gtk-4.0-dev libglu1-mesa-dev libjack-jackd2-dev lv2-dev 50 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9 51 | sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 9 52 | 53 | - name: Get latest CMake 54 | uses: lukka/get-cmake@latest 55 | 56 | - name: Checkout code 57 | uses: actions/checkout@v2 58 | with: 59 | submodules: true 60 | 61 | - name: Configure 62 | env: 63 | CMAKE_ARGS: ${{ matrix.cmake_args }} 64 | shell: bash 65 | run: cmake -Bbuild $CMAKE_ARGS 66 | 67 | - name: Build 68 | env: 69 | BUILD_ARGS: ${{ matrix.build_args }} 70 | shell: bash 71 | run: cmake --build build --parallel 4 $BUILD_ARGS 72 | 73 | # - name: Setup debug session 74 | # uses: mxschmitt/action-tmate@v3 75 | 76 | - name: Unit Tests 77 | if: runner.os == 'Linux' 78 | run: build/ChowMatrix --unit-tests --all 79 | 80 | # - name: Pluginval 81 | # if: runner.os == 'Windows' || runner.os == 'MacOS' 82 | # run: bash scripts/validate.sh 83 | -------------------------------------------------------------------------------- /.github/workflows/ios.yml: -------------------------------------------------------------------------------- 1 | name: CI-iOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | branches: 10 | - main 11 | - develop 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build_and_test: 16 | if: contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false 17 | name: Build AUv3 plugin for iOS 18 | runs-on: macos-latest 19 | 20 | steps: 21 | - name: Get latest CMake 22 | uses: lukka/get-cmake@latest 23 | 24 | - name: Checkout code 25 | uses: actions/checkout@v2 26 | with: 27 | submodules: recursive 28 | 29 | - uses: Apple-Actions/import-codesign-certs@v1 30 | with: 31 | p12-file-base64: ${{ secrets.IOS_CERTIFICATES_BASE64 }} 32 | p12-password: ${{ secrets.IOS_CERTIFICATES_PASS }} 33 | 34 | - uses: Apple-Actions/download-provisioning-profiles@v1 35 | with: 36 | bundle-id: com.chowdsp.ChowMatrix* 37 | issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} 38 | api-key-id: ${{ secrets.APPSTORE_KEY_ID }} 39 | api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }} 40 | 41 | - name: Configure 42 | shell: bash 43 | run: cmake -Bbuild-ios -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=11.4 -DCMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY="1,2" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE="Manual" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY="Apple Distribution" -DCMAKE_XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER="ChowMatrix" -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=${{ secrets.APPLE_TEAM_ID }} 44 | 45 | - name: Build 46 | shell: bash 47 | run: cmake --build build-ios --config Release --parallel 4 | xcpretty 48 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@main 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | name: manual 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'manual/**' 9 | 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build_and_test: 14 | name: Build and deploy manual 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Install Linux Deps 19 | run: sudo apt install texlive-xetex 20 | 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: false 25 | 26 | - name: Build manual 27 | working-directory: ${{github.workspace}}/manual 28 | run: make 29 | 30 | - name: Deploy manual 31 | uses: appleboy/scp-action@master 32 | with: 33 | host: ccrma-gate.stanford.edu 34 | username: jatin 35 | password: ${{ secrets.CCRMA_PASS }} 36 | source: "manual/ChowMatrixManual.pdf" 37 | target: "~/Library/Web/chowdsp/Products" 38 | strip_components: 1 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | build*/ 3 | bin/ 4 | *_Packaged 5 | *.exe 6 | *.dmg 7 | 8 | # IDE settings 9 | .vscode/ 10 | .idea/ 11 | 12 | # Mac extras 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/foleys_gui_magic"] 2 | path = modules/foleys_gui_magic 3 | url = https://github.com/Chowdhury-DSP/foleys_gui_magic.git 4 | [submodule "modules/chowdsp_utils"] 5 | path = modules/chowdsp_utils 6 | url = https://github.com/Chowdhury-DSP/chowdsp_utils.git 7 | [submodule "modules/JUCE"] 8 | path = modules/JUCE 9 | url = https://github.com/lv2-porting-project/JUCE 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.3.0] 2021-12-21 6 | - Added iOS/AUv3 support. 7 | - Added Pro Tools/AAX support. 8 | - Added forwarding parameters to expose node parameters to DAWs. 9 | - Added "Alien" delay mode. 10 | - Added "Reverse" delay parameter. 11 | - Added optional automatic gain compensation for wet signal. 12 | - Added lock/unlock options for parameter randomisation. 13 | - Fixed "hang" when bringing delay to 0 ms in "Analog" mode. 14 | - Improved performance for pan modulation. 15 | - Improved preset management system. 16 | - Added option to throttle graphics rendering for the Matrix View. 17 | - Improved memory usage. 18 | 19 | ## [1.2.0] 2021-04-23 20 | - Added new delay modes: 21 | - Liquid (Sinc interpolation) 22 | - Super Liquid (more ideal Sinc interpolation) 23 | - Lo-Fi (4096-stage BBD delay emulation) 24 | - Analog (16384-stage BBD delay emulation) 25 | - Added parameter modulation targets for host control. 26 | - Added insanity reset/insanity lock behaviour. 27 | - Support for Macs with Apple Silicon. 28 | - Created installer packages for Windows/Mac/Linux. 29 | 30 | ## [1.1.0] 2021-01-13 31 | - Added individual node modulation, for delay time and panning. 32 | - Added pitch-shifting and diffusion for each delay node. 33 | - Added LV2 compilation for Linux. 34 | - Made Graph View scrollable. 35 | - Added A/B buttons for switching plugin state. 36 | - Added individual node colouring. 37 | - Extended range for tempo-synced delay times. 38 | - Fixed node distortion bug in Logic/Garageband. 39 | - Fixed bug with incorrect delay times between sibling nodes. 40 | - Fixed preset menu not returning to currently selected preset. 41 | 42 | ## [1.0.0] 2020-11-25 43 | - Initial release. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, jatinchowdhury18 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHOW Matrix 2 | 3 | [![Download Latest](https://img.shields.io/badge/download-latest-blue.svg)](https://github.com/Chowdhury-DSP/ChowMatrix/releases/latest) 4 | ![CI](https://github.com/Chowdhury-DSP/ChowMatrix/workflows/CI/badge.svg) 5 | [![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 6 | [![Downloads](https://img.shields.io/github/downloads/Chowdhury-DSP/ChowMatrix/total)](https://somsubhra.github.io/github-release-stats/?username=Chowdhury-DSP&repository=ChowMatrix&page=1&per_page=30) 7 | 8 | CHOW Matrix is a delay effect, made up of an infinitely growable 9 | tree of delay lines, each with individual controls for feedback, 10 | panning, distortion, and more. ChowMatrix is available as a VST, 11 | VST3, AU, LV2, AUv3, AAX, and Standalone Application. 12 | 13 | 14 | ### Quick Links 15 | - [Latest Release](https://chowdsp.com/products.html#matrix) 16 | - [ChowMatrix for iOS](https://apps.apple.com/us/app/chowmatrix/id1598726146) 17 | - [Nightly Builds](https://chowdsp.com/nightly.html#matrix) 18 | - [User Manual](https://ccrma.stanford.edu/~jatin/chowdsp/Products/ChowMatrixManual.pdf) 19 | 20 | Pic 21 | 22 | 23 | ## Installing 24 | 25 | To download the latest release, visit the 26 | [official website](https://chowdsp.com/products.html#matrix). 27 | If you would like to try the most recent builds (potentially 28 | unstable) check out the 29 | [nightly builds](https://chowdsp.com/nightly.html#matrix). 30 | 31 | 32 | ## Building 33 | 34 | To build from source, you must have CMake installed. 35 | 36 | ```bash 37 | # Clone the repository 38 | $ git clone https://github.com/Chowdhury-DSP/ChowMatrix.git 39 | $ cd ChowMatrix 40 | 41 | # initialize and set up submodules 42 | $ git submodule update --init --recursive 43 | 44 | # build with CMake 45 | $ cmake -Bbuild 46 | $ cmake --build build --config Release 47 | ``` 48 | The built plugins will be located in `ChowMatrix/build/ChowMatrix_artefacts/` 49 | 50 | 51 | ## Credits 52 | 53 | - GUI Design - [Margus Mets](mailto:hello@mmcreative.eu) 54 | - GUI Framework - [Plugin GUI Magic](https://github.com/ffAudio/PluginGUIMagic) 55 | - Dilogarithm function - [Polylogarithm](https://github.com/Expander/polylogarithm) 56 | - Extra Icons - [FontAwesome](https://fontawesome.com/) 57 | 58 | 59 | ## License 60 | 61 | ChowMatrix is open source, and is licensed under the BSD 3-clause license. 62 | Enjoy! 63 | -------------------------------------------------------------------------------- /installers/mac/Intro.txt: -------------------------------------------------------------------------------- 1 | This application will install the ChowMatrix audio plugin version ##APPVERSION## to your computer. 2 | -------------------------------------------------------------------------------- /installers/mac/build_mac_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script_file=ChowMatrix.pkgproj 4 | 5 | app_version=$(cut -f 2 -d '=' <<< "$(grep 'CMAKE_PROJECT_VERSION:STATIC' ../../build/CMakeCache.txt)") 6 | echo "Setting app version: $app_version..." 7 | sed -i '' "s/##APPVERSION##/${app_version}/g" $script_file 8 | sed -i '' "s/##APPVERSION##/${app_version}/g" Intro.txt 9 | 10 | echo "Copying License..." 11 | cp ../../LICENSE LICENSE.txt 12 | 13 | # build installer 14 | echo Building... 15 | /usr/local/bin/packagesbuild $script_file 16 | 17 | # reset version number 18 | sed -i '' "s/${app_version}/##APPVERSION##/g" $script_file 19 | sed -i '' "s/${app_version}/##APPVERSION##/g" Intro.txt 20 | 21 | # clean up license file 22 | rm LICENSE.txt 23 | 24 | # sign the installer package 25 | echo "Signing installer package..." 26 | TEAM_ID=$(more ~/Developer/mac_id) 27 | pkg_dir=ChowMatrix_Installer_Packaged 28 | rm -Rf $pkg_dir 29 | mkdir $pkg_dir 30 | productsign -s "$TEAM_ID" ../../build/ChowMatrix.pkg $pkg_dir/ChowMatrix-signed.pkg 31 | 32 | echo "Notarizing installer package..." 33 | INSTALLER_PASS=$(more ~/Developer/mac_installer_pass) 34 | npx notarize-cli --file $pkg_dir/ChowMatrix-signed.pkg --bundle-id com.chowdsp.ChowMatrix --asc-provider "$TEAM_ID" --username chowdsp@gmail.com --password "$INSTALLER_PASS" 35 | 36 | echo "Building disk image..." 37 | vol_name=Install_ChowMatrix-$app_version 38 | hdiutil create "$vol_name.dmg" -fs HFS+ -srcfolder $pkg_dir -format UDZO -volname "$vol_name" 39 | -------------------------------------------------------------------------------- /installers/windows/build_win_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_file=ChowMatrix_Install_Script.iss 4 | 5 | app_version=$(cut -f 2 -d '=' <<< "$(grep 'CMAKE_PROJECT_VERSION:STATIC' ../../build/CMakeCache.txt)") 6 | echo "Setting app version: $app_version..." 7 | sed -i "s/##APPVERSION##/${app_version}/g" $script_file 8 | 9 | # build installer 10 | echo Building... 11 | $"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" $script_file 12 | 13 | # reset version number 14 | sed -i "s/${app_version}/##APPVERSION##/g" $script_file 15 | 16 | exec="ChowMatrix-Win-$app_version.exe" 17 | direc=$PWD 18 | 19 | # sign 20 | # echo "Signing installer..." 21 | # $"cd" "C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x64" 22 | # $"./signtool.exe" sign -tr http://timestamp.digicert.com -td sha256 -fd sha256 -a -debug $direc/$exec 23 | # $"./signtool.exe" verify -pa $direc/$exec 24 | 25 | echo SUCCESS 26 | -------------------------------------------------------------------------------- /installers/windows/chowmatrix.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/installers/windows/chowmatrix.ico -------------------------------------------------------------------------------- /manual/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.log 3 | *.out 4 | *.sync* 5 | -------------------------------------------------------------------------------- /manual/ChowMatrixManual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/manual/ChowMatrixManual.pdf -------------------------------------------------------------------------------- /manual/Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = ChowMatrixManual.tex manual.cls 2 | TEX_FLAGS = 3 | 4 | PDFS = $(patsubst %.tex, %.pdf, $(SOURCES)) 5 | 6 | .PHONY: clean 7 | 8 | # Run xelatex twice to generate references 9 | %.pdf: %.tex 10 | xelatex $(TEX_FLAGS) $< 11 | xelatex $(TEX_FLAGS) $< 12 | 13 | all: $(PDFS) 14 | 15 | clean: 16 | rm -f *.{log,pdf,out,aux,fls,fdb*,sync*} 17 | 18 | deploy: $(PDFS) 19 | scp ChowMatrixManual.pdf jatin@ccrma-gate.stanford.edu:~/Library/Web/chowdsp/Products 20 | -------------------------------------------------------------------------------- /manual/manual.cls: -------------------------------------------------------------------------------- 1 | \LoadClass{article} 2 | 3 | % for figures 4 | \usepackage{graphicx} 5 | 6 | % Needed to manage colors 7 | \RequirePackage{xcolor} 8 | \colorlet{theme}{black} 9 | 10 | % Manage name 11 | \newcommand*{\pluginname}[1]{\def\@pluginname{#1}} 12 | 13 | % Manage spacing 14 | \def\newpar{\newline\newline} 15 | \usepackage[skip=2.5pt]{caption} 16 | \setlength{\belowcaptionskip}{-12pt} 17 | 18 | % Needed to manage fonts 19 | \RequirePackage[default,opentype]{sourcesanspro} 20 | \usepackage[sfdefault,light]{roboto} 21 | \newcommand*{\bodyfont}{\roboto} % {\sourcesanspro} 22 | 23 | \newcommand\boldtheme[1]{\textcolor{theme}{\textbf{#1}}} 24 | 25 | % format hyperlinks 26 | \usepackage{hyperref} 27 | \hypersetup{ 28 | colorlinks=true, 29 | linkcolor=blue, 30 | filecolor=magenta, 31 | urlcolor=blue, 32 | pdfstartview=FitW 33 | } 34 | 35 | % manage title formats 36 | \usepackage{titlesec} 37 | 38 | \titleformat{\section}[block]{\fontsize{18pt}{1em}% 39 | \bodyfont\color{black}\bfseries\filcenter}{}{0em}% 40 | {}[\color{theme}{\titlerule[1pt]}] 41 | 42 | \titleformat{\subsection}[hang]{\fontsize{16pt}{1em}% 43 | \bodyfont\color{black}\bfseries\raggedright}{}{0em}% 44 | {}[\color{theme}{\titlerule[1pt]}] 45 | 46 | \titleformat{\subsubsection}[hang]{\fontsize{11pt}{1em}% 47 | \bodyfont\color{black}\bfseries\raggedright}{}{0em}% 48 | {}[\color{theme}{\titlerule[1pt]}] 49 | 50 | % fancy footer 51 | \usepackage{fancyhdr} 52 | \usepackage[bottom,flushmargin]{footmisc} 53 | \pagestyle{fancy} 54 | \fancyhf{} 55 | \fancyfoot[RO]{\color{gray}\textbf{\@pluginname} Plugin User Manual \textasciitilde \ {\the\numexpr2*\value{page}-1\relax}/{\the\numexpr2*\value{page}\relax}} 56 | \renewcommand{\headrulewidth}{0pt} 57 | \renewcommand{\footrulewidth}{1pt} 58 | \def\footrule{{\if@fancyplain\let\footrulewidth\plainfootrulewidth\fi 59 | \leavevmode\rlap{\color{theme}\rule{\textwidth}{\footrulewidth}} 60 | \vskip\footruleskip}} 61 | -------------------------------------------------------------------------------- /manual/screenshots/BottomBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/manual/screenshots/BottomBar.png -------------------------------------------------------------------------------- /manual/screenshots/DetailsView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/manual/screenshots/DetailsView.png -------------------------------------------------------------------------------- /manual/screenshots/GraphView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/manual/screenshots/GraphView.png -------------------------------------------------------------------------------- /manual/screenshots/full_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/manual/screenshots/full_gui.png -------------------------------------------------------------------------------- /modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(JUCE) 2 | include(cmake/SubprojectVersion.cmake) 3 | subproject_version(JUCE juce_version) 4 | message(STATUS "VERSION for JUCE: ${juce_version}") 5 | 6 | juce_add_modules(foleys_gui_magic) 7 | juce_add_modules(chowdsp_utils/modules/chowdsp_dsp) 8 | juce_add_modules(chowdsp_utils/modules/chowdsp_gui) 9 | juce_add_modules(chowdsp_utils/modules/chowdsp_plugin_utils) 10 | add_subdirectory(polylogarithm) 11 | 12 | include(cmake/WarningFlags.cmake) 13 | add_library(juce_plugin_modules STATIC) 14 | target_link_libraries(juce_plugin_modules 15 | PRIVATE 16 | BinaryData 17 | juce::juce_audio_utils 18 | juce::juce_audio_plugin_client 19 | juce::juce_dsp 20 | foleys_gui_magic 21 | chowdsp_dsp 22 | chowdsp_gui 23 | chowdsp_plugin_utils 24 | polylogarithm 25 | PUBLIC 26 | juce::juce_recommended_config_flags 27 | juce::juce_recommended_lto_flags 28 | warning_flags 29 | ) 30 | 31 | target_compile_definitions(juce_plugin_modules 32 | PUBLIC 33 | JUCE_DISPLAY_SPLASH_SCREEN=0 34 | JUCE_REPORT_APP_USAGE=0 35 | JUCE_WEB_BROWSER=0 36 | JUCE_USE_CURL=0 37 | JUCE_VST3_CAN_REPLACE_VST2=0 38 | JUCE_JACK=1 39 | JUCE_ALSA=1 40 | JUCE_MODAL_LOOPS_PERMITTED=1 41 | FOLEYS_SHOW_GUI_EDITOR_PALLETTE=0 42 | FOLEYS_ENABLE_BINARY_DATA=1 43 | JucePlugin_Manufacturer="chowdsp" 44 | JucePlugin_VersionString="${CMAKE_PROJECT_VERSION}" 45 | JucePlugin_Name="${CMAKE_PROJECT_NAME}" 46 | INTERFACE 47 | $ 48 | ) 49 | 50 | target_include_directories(juce_plugin_modules 51 | INTERFACE 52 | $ 53 | ) 54 | 55 | set_target_properties(juce_plugin_modules PROPERTIES 56 | POSITION_INDEPENDENT_CODE TRUE 57 | VISIBILITY_INLINES_HIDDEN TRUE 58 | C_VISBILITY_PRESET hidden 59 | CXX_VISIBILITY_PRESET hidden 60 | ) 61 | 62 | if(IOS) 63 | target_link_libraries(juce_plugin_modules PRIVATE juce::juce_product_unlocking) 64 | target_compile_definitions(juce_plugin_modules PUBLIC JUCE_IN_APP_PURCHASES=1) 65 | endif() 66 | 67 | if(NOT APPLE) 68 | message(STATUS "Linking with OpenGL") 69 | target_link_libraries(juce_plugin_modules PRIVATE juce::juce_opengl) 70 | target_compile_definitions(juce_plugin_modules PUBLIC FOLEYS_ENABLE_OPEN_GL_CONTEXT=1) 71 | endif() 72 | 73 | include(cmake/pluginval.cmake) 74 | -------------------------------------------------------------------------------- /modules/cmake/SubprojectVersion.cmake: -------------------------------------------------------------------------------- 1 | # subproject_version( ) 2 | # 3 | # Extract version of a sub-project, which was previously included with add_subdirectory(). 4 | function(subproject_version subproject_name VERSION_VAR) 5 | # Read CMakeLists.txt for subproject and extract project() call(s) from it. 6 | file(STRINGS "${${subproject_name}_SOURCE_DIR}/CMakeLists.txt" project_calls REGEX "[ \t]*project\\(") 7 | # For every project() call try to extract its VERSION option 8 | foreach(project_call ${project_calls}) 9 | string(REGEX MATCH "VERSION[ ]+([^ )]+)" version_param "${project_call}") 10 | if(version_param) 11 | set(version_value "${CMAKE_MATCH_1}") 12 | endif() 13 | endforeach() 14 | if(version_value) 15 | set(${VERSION_VAR} "${version_value}" PARENT_SCOPE) 16 | else() 17 | message("WARNING: Cannot extract version for subproject '${subproject_name}'") 18 | endif() 19 | 20 | endfunction(subproject_version) 21 | -------------------------------------------------------------------------------- /modules/cmake/WarningFlags.cmake: -------------------------------------------------------------------------------- 1 | add_library(warning_flags INTERFACE) 2 | 3 | if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) 4 | target_compile_options(warning_flags INTERFACE 5 | /W4 # base warning level 6 | /wd4458 # declaration hides class member (from Foley's GUI Magic) 7 | /wd4505 # since VS2019 doesn't handle [[ maybe_unused ]] for static functions (RTNeural::debug_print) 8 | ) 9 | elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")) 10 | target_compile_options(warning_flags INTERFACE 11 | -Wall -Wshadow-all -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized 12 | -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion 13 | -Wconditional-uninitialized -Woverloaded-virtual -Wreorder 14 | -Wconstant-conversion -Wsign-conversion -Wunused-private-field 15 | -Wbool-conversion -Wno-extra-semi -Wunreachable-code 16 | -Wzero-as-null-pointer-constant -Wcast-align 17 | -Wno-inconsistent-missing-destructor-override -Wshift-sign-overflow 18 | -Wnullable-to-nonnull-conversion -Wno-missing-field-initializers 19 | -Wno-ignored-qualifiers -Wpedantic -Wno-pessimizing-move 20 | # These lines suppress some custom warnings. 21 | # Comment them out to be more strict. 22 | -Wno-shadow-field-in-constructor 23 | # Needed for ARM processor, OSX versions below 10.14 24 | -fno-aligned-allocation 25 | ) 26 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 27 | target_compile_options(warning_flags INTERFACE 28 | -Wall -Wextra -Wstrict-aliasing -Wuninitialized -Wunused-parameter 29 | -Wsign-compare -Woverloaded-virtual -Wreorder -Wunreachable-code 30 | -Wzero-as-null-pointer-constant -Wcast-align -Wno-implicit-fallthrough 31 | -Wno-maybe-uninitialized -Wno-missing-field-initializers -Wno-pedantic 32 | -Wno-ignored-qualifiers -Wno-unused-function -Wno-pessimizing-move 33 | # From LV2 Wrapper 34 | -Wno-parentheses -Wno-deprecated-declarations -Wno-redundant-decls 35 | # These lines suppress some custom warnings. 36 | # Comment them out to be more strict. 37 | -Wno-redundant-move 38 | ) 39 | 40 | if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0.0") 41 | target_compile_options(warning_flags INTERFACE "-Wno-strict-overflow") 42 | endif() 43 | endif() 44 | -------------------------------------------------------------------------------- /modules/cmake/pluginval.cmake: -------------------------------------------------------------------------------- 1 | set_property(GLOBAL PROPERTY pluginval_setup) 2 | function(create_pluginval_target target plugin) 3 | message(STATUS "Creating pluginval target for ${target}") 4 | 5 | if (APPLE) 6 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_macOS.zip") 7 | set(pluginval_exe pluginval/pluginval.app/Contents/MacOS/pluginval) 8 | elseif (WIN32) 9 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_Windows.zip") 10 | set(pluginval_exe pluginval/pluginval.exe) 11 | else () 12 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_Linux.zip") 13 | set(pluginval_exe pluginval/pluginval) 14 | endif () 15 | 16 | get_property(is_pluginval_setup GLOBAL PROPERTY pluginval_setup) 17 | if ("${is_pluginval_setup}" STREQUAL "yes") 18 | message(STATUS "Skipping pluginval setup...") 19 | else () 20 | add_custom_target(download-pluginval) 21 | add_custom_command(TARGET download-pluginval 22 | POST_BUILD 23 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 24 | COMMAND cmake -E make_directory pluginval 25 | COMMAND curl -L ${pluginval_url} -o pluginval/pluginval.zip 26 | COMMAND cd pluginval && unzip -o pluginval.zip 27 | ) 28 | add_custom_target(pluginval-all) 29 | set_property(GLOBAL PROPERTY pluginval_setup "yes") 30 | endif() 31 | 32 | set(name "${target}-pluginval") 33 | add_custom_target(${name}) 34 | add_dependencies(${name} ${target}) 35 | add_dependencies(${name} download-pluginval) 36 | get_target_property(plugin_location ${target} LIBRARY_OUTPUT_DIRECTORY) 37 | add_custom_command(TARGET ${name} 38 | POST_BUILD 39 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 40 | COMMAND ${pluginval_exe} --validate-in-process --strictness-level 5 --timeout-ms 600000 --output-dir "." --validate "${plugin_location}/${plugin}" || exit 1 41 | ) 42 | add_dependencies(pluginval-all ${name}) 43 | endfunction() 44 | -------------------------------------------------------------------------------- /modules/polylogarithm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(polylogarithm STATIC 2 | Li2.cpp 3 | ) 4 | 5 | # Need to build with -fPIC flag on Linux 6 | set_target_properties(polylogarithm PROPERTIES 7 | POSITION_INDEPENDENT_CODE TRUE) 8 | -------------------------------------------------------------------------------- /modules/polylogarithm/Li2.hpp: -------------------------------------------------------------------------------- 1 | // ==================================================================== 2 | // This file is part of Polylogarithm. 3 | // 4 | // Polylogarithm is licenced under the GNU Lesser General Public 5 | // License (GNU LGPL) version 3. 6 | // ==================================================================== 7 | 8 | #pragma once 9 | #include 10 | 11 | namespace polylogarithm { 12 | 13 | /// real polylogarithm with n=2 (dilogarithm) 14 | double Li2(double) noexcept; 15 | 16 | /// real polylogarithm with n=2 (dilogarithm) with long double precision 17 | long double Li2(long double) noexcept; 18 | 19 | } // namespace polylogarithm 20 | -------------------------------------------------------------------------------- /res/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | juce_add_binary_data(BinaryData SOURCES 2 | gui.xml 3 | gui_ios.xml 4 | 5 | Diceroll.png 6 | Panic.png 7 | home.svg 8 | undo-solid.png 9 | cog-solid.svg 10 | LeftArrow.svg 11 | RightArrow.svg 12 | ellipsis-h-solid.png 13 | 14 | presets/Crazy.chowpreset 15 | presets/Default.chowpreset 16 | presets/Lush.chowpreset 17 | presets/Rhythmic.chowpreset 18 | presets/Chord.chowpreset 19 | presets/Wobble.chowpreset 20 | presets/Wonky.chowpreset 21 | ) 22 | 23 | # Need to build BinaryData with -fPIC flag on Linux 24 | set_target_properties(BinaryData PROPERTIES 25 | POSITION_INDEPENDENT_CODE TRUE) 26 | -------------------------------------------------------------------------------- /res/Diceroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/res/Diceroll.png -------------------------------------------------------------------------------- /res/LeftArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /res/Panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/res/Panic.png -------------------------------------------------------------------------------- /res/RightArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /res/cog-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/ellipsis-h-solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/res/ellipsis-h-solid.png -------------------------------------------------------------------------------- /res/home.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/res/logo.png -------------------------------------------------------------------------------- /res/presets/Default.chowpreset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 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 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /res/undo-solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/ChowMatrix/40d8e0ef1f752a6843099ff3dfc3d99132b332eb/res/undo-solid.png -------------------------------------------------------------------------------- /scripts/ios_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | TEAM_ID=$(more ~/Developer/mac_id) 5 | 6 | if [ "$1" == "help" ]; then 7 | echo "Run bash ios_build.sh build clean" 8 | echo "Run bash ios_build.sh version" 9 | echo "Go to Xcode Archive Organizer and upload!" 10 | exit 11 | fi 12 | 13 | if [ "$1" == "build" ] || [ "$1" == "configure" ]; then 14 | echo "Running CMake configuration..." 15 | 16 | # clean up old builds 17 | if [ "$2" == "clean" ]; then rm -Rf build-ios; fi 18 | 19 | # generate new builds 20 | if [ "$1" == "configure" ]; then 21 | cmake -Bbuild-ios -GXcode -DCMAKE_SYSTEM_NAME=iOS \ 22 | -DCMAKE_OSX_DEPLOYMENT_TARGET=11.4 \ 23 | -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM="$TEAM_ID" \ 24 | -DCMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY="1,2" \ 25 | -DCMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE="NO" \ 26 | -DCMAKE_XCODE_ATTRIBUTE_ENABLE_IN_APP_PURCHASE="YES" 27 | fi 28 | 29 | if [ "$1" == "build" ]; then 30 | xcodebuild -project build-ios/ChowMatrix.xcodeproj \ 31 | -scheme ChowMatrix_Standalone archive -configuration Release \ 32 | -sdk iphoneos -jobs 12 -archivePath ChowMatrix.xcarchive | xcpretty 33 | fi 34 | fi 35 | 36 | if [ "$1" == "version" ]; then 37 | # set version number to include commit hash 38 | COMMIT=$(git log --pretty=format:'%h' -n 1) 39 | VERSION=$(cut -f 2 -d '=' <<< "$(grep 'CMAKE_PROJECT_VERSION:STATIC' build-ios/CMakeCache.txt)") 40 | BUILD_NUMBER="$VERSION-$COMMIT" 41 | echo "Setting version for archive: $BUILD_NUMBER" 42 | 43 | PLIST=ChowMatrix.xcarchive/Info.plist 44 | /usr/libexec/Plistbuddy -c "Set ApplicationProperties:CFBundleVersion $BUILD_NUMBER" "$PLIST" 45 | 46 | # move to archives folder so Xcode can find it 47 | archive_dir="$HOME/Library/Developer/Xcode/Archives/$(date '+%Y-%m-%d')" 48 | echo "Moving to directory: $archive_dir" 49 | mkdir -p "$archive_dir" 50 | mv ChowMatrix.xcarchive "$archive_dir/ChowMatrix-$COMMIT.xcarchive" 51 | fi 52 | 53 | -------------------------------------------------------------------------------- /scripts/mac_builds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit on failure 4 | set -e 5 | 6 | # clean up old builds 7 | rm -Rf build/ 8 | rm -Rf bin/*Mac* 9 | 10 | # set up build VST 11 | VST_PATH=~/Developer/Plugin_SDKs/VST2_SDK/ 12 | sed -i '' "9s~.*~juce_set_vst2_sdk_path(${VST_PATH})~" CMakeLists.txt 13 | 14 | # cmake new builds 15 | TEAM_ID=$(more ~/Developer/mac_id) 16 | cmake -Bbuild -GXcode -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY="Developer ID Application" \ 17 | -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM="$TEAM_ID" \ 18 | -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE="Manual" \ 19 | -D"CMAKE_OSX_ARCHITECTURES=arm64;x86_64" \ 20 | -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \ 21 | -DCMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS="--timestamp" \ 22 | -DMACOS_RELEASE=ON 23 | cmake --build build --config Release -j8 | xcpretty 24 | 25 | # copy builds to bin 26 | mkdir -p bin/Mac 27 | declare -a plugins=("ChowMatrix") 28 | for plugin in "${plugins[@]}"; do 29 | cp -R build/${plugin}_artefacts/Release/Standalone/${plugin}.app bin/Mac/${plugin}.app 30 | cp -R build/${plugin}_artefacts/Release/VST/${plugin}.vst bin/Mac/${plugin}.vst 31 | cp -R build/${plugin}_artefacts/Release/VST3/${plugin}.vst3 bin/Mac/${plugin}.vst3 32 | cp -R build/${plugin}_artefacts/Release/AU/${plugin}.component bin/Mac/${plugin}.component 33 | done 34 | 35 | # reset CMakeLists.txt 36 | git restore CMakeLists.txt 37 | 38 | # run auval 39 | echo "Running AU validation..." 40 | rm -Rf ~/Library/Audio/Plug-Ins/Components/${plugin}.component 41 | cp -R build/${plugin}_artefacts/Release/AU/${plugin}.component ~/Library/Audio/Plug-Ins/Components 42 | manu=$(cut -f 6 -d ' ' <<< "$(grep 'PLUGIN_MANUFACTURER_CODE' CMakeLists.txt)") 43 | code=$(cut -f 6 -d ' ' <<< "$(grep 'PLUGIN_CODE' CMakeLists.txt)") 44 | 45 | set +e 46 | auval_result=$(auval -v aufx "$code" "$manu") 47 | auval_code="$?" 48 | echo "AUVAL code: $auval_code" 49 | 50 | if [ "$auval_code" != 0 ]; then 51 | echo "$auval_result" 52 | echo "auval FAIL!!!" 53 | exit 1 54 | else 55 | echo "auval PASSED" 56 | fi 57 | 58 | # zip builds 59 | echo "Zipping builds..." 60 | VERSION=$(cut -f 2 -d '=' <<< "$(grep 'CMAKE_PROJECT_VERSION:STATIC' build/CMakeCache.txt)") 61 | ( 62 | cd bin 63 | rm -f "ChowMatrix-Mac-${VERSION}.zip" 64 | zip -r "ChowMatrix-Mac-${VERSION}.zip" Mac 65 | ) 66 | 67 | # create installer 68 | echo "Creating installer..." 69 | ( 70 | cd installers/mac 71 | bash build_mac_installer.sh 72 | ) 73 | -------------------------------------------------------------------------------- /scripts/win_builds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | build64(){ 4 | cmake -Bbuild -G"Visual Studio 16 2019" -A x64 -DASIOSDK_DIR="C:\\SDKs\\ASIO_SDK" 5 | cmake --build build --config Release -j4 6 | } 7 | 8 | build32(){ 9 | cmake -Bbuild32 -G"Visual Studio 16 2019" -A Win32 -DASIOSDK_DIR="C:\\SDKs\\ASIO_SDK" 10 | cmake --build build32 --config Release -j4 11 | } 12 | 13 | # exit on failure 14 | set -e 15 | 16 | # clean up old builds 17 | rm -Rf build/ 18 | rm -Rf build32/ 19 | rm -Rf bin/*Win64* 20 | rm -Rf bin/*Win32* 21 | 22 | # set up VST SDK paths 23 | sed -i -e "9s/#//" CMakeLists.txt 24 | 25 | # cmake new builds 26 | build64 & 27 | build32 & 28 | wait 29 | 30 | # copy builds to bin 31 | mkdir -p bin/Win64 32 | mkdir -p bin/Win32 33 | declare -a plugins=("ChowMatrix") 34 | for plugin in "${plugins[@]}"; do 35 | cp -R build/${plugin}_artefacts/Release/Standalone/${plugin}.exe bin/Win64/${plugin}.exe 36 | cp -R build/${plugin}_artefacts/Release/VST/${plugin}.dll bin/Win64/${plugin}.dll 37 | cp -R build/${plugin}_artefacts/Release/VST3/${plugin}.vst3 bin/Win64/${plugin}.vst3 38 | 39 | cp -R build32/${plugin}_artefacts/Release/Standalone/${plugin}.exe bin/Win32/${plugin}.exe 40 | cp -R build32/${plugin}_artefacts/Release/VST/${plugin}.dll bin/Win32/${plugin}.dll 41 | cp -R build32/${plugin}_artefacts/Release/VST3/${plugin}.vst3 bin/Win32/${plugin}.vst3 42 | done 43 | 44 | # reset CMakeLists.txt 45 | git restore CMakeLists.txt 46 | 47 | # zip builds 48 | VERSION=$(cut -f 2 -d '=' <<< "$(grep 'CMAKE_PROJECT_VERSION:STATIC' build/CMakeCache.txt)") 49 | ( 50 | cd bin 51 | rm -f "ChowMatrix-Win64-${VERSION}.zip" 52 | rm -f "ChowMatrix-Win32-${VERSION}.zip" 53 | zip -r "ChowMatrix-Win64-${VERSION}.zip" Win64 54 | zip -r "ChowMatrix-Win32-${VERSION}.zip" Win32 55 | ) 56 | 57 | # create installer 58 | echo "Creating installer..." 59 | ( 60 | cd installers/windows 61 | bash build_win_installer.sh 62 | ) 63 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(BUILD_HEADLESS "Build ChowMatrix headless utilities" OFF) 2 | if(NOT (IOS OR MACOS_RELEASE) AND BUILD_HEADLESS) 3 | message(STATUS "Building ChowMatrix Headless!") 4 | add_subdirectory(headless) 5 | endif() 6 | 7 | target_sources(ChowMatrix PRIVATE 8 | ChowMatrix.cpp 9 | NodeManager.cpp 10 | 11 | dsp/BaseNode.cpp 12 | dsp/DelayNode.cpp 13 | dsp/InputNode.cpp 14 | dsp/Delay/DelayProc.cpp 15 | dsp/Delay/Diffusion.cpp 16 | dsp/Delay/Reverser.cpp 17 | dsp/Delay/PitchShiftWrapper.cpp 18 | dsp/Delay/VariableDelay.cpp 19 | dsp/Distortion/Distortion.cpp 20 | dsp/Distortion/LookupTables.cpp 21 | dsp/Parameters/BaseController.cpp 22 | dsp/Parameters/DelayTypeControl.cpp 23 | dsp/Parameters/HostParamControl.cpp 24 | dsp/Parameters/InsanityControl.cpp 25 | dsp/Parameters/InsanityLockHelper.cpp 26 | dsp/Parameters/NodeParamControl.cpp 27 | dsp/Parameters/RandomiseLockHelper.cpp 28 | dsp/Parameters/ParamHelpers.cpp 29 | dsp/Parameters/SyncControl.cpp 30 | 31 | gui/AutoUpdating.cpp 32 | gui/ParamSlider.cpp 33 | gui/BottomBar/ABComp.cpp 34 | gui/BottomBar/BottomBarLNF.cpp 35 | gui/BottomBar/HostControlMenu.cpp 36 | gui/BottomBar/HostControlMenuComp.cpp 37 | gui/DetailsView/NodeDetails.cpp 38 | gui/DetailsView/NodeDetailsComponent.cpp 39 | gui/DetailsView/NodeDetailsGUI.cpp 40 | gui/DetailsView/NodeDetailsViewport.cpp 41 | gui/IOSUtils/LongPressActionHelper.cpp 42 | gui/IOSUtils/TipJar.cpp 43 | gui/MatrixView/DelayNodeComponent.cpp 44 | gui/MatrixView/GraphView.cpp 45 | gui/MatrixView/GraphViewport.cpp 46 | gui/MatrixView/MatrixAurora.cpp 47 | gui/MatrixView/NodeCompManager.cpp 48 | gui/MatrixView/NodeComponent.cpp 49 | 50 | state/PresetManager.cpp 51 | state/StateManager.cpp 52 | ) 53 | 54 | target_precompile_headers(ChowMatrix PRIVATE pch.h) 55 | -------------------------------------------------------------------------------- /src/ChowMatrix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NodeManager.h" 4 | #include "dsp/InputNode.h" 5 | #include "dsp/Parameters/DelayTypeControl.h" 6 | #include "dsp/Parameters/HostParamControl.h" 7 | #include "dsp/Parameters/InsanityControl.h" 8 | #include "dsp/Parameters/NodeParamControl.h" 9 | #include "dsp/Parameters/SyncControl.h" 10 | #include "gui/MatrixView/GraphViewport.h" 11 | #include "state/StateManager.h" 12 | #include 13 | 14 | #if CHOWDSP_AUTO_UPDATE 15 | #include "gui/AutoUpdating.h" 16 | #endif // CHOWDSP_AUTO_UPDATE 17 | 18 | /** 19 | * Main class for the Matrix plugin 20 | */ 21 | class ChowMatrix : public chowdsp::PluginBase 22 | { 23 | public: 24 | ChowMatrix(); 25 | 26 | static void addParameters (Parameters& params); 27 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 28 | void releaseResources() override; 29 | void processAudioBlock (AudioBuffer& buffer) override; 30 | 31 | AudioProcessorEditor* createEditor() override; 32 | 33 | void getStateInformation (MemoryBlock& destData) override; 34 | void setStateInformation (const void* data, int sizeInBytes) override; 35 | 36 | /** Handle to access node manager */ 37 | NodeManager& getManager() { return manager; } 38 | 39 | /** Handle to read insanity parameter (used by MatrixAurora) */ 40 | std::atomic* getInsanityParam() const noexcept { return insanityControl.getParameter(); } 41 | 42 | /** Access to array of input nodes */ 43 | std::array* getNodes() { return &inputNodes; } 44 | StateManager& getStateManager() { return stateManager; } 45 | HostParamControl& getHostControl() { return hostParamControl; } 46 | AudioProcessorValueTreeState& getVTS() { return vts; } 47 | 48 | private: 49 | std::array inputNodes; 50 | NodeManager manager; 51 | 52 | std::atomic* dryParamDB = nullptr; 53 | std::atomic* wetParamDB = nullptr; 54 | std::atomic* wetGainCompParam = nullptr; 55 | 56 | AudioBuffer chBuffers[2]; 57 | 58 | AudioBuffer dryBuffer; 59 | dsp::Gain dryGain; 60 | dsp::Gain wetGain; 61 | 62 | InsanityControl insanityControl; 63 | HostParamControl hostParamControl; 64 | DelayTypeControl delayTypeControl; 65 | SyncControl syncControl; 66 | NodeParamControl nodeParamControl; 67 | 68 | // create shared resources here so that 69 | // loading new nodes is fast 70 | SharedResourcePointer luts; 71 | SharedResourcePointer delayStore; 72 | 73 | StateManager stateManager; 74 | 75 | #if CHOWDSP_AUTO_UPDATE 76 | AutoUpdater updater; 77 | #endif // CHOWDSP_AUTO_UPDATE 78 | 79 | std::unique_ptr graphViewPtr; 80 | 81 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowMatrix) 82 | }; 83 | -------------------------------------------------------------------------------- /src/NodeManager.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeManager.h" 2 | 3 | void NodeManager::doForNodes (DBaseNode* root, std::function nodeFunc) 4 | { 5 | for (int i = 0; i < root->getNumChildren(); ++i) 6 | { 7 | auto child = root->getChild (i); 8 | nodeFunc (child); 9 | doForNodes (child, nodeFunc); 10 | } 11 | } 12 | 13 | void NodeManager::doForNodes (std::array* nodes, std::function nodeFunc) 14 | { 15 | for (auto& node : *nodes) 16 | NodeManager::doForNodes (&node, nodeFunc); 17 | } 18 | 19 | void NodeManager::initialise (std::array* _nodes) 20 | { 21 | nodes = _nodes; 22 | nodeCount = 0; 23 | 24 | for (auto& node : *nodes) 25 | { 26 | node.addNodeListener (this); 27 | doForNodes (&node, [=] (DelayNode* n) { nodeAdded (n); }); 28 | } 29 | } 30 | 31 | void NodeManager::nodeAdded (DelayNode* newNode) 32 | { 33 | newNode->addNodeListener (this); 34 | newNode->setIndex (nodeCount++); 35 | newNode->setSoloed (newNodeSoloState); 36 | } 37 | 38 | void NodeManager::nodeRemoved (DelayNode* nodeToRemove) 39 | { 40 | nodeToRemove->removeNodeListener (this); 41 | if (nodeToRemove->getSelected()) 42 | selectedNodePtr = nullptr; 43 | 44 | nodeCount = 0; 45 | doForNodes (nodes, [=] (DelayNode* n) { n->setIndex (nodeCount++); }); 46 | } 47 | 48 | void NodeManager::setParameterDiff (DelayNode* sourceNode, const String& paramID, float diff01) 49 | { 50 | doForNodes (nodes, [=] (DelayNode* n) { 51 | if (n == sourceNode) 52 | return; 53 | 54 | n->setNodeParameterDiff (paramID, diff01); 55 | }); 56 | } 57 | 58 | void NodeManager::setParameterDefault (DelayNode* sourceNode, const String& paramID) 59 | { 60 | doForNodes (nodes, [=] (DelayNode* n) { 61 | if (n == sourceNode) 62 | return; 63 | 64 | n->setNodeParameterToDefault (paramID); 65 | }); 66 | } 67 | 68 | void NodeManager::setSelected (DelayNode* selectedNode, ActionSource source) 69 | { 70 | selectedNodePtr = selectedNode; 71 | 72 | doForNodes (nodes, [=] (DelayNode* n) { 73 | if (n->getSelected() && selectedNode == n) // already selected! 74 | return; 75 | else if (n->getSelected()) // is currently selected, but no longer 76 | n->setSelected (false); 77 | else if (selectedNode == n) // should now be seleced 78 | n->setSelected (true); 79 | }); 80 | 81 | listeners.call (&Listener::nodeSelected, selectedNode, source); 82 | } 83 | 84 | DelayNode* NodeManager::getSelected() const noexcept 85 | { 86 | return selectedNodePtr; 87 | } 88 | 89 | void NodeManager::setSoloed (DelayNode* soloedNode, ActionSource source) 90 | { 91 | newNodeSoloState = soloedNode == nullptr ? DelayNode::SoloState::None : DelayNode::SoloState::Mute; 92 | 93 | doForNodes (nodes, [=] (DelayNode* n) { 94 | if (soloedNode == nullptr) // "un-solo" and currently soloed nodes 95 | { 96 | n->setSoloed (DelayNode::SoloState::None); 97 | return; 98 | } 99 | 100 | if (n == soloedNode) // Node that has been selected for soloing 101 | { 102 | if (n->getSoloed() == DelayNode::SoloState::Solo) // already soloed 103 | return; 104 | 105 | n->setSoloed (DelayNode::SoloState::Solo); // should now be soloed 106 | return; 107 | } 108 | 109 | // any other nodes should be muted 110 | n->setSoloed (DelayNode::SoloState::Mute); 111 | }); 112 | 113 | listeners.call (&Listener::nodeSoloed, soloedNode, source); 114 | } 115 | -------------------------------------------------------------------------------- /src/NodeManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dsp/DelayNode.h" 4 | #include "dsp/InputNode.h" 5 | 6 | /** 7 | * Utility class to manage node graph 8 | */ 9 | class NodeManager : private DBaseNode::Listener 10 | { 11 | public: 12 | NodeManager() = default; 13 | 14 | /** Iterate through node tree and perform nodeFunc for all nodes */ 15 | static void doForNodes (DBaseNode* root, std::function nodeFunc); 16 | static void doForNodes (std::array* nodes, std::function nodeFunc); 17 | 18 | /** Initialise node manager with an array of root nodes */ 19 | void initialise (std::array* _nodes); 20 | 21 | // Node listener overrides 22 | void nodeAdded (DelayNode* newNode) override; 23 | void nodeRemoved (DelayNode* nodeToRemove) override; 24 | void setParameterDiff (DelayNode* sourceNode, const String& paramID, float diff01) override; 25 | void setParameterDefault (DelayNode* sourceNode, const String& paramID) override; 26 | void nodeInsanityLockChanged (DelayNode* node) override { listeners.call (&Listener::nodeInsanityLockChanged, node); } 27 | 28 | /** Sources that can trigger node selection */ 29 | enum class ActionSource 30 | { 31 | GraphView, 32 | DetailsView 33 | }; 34 | 35 | // Manage selected node 36 | void setSelected (DelayNode* node, ActionSource source); 37 | DelayNode* getSelected() const noexcept; 38 | 39 | // Manage soloed node 40 | void setSoloed (DelayNode* node, ActionSource source); 41 | 42 | class Listener 43 | { 44 | public: 45 | virtual ~Listener() {} 46 | virtual void nodeSelected (DelayNode* /*selectedNode*/, ActionSource /*source*/) {} 47 | virtual void nodeSoloed (DelayNode* /*soloedNode*/, ActionSource /*source*/) {} 48 | virtual void nodeInsanityLockChanged (DelayNode* /*node*/) {} 49 | }; 50 | 51 | void addListener (Listener* l) { listeners.add (l); } 52 | void removeListener (Listener* l) { listeners.remove (l); } 53 | 54 | private: 55 | std::array* nodes = nullptr; 56 | int nodeCount = 0; 57 | DelayNode* selectedNodePtr = nullptr; 58 | DelayNode::SoloState newNodeSoloState = DelayNode::SoloState::None; 59 | 60 | ListenerList listeners; 61 | 62 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NodeManager) 63 | }; 64 | -------------------------------------------------------------------------------- /src/dsp/BaseNode.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseNode.h" 2 | #include "../gui/MatrixView/DelayNodeComponent.h" 3 | #include "DelayNode.h" 4 | #include "InputNode.h" 5 | 6 | template 7 | BaseNode::BaseNode() 8 | { 9 | } 10 | 11 | template 12 | void BaseNode::prepare (double newSampleRate, int newSamplesPerBlock) 13 | { 14 | sampleRate = newSampleRate; 15 | samplesPerBlock = newSamplesPerBlock; 16 | childBuffer.setSize (1, newSamplesPerBlock); 17 | 18 | for (auto* child : children) 19 | child->prepare (sampleRate, samplesPerBlock); 20 | } 21 | 22 | template 23 | void BaseNode::process (AudioBuffer& inBuffer, AudioBuffer& outBuffer) 24 | { 25 | for (auto* child : children) 26 | { 27 | childBuffer.makeCopyOf (inBuffer, true); 28 | child->process (childBuffer, outBuffer); 29 | } 30 | } 31 | 32 | template 33 | float BaseNode::getNodeLevel (float inputLevel) const 34 | { 35 | float outputLevel = 0.0f; 36 | for (auto* child : children) 37 | outputLevel += child->getNodeLevel (inputLevel); 38 | 39 | return outputLevel; 40 | } 41 | 42 | template 43 | NodeComponent* BaseNode::getEditor() 44 | { 45 | return editor; 46 | } 47 | 48 | template 49 | Child* BaseNode::addChild() 50 | { 51 | // create child 52 | auto newChild = std::make_unique(); 53 | 54 | // make this node parent of new child 55 | newChild->setParent (this); 56 | 57 | // add child to array of children 58 | // this must be done last so child is prepared before called to process audio 59 | auto* createdChild = children.add (std::move (newChild)); 60 | 61 | // tell listeners about new child 62 | nodeListeners.call (&BaseNode::Listener::nodeAdded, createdChild); 63 | 64 | return createdChild; 65 | } 66 | 67 | template 68 | void BaseNode::removeChild (Child* childToRemove) 69 | { 70 | // remove child from children array 71 | int indexToRemove = children.indexOf (childToRemove); 72 | 73 | // put child in `toBeDeleted` placeholder 74 | nodeBeingDeleted.reset (children.removeAndReturn (indexToRemove)); 75 | } 76 | 77 | template 78 | void BaseNode::clearChildren() 79 | { 80 | while (! children.isEmpty()) 81 | children.getLast()->deleteNode(); 82 | 83 | nodeBeingDeleted.reset(); 84 | } 85 | 86 | template 87 | void BaseNode::setParent (BaseNode* newParent) 88 | { 89 | parent = newParent; 90 | prepare (parent->sampleRate, parent->samplesPerBlock); 91 | } 92 | 93 | template 94 | XmlElement* BaseNode::saveXml() 95 | { 96 | if (children.isEmpty()) 97 | return new XmlElement ("no_children"); 98 | 99 | std::unique_ptr xml = std::make_unique ("children"); 100 | for (auto* child : children) 101 | xml->addChildElement (child->saveXml()); 102 | 103 | return xml.release(); 104 | } 105 | 106 | template 107 | void BaseNode::loadXml (XmlElement* xml) 108 | { 109 | if (xml == nullptr) 110 | return; 111 | 112 | if (xml->hasTagName ("children")) 113 | { 114 | for (auto* childXml : xml->getChildIterator()) 115 | addChild()->loadXml (childXml); 116 | } 117 | } 118 | 119 | template class BaseNode; 120 | -------------------------------------------------------------------------------- /src/dsp/BaseNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class NodeComponent; 6 | class GraphView; 7 | 8 | /** 9 | * Base class to provide shared functionality for Delay Nodes 10 | * and Input Nodes 11 | */ 12 | template 13 | class BaseNode 14 | { 15 | public: 16 | BaseNode(); 17 | virtual ~BaseNode() = default; 18 | 19 | /** Create a GUI editor for this node */ 20 | virtual std::unique_ptr createNodeEditor (GraphView*) = 0; 21 | 22 | /** Access the editor for this node, or nullptr if editor does not exist */ 23 | NodeComponent* getEditor(); 24 | 25 | virtual void prepare (double sampleRate, int samplesPerBlock); 26 | virtual void process (AudioBuffer& inBuffer, AudioBuffer& outBuffer); 27 | 28 | virtual float getNodeLevel (float inputLevel) const; 29 | 30 | // Adding/removing children 31 | Child* addChild(); 32 | void removeChild (Child* childToRemove); 33 | void clearChildren(); 34 | 35 | // Managing children 36 | int getNumChildren() const noexcept { return children.size(); } 37 | Child* getChild (int idx) { return children[idx]; } 38 | BaseNode* getParent() { return parent; } 39 | virtual void setParent (BaseNode* parent); 40 | 41 | struct Listener 42 | { 43 | virtual ~Listener() = default; 44 | virtual void nodeAdded (Child* /*newNode*/) {} 45 | virtual void nodeRemoved (Child* /*nodeToRemove*/) {} 46 | virtual void nodeIndexChanged (Child* /*node*/, int /*oldIndex*/, int /*newIndex*/) {} 47 | 48 | virtual void setParameterDiff (Child* /*node*/, const String& /*paramID*/, float /*diff01*/) {} 49 | virtual void setParameterDefault (Child* /*node*/, const String& /*paramID*/) {} 50 | virtual void nodeInsanityLockChanged (Child* /*node*/) {} 51 | 52 | virtual void beginParameterChange (const StringArray& /*paramIDs*/, Child* /*node*/) {} 53 | virtual void endParameterChange (const StringArray& /*paramIDs*/, Child* /*node*/) {} 54 | virtual void applyParameterChange (const String& /*paramID*/, Child* /*node*/, float /*value01*/) {} 55 | 56 | virtual void addParameterMenus (PopupMenu& /*parentMenu*/, const String& /*paramID*/, Child* /*node*/) {} 57 | 58 | virtual void saveExtraNodeState (XmlElement* /*nodeState*/, Child* /*node*/) {} 59 | virtual void loadExtraNodeState (XmlElement* /*nodeState*/, Child* /*node*/) {} 60 | }; 61 | 62 | void addNodeListener (Listener* l) { nodeListeners.add (l); } 63 | void removeNodeListener (Listener* l) { nodeListeners.remove (l); } 64 | 65 | virtual XmlElement* saveXml(); 66 | virtual void loadXml (XmlElement*); 67 | 68 | protected: 69 | NodeComponent* editor = nullptr; 70 | BaseNode* parent = nullptr; 71 | 72 | OwnedArray children; 73 | ListenerList nodeListeners; 74 | 75 | private: 76 | std::unique_ptr nodeBeingDeleted; 77 | 78 | AudioBuffer childBuffer; 79 | 80 | double sampleRate = 44100.0; 81 | int samplesPerBlock = 256; 82 | 83 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BaseNode) 84 | }; 85 | -------------------------------------------------------------------------------- /src/dsp/Delay/DelayProc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Distortion/Distortion.h" 4 | #include "Diffusion.h" 5 | #include "PitchShiftWrapper.h" 6 | #include "Reverser.h" 7 | #include "TempoSyncUtils.h" 8 | #include "VariableDelay.h" 9 | #include 10 | 11 | /** 12 | * Audio processor that implements delay line with feedback, 13 | * including filtering and distortion in the feedback path 14 | */ 15 | class DelayProc 16 | { 17 | public: 18 | DelayProc(); 19 | 20 | // processing functions 21 | void prepare (const dsp::ProcessSpec& spec); 22 | void reset(); 23 | 24 | template 25 | void process (const ProcessContext& context); 26 | 27 | // flush delay line state 28 | void flushDelay(); 29 | 30 | struct Parameters 31 | { 32 | float delayMs; 33 | float feedback; 34 | float lpfFreq; 35 | float hpfFreq; 36 | float distortion; 37 | float pitchSt; 38 | float diffAmt; 39 | float revTimeMs; 40 | const AudioProcessorValueTreeState::Parameter* modFreq; 41 | float modDepth; 42 | float tempoBPM; 43 | bool lfoSynced; 44 | AudioPlayHead* playhead; 45 | }; 46 | 47 | void setParameters (const Parameters& params, bool force = false); 48 | void setDelayType (VariableDelay::DelayType type) { delay->setDelayType (type); } 49 | float getModDepth() const noexcept { return 1000.0f * delayModValue / fs; } 50 | 51 | private: 52 | template 53 | inline SampleType processSample (SampleType x, size_t ch); 54 | 55 | template 56 | inline SampleType processSampleSmooth (SampleType x, size_t ch); 57 | 58 | SharedResourcePointer delayStore; 59 | std::unique_ptr delay; 60 | // VariableDelay delay { 1 << 19 }; 61 | 62 | float fs = 44100.0f; 63 | 64 | SmoothedValue delaySmooth; 65 | SmoothedValue feedback; 66 | SmoothedValue inGain; 67 | std::vector state; 68 | 69 | enum 70 | { 71 | lpfIdx, 72 | hpfIdx, 73 | diffusionIdx, 74 | distortionIdx, 75 | reverserIdx, 76 | pitchIdx, 77 | }; 78 | 79 | MyProcessorChain< 80 | chowdsp::IIR::Filter, 81 | chowdsp::IIR::Filter, 82 | Diffusion, 83 | Distortion, 84 | Reverser, 85 | PitchShiftWrapper> 86 | procs; 87 | 88 | TempoSyncUtils::SyncedLFO modSine; 89 | float delayModValue = 0.0f; 90 | float modDepth = 0.0f; 91 | float modDepthFactor = 1.0f; 92 | 93 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayProc) 94 | }; 95 | -------------------------------------------------------------------------------- /src/dsp/Delay/DelayStore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VariableDelay.h" 4 | 5 | /** A store to create delay objects more quickly. */ 6 | class DelayStore 7 | { 8 | public: 9 | DelayStore() 10 | { 11 | // start with a bunch in the queue 12 | for (int i = 0; i < storeSize; ++i) 13 | loadNewDelay(); 14 | } 15 | 16 | VariableDelay* getNextDelay() 17 | { 18 | SpinLock::ScopedLockType nextDelayLock (delayStoreLock); 19 | 20 | auto* delay = delayFutureStore.front().get().release(); 21 | delayFutureStore.pop_front(); 22 | loadNewDelay(); 23 | 24 | return delay; 25 | } 26 | 27 | private: 28 | void loadNewDelay() 29 | { 30 | delayFutureStore.push_back (std::async (std::launch::async, [] { 31 | auto newDelay = std::make_unique (1 << 19); 32 | newDelay->prepare ({ 48000.0, 512, 1 }); 33 | newDelay->reset(); 34 | return std::move (newDelay); 35 | })); 36 | } 37 | 38 | static constexpr int storeSize = 8; 39 | 40 | std::deque>> delayFutureStore; 41 | SpinLock delayStoreLock; 42 | 43 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayStore) 44 | }; 45 | -------------------------------------------------------------------------------- /src/dsp/Delay/Diffusion.cpp: -------------------------------------------------------------------------------- 1 | #include "Diffusion.h" 2 | 3 | namespace 4 | { 5 | constexpr double smoothTime = 0.01; 6 | constexpr float allpassFreq = 200.0f; 7 | } // namespace 8 | 9 | Diffusion::Diffusion() 10 | { 11 | depthSmooth.setCurrentAndTargetValue (0.0f); 12 | reset(); 13 | } 14 | 15 | void Diffusion::setDepth (float depth, bool force) 16 | { 17 | if (force) 18 | depthSmooth.setCurrentAndTargetValue (depth); 19 | else 20 | depthSmooth.setTargetValue (depth); 21 | } 22 | 23 | void Diffusion::prepare (const dsp::ProcessSpec& spec) 24 | { 25 | fs = (float) spec.sampleRate; 26 | depthSmooth.reset (spec.sampleRate, smoothTime); 27 | 28 | calcCoefs (allpassFreq); 29 | reset(); 30 | } 31 | 32 | void Diffusion::reset() 33 | { 34 | std::fill (z, &z[maxNumStages + 1], 0.0f); 35 | } 36 | 37 | void Diffusion::calcCoefs (float freq) 38 | { 39 | const float RC = 1.0f / (MathConstants::twoPi * freq); 40 | const float b0s = RC; 41 | const float b1s = -1.0f; 42 | const float a0s = b0s; 43 | const float a1s = 1.0f; 44 | const auto K = 2.0f * fs; 45 | 46 | const auto a0 = a0s * K + a1s; 47 | b[0] = (b0s * K + b1s) / a0; 48 | b[1] = (-b0s * K + b1s) / a0; 49 | a[0] = 1.0f; 50 | a[1] = (-a0s * K + a1s) / a0; 51 | } 52 | -------------------------------------------------------------------------------- /src/dsp/Delay/Diffusion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Class for delay line diffusion processing 5 | * 6 | * The DSP is made up of many stages of first-order allpass 7 | * filters, with linear fading between stages. 8 | */ 9 | class Diffusion 10 | { 11 | public: 12 | Diffusion(); 13 | 14 | void setDepth (float depth, bool force); 15 | void prepare (const dsp::ProcessSpec& spec); 16 | void reset(); 17 | 18 | inline float processSample (float x) 19 | { 20 | auto numStages = depthSmooth.getNextValue() * maxNumStages; 21 | const auto numStagesInt = static_cast (numStages); 22 | 23 | // process integer stages 24 | for (size_t stage = 0; stage < numStagesInt; ++stage) 25 | x = processStage (x, stage); 26 | 27 | // process fractional stage 28 | float stageFrac = numStages - numStagesInt; 29 | x = stageFrac * processStage (x, numStagesInt) + (1.0f - stageFrac) * x; 30 | 31 | return x; 32 | } 33 | 34 | inline float processStage (float x, size_t stage) 35 | { 36 | float y = z[stage] + x * b[0]; 37 | z[stage] = x * b[1] - y * a[1]; 38 | return y; 39 | } 40 | 41 | private: 42 | void calcCoefs (float fc); 43 | 44 | static constexpr size_t maxNumStages = 100; 45 | 46 | float a[2] = { 1.0f, 0.0f }; 47 | float b[2] = { 1.0f, 0.0f }; 48 | float z[maxNumStages + 1]; 49 | 50 | float fs = 44100.0f; 51 | SmoothedValue depthSmooth; 52 | 53 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Diffusion) 54 | }; 55 | -------------------------------------------------------------------------------- /src/dsp/Delay/PitchShiftWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "PitchShiftWrapper.h" 2 | 3 | PitchShiftWrapper::PitchShiftWrapper() 4 | { 5 | processFunc = &PitchShiftWrapper::processSampleInternal; 6 | } 7 | 8 | void PitchShiftWrapper::prepare (const dsp::ProcessSpec& spec) 9 | { 10 | jassert (spec.numChannels == 1); 11 | shifter.prepare (spec); 12 | pitchStSmooth.reset (spec.sampleRate, 0.05); 13 | crossfade.reset (spec.sampleRate, 0.05); 14 | } 15 | 16 | void PitchShiftWrapper::reset() 17 | { 18 | shifter.reset(); 19 | } 20 | 21 | void PitchShiftWrapper::setPitchSemitones (float pitch, bool force) 22 | { 23 | if (force) 24 | { 25 | shifter.setShiftSemitones (pitch); 26 | pitchStSmooth.setCurrentAndTargetValue (pitch); 27 | } 28 | else 29 | { 30 | pitchStSmooth.setTargetValue (pitch); 31 | } 32 | 33 | const auto current = pitchStSmooth.getCurrentValue(); 34 | const auto target = pitchStSmooth.getTargetValue(); 35 | if (target == 0.0f && current == 0.0f) // bypass 36 | { 37 | shifter.reset(); 38 | crossfade.setCurrentAndTargetValue (0.0f); 39 | processFunc = &PitchShiftWrapper::processSampleInternalBypass; 40 | } 41 | else if (target == 0.0f && current != 0.0f) // fade to bypass 42 | { 43 | crossfade.setTargetValue (0.0f); 44 | processFunc = &PitchShiftWrapper::processSampleInternalFade; 45 | } 46 | else if (target != 0.0f && current == 0.0f) // fade from bypass 47 | { 48 | crossfade.setTargetValue (1.0f); 49 | processFunc = &PitchShiftWrapper::processSampleInternalFade; 50 | } 51 | else if (pitchStSmooth.isSmoothing()) // smooth pitch change 52 | { 53 | crossfade.setCurrentAndTargetValue (1.0f); 54 | processFunc = &PitchShiftWrapper::processSampleInternalSmooth; 55 | } 56 | else // normal processing! 57 | { 58 | crossfade.setCurrentAndTargetValue (1.0f); 59 | processFunc = &PitchShiftWrapper::processSampleInternal; 60 | } 61 | } 62 | 63 | inline float PitchShiftWrapper::processSampleInternal (float x) noexcept 64 | { 65 | return shifter.processSample (0, x); 66 | } 67 | 68 | inline float PitchShiftWrapper::processSampleInternalSmooth (float x) noexcept 69 | { 70 | shifter.setShiftSemitones (pitchStSmooth.getNextValue()); 71 | return shifter.processSample (0, x); 72 | } 73 | 74 | inline float PitchShiftWrapper::processSampleInternalFade (float x) noexcept 75 | { 76 | shifter.setShiftSemitones (pitchStSmooth.getNextValue()); 77 | auto wetGain = crossfade.getNextValue(); 78 | return wetGain * shifter.processSample (0, x) + (1.0f - wetGain) * x; 79 | } 80 | 81 | inline float PitchShiftWrapper::processSampleInternalBypass (float x) noexcept 82 | { 83 | return x; 84 | } 85 | -------------------------------------------------------------------------------- /src/dsp/Delay/PitchShiftWrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** Wrapper for chowdsp::PitchShift */ 6 | class PitchShiftWrapper 7 | { 8 | public: 9 | PitchShiftWrapper(); 10 | 11 | void prepare (const dsp::ProcessSpec& spec); 12 | void reset(); 13 | void setPitchSemitones (float pitch, bool force = false); 14 | 15 | inline float processSample (float x) noexcept 16 | { 17 | return (this->*processFunc) (x); 18 | } 19 | 20 | private: 21 | inline float processSampleInternal (float x) noexcept; 22 | inline float processSampleInternalSmooth (float x) noexcept; 23 | inline float processSampleInternalFade (float x) noexcept; 24 | inline float processSampleInternalBypass (float x) noexcept; 25 | 26 | using Processor = float (PitchShiftWrapper::*) (float); 27 | Processor processFunc; 28 | 29 | SmoothedValue pitchStSmooth; 30 | SmoothedValue crossfade; 31 | chowdsp::PitchShifter shifter { 4096, 256 }; 32 | 33 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PitchShiftWrapper) 34 | }; 35 | -------------------------------------------------------------------------------- /src/dsp/Delay/Reverser.cpp: -------------------------------------------------------------------------------- 1 | #include "Reverser.h" 2 | 3 | namespace 4 | { 5 | int nearestEvenInt (int to) 6 | { 7 | return (to % 2 == 0) ? to : (to + 1); 8 | } 9 | } // namespace 10 | 11 | void Reverser::prepare (const dsp::ProcessSpec& spec) 12 | { 13 | fs = (float) spec.sampleRate; 14 | maxWindowSize = (int) fs; // 1 second 15 | 16 | windowBuffer.setSize (1, maxWindowSize); 17 | window = windowBuffer.getWritePointer (0); 18 | 19 | reverseBuffer.setSize (1, maxWindowSize * 4); 20 | revBuffPtr = reverseBuffer.getWritePointer (0); 21 | 22 | reset(); 23 | } 24 | 25 | void Reverser::reset() 26 | { 27 | reverseBuffer.clear(); 28 | windowSize = -1; // this will get reset next time setReverseTime is called 29 | } 30 | 31 | void Reverser::setReverseTime (float revTimeMs) 32 | { 33 | auto newWindowSize = nearestEvenInt (int (revTimeMs * fs / 1000.0f)); 34 | newWindowSize = newWindowSize > 0 ? jmax (newWindowSize, 8) : 0; 35 | if (windowSize == newWindowSize) 36 | return; 37 | 38 | if (newWindowSize == 0) 39 | { 40 | windowSize = 0; 41 | reverseBuffer.clear(); 42 | bypass = true; 43 | return; 44 | } 45 | 46 | bypass = false; 47 | 48 | windowSize = newWindowSize; 49 | doubleWindowSize = 2 * windowSize; 50 | halfWindowSize = windowSize / 2; 51 | 52 | bufferWrite = 0; 53 | bufferRead1 = doubleWindowSize; 54 | bufferRead2 = bufferRead1 + halfWindowSize; 55 | 56 | dsp::WindowingFunction::fillWindowingTables (window, 57 | (size_t) windowSize, 58 | dsp::WindowingFunction::hann, 59 | false); 60 | } 61 | -------------------------------------------------------------------------------- /src/dsp/Delay/Reverser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Time reversal effect, based on this paper: https://ccrma.stanford.edu/~hskim08/files/pdf/DAFx14_sttr.pdf 7 | */ 8 | class Reverser 9 | { 10 | public: 11 | Reverser() = default; 12 | 13 | void prepare (const dsp::ProcessSpec& spec); 14 | void reset(); 15 | void setReverseTime (float revTimeMs); 16 | 17 | inline float processSample (float x) 18 | { 19 | if (bypass) 20 | return x; 21 | 22 | // read output sample from reverse buffer 23 | float y = 0.0f; 24 | y += revBuffPtr[bufferRead1] * window[bufferRead1 % windowSize]; 25 | y += revBuffPtr[bufferRead2] * window[bufferRead2 % windowSize]; 26 | 27 | // update read pointers 28 | bufferRead1--; 29 | bufferRead1 = bufferRead1 > 0 ? bufferRead1 : doubleWindowSize; 30 | 31 | bufferRead2--; 32 | bufferRead2 = bufferRead2 > halfWindowSize ? bufferRead2 : doubleWindowSize + halfWindowSize; 33 | 34 | // push new sample into reverse buffer 35 | revBuffPtr[bufferWrite] = x; 36 | revBuffPtr[bufferWrite + doubleWindowSize] = x; 37 | 38 | // update write pointers 39 | bufferWrite++; 40 | bufferWrite = bufferWrite >= doubleWindowSize ? 0 : bufferWrite; 41 | 42 | return y; 43 | } 44 | 45 | private: 46 | bool bypass = false; 47 | 48 | AudioBuffer reverseBuffer; 49 | float* revBuffPtr = nullptr; 50 | 51 | float fs = 48000.0f; 52 | int maxWindowSize = 8192; 53 | 54 | AudioBuffer windowBuffer; 55 | float* window = nullptr; 56 | 57 | int bufferWrite, bufferRead1, bufferRead2; 58 | int windowSize, halfWindowSize, doubleWindowSize; 59 | 60 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverser) 61 | }; 62 | -------------------------------------------------------------------------------- /src/dsp/Delay/TempoSyncUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Parameters/ParamHelpers.h" 4 | 5 | /** 6 | * Utils for converting delay parameters 7 | * to tempo-synced delay times. 8 | */ 9 | namespace TempoSyncUtils 10 | { 11 | /** A simple struct containing a rhythmic delay length */ 12 | struct DelayRhythm 13 | { 14 | constexpr DelayRhythm (const std::string_view& name, const std::string_view& label, double tempoFactor) : name (name), 15 | label (label), 16 | tempoFactor (tempoFactor) {} 17 | 18 | inline String getLabel() const { return String (static_cast (label)); } 19 | 20 | std::string_view name; 21 | std::string_view label; 22 | double tempoFactor; 23 | }; 24 | 25 | static constexpr std::array rhythms { 26 | DelayRhythm ("Thirty-Second", "1/32", 0.125), 27 | DelayRhythm ("Thirty-Second Dot", "1/32 D", 0.125 * 1.5), 28 | DelayRhythm ("Sixteenth Triplet", "1/16 T", 0.5 / 3.0), 29 | DelayRhythm ("Sixteenth", "1/16", 0.25), 30 | DelayRhythm ("Sixteenth Dot", "1/16 D", 0.25 * 1.5), 31 | DelayRhythm ("Eigth Triplet", "1/8 T", 1.0 / 3.0), 32 | DelayRhythm ("Eigth", "1/8", 0.5), 33 | DelayRhythm ("Eigth Dot", "1/8 D", 0.5 * 1.5), 34 | DelayRhythm ("Quarter Triplet", "1/4 T", 2.0 / 3.0), 35 | DelayRhythm ("Quarter", "1/4", 1.0), 36 | DelayRhythm ("Quarter Dot", "1/4 D", 1.0 * 1.5), 37 | DelayRhythm ("Half Triplet", "1/2 T", 4.0 / 3.0), 38 | DelayRhythm ("Half", "1/2", 2.0), 39 | DelayRhythm ("Half Dot", "1/2 D", 2.0 * 1.5), 40 | DelayRhythm ("Whole Triplet", "1/1 T", 8.0 / 3.0), 41 | DelayRhythm ("Whole", "1/1", 4.0), 42 | DelayRhythm ("Whole Dot", "1/1 D", 4.0 * 1.5), 43 | DelayRhythm ("Two Whole Triplet", "2/1 T", 16.0 / 3.0), 44 | DelayRhythm ("Two Whole", "2/1", 8.0), 45 | }; 46 | 47 | /** Return time in seconds for rhythm and tempo */ 48 | static inline double getTimeForRythm (double tempoBPM, const DelayRhythm& rhythm) 49 | { 50 | const auto beatLength = 1.0 / (tempoBPM / 60.0); 51 | return beatLength * rhythm.tempoFactor; 52 | } 53 | 54 | /** Returns the corresponding rhythm for a 0-1 param value */ 55 | static inline const DelayRhythm& getRhythmForParam (float param01) 56 | { 57 | auto idx = static_cast ((rhythms.size() - 1) * std::pow (param01, 1.5f)); 58 | return rhythms[idx]; 59 | } 60 | 61 | /** Sine-wave oscillator synced to the song tempo */ 62 | class SyncedLFO : public chowdsp::SineWave 63 | { 64 | public: 65 | SyncedLFO() {} 66 | virtual ~SyncedLFO() {} 67 | 68 | // Sets the correct oscillator frequency for a given tempo 69 | void setFreqSynced (const Parameter* freqParam, float tempoBPM) 70 | { 71 | const auto& rhythm = getRhythmForParam (freqParam->convertTo0to1 (*freqParam)); 72 | float freqValue = 1.0f / (float) getTimeForRythm ((double) tempoBPM, rhythm); 73 | setFrequency (freqValue); 74 | } 75 | 76 | // resets the LFO when the DAW starts playing 77 | void setPlayHead (AudioPlayHead* playhead) 78 | { 79 | if (playhead == nullptr) 80 | return; 81 | 82 | AudioPlayHead::CurrentPositionInfo info; 83 | playhead->getCurrentPosition (info); 84 | 85 | if (info.isPlaying && ! wasPlaying) 86 | reset(); 87 | 88 | wasPlaying = info.isPlaying; 89 | } 90 | 91 | private: 92 | bool wasPlaying = false; 93 | 94 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SyncedLFO) 95 | }; 96 | 97 | } // namespace TempoSyncUtils 98 | -------------------------------------------------------------------------------- /src/dsp/Delay/VariableDelay.cpp: -------------------------------------------------------------------------------- 1 | #include "VariableDelay.h" 2 | 3 | VariableDelay::VariableDelay (size_t size) : l0Delay (static_cast (size)), 4 | l1Delay (static_cast (size)), 5 | l3Delay (static_cast (size)), 6 | l5Delay (static_cast (size)), 7 | sinc16Delay (static_cast (size)), 8 | sinc32Delay (static_cast (size)) 9 | { 10 | } 11 | 12 | void VariableDelay::setDelayForce (float newDelayInSamples) noexcept 13 | { 14 | delaySmooth.setCurrentAndTargetValue (newDelayInSamples); 15 | 16 | for (auto* delay : delays) 17 | delay->setDelay (newDelayInSamples); 18 | } 19 | 20 | void VariableDelay::setDelayType (DelayType newType) 21 | { 22 | auto oldType = type; 23 | 24 | // copy state and parameters first... 25 | delays[newType]->setDelay (delays[oldType]->getDelay()); 26 | delays[newType]->copyState (*delays[oldType]); 27 | 28 | // then set new type 29 | type = newType; 30 | 31 | switch (type) 32 | { 33 | case BBDShort: 34 | makeupGain = 0.85f; 35 | delays[type]->reset(); 36 | break; 37 | case BBDLong: 38 | makeupGain = 0.65f; 39 | delays[type]->reset(); 40 | break; 41 | default: 42 | makeupGain = 1.0f; 43 | }; 44 | } 45 | 46 | void VariableDelay::delayBlockStart() noexcept 47 | { 48 | delays[type]->setDelay (delaySmooth.getCurrentValue()); 49 | 50 | if (type == BBDShort) 51 | bbdShortDelay.setFilterFreq (2000.0f); 52 | 53 | if (type == BBDLong) 54 | bbdLongDelay.setFilterFreq (9000.0f); 55 | } 56 | 57 | void VariableDelay::prepare (const juce::dsp::ProcessSpec& spec) 58 | { 59 | delaySmooth.reset (spec.sampleRate, 0.1); 60 | 61 | for (auto* delay : delays) 62 | delay->prepare (spec); 63 | } 64 | 65 | void VariableDelay::reset() 66 | { 67 | for (auto* delay : delays) 68 | delay->reset(); 69 | } 70 | -------------------------------------------------------------------------------- /src/dsp/Delay/VariableDelay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Delay class with variable interpolation type 7 | */ 8 | class VariableDelay 9 | { 10 | public: 11 | VariableDelay (size_t size); 12 | 13 | enum DelayType 14 | { 15 | NoInterp, 16 | LinearInterp, 17 | ThirdInterp, 18 | FifthInterp, 19 | Sinc16, 20 | Sinc32, 21 | BBDShort, 22 | BBDLong, 23 | BBDAlien, 24 | }; 25 | 26 | // manage parameters 27 | inline void setDelay (float newDelayInSamples) noexcept 28 | { 29 | delaySmooth.setTargetValue (newDelayInSamples); 30 | } 31 | 32 | // call this at the start of processinf a block of data 33 | void delayBlockStart() noexcept; 34 | 35 | void setDelayForce (float newDelayInSamples) noexcept; 36 | void setDelayType (DelayType newType); 37 | bool isDelaySmoothing() const { return delaySmooth.isSmoothing(); } 38 | 39 | // delegate everything else to dsp::DelayLine 40 | void prepare (const juce::dsp::ProcessSpec& spec); 41 | void reset(); 42 | 43 | inline void pushSample (int channel, float sample) 44 | { 45 | delays[type]->pushSample (channel, sample); 46 | } 47 | 48 | inline void pushSampleSmooth (int channel, float sample) 49 | { 50 | delays[type]->setDelay (delaySmooth.getNextValue()); 51 | delays[type]->pushSample (channel, sample); 52 | } 53 | 54 | inline float popSample (int channel) 55 | { 56 | return makeupGain * delays[type]->popSample (channel); 57 | } 58 | 59 | private: 60 | chowdsp::DelayLine l0Delay; 61 | chowdsp::DelayLine l1Delay; 62 | chowdsp::DelayLine l3Delay; 63 | chowdsp::DelayLine l5Delay; 64 | chowdsp::DelayLine> sinc16Delay; 65 | chowdsp::DelayLine> sinc32Delay; 66 | chowdsp::BBD::BBDDelayWrapper<4096> bbdShortDelay; 67 | chowdsp::BBD::BBDDelayWrapper<16384> bbdLongDelay; 68 | chowdsp::BBD::BBDDelayWrapper<8192, true> bbdAlienDelay; 69 | 70 | std::array*, 9> delays { &l0Delay, &l1Delay, &l3Delay, &l5Delay, &sinc16Delay, &sinc32Delay, &bbdShortDelay, &bbdLongDelay, &bbdAlienDelay }; 71 | DelayType type = ThirdInterp; 72 | 73 | SmoothedValue delaySmooth; 74 | float makeupGain = 1.0f; 75 | 76 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VariableDelay) 77 | }; 78 | -------------------------------------------------------------------------------- /src/dsp/Distortion/Distortion.cpp: -------------------------------------------------------------------------------- 1 | #include "Distortion.h" 2 | 3 | Distortion::Distortion() 4 | { 5 | gain.setCurrentAndTargetValue (1.0f); 6 | } 7 | 8 | void Distortion::prepare (const dsp::ProcessSpec& spec) 9 | { 10 | adaa.prepare(); 11 | gain.reset (spec.sampleRate, 0.05); 12 | } 13 | 14 | void Distortion::reset() 15 | { 16 | adaa.prepare(); 17 | gain.setCurrentAndTargetValue (gain.getTargetValue()); 18 | } 19 | 20 | void ADAA2::prepare() 21 | { 22 | tables->prepare(); 23 | 24 | x1 = 0.0; 25 | x2 = 0.0; 26 | ad2_x1 = 0.0; 27 | d2 = 0.0; 28 | } 29 | -------------------------------------------------------------------------------- /src/dsp/Distortion/Distortion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ADAAConsts 6 | { 7 | constexpr double TOL = 1.0e-4; 8 | } 9 | 10 | /** 11 | * Class to implement 2nd-order anti-derivative anti-aliasing 12 | */ 13 | class ADAA2 14 | { 15 | public: 16 | ADAA2() = default; 17 | 18 | void prepare(); 19 | 20 | inline double process (double x) noexcept 21 | { 22 | bool illCondition = std::abs (x - x2) < ADAAConsts::TOL; 23 | double d1 = calcD1 (x); 24 | 25 | double y = illCondition ? fallback (x) : (2.0 / (x - x2)) * (d1 - d2); 26 | 27 | // update state 28 | d2 = d1; 29 | x2 = x1; 30 | x1 = x; 31 | 32 | return y; 33 | } 34 | 35 | private: 36 | inline double calcD1 (double x0) noexcept 37 | { 38 | bool illCondition = std::abs (x0 - x1) < ADAAConsts::TOL; 39 | double ad2_x0 = tables->lut_AD2.processSample (x0); 40 | 41 | double y = illCondition ? tables->lut_AD1.processSample (0.5 * (x0 + x1)) : (ad2_x0 - ad2_x1) / (x0 - x1); 42 | 43 | ad2_x1 = ad2_x0; 44 | return y; 45 | } 46 | 47 | inline double fallback (double x) const noexcept 48 | { 49 | double xBar = 0.5 * (x + x2); 50 | double delta = xBar - x; 51 | 52 | bool illCondition = std::abs (delta) < ADAAConsts::TOL; 53 | return illCondition ? tables->lut.processSample (0.5 * (xBar + x)) : (2.0 / delta) * (tables->lut_AD1.processSample (xBar) + (tables->lut_AD2.processSample (x) - tables->lut_AD2.processSample (xBar)) / delta); 54 | } 55 | 56 | double x1 = 0.0; 57 | double x2 = 0.0; 58 | double ad2_x1 = 0.0; 59 | double d2 = 0.0; 60 | 61 | SharedResourcePointer tables; 62 | 63 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ADAA2) 64 | }; 65 | 66 | /** 67 | * Class to implement tanh distortion 68 | */ 69 | class Distortion 70 | { 71 | public: 72 | Distortion(); 73 | 74 | void setGain (float newGain) { gain.setTargetValue (newGain); } 75 | void prepare (const dsp::ProcessSpec& spec); 76 | void reset(); 77 | 78 | inline float processSample (float x) 79 | { 80 | float curGain = gain.getNextValue(); 81 | 82 | if (curGain < 0.6f) 83 | return x; 84 | 85 | return (float) adaa.process ((double) (curGain * x)) / curGain; 86 | } 87 | 88 | private: 89 | ADAA2 adaa; 90 | SmoothedValue gain; 91 | 92 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Distortion) 93 | }; 94 | -------------------------------------------------------------------------------- /src/dsp/Distortion/LookupTables.cpp: -------------------------------------------------------------------------------- 1 | #include "LookupTables.h" 2 | #include 3 | 4 | namespace 5 | { 6 | constexpr int N = 1 << 17; 7 | constexpr double minVal = -10.0; 8 | constexpr double maxVal = 10.0; 9 | } // namespace 10 | 11 | inline double func (double x) noexcept 12 | { 13 | return std::tanh (x); 14 | } 15 | 16 | /** First antiderivative of hard clipper */ 17 | inline double func_AD1 (double x) noexcept 18 | { 19 | return std::log (std::cosh (x)); 20 | } 21 | 22 | /** Second antiderivative of hard clipper */ 23 | inline double func_AD2 (double x) noexcept 24 | { 25 | const auto expVal = std::exp (-2 * x); 26 | return 0.5 * ((double) polylogarithm::Li2 (-expVal) - x * (x + 2.0 * std::log (expVal + 1.) - 2.0 * std::log (std::cosh (x)))) 27 | + (std::pow (MathConstants::pi, 2) / 24.0); 28 | } 29 | 30 | void LookupTables::prepare() 31 | { 32 | for (auto& future : futures) 33 | { 34 | future.wait(); 35 | } 36 | 37 | futures.clear(); 38 | } 39 | 40 | LookupTables::LookupTables() 41 | { 42 | // loading the lookup tables takes a while, so let's do it asynchronously 43 | auto makeLUTAsync = [=] (auto lutInit) { 44 | futures.push_back (std::async (std::launch::async, lutInit)); 45 | }; 46 | 47 | makeLUTAsync ([=] { lut.initialise ([=] (double x) { return func (x); }, minVal, maxVal, N); }); 48 | makeLUTAsync ([=] { lut_AD1.initialise ([=] (double x) { return func_AD1 (x); }, 2 * minVal, 2 * maxVal, 4 * N); }); 49 | makeLUTAsync ([=] { lut_AD2.initialise ([=] (double x) { return func_AD2 (x); }, 4 * minVal, 4 * maxVal, 16 * N); }); 50 | } 51 | -------------------------------------------------------------------------------- /src/dsp/Distortion/LookupTables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Shared resource class to store lookup tables 7 | * for nonlinear functions 8 | */ 9 | class LookupTables 10 | { 11 | public: 12 | LookupTables(); 13 | 14 | void prepare(); 15 | 16 | dsp::LookupTableTransform lut; // tanh 17 | dsp::LookupTableTransform lut_AD1; // anti-derivative of tanh 18 | dsp::LookupTableTransform lut_AD2; // 2nd anti-derivative of tanh 19 | 20 | private: 21 | std::vector> futures; 22 | 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookupTables) 24 | }; 25 | -------------------------------------------------------------------------------- /src/dsp/InputNode.cpp: -------------------------------------------------------------------------------- 1 | #include "InputNode.h" 2 | #include "../gui/MatrixView/InputNodeComponent.h" 3 | 4 | XmlElement* InputNode::saveXml() 5 | { 6 | std::unique_ptr xml = std::make_unique ("input_node"); 7 | xml->addChildElement (DBaseNode::saveXml()); 8 | 9 | return xml.release(); 10 | } 11 | 12 | void InputNode::loadXml (XmlElement* xmlState) 13 | { 14 | if (xmlState == nullptr) 15 | return; 16 | 17 | if (xmlState->hasTagName ("input_node")) 18 | { 19 | if (auto* childrenXml = xmlState->getChildByName ("children")) 20 | DBaseNode::loadXml (childrenXml); 21 | } 22 | } 23 | 24 | std::unique_ptr InputNode::createNodeEditor (GraphView* view) 25 | { 26 | auto editorPtr = std::make_unique (*this, view); 27 | editor = editorPtr.get(); 28 | return std::move (editorPtr); 29 | } 30 | -------------------------------------------------------------------------------- /src/dsp/InputNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DelayNode.h" 4 | 5 | /** 6 | * Class for audio Input Node 7 | */ 8 | class InputNode : public DBaseNode 9 | { 10 | public: 11 | InputNode() = default; 12 | 13 | XmlElement* saveXml() override; 14 | void loadXml (XmlElement*) override; 15 | 16 | std::unique_ptr createNodeEditor (GraphView*) override; 17 | 18 | private: 19 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputNode) 20 | }; 21 | -------------------------------------------------------------------------------- /src/dsp/Parameters/BaseController.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseController.h" 2 | #include "../../NodeManager.h" 3 | 4 | BaseController::BaseController (AudioProcessorValueTreeState& vts, 5 | std::array* nodes, 6 | StringArray paramsToListenFor) : vts (vts), 7 | nodes (nodes), 8 | paramsToListenFor (paramsToListenFor) 9 | { 10 | for (auto& node : *nodes) 11 | { 12 | node.addNodeListener (this); 13 | NodeManager::doForNodes (&node, [=] (DelayNode* n) { n->addNodeListener (this); }); 14 | } 15 | 16 | for (const auto& param : paramsToListenFor) 17 | vts.addParameterListener (param, this); 18 | } 19 | 20 | BaseController::~BaseController() 21 | { 22 | NodeManager::doForNodes (nodes, [=] (DelayNode* n) { n->removeNodeListener (this); }); 23 | 24 | for (const auto& param : paramsToListenFor) 25 | vts.removeParameterListener (param, this); 26 | } 27 | 28 | void BaseController::nodeAdded (DelayNode* newNode) 29 | { 30 | newNode->addNodeListener (this); 31 | newNodeAdded (newNode); 32 | } 33 | 34 | void BaseController::nodeRemoved (DelayNode* nodeToRemove) 35 | { 36 | newNodeRemoved (nodeToRemove); 37 | nodeToRemove->removeNodeListener (this); 38 | } 39 | 40 | void BaseController::nodeIndexChanged (DelayNode* node, int oldIndex, int newIndex) 41 | { 42 | nodeIndexHasChanged (node, oldIndex, newIndex); 43 | } 44 | 45 | void BaseController::doForNodes (std::function nodeFunc) 46 | { 47 | NodeManager::doForNodes (nodes, nodeFunc); 48 | } 49 | -------------------------------------------------------------------------------- /src/dsp/Parameters/BaseController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../InputNode.h" 4 | 5 | /** 6 | * Base class for some parameter controller that needs 7 | * access to all active nodes 8 | */ 9 | class BaseController : private AudioProcessorValueTreeState::Listener, 10 | private DBaseNode::Listener 11 | { 12 | public: 13 | using Parameters = std::vector>; 14 | 15 | BaseController (AudioProcessorValueTreeState& vts, 16 | std::array* nodes, 17 | StringArray paramsToListenFor); 18 | ~BaseController() override; 19 | 20 | protected: 21 | virtual void newNodeAdded (DelayNode*) {} 22 | virtual void newNodeRemoved (DelayNode*) {} 23 | virtual void nodeIndexHasChanged (DelayNode* /*node*/, int /*oldIndex*/, int /*newIndex*/) {} 24 | void doForNodes (std::function nodeFunc); 25 | 26 | private: 27 | void nodeAdded (DelayNode* newNode) override; 28 | void nodeRemoved (DelayNode* nodeToRemove) override; 29 | void nodeIndexChanged (DelayNode* node, int oldIndex, int newIndex) override; 30 | 31 | AudioProcessorValueTreeState& vts; 32 | std::array* nodes; 33 | StringArray paramsToListenFor; 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BaseController) 36 | }; 37 | -------------------------------------------------------------------------------- /src/dsp/Parameters/DelayTypeControl.cpp: -------------------------------------------------------------------------------- 1 | #include "DelayTypeControl.h" 2 | #include "../../NodeManager.h" 3 | 4 | namespace 5 | { 6 | const String delayTypeTag = "delay_type"; 7 | } 8 | 9 | static inline VariableDelay::DelayType getDelayType (float param) 10 | { 11 | return static_cast (int (param)); 12 | } 13 | 14 | DelayTypeControl::DelayTypeControl (AudioProcessorValueTreeState& vts, std::array* nodes, StateManager& stateMgr) : BaseController (vts, nodes, { delayTypeTag }), 15 | stateManager (stateMgr) 16 | { 17 | delayTypeParam = vts.getRawParameterValue (delayTypeTag); 18 | parameterChanged (delayTypeTag, delayTypeParam->load()); 19 | } 20 | 21 | void DelayTypeControl::addParameters (Parameters& params) 22 | { 23 | chowdsp::ParamUtils::emplace_param (params, 24 | delayTypeTag, 25 | "Delay Type", 26 | StringArray ({ "Glitch", "Rough", "Smooth", "Ultra Smooth", "Liquid", "Super Liquid", "Lo-Fi", "Analog", "Alien" }), 27 | 2); 28 | } 29 | 30 | void DelayTypeControl::parameterChanged (const String& paramID, float newValue) 31 | { 32 | if (stateManager.getIsLoading()) 33 | { 34 | // StateManager is currently loading a new state. 35 | // Let's wait until it's done and call again... 36 | Thread::sleep (2); 37 | MessageManager::callAsync ([=] { parameterChanged (paramID, newValue); }); 38 | } 39 | else 40 | { 41 | const SpinLock::ScopedTryLockType stateLoadTryLock (stateManager.getStateLoadLock()); 42 | 43 | if (stateLoadTryLock.isLocked()) 44 | { 45 | // We're sure it's safe to set delay types now! 46 | auto type = getDelayType (newValue); 47 | doForNodes ([=] (DelayNode* n) { n->setDelayType (type); }); 48 | } 49 | else 50 | { 51 | // Can't reset delay types while processing audio! 52 | // Let's wait and try again... 53 | Thread::sleep (2); 54 | MessageManager::callAsync ([=] { parameterChanged (paramID, newValue); }); 55 | } 56 | } 57 | } 58 | 59 | void DelayTypeControl::newNodeAdded (DelayNode* newNode) 60 | { 61 | newNode->setDelayType (getDelayType (delayTypeParam->load())); 62 | } 63 | -------------------------------------------------------------------------------- /src/dsp/Parameters/DelayTypeControl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Delay/VariableDelay.h" 4 | #include "BaseController.h" 5 | #include "state/StateManager.h" 6 | 7 | /** 8 | * Utility class to manage delay interpolation types 9 | */ 10 | class DelayTypeControl : private BaseController 11 | { 12 | public: 13 | DelayTypeControl (AudioProcessorValueTreeState& vts, std::array* nodes, StateManager& stateMgr); 14 | 15 | static void addParameters (Parameters& params); 16 | void newNodeAdded (DelayNode* newNode) override; 17 | 18 | void parameterChanged (const String&, float newValue) override; 19 | std::atomic* getParameter() const noexcept { return delayTypeParam; } 20 | 21 | private: 22 | std::atomic* delayTypeParam = nullptr; 23 | StateManager& stateManager; 24 | 25 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayTypeControl) 26 | }; 27 | -------------------------------------------------------------------------------- /src/dsp/Parameters/HostParamControl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseController.h" 4 | 5 | class HostParamControl : public BaseController 6 | { 7 | public: 8 | HostParamControl (AudioProcessorValueTreeState& vts, std::array* nodes); 9 | 10 | static void addParameters (Parameters& params); 11 | void newNodeAdded (DelayNode* newNode) override; 12 | void newNodeRemoved (DelayNode* newNode) override; 13 | 14 | void parameterChanged (const String&, float newValue) override; 15 | 16 | void addParameterMenus (PopupMenu& parentMenu, const String& paramID, DelayNode* node) override; 17 | 18 | void beginParameterChange (const StringArray& paramIDs, DelayNode* node) override; 19 | void endParameterChange (const StringArray& paramIDs, DelayNode* node) override; 20 | void applyParameterChange (const String& paramID, DelayNode* node, float value01) override; 21 | 22 | void saveExtraNodeState (XmlElement* nodeState, DelayNode* node) override; 23 | void loadExtraNodeState (XmlElement* nodeState, DelayNode* node) override; 24 | 25 | void saveGlobalMap (XmlElement* mapXml); 26 | void loadGlobalMap (XmlElement* mapXml); 27 | 28 | void loadParamList (StringArray& paramList, std::vector>& xCallbacks, size_t mapIdx); 29 | constexpr size_t getNumAssignableParams() const noexcept { return numParams; } 30 | 31 | private: 32 | struct MapInfo 33 | { 34 | DelayNode* nodePtr = nullptr; 35 | String mappedParamID; 36 | }; 37 | 38 | using MapIter = std::vector::const_iterator; 39 | using GlobalIter = std::vector::const_iterator; 40 | 41 | bool doForParamMap (DelayNode* node, const String& paramID, size_t mapIdx, std::function found, std::function notFound); 42 | MapIter findMap (DelayNode* node, const String& paramID, size_t mapIdx) const; 43 | void toggleParamMap (DelayNode* node, const String& paramID, size_t mapIdx); 44 | 45 | bool doForGroupMap (const String& paramID, size_t mapIdx, std::function found, std::function notFound); 46 | GlobalIter findMap (const String& paramID, size_t mapIdx) const; 47 | void toggleGroupParamMap (DelayNode* node, const String& paramID, size_t mapIdx); 48 | 49 | bool isParamMapped (size_t mapIdx) const noexcept; 50 | bool doForBothMaps (DelayNode* node, const String& paramID, size_t mapIdx, std::function found, std::function notFound); 51 | 52 | static constexpr size_t numParams = 8; 53 | static inline StringArray paramIDs; 54 | 55 | std::array, numParams> paramGroupMaps; 56 | std::array, numParams> paramControlMaps; 57 | std::array parameterHandles; 58 | 59 | friend class HostControlTest; 60 | 61 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HostParamControl) 62 | }; 63 | -------------------------------------------------------------------------------- /src/dsp/Parameters/InsanityControl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseController.h" 4 | 5 | /** 6 | * Utility class to manage insanity controls 7 | */ 8 | class InsanityControl : private BaseController, 9 | private Timer 10 | { 11 | public: 12 | InsanityControl (AudioProcessorValueTreeState& vts, std::array* nodes); 13 | 14 | static void addParameters (Parameters& params); 15 | void newNodeAdded (DelayNode* newNode) override; 16 | 17 | void timerCallback() override; 18 | void parameterChanged (const String& paramID, float newValue) override; 19 | std::atomic* getParameter() const noexcept { return insanityParam; } 20 | 21 | /** 22 | * Resets the delay parameters to what they were 23 | * before insanity was turned on. 24 | */ 25 | void resetInsanityState(); 26 | 27 | private: 28 | void insanityStarting(); 29 | void insanityEnding(); 30 | 31 | std::atomic* insanityParam = nullptr; 32 | int timerFreq = 10; 33 | 34 | std::default_random_engine generator; 35 | std::uniform_real_distribution delay_dist { -0.05f, 0.05f }; 36 | std::uniform_real_distribution pan_dist { -0.1f, 0.1f }; 37 | 38 | float lastInsanity = 0.0f; 39 | std::unordered_map> insanityResetMap; 40 | std::unordered_map> insanityEndingMap; 41 | 42 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InsanityControl) 43 | }; 44 | -------------------------------------------------------------------------------- /src/dsp/Parameters/InsanityLockHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "InsanityLockHelper.h" 2 | 3 | InsanityLockHelper::InsanityLockHelper (std::function onParamLockChange) : onParamLockChange (onParamLockChange) 4 | { 5 | } 6 | 7 | void InsanityLockHelper::setInsanityLock (const String& paramID, bool shouldBeLocked, bool shouldBeReset) 8 | { 9 | jassert (! (shouldBeLocked && shouldBeReset)); // can't have both! 10 | 11 | const auto isLocked = lockedParams.contains (paramID); 12 | const auto isReset = resetParams.contains (paramID); 13 | 14 | if (shouldBeLocked == isLocked && shouldBeReset == isReset) // no change! 15 | return; 16 | 17 | if (! shouldBeLocked && ! shouldBeReset) // unlock 18 | { 19 | lockedParams.removeString (paramID); 20 | resetParams.removeString (paramID); 21 | } 22 | else if (shouldBeLocked) 23 | { 24 | lockedParams.addIfNotAlreadyThere (paramID); 25 | resetParams.removeString (paramID); 26 | } 27 | else if (shouldBeReset) 28 | { 29 | resetParams.addIfNotAlreadyThere (paramID); 30 | lockedParams.removeString (paramID); 31 | } 32 | 33 | onParamLockChange(); 34 | } 35 | 36 | void InsanityLockHelper::toggleInsanityLock (const String& paramID) 37 | { 38 | const auto isLocked = lockedParams.contains (paramID); 39 | const auto isReset = resetParams.contains (paramID); 40 | 41 | if (isLocked) 42 | { 43 | lockedParams.removeString (paramID); 44 | resetParams.addIfNotAlreadyThere (paramID); 45 | } 46 | else if (isReset) 47 | { 48 | resetParams.removeString (paramID); 49 | } 50 | else 51 | { 52 | lockedParams.addIfNotAlreadyThere (paramID); 53 | } 54 | 55 | onParamLockChange(); 56 | } 57 | 58 | void InsanityLockHelper::createPopupMenu (PopupMenu& parent, const String& paramID) 59 | { 60 | bool isLocked = isParamLocked (paramID); 61 | bool isReset = shouldParamReset (paramID); 62 | 63 | const std::map> settings { 64 | { 1, { "Unlock", ! (isLocked || isReset) } }, 65 | { 2, { "Lock", isLocked } }, 66 | { 3, { "Reset", isReset } } 67 | }; 68 | 69 | PopupMenu insanityLockMenu; 70 | for (const auto& [idx, setting] : settings) 71 | { 72 | const auto& [name, isOn] = setting; 73 | PopupMenu::Item item (name); 74 | item.itemID = idx; 75 | item.setColour (Colour (isOn ? 0xFF21CCA5 : 0xFFFFFFFF)); 76 | 77 | bool setLock = name == "Lock"; 78 | bool setReset = name == "Reset"; 79 | item.action = [=] { setInsanityLock (paramID, setLock, setReset); }; 80 | 81 | insanityLockMenu.addItem (item); 82 | } 83 | 84 | parent.addSubMenu ("Insanity Lock:", insanityLockMenu); 85 | } 86 | 87 | void InsanityLockHelper::saveState (XmlElement* xml) 88 | { 89 | xml->setAttribute ("locked", lockedParams.joinIntoString (",") + ","); 90 | xml->setAttribute ("reset", resetParams.joinIntoString (",") + ","); 91 | } 92 | 93 | void InsanityLockHelper::loadState (XmlElement* xml) 94 | { 95 | ParamHelpers::loadStringArray (lockedParams, xml->getStringAttribute ("locked", String())); 96 | ParamHelpers::loadStringArray (resetParams, xml->getStringAttribute ("reset", String())); 97 | } 98 | -------------------------------------------------------------------------------- /src/dsp/Parameters/InsanityLockHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ParamHelpers.h" 4 | 5 | /** 6 | * Helper class to manage parameter insanity 7 | * lock and reset functionality 8 | */ 9 | class InsanityLockHelper 10 | { 11 | public: 12 | InsanityLockHelper (std::function onParamLockChange); 13 | 14 | void toggleInsanityLock (const String& paramID); 15 | void setInsanityLock (const String& paramID, bool shouldBeLocked, bool shouldBeReset); 16 | 17 | bool isParamLocked (const String& paramID) const noexcept { return lockedParams.contains (paramID); } 18 | bool shouldParamReset (const String& paramID) const noexcept { return resetParams.contains (paramID); } 19 | 20 | void createPopupMenu (PopupMenu& parent, const String& paramID); 21 | 22 | void saveState (XmlElement* xml); 23 | void loadState (XmlElement* xml); 24 | 25 | private: 26 | StringArray lockedParams; 27 | StringArray resetParams; 28 | 29 | std::function onParamLockChange; 30 | 31 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InsanityLockHelper) 32 | }; 33 | -------------------------------------------------------------------------------- /src/dsp/Parameters/NodeParamControl.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeParamControl.h" 2 | 3 | namespace 4 | { 5 | constexpr int maxNumNodes = 50; 6 | 7 | String getForwardParamID (int nodeNum, int paramNum) 8 | { 9 | return "node_" + String (nodeNum) + "_" + ParamHelpers::getParamID (paramNum); 10 | } 11 | 12 | template 13 | void doForParams (FuncType&& func) 14 | { 15 | for (int nodeNum = 0; nodeNum < maxNumNodes; ++nodeNum) 16 | for (int paramNum = 0; paramNum < ParamHelpers::numParams; ++paramNum) 17 | func (nodeNum, paramNum); 18 | } 19 | 20 | template 21 | void doForNodeParams (DelayNode* node, FuncType&& func) 22 | { 23 | const auto nodeIndex = node->getIndex(); 24 | if (nodeIndex >= maxNumNodes) 25 | return; 26 | 27 | int paramIndex = 0; 28 | for (auto* param : node->getParameters()) 29 | { 30 | auto* paramCast = dynamic_cast (param); 31 | jassert (paramCast != nullptr); 32 | 33 | int forwardIndex = nodeIndex * ParamHelpers::numParams + (paramIndex++); 34 | func (paramCast, nodeIndex, forwardIndex); 35 | } 36 | } 37 | 38 | template 39 | void doForNodeParamIndexes (int nodeIndex, FuncType&& func) 40 | { 41 | if (nodeIndex >= maxNumNodes) 42 | return; 43 | 44 | for (int paramIndex = 0; paramIndex < ParamHelpers::numParams; ++paramIndex) 45 | { 46 | int forwardIndex = nodeIndex * ParamHelpers::numParams + paramIndex; 47 | func (forwardIndex); 48 | } 49 | } 50 | } // namespace 51 | 52 | NodeParamControl::NodeParamControl (AudioProcessorValueTreeState& vts, std::array* nodes, std::unique_ptr& presetMgr) 53 | : BaseController (vts, nodes, StringArray()), presetManager (presetMgr) 54 | { 55 | doForParams ([&] (int nodeNum, int paramNum) { 56 | auto id = getForwardParamID (nodeNum, paramNum); 57 | auto forwardedParam = std::make_unique (id, nullptr, "Blank"); 58 | 59 | forwardedParam->setProcessor (&vts.processor); 60 | forwardedParams.add (forwardedParam.get()); 61 | forwardedParam->addListener (this); 62 | vts.processor.addParameter (forwardedParam.release()); 63 | }); 64 | } 65 | 66 | NodeParamControl::~NodeParamControl() 67 | { 68 | for (auto* param : forwardedParams) 69 | param->setParam (nullptr); 70 | } 71 | 72 | void NodeParamControl::newNodeAdded (DelayNode* newNode) 73 | { 74 | doForNodeParams (newNode, [=] (RangedAudioParameter* param, int nodeIndex, int forwardIndex) { forwardedParams[forwardIndex]->setParam (param, "Node " + String (nodeIndex + 1) + ": " + param->getName (1024)); }); 75 | 76 | setCurrentPresetDirty(); 77 | } 78 | 79 | void NodeParamControl::newNodeRemoved (DelayNode* node) 80 | { 81 | doForNodeParamIndexes (node->getIndex(), [=] (int forwardIndex) { forwardedParams[forwardIndex]->setParam (nullptr); }); 82 | 83 | setCurrentPresetDirty(); 84 | } 85 | 86 | void NodeParamControl::nodeIndexHasChanged (DelayNode* node, int oldIndex, int /*newIndex*/) 87 | { 88 | doForNodeParamIndexes (oldIndex, [=] (int forwardIndex) { forwardedParams[forwardIndex]->setParam (nullptr); }); 89 | 90 | newNodeAdded (node); 91 | } 92 | 93 | void NodeParamControl::parameterValueChanged (int /*paramIndex*/, float /*newValue*/) 94 | { 95 | setCurrentPresetDirty(); 96 | } 97 | 98 | void NodeParamControl::setCurrentPresetDirty() 99 | { 100 | if (presetManager == nullptr) 101 | return; 102 | 103 | // Getting a crash here in some DAWs, so let's comment this out for now! 104 | // presetManager->setIsDirty (true); 105 | } 106 | -------------------------------------------------------------------------------- /src/dsp/Parameters/NodeParamControl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseController.h" 4 | #include "ParamHelpers.h" 5 | 6 | class NodeParamControl : public BaseController, 7 | private AudioProcessorParameter::Listener 8 | { 9 | public: 10 | NodeParamControl (AudioProcessorValueTreeState& vts, std::array* nodes, std::unique_ptr& presetMgr); 11 | ~NodeParamControl() override; 12 | 13 | void newNodeAdded (DelayNode* newNode) override; 14 | void newNodeRemoved (DelayNode* newNode) override; 15 | void nodeIndexHasChanged (DelayNode* node, int oldIndex, int newIndex) override; 16 | 17 | void parameterChanged (const String& /*parameterID*/, float /*newValue*/) override {} 18 | 19 | void parameterValueChanged (int parameterIndex, float newValue) override; 20 | void parameterGestureChanged (int, bool) override {} 21 | 22 | private: 23 | void setCurrentPresetDirty(); 24 | 25 | Array forwardedParams; 26 | 27 | std::unique_ptr& presetManager; 28 | 29 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NodeParamControl) 30 | }; 31 | -------------------------------------------------------------------------------- /src/dsp/Parameters/ParamHelpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ParamTags 6 | { 7 | const String delayTag = "DLY"; 8 | const String panTag = "PAN"; 9 | const String fbTag = "FDBK"; 10 | const String gainTag = "GAIN"; 11 | const String lpfTag = "LPF"; 12 | const String hpfTag = "HPF"; 13 | const String distTag = "DIST"; 14 | const String pitchTag = "PITCH"; 15 | const String diffTag = "DIFF"; 16 | const String revTag = "REV"; 17 | const String modFreqTag = "MOD_FREQ"; 18 | const String delayModTag = "MOD_DELAY"; 19 | const String panModTag = "MOD_PAN"; 20 | } // namespace ParamTags 21 | 22 | using Parameter = AudioProcessorValueTreeState::Parameter; 23 | 24 | namespace ParamHelpers 25 | { 26 | // parameter constants 27 | constexpr float maxDelay = 1500.0f; 28 | constexpr float centreDelay = 200.0f; 29 | constexpr float maxFeedback = 0.99f; 30 | constexpr float maxGain = 12.0f; 31 | constexpr float minLPF = 200.0f; 32 | constexpr float maxLPF = 20000.0f; 33 | constexpr float minHPF = 20.0f; 34 | constexpr float maxHPF = 2000.0f; 35 | constexpr float maxPitch = 12.0f; 36 | constexpr float minModFreq = 0.0f; 37 | constexpr float maxModFreq = 5.0f; 38 | constexpr int numParams = 13; 39 | 40 | /** Sets a parameter value */ 41 | void setParameterValue (Parameter* param, float newVal); 42 | 43 | /** Creates a parameter layout for a DelayNode */ 44 | AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 45 | 46 | String delayValToString (float delayVal); 47 | float stringToDelayVal (const String& s); 48 | 49 | String panValToString (float panVal); 50 | float stringToPanVal (const String& s); 51 | 52 | String fbValToString (float fbVal); 53 | float stringToFbVal (const String& s); 54 | 55 | String gainValToString (float gainVal); 56 | float stringToGainVal (const String& s); 57 | 58 | String pitchValToString (float pitchVal); 59 | float stringToPitchVal (const String& s); 60 | 61 | String freqValToString (float freqVal); 62 | float stringToFreqVal (const String& s); 63 | 64 | String percentValToString (float percentVal); 65 | float stringToPercentVal (const String& s); 66 | 67 | using StringToValFunc = float (*) (const String&); 68 | StringToValFunc getStringFuncForParam (const String& paramID); 69 | 70 | /** Creates a tooltip for a given paramID */ 71 | String getTooltip (const String& paramID); 72 | 73 | /** Gets a parameter name for a given parameter ID */ 74 | String getName (const String& paramID); 75 | 76 | /** Gets the parameter ID for a given index */ 77 | String getParamID (int index); 78 | 79 | /** Loads parameters from a comma-separated string into a StringArray */ 80 | void loadStringArray (StringArray& array, String string); 81 | 82 | } // namespace ParamHelpers 83 | -------------------------------------------------------------------------------- /src/dsp/Parameters/RandomiseLockHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "RandomiseLockHelper.h" 2 | 3 | RandomiseLockHelper::RandomiseLockHelper() 4 | { 5 | lockedParams.add (ParamTags::pitchTag); 6 | lockedParams.add (ParamTags::modFreqTag); 7 | } 8 | 9 | void RandomiseLockHelper::getLockedParamsFromParent (const RandomiseLockHelper& parentHelper) 10 | { 11 | // copy locked params from parent 12 | lockedParams = parentHelper.lockedParams; 13 | } 14 | 15 | void RandomiseLockHelper::toggleRandomiseLock (const String& paramID) 16 | { 17 | if (lockedParams.contains (paramID)) 18 | lockedParams.removeString (paramID); 19 | else 20 | lockedParams.addIfNotAlreadyThere (paramID); 21 | } 22 | 23 | void RandomiseLockHelper::createPopupMenu (PopupMenu& parent, const String& paramID) 24 | { 25 | bool isLocked = isParamLocked (paramID); 26 | 27 | PopupMenu randLockMenu; 28 | for (int i = 0; i < 2; ++i) 29 | { 30 | PopupMenu::Item item (i == 0 ? "Unlock" : "Lock"); 31 | item.itemID = i + 1; 32 | item.setColour (Colour (i == (int) isLocked ? 0xFF21CCA5 : 0xFFFFFFFF)); 33 | item.action = [=] { toggleRandomiseLock (paramID); }; 34 | 35 | randLockMenu.addItem (item); 36 | } 37 | 38 | parent.addSubMenu ("Randomise Lock:", randLockMenu); 39 | } 40 | 41 | void RandomiseLockHelper::saveState (XmlElement* xml) 42 | { 43 | xml->setAttribute ("rand_lock", lockedParams.joinIntoString (",") + ","); 44 | } 45 | 46 | void RandomiseLockHelper::loadState (XmlElement* xml) 47 | { 48 | ParamHelpers::loadStringArray (lockedParams, xml->getStringAttribute ("rand_lock", String())); 49 | } 50 | -------------------------------------------------------------------------------- /src/dsp/Parameters/RandomiseLockHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ParamHelpers.h" 4 | 5 | /** 6 | * Helper class to manage parameter randomise 7 | * lock functionality 8 | */ 9 | class RandomiseLockHelper 10 | { 11 | public: 12 | RandomiseLockHelper(); 13 | 14 | void getLockedParamsFromParent (const RandomiseLockHelper& parentHelper); 15 | 16 | void toggleRandomiseLock (const String& paramID); 17 | bool isParamLocked (const String& paramID) const noexcept { return lockedParams.contains (paramID); } 18 | 19 | void createPopupMenu (PopupMenu& parent, const String& paramID); 20 | 21 | void saveState (XmlElement* xml); 22 | void loadState (XmlElement* xml); 23 | 24 | private: 25 | StringArray lockedParams; 26 | 27 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RandomiseLockHelper) 28 | }; 29 | -------------------------------------------------------------------------------- /src/dsp/Parameters/SyncControl.cpp: -------------------------------------------------------------------------------- 1 | #include "SyncControl.h" 2 | #include "../../NodeManager.h" 3 | 4 | namespace 5 | { 6 | const String syncTag = "snyc"; 7 | } 8 | 9 | SyncControl::SyncControl (AudioProcessorValueTreeState& vts, std::array* nodes) : BaseController (vts, nodes, { syncTag }) 10 | { 11 | syncParam = vts.getRawParameterValue (syncTag); 12 | parameterChanged (syncTag, syncParam->load()); 13 | } 14 | 15 | void SyncControl::addParameters (Parameters& params) 16 | { 17 | chowdsp::ParamUtils::emplace_param (params, syncTag, "Sync", false); 18 | } 19 | 20 | void SyncControl::setTempo (AudioPlayHead* playhead) 21 | { 22 | double newTempo = 120.0; 23 | 24 | // get tempo from host 25 | if (playhead) 26 | { 27 | AudioPlayHead::CurrentPositionInfo posInfo; 28 | playhead->getCurrentPosition (posInfo); 29 | newTempo = posInfo.bpm > 10.0 ? posInfo.bpm : 120.0; 30 | } 31 | 32 | // load new tempo if needed 33 | if (newTempo != tempo.load()) 34 | { 35 | tempo.store (newTempo); 36 | doForNodes ([=] (DelayNode* n) { n->setTempo (newTempo); }); 37 | } 38 | 39 | doForNodes ([=] (DelayNode* n) { n->setPlayHead (playhead); }); 40 | } 41 | 42 | void SyncControl::newNodeAdded (DelayNode* newNode) 43 | { 44 | newNode->setTempo (tempo.load()); 45 | newNode->setDelaySync (static_cast (syncParam->load())); 46 | } 47 | 48 | void SyncControl::parameterChanged (const String&, float newValue) 49 | { 50 | if (MessageManager::getInstance()->isThisTheMessageThread()) 51 | { 52 | doForNodes ([=] (DelayNode* n) { n->setDelaySync (static_cast (newValue)); }); 53 | } 54 | else 55 | { 56 | MessageManager::callAsync ([=] { doForNodes ([=] (DelayNode* n) { n->setDelaySync (static_cast (newValue)); }); }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/dsp/Parameters/SyncControl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseController.h" 4 | 5 | /** 6 | * Utility class to manage sync/free delay modes 7 | */ 8 | class SyncControl : private BaseController 9 | { 10 | public: 11 | SyncControl (AudioProcessorValueTreeState& vts, std::array* nodes); 12 | 13 | static void addParameters (Parameters& params); 14 | void newNodeAdded (DelayNode* newNode) override; 15 | 16 | void parameterChanged (const String&, float newValue) override; 17 | void setTempo (AudioPlayHead* playhead); 18 | 19 | private: 20 | std::atomic* syncParam = nullptr; 21 | std::atomic tempo = 120.0; 22 | 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SyncControl) 24 | }; 25 | -------------------------------------------------------------------------------- /src/dsp/ProcessorBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** Base class to override (mostly) unused juce::AudioProcessor functions */ 6 | class ProcessorBase : public AudioProcessor 7 | { 8 | public: 9 | ProcessorBase (String name = String()) : name (name) {} 10 | 11 | const String getName() const override { return name; } 12 | 13 | double getTailLengthSeconds() const override { return 0.0; } 14 | 15 | void prepareToPlay (double /*sampleRate*/, int /*samplesPerBlock*/) override {} 16 | void processBlock (AudioBuffer&, MidiBuffer&) override {} 17 | void releaseResources() override {} 18 | 19 | bool acceptsMidi() const override { return false; } 20 | bool producesMidi() const override { return false; } 21 | 22 | AudioProcessorEditor* createEditor() override { return nullptr; } 23 | bool hasEditor() const override { return false; } 24 | 25 | int getNumPrograms() override { return 0; } 26 | void setCurrentProgram (int /*index*/) override {} 27 | int getCurrentProgram() override { return 0; } 28 | 29 | const String getProgramName (int /*index*/) override { return {}; } 30 | void changeProgramName (int /*index*/, const String& /*newName*/) override {} 31 | 32 | void getStateInformation (MemoryBlock& /*destData*/) override {} 33 | void setStateInformation (const void* /*data*/, int /*sizeInBytes*/) override {} 34 | 35 | private: 36 | String name; 37 | 38 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorBase) 39 | }; 40 | -------------------------------------------------------------------------------- /src/gui/AutoUpdating.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if CHOWDSP_AUTO_UPDATE 4 | 5 | #include 6 | 7 | class AutoUpdater : public Component 8 | { 9 | public: 10 | AutoUpdater(); 11 | ~AutoUpdater(); 12 | 13 | void paint (Graphics& g) override; 14 | void resized() override; 15 | 16 | void showUpdaterScreen (Component* parent); 17 | 18 | bool runAutoUpdateCheck(); 19 | void noButtonPressed(); 20 | void yesButtonPressed(); 21 | 22 | private: 23 | File getUpdateCheckFile(); 24 | String getLatestVersion(); 25 | String getUpdateFileVersion (const File& updateFile); 26 | bool getUpdateFileYesNo (const File& updateFile); 27 | void editUpdateCheckFile (String version, bool wantsUpdate); 28 | void parentSizeChanged() override; 29 | 30 | chowdsp::VersionUtils::Version newVersion { JucePlugin_VersionString }; 31 | 32 | TextButton yesButton { "Yes" }; 33 | TextButton noButton { "No" }; 34 | 35 | std::future needsUpdate; 36 | 37 | chowdsp::SharedLNFAllocator lnfAllocator; 38 | 39 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoUpdater) 40 | }; 41 | 42 | #endif // CHOWDSP_AUTO_UPDATE 43 | -------------------------------------------------------------------------------- /src/gui/BottomBar/ABComp.cpp: -------------------------------------------------------------------------------- 1 | #include "ABComp.h" 2 | 3 | struct ABLNF : public BottomBarLNF 4 | { 5 | Font getTextButtonFont (TextButton&, int) override { return Font (15.0f).boldened(); } 6 | 7 | void drawButtonText (Graphics& g, TextButton& button, bool, bool) override 8 | { 9 | Font font (getTextButtonFont (button, button.getHeight())); 10 | g.setFont (font); 11 | g.setColour (button.findColour (button.getToggleState() ? TextButton::textColourOnId 12 | : TextButton::textColourOffId) 13 | .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); 14 | 15 | const int yIndent = jmin (4, button.proportionOfHeight (0.3f)); 16 | const int leftIndent = 2; 17 | const int rightIndent = 2; 18 | const int textWidth = button.getWidth() - leftIndent - rightIndent; 19 | 20 | if (textWidth > 0) 21 | g.drawFittedText (button.getButtonText(), 22 | leftIndent, 23 | yIndent, 24 | textWidth, 25 | button.getHeight() - yIndent * 2, 26 | Justification::centred, 27 | 2); 28 | } 29 | }; 30 | 31 | ABComp::ABComp (StateManager& manager) : manager (manager) 32 | { 33 | auto setupButton = [=, &manager] (TextButton& button, String name, String text, String tooltip, int index) { 34 | button.setColour (TextButton::buttonColourId, Colours::transparentBlack); 35 | button.setColour (TextButton::buttonOnColourId, Colour (0xFFC954D4).brighter (0.2f)); 36 | 37 | button.setName (name); 38 | button.setButtonText (text); 39 | button.setTooltip (tooltip); 40 | button.setClickingTogglesState (false); 41 | 42 | button.setLookAndFeel (lnfAllocator->getLookAndFeel()); 43 | addAndMakeVisible (button); 44 | 45 | button.onClick = [=, &manager] { 46 | manager.setCurrentABState (index); 47 | refreshStates(); 48 | }; 49 | }; 50 | 51 | setupButton (aButton, "A/B", "A", "Load state \"A\"", 0); 52 | setupButton (bButton, "A/B", "B", "Load state \"B\"", 1); 53 | setupButton (arrowButton, "A/B", String(), String(), -1); 54 | arrowButton.onClick = std::bind (&StateManager::copyABState, &manager); 55 | 56 | refreshStates(); 57 | } 58 | 59 | ABComp::~ABComp() 60 | { 61 | aButton.setLookAndFeel (nullptr); 62 | bButton.setLookAndFeel (nullptr); 63 | arrowButton.setLookAndFeel (nullptr); 64 | } 65 | 66 | void ABComp::refreshStates() 67 | { 68 | auto selectedState = manager.getCurrentABState(); 69 | 70 | if (selectedState == 0) 71 | { 72 | aButton.setToggleState (true, dontSendNotification); 73 | bButton.setToggleState (false, dontSendNotification); 74 | arrowButton.setButtonText (">"); 75 | arrowButton.setTooltip ("Copy the current state into state B"); 76 | } 77 | else if (selectedState == 1) 78 | { 79 | aButton.setToggleState (false, dontSendNotification); 80 | bButton.setToggleState (true, dontSendNotification); 81 | arrowButton.setButtonText ("<"); 82 | arrowButton.setTooltip ("Copy the current state into state A"); 83 | } 84 | 85 | repaint(); 86 | } 87 | 88 | void ABComp::paint (Graphics&) 89 | { 90 | } 91 | 92 | void ABComp::resized() 93 | { 94 | const auto thirdWidth = getWidth() / 3; 95 | aButton.setBounds (0 * thirdWidth, 0, thirdWidth, getHeight()); 96 | arrowButton.setBounds (1 * thirdWidth, 0, thirdWidth, getHeight()); 97 | bButton.setBounds (2 * thirdWidth, 0, thirdWidth, getHeight()); 98 | } 99 | -------------------------------------------------------------------------------- /src/gui/BottomBar/ABComp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../ChowMatrix.h" 4 | #include "../../state/StateManager.h" 5 | #include "BottomBarLNF.h" 6 | 7 | /** GUI component to manage A/B states */ 8 | class ABComp : public Component 9 | { 10 | public: 11 | ABComp (StateManager& manager); 12 | ~ABComp(); 13 | 14 | void paint (Graphics& g) override; 15 | void resized() override; 16 | 17 | void refreshStates(); 18 | 19 | private: 20 | StateManager& manager; 21 | 22 | TextButton aButton; 23 | TextButton bButton; 24 | TextButton arrowButton; 25 | 26 | chowdsp::SharedLNFAllocator lnfAllocator; 27 | 28 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ABComp) 29 | }; 30 | 31 | class ABCompItem : public foleys::GuiItem 32 | { 33 | public: 34 | FOLEYS_DECLARE_GUI_FACTORY (ABCompItem) 35 | 36 | ABCompItem (foleys::MagicGUIBuilder& builder, const ValueTree& node) : foleys::GuiItem (builder, node) 37 | { 38 | if (auto* proc = dynamic_cast (builder.getMagicState().getProcessor())) 39 | { 40 | comp = std::make_unique (proc->getStateManager()); 41 | addAndMakeVisible (comp.get()); 42 | } 43 | } 44 | 45 | void update() override 46 | { 47 | } 48 | 49 | Component* getWrappedComponent() override 50 | { 51 | return comp.get(); 52 | } 53 | 54 | private: 55 | std::unique_ptr comp; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ABCompItem) 58 | }; 59 | -------------------------------------------------------------------------------- /src/gui/BottomBar/BottomBarLNF.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** Look and feel for the bottom bar section */ 6 | class BottomBarLNF : public chowdsp::ChowLNF 7 | { 8 | public: 9 | BottomBarLNF(); 10 | virtual ~BottomBarLNF() = default; 11 | 12 | protected: 13 | void drawRotarySlider (Graphics& g, int, int, int, int, float, const float, const float, Slider& slider) override; 14 | 15 | Slider::SliderLayout getSliderLayout (Slider& slider) override; 16 | Label* createSliderTextBox (Slider& slider) override; 17 | 18 | void drawComboBox (Graphics& g, int width, int height, bool, int, int, int, int, ComboBox& box) override; 19 | void positionComboBoxText (ComboBox& box, Label& label) override; 20 | 21 | void drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; 22 | 23 | Font getTextButtonFont (TextButton&, int buttonHeight) override 24 | { 25 | float fontHeight = jmin (17.0f, (float) buttonHeight * 0.85f); 26 | return Font { fontHeight, Font::bold }; 27 | } 28 | 29 | void drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool /*isTicked*/, const bool hasSubMenu, const String& text, const String& shortcutKeyText, const Drawable* icon, const Colour* const textColourToUse) override 30 | { 31 | LookAndFeel_V4::drawPopupMenuItem (g, area, isSeparator, isActive, isHighlighted, false /*isTicked*/, hasSubMenu, text, shortcutKeyText, icon, textColourToUse); 32 | } 33 | 34 | void drawPopupMenuBackground (Graphics& g, int width, int height) override 35 | { 36 | g.fillAll (findColour (PopupMenu::backgroundColourId)); 37 | ignoreUnused (width, height); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/gui/BottomBar/HostControlMenu.cpp: -------------------------------------------------------------------------------- 1 | #include "HostControlMenu.h" 2 | #include "../IOSUtils/PopupMenuOptionsHelpers.h" 3 | #include "HostControlMenuComp.h" 4 | 5 | HostControlMenu::HostControlMenu (HostParamControl& controller) : controller (controller) 6 | { 7 | cog = Drawable::createFromImageData (BinaryData::cogsolid_svg, BinaryData::cogsolid_svgSize); 8 | cog->replaceColour (Colours::black, Colours::white); 9 | 10 | setTooltip ("Lists which parameters are currently assigned to targets"); 11 | } 12 | 13 | void HostControlMenu::paint (Graphics& g) 14 | { 15 | auto bounds = getLocalBounds().toFloat(); 16 | auto dim = jmin (bounds.getWidth(), bounds.getHeight()); 17 | bounds.setSize (dim, dim); 18 | bounds.reduce (6.0f, 6.0f); 19 | 20 | auto placement = RectanglePlacement (RectanglePlacement::stretchToFit); 21 | cog->drawWithin (g, bounds, placement, 1.0f); 22 | } 23 | 24 | void HostControlMenu::mouseDown (const MouseEvent& e) 25 | { 26 | PopupMenu menu; 27 | for (size_t i = 0; i < controller.getNumAssignableParams(); ++i) 28 | menu.addCustomItem ((int) i + 1, std::make_unique (controller, i)); 29 | 30 | auto popupOptions = PopupMenuOptionsHelpers::createPopupMenuOptions (this) 31 | .withPreferredPopupDirection (PopupMenu::Options::PopupDirection::upwards); 32 | 33 | menu.setLookAndFeel (&getLookAndFeel()); 34 | menu.showMenuAsync (popupOptions, 35 | [=] (int id) { 36 | if (id > 0) 37 | mouseDown (e); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/gui/BottomBar/HostControlMenu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ChowMatrix.h" 4 | #include "dsp/Parameters/HostParamControl.h" 5 | 6 | class HostControlMenu : public Component, 7 | public SettableTooltipClient 8 | { 9 | public: 10 | HostControlMenu (HostParamControl& controller); 11 | 12 | void paint (Graphics& g) override; 13 | void mouseDown (const MouseEvent& e) override; 14 | 15 | private: 16 | HostParamControl& controller; 17 | std::unique_ptr cog; 18 | 19 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HostControlMenu) 20 | }; 21 | 22 | class HostControlMenuItem : public foleys::GuiItem 23 | { 24 | public: 25 | FOLEYS_DECLARE_GUI_FACTORY (HostControlMenuItem) 26 | 27 | HostControlMenuItem (foleys::MagicGUIBuilder& builder, const ValueTree& node) : foleys::GuiItem (builder, node) 28 | { 29 | if (auto* proc = dynamic_cast (builder.getMagicState().getProcessor())) 30 | { 31 | comp = std::make_unique (proc->getHostControl()); 32 | addAndMakeVisible (comp.get()); 33 | } 34 | } 35 | 36 | void update() override 37 | { 38 | comp->repaint(); 39 | } 40 | 41 | Component* getWrappedComponent() override 42 | { 43 | return comp.get(); 44 | } 45 | 46 | private: 47 | std::unique_ptr comp; 48 | 49 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HostControlMenuItem) 50 | }; 51 | -------------------------------------------------------------------------------- /src/gui/BottomBar/HostControlMenuComp.cpp: -------------------------------------------------------------------------------- 1 | #include "HostControlMenuComp.h" 2 | 3 | HostControlMenuComp::HostControlMenuComp (HostParamControl& controller, size_t idx) : PopupMenu::CustomComponent (false), 4 | name ("Assign " + String (idx + 1)) 5 | { 6 | controller.loadParamList (paramList, xCallbacks, idx); 7 | 8 | for (auto& xCallback : xCallbacks) 9 | { 10 | auto newButton = xButtons.add (std::make_unique ("Delete", DrawableButton::ImageFitted)); 11 | createXButton (*newButton); 12 | addAndMakeVisible (newButton); 13 | newButton->onClick = [=] { 14 | xCallback(); 15 | triggerMenuItem(); 16 | }; 17 | } 18 | } 19 | 20 | void HostControlMenuComp::createXButton (DrawableButton& xButton) 21 | { 22 | Rectangle buttonZone (-10.0f, -10.0f, 20.0f, 20.0f); 23 | 24 | DrawableRectangle redSquare; 25 | redSquare.setRectangle (buttonZone); 26 | redSquare.setFill (Colours::red); 27 | 28 | Path p; 29 | constexpr auto thickness = 1.5f; 30 | constexpr auto indent = 2.0f; 31 | p.addRectangle (-8.0f, -thickness, 20.0f - indent * 2.0f, thickness * 2.0f); 32 | p.addRectangle (-thickness, -8.0f, thickness * 2.0f, 10.0f - indent - thickness); 33 | p.addRectangle (-thickness, thickness, thickness * 2.0f, 10.0f - indent - thickness); 34 | p.setUsingNonZeroWinding (false); 35 | p.applyTransform (AffineTransform::rotation (MathConstants::halfPi / 2.0f)); 36 | 37 | DrawablePath dp; 38 | dp.setPath (p); 39 | dp.setFill (Colours::white); 40 | 41 | DrawableComposite buttonImage; 42 | buttonImage.addAndMakeVisible (redSquare.createCopy().release()); 43 | buttonImage.addAndMakeVisible (dp.createCopy().release()); 44 | 45 | xButton.setImages (&buttonImage); 46 | } 47 | 48 | void HostControlMenuComp::getIdealSize (int& idealWidth, int& idealHeight) 49 | { 50 | idealWidth = 250; 51 | idealHeight = labelHeight * jmax (paramList.size(), 1); 52 | } 53 | 54 | void HostControlMenuComp::paint (Graphics& g) 55 | { 56 | auto bounds = getLocalBounds(); 57 | bounds.removeFromRight (20); // save space for xButtons 58 | 59 | g.setColour (Colours::white); 60 | g.setFont (Font (font).boldened()); 61 | 62 | auto labelBox = bounds.removeFromLeft (60); 63 | g.drawFittedText (name, labelBox.removeFromTop (labelHeight), Justification::centred, 1); 64 | 65 | if (paramList.isEmpty()) 66 | { 67 | g.setColour (Colours::lightgrey.withAlpha (0.85f)); 68 | g.setFont (Font (font)); 69 | g.drawFittedText ("None", bounds, Justification::centred, 1); 70 | } 71 | else 72 | { 73 | g.setColour (Colours::white); 74 | g.setFont (Font (font)); 75 | for (auto& pString : paramList) 76 | g.drawFittedText (pString, bounds.removeFromTop (labelHeight), Justification::centred, 1); 77 | } 78 | 79 | g.setColour (Colours::lightgrey.withAlpha (0.25f)); 80 | auto lineY = (float) getHeight(); 81 | g.drawLine (0, lineY, (float) getWidth(), lineY); 82 | } 83 | 84 | void HostControlMenuComp::resized() 85 | { 86 | auto buttonsBox = getLocalBounds().removeFromRight (20); 87 | 88 | for (auto* xButton : xButtons) 89 | xButton->setBounds (buttonsBox.removeFromTop (labelHeight)); 90 | } 91 | -------------------------------------------------------------------------------- /src/gui/BottomBar/HostControlMenuComp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dsp/Parameters/HostParamControl.h" 4 | 5 | class HostControlMenuComp : public PopupMenu::CustomComponent 6 | { 7 | public: 8 | HostControlMenuComp (HostParamControl& controller, size_t idx); 9 | static void createXButton (DrawableButton& xButton); 10 | 11 | void getIdealSize (int& idealWidth, int& idealHeight) override; 12 | void paint (Graphics& g) override; 13 | void resized() override; 14 | 15 | private: 16 | String name; 17 | StringArray paramList; 18 | 19 | OwnedArray xButtons; 20 | std::vector> xCallbacks; 21 | 22 | #if JUCE_IOS 23 | static constexpr float font = 18.0f; 24 | static constexpr int labelHeight = 30; 25 | #else 26 | static constexpr float font = 16.0f; 27 | static constexpr int labelHeight = 25; 28 | #endif 29 | 30 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HostControlMenuComp) 31 | }; 32 | -------------------------------------------------------------------------------- /src/gui/BottomBar/TextSlider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BottomBarLNF.h" 4 | 5 | /* Slider that shows only a text bubble */ 6 | class TextSlider : public Slider 7 | { 8 | public: 9 | TextSlider() 10 | { 11 | setLookAndFeel (lnfAllocator->getLookAndFeel()); 12 | } 13 | 14 | ~TextSlider() override 15 | { 16 | setLookAndFeel (nullptr); 17 | } 18 | 19 | void mouseUp (const MouseEvent& e) override 20 | { 21 | Slider::mouseUp (e); 22 | 23 | multiClicking = e.getNumberOfClicks() > 1; 24 | bool dontShowLabel = e.mouseWasDraggedSinceMouseDown() || e.mods.isAnyModifierKeyDown() || e.mods.isPopupMenu() || multiClicking; 25 | if (! dontShowLabel) 26 | { 27 | Timer::callAfterDelay (270, 28 | [=] { 29 | if (multiClicking) 30 | { 31 | multiClicking = false; 32 | return; 33 | } 34 | 35 | showTextBox(); 36 | }); 37 | } 38 | } 39 | 40 | private: 41 | bool multiClicking = false; 42 | 43 | chowdsp::SharedLNFAllocator lnfAllocator; 44 | 45 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextSlider) 46 | }; 47 | -------------------------------------------------------------------------------- /src/gui/BottomBar/TextSliderItem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TextSlider.h" 4 | 5 | class TextSliderItem : public foleys::GuiItem 6 | { 7 | public: 8 | FOLEYS_DECLARE_GUI_FACTORY (TextSliderItem) 9 | 10 | TextSliderItem (foleys::MagicGUIBuilder& builder, const ValueTree& node) : foleys::GuiItem (builder, node) 11 | { 12 | setColourTranslation ( 13 | { { "slider-background", juce::Slider::backgroundColourId }, 14 | { "slider-thumb", juce::Slider::thumbColourId }, 15 | { "slider-track", juce::Slider::trackColourId }, 16 | { "rotary-fill", juce::Slider::rotarySliderFillColourId }, 17 | { "rotary-outline", juce::Slider::rotarySliderOutlineColourId }, 18 | { "slider-text", juce::Slider::textBoxTextColourId }, 19 | { "slider-text-background", juce::Slider::textBoxBackgroundColourId }, 20 | { "slider-text-highlight", juce::Slider::textBoxHighlightColourId }, 21 | { "slider-text-outline", juce::Slider::textBoxOutlineColourId } }); 22 | 23 | slider = std::make_unique(); 24 | addAndMakeVisible (slider.get()); 25 | } 26 | 27 | void update() override 28 | { 29 | attachment.reset(); 30 | 31 | slider->setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); 32 | 33 | auto paramID = configNode.getProperty (foleys::IDs::parameter, juce::String()).toString(); 34 | if (paramID.isNotEmpty()) 35 | attachment = getMagicState().createAttachment (paramID, *slider.get()); 36 | } 37 | 38 | std::vector getSettableProperties() const override 39 | { 40 | std::vector itemProperties; 41 | 42 | itemProperties.push_back ({ configNode, foleys::IDs::parameter, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenuLambda() }); 43 | 44 | return itemProperties; 45 | } 46 | 47 | juce::Component* getWrappedComponent() override 48 | { 49 | return slider.get(); 50 | } 51 | 52 | protected: 53 | std::unique_ptr slider; 54 | 55 | private: 56 | std::unique_ptr attachment; 57 | 58 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextSliderItem) 59 | }; 60 | -------------------------------------------------------------------------------- /src/gui/BottomBar/WetGainSlider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../IOSUtils/LongPressActionHelper.h" 4 | #include "../IOSUtils/PopupMenuOptionsHelpers.h" 5 | #include "BottomBarLNF.h" 6 | #include "ChowMatrix.h" 7 | #include "TextSliderItem.h" 8 | 9 | class WetGainSlider : public TextSlider 10 | { 11 | public: 12 | explicit WetGainSlider (AudioProcessorValueTreeState& vts) : attachment ( 13 | *vts.getParameter ("wet_gain_comp_param"), 14 | [=] (float v) { updateMenu (v == 1.0f); }, 15 | vts.undoManager) 16 | { 17 | attachment.sendInitialUpdate(); 18 | gainCompMenu.setLookAndFeel (lnfAllocator->getLookAndFeel()); 19 | 20 | #if JUCE_IOS 21 | longPressAction.longPressCallback = [=] (Point) { 22 | gainCompMenu.showMenuAsync (PopupMenuOptionsHelpers::createPopupMenuOptions (this)); 23 | }; 24 | #endif 25 | } 26 | 27 | void mouseDown (const MouseEvent& e) override 28 | { 29 | if (e.mods.isPopupMenu()) 30 | { 31 | gainCompMenu.showMenuAsync (PopupMenuOptionsHelpers::createPopupMenuOptions (this)); 32 | return; 33 | } 34 | 35 | TextSlider::mouseDown (e); 36 | 37 | #if JUCE_IOS 38 | longPressAction.startPress (e.getMouseDownPosition()); 39 | #endif 40 | } 41 | 42 | void mouseDrag (const MouseEvent& e) override 43 | { 44 | TextSlider::mouseDrag (e); 45 | 46 | #if JUCE_IOS 47 | longPressAction.setDragDistance ((float) e.getDistanceFromDragStart()); 48 | #endif 49 | } 50 | 51 | void mouseUp (const MouseEvent& e) override 52 | { 53 | TextSlider::mouseUp (e); 54 | 55 | #if JUCE_IOS 56 | longPressAction.abortPress(); 57 | #endif 58 | } 59 | 60 | void updateMenu (bool gainCompOn) 61 | { 62 | PopupMenu::Item gainCompItem; 63 | gainCompItem.itemID = 1; 64 | gainCompItem.text = "Automatic Gain Compensation"; 65 | gainCompItem.action = [=] { attachment.setValueAsCompleteGesture ((float) ! gainCompOn); }; 66 | gainCompItem.colour = gainCompOn ? Colour (0xFF21CCA5) : Colours::white; 67 | 68 | gainCompMenu.clear(); 69 | gainCompMenu.addItem (gainCompItem); 70 | } 71 | 72 | private: 73 | ParameterAttachment attachment; 74 | 75 | PopupMenu gainCompMenu; 76 | chowdsp::SharedLNFAllocator lnfAllocator; 77 | 78 | #if JUCE_IOS 79 | LongPressActionHelper longPressAction; 80 | #endif 81 | 82 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WetGainSlider) 83 | }; 84 | 85 | class WetGainSliderItem : public TextSliderItem 86 | { 87 | public: 88 | FOLEYS_DECLARE_GUI_FACTORY (WetGainSliderItem) 89 | 90 | WetGainSliderItem (foleys::MagicGUIBuilder& builder, const ValueTree& node) : TextSliderItem (builder, node) 91 | { 92 | auto* plugin = dynamic_cast (builder.getMagicState().getProcessor()); 93 | slider = std::make_unique (plugin->getVTS()); 94 | addAndMakeVisible (slider.get()); 95 | } 96 | 97 | private: 98 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WetGainSliderItem) 99 | }; 100 | -------------------------------------------------------------------------------- /src/gui/DetailsView/NodeDetails.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../NodeManager.h" 4 | #include "../NodeInfo.h" 5 | #include "gui/IOSUtils/LongPressActionHelper.h" 6 | 7 | namespace DetailsConsts 8 | { 9 | constexpr int buttonHeight = 45; 10 | constexpr int circleRadius = 16; 11 | } // namespace DetailsConsts 12 | 13 | class NodeDetails : public Component 14 | { 15 | public: 16 | NodeDetails (DelayNode& node, NodeManager& manager); 17 | ~NodeDetails() override; 18 | 19 | void resized() override; 20 | DelayNode* getNode() { return nodeInfo.getNode(); } 21 | 22 | void setSelected() { manager.setSelected (getNode(), NodeManager::ActionSource::DetailsView); } 23 | void setSoloed() { manager.setSoloed (getNode(), NodeManager::ActionSource::DetailsView); } 24 | 25 | struct Button : Component, SettableTooltipClient 26 | { 27 | Button (NodeDetails& nodeDetails); 28 | 29 | enum ColourIDs 30 | { 31 | nodeColour, 32 | selectedColour 33 | }; 34 | 35 | void mouseDown (const MouseEvent& e) override; 36 | void mouseDrag (const MouseEvent& e) override; 37 | void mouseUp (const MouseEvent& e) override; 38 | 39 | bool keyPressed (const KeyPress& key) override; 40 | void paint (Graphics& g) override; 41 | 42 | private: 43 | NodeDetails& nodeDetails; 44 | 45 | #if JUCE_IOS 46 | LongPressActionHelper longPressAction; 47 | chowdsp::SharedLNFAllocator lnfAllocator; 48 | #endif 49 | }; 50 | 51 | private: 52 | NodeManager& manager; 53 | NodeInfo nodeInfo; 54 | Button button; 55 | 56 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NodeDetails) 57 | }; 58 | -------------------------------------------------------------------------------- /src/gui/DetailsView/NodeDetailsComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../ChowMatrix.h" 4 | #include "NodeDetails.h" 5 | 6 | class NodeDetailsComponent : public Component, 7 | private DBaseNode::Listener 8 | { 9 | public: 10 | NodeDetailsComponent (ChowMatrix& plugin); 11 | ~NodeDetailsComponent() override; 12 | 13 | void paint (Graphics& g) override; 14 | void resized() override; 15 | void mouseDown (const MouseEvent& e) override; 16 | 17 | void nodeAdded (DelayNode* newNode) override; 18 | void nodeRemoved (DelayNode* nodeToRemove) override; 19 | 20 | void setMinWidth (int newMinWidth); 21 | int calcWidth() const; 22 | 23 | NodeDetails* getNodeDetails (DelayNode* node); 24 | Point getNodeDetailsPosition (DelayNode* node); 25 | 26 | private: 27 | void addNode (DelayNode* node); 28 | 29 | ChowMatrix& plugin; 30 | OwnedArray nodes; 31 | int minWidth = 0; 32 | 33 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NodeDetailsComponent) 34 | }; 35 | -------------------------------------------------------------------------------- /src/gui/DetailsView/NodeDetailsGUI.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeDetailsGUI.h" 2 | #include "../../dsp/Parameters/ParamHelpers.h" 3 | 4 | NodeDetailsGUI::NodeDetailsGUI (ChowMatrix& chowMatrix) : nodeDetailsViewport (chowMatrix) 5 | { 6 | setColour (nodeColour, Colours::green); 7 | setColour (nodeSelectedColour, Colours::pink); 8 | setColour (scrollThumbColour, Colours::pink); 9 | setColour (scrollTrackColour, Colours::grey); 10 | 11 | auto node = std::make_unique(); 12 | auto& params = node->getParameters(); 13 | for (int i = 0; i < 1 + params.size(); ++i) 14 | { 15 | String name = "Node"; 16 | String text = "Node"; 17 | String param = "parameters"; 18 | if (i > 0) 19 | { 20 | name = params[i - 1]->getName (1024); 21 | text = name; 22 | param = name; 23 | } 24 | 25 | auto* l = labels.add (std::make_unique