├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ └── cmake_ctest.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── VERSION ├── docs └── valentine_screenshot.png ├── libs └── tote_bag │ ├── assets │ ├── arrow_left.svg │ ├── arrow_right.svg │ ├── totie_outline.svg │ ├── totie_pink.svg │ └── totie_watermark.svg │ ├── dsp │ ├── AudioHelpers.h │ ├── CircularBuffer.h │ ├── Compressor.cpp │ ├── Compressor.h │ ├── DigiDegraders.cpp │ ├── DigiDegraders.h │ ├── EnvelopeDetector.cpp │ ├── EnvelopeDetector.h │ ├── LagrangeInterpolator.h │ ├── Saturation.cpp │ ├── Saturation.h │ └── ThiranAllpass.h │ ├── juce_gui │ ├── components │ │ ├── panels │ │ │ ├── InfoPanel.cpp │ │ │ ├── InfoPanel.h │ │ │ ├── PresetPanel.cpp │ │ │ ├── PresetPanel.h │ │ │ ├── VerticalMeterPanel.cpp │ │ │ ├── VerticalMeterPanel.h │ │ │ └── tbl_PresetPanelModel.h │ │ └── widgets │ │ │ ├── DrawableParameterButton.cpp │ │ │ ├── DrawableParameterButton.h │ │ │ ├── FlatTextButton.cpp │ │ │ ├── FlatTextButton.h │ │ │ ├── FlatTextChooser.cpp │ │ │ ├── FlatTextChooser.h │ │ │ ├── LabelSlider.cpp │ │ │ ├── LabelSlider.h │ │ │ ├── ParameterSlider.cpp │ │ │ ├── ParameterSlider.h │ │ │ ├── ParameterTextButton.cpp │ │ │ ├── ParameterTextButton.h │ │ │ ├── tbl_ToggleButton.cpp │ │ │ └── tbl_ToggleButton.h │ ├── lookandfeel │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── LookAndFeelConstants.h │ │ └── MeterLookAndFeelMethods.h │ ├── managers │ │ ├── RadioButtonGroupManager.cpp │ │ ├── RadioButtonGroupManager.h │ │ ├── ToteBagPresetManager.cpp │ │ └── ToteBagPresetManager.h │ └── utilities │ │ ├── GraphicsUtilities.cpp │ │ ├── GraphicsUtilities.h │ │ ├── tbl_font.cpp │ │ └── tbl_font.h │ └── utils │ ├── macros.hpp │ ├── tbl_math.hpp │ └── type_helpers.hpp ├── scripts ├── installer_mac │ ├── make_installer.sh │ └── resources │ │ ├── License.txt │ │ └── Readme.rtf └── installer_win │ └── installer.iss ├── src ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp ├── PluginProcessor.h ├── ValentineParameters.h └── gui │ ├── assets │ ├── fonts │ │ ├── Roboto-Regular.ttf │ │ ├── RobotoMono-Medium.ttf │ │ └── RobotoMono-Regular.ttf │ └── logos │ │ └── val_totebag_logo.svg │ └── panels │ ├── ValentineBottomRowPanel.cpp │ ├── ValentineBottomRowPanel.h │ ├── ValentineCenterPanel.cpp │ ├── ValentineCenterPanel.h │ ├── ValentineMainPanel.cpp │ ├── ValentineMainPanel.h │ ├── ValentineTopRowPanel.cpp │ └── ValentineTopRowPanel.h ├── tests ├── AudioHelpersTests.cpp ├── AudioHelpersTests.hpp ├── MathTests.cpp └── PluginBasics.cpp └── version.h.in /.clang-format: -------------------------------------------------------------------------------- 1 | # https://forum.juce.com/t/automatic-juce-like-code-formatting-with-clang-format/31624/20 2 | --- 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Left 8 | AlignOperands: Align 9 | AlignTrailingComments: false 10 | AllowAllArgumentsOnNextLine: false 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: Never 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: Never 16 | AllowShortLambdasOnASingleLine: All 17 | AllowShortLoopsOnASingleLine: false 18 | AlwaysBreakAfterDefinitionReturnType: None 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: false 21 | AlwaysBreakTemplateDeclarations: Yes 22 | BinPackArguments: false 23 | BinPackParameters: false 24 | BreakAfterJavaFieldAnnotations: false 25 | BreakBeforeBinaryOperators: NonAssignment 26 | BreakBeforeBraces: Custom 27 | BraceWrapping: # Allman except for lambdas 28 | AfterClass: true 29 | AfterCaseLabel: true 30 | AfterEnum: true 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterStruct: true 34 | BeforeElse: true 35 | AfterControlStatement: Always 36 | BeforeLambdaBody: false 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: true 39 | BreakStringLiterals: false 40 | ColumnLimit: 90 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | IndentCaseLabels: true 49 | IndentPPDirectives: BeforeHash 50 | IndentWidth: 4 51 | IndentWrappedFunctionNames: true 52 | KeepEmptyLinesAtTheStartOfBlocks: false 53 | Language: Cpp 54 | MaxEmptyLinesToKeep: 1 55 | FixNamespaceComments: false 56 | NamespaceIndentation: None 57 | PointerAlignment: Left 58 | ReflowComments: false 59 | SortIncludes: true 60 | SpaceAfterCStyleCast: true 61 | SpaceAfterLogicalNot: false 62 | SpaceBeforeAssignmentOperators: true 63 | SpaceBeforeCpp11BracedList: true 64 | SpaceBeforeParens: NonEmptyParentheses 65 | SpaceInEmptyParentheses: false 66 | SpaceBeforeInheritanceColon: true 67 | SpacesInAngles: false 68 | SpacesInCStyleCastParentheses: false 69 | SpacesInContainerLiterals: true 70 | SpacesInParentheses: false 71 | SpacesInSquareBrackets: false 72 | Standard: "c++20" 73 | TabWidth: 4 74 | UseTab: Never 75 | UseCRLF: false 76 | --- 77 | Language: ObjC 78 | BasedOnStyle: Chromium 79 | BreakBeforeBraces: Allman 80 | ColumnLimit: 0 81 | IndentWidth: 4 82 | KeepEmptyLinesAtTheStartOfBlocks: false 83 | ObjCSpaceAfterProperty: true 84 | ObjCSpaceBeforeProtocolList: true 85 | PointerAlignment: Left 86 | SpacesBeforeTrailingComments: 1 87 | TabWidth: 4 88 | UseTab: Never -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JoseDiazRohena 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/cmake_ctest.yml: -------------------------------------------------------------------------------- 1 | name: Valentine 2 | 3 | on: 4 | workflow_dispatch: # lets you run a build from the UI 5 | push: 6 | 7 | # When pushing new commits, cancel any running builds on that branch 8 | concurrency: 9 | group: ${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | env: 13 | PROJECT_NAME: Valentine 14 | BUILD_TYPE: Release 15 | BUILD_DIR: Builds 16 | SCRIPTS_DIR: scripts 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | DISPLAY: :0 # linux pluginval needs this 19 | CMAKE_BUILD_PARALLEL_LEVEL: 3 # Use up to 3 cpus to build juceaide, etc 20 | HOMEBREW_NO_INSTALL_CLEANUP: 1 21 | 22 | # jobs are run in paralell on different machines 23 | # all steps run in series 24 | jobs: 25 | build_and_test: 26 | name: ${{ matrix.name }} 27 | runs-on: ${{ matrix.os }} 28 | strategy: 29 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 30 | matrix: 31 | include: 32 | - name: Linux 33 | os: ubuntu-22.04 34 | pluginval-binary: ./pluginval 35 | pluginval-strictness: 10 36 | - name: macOS 37 | os: macos-12 38 | pluginval-binary: pluginval.app/Contents/MacOS/pluginval 39 | pluginval-strictness: 10 40 | - name: windows 41 | os: windows-latest 42 | pluginval-binary: ./pluginval.exe 43 | pluginval-strictness: 10 44 | 45 | steps: 46 | - name: Set up Clang 47 | if: ${{ matrix.name != 'macOS' }} 48 | uses: egor-tensin/setup-clang@v1.4 49 | 50 | - name: Install JUCE's Linux Deps 51 | if: runner.os == 'Linux' 52 | # Thanks to McMartin & co https://forum.juce.com/t/list-of-juce-dependencies-under-linux/15121/44 53 | run: | 54 | sudo apt-get update && sudo apt install libasound2-dev libx11-dev libxinerama-dev libxext-dev libfreetype6-dev libwebkit2gtk-4.0-dev libglu1-mesa-dev xvfb fluxbox ninja-build 55 | sudo /usr/bin/Xvfb $DISPLAY & 56 | 57 | # This lets us use sscache on Windows 58 | # We need to install ccache here for Windows to grab the right version 59 | - name: Install Ninja (Windows) 60 | if: runner.os == 'Windows' 61 | shell: bash 62 | run: choco install ninja ccache 63 | 64 | - name: Install macOS Deps 65 | if: ${{ matrix.name == 'macOS' }} 66 | run: brew install ninja osxutils 67 | 68 | - name: Checkout 69 | uses: actions/checkout@v3.3.0 70 | with: 71 | submodules: true # Gotta get JUCE populated 72 | 73 | - name: Setup Environment Variables 74 | shell: bash 75 | run: | 76 | VERSION=$(cat VERSION) 77 | echo "ARTIFACTS_PATH=${{ env.BUILD_DIR }}/${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}" >> $GITHUB_ENV 78 | echo "VST3_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/VST3/${{ env.PROJECT_NAME }}.vst3" >> $GITHUB_ENV 79 | echo "AU_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AU/${{ env.PROJECT_NAME }}.component" >> $GITHUB_ENV 80 | echo "AUV3_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AUv3/${{ env.PROJECT_NAME }}.appex" >> $GITHUB_ENV 81 | PRODUCTFILE=`echo ${{ env.PROJECT_NAME }} | tr ' ' '-' | tr '[:upper:]' '[:lower:]'` 82 | echo "OUTPUT_FILENAME=$PRODUCTFILE-$VERSION-${{ matrix.name }}" >> $GITHUB_ENV 83 | echo "VERSION_NUMBER=$VERSION" >> $GITHUB_ENV 84 | 85 | - name: Ccache for gh actions 86 | uses: hendrikmuhs/ccache-action@v1.2.8 87 | with: 88 | key: v2-${{ matrix.os }}-${{ matrix.type }} 89 | 90 | - name: Setup Keychain and Import Signing Certificates 91 | if: ${{ matrix.name == 'macOS' }} 92 | shell: bash 93 | env: 94 | TEAM_ID: ${{ secrets.TEAM_ID }} 95 | APP_SPECIFIC_PWD: ${{ secrets.NOTARIZATION_PASSWORD }} 96 | APP_CERT_PWD: ${{ secrets.DEV_ID_APP_PASSWORD }} 97 | APP_CERT_BASE64: ${{ secrets.DEV_ID_APP_CERT}} 98 | APP_CERT_PATH: ${{ runner.temp }}/app_certificate.p12 99 | INSTALL_CERT_PWD: ${{ secrets. DEV_ID_INSTALL_PASSWORD }} 100 | INSTALL_CERT_BASE64: ${{ secrets.DEV_ID_INSTALL_CERT }} 101 | INSTALL_CERT_PATH: ${{ runner.temp }}/install_certificate.p12 102 | KEYCHAINPWD: ${{ secrets.MACOS_CI_KEYCHAIN_PWD }} 103 | KEYCHAINPATH: ${{ runner.temp }}/app-signing.keychain-db 104 | # create and setup a temporary keychain, then decode the certificate and setup creditials for codesigning and notarization 105 | run: | 106 | security create-keychain -p "$KEYCHAINPWD" "$KEYCHAINPATH" 107 | 108 | # lock the keychain after a 6 hour timeout 109 | security set-keychain-settings -lut 21600 "$KEYCHAINPATH" 110 | 111 | # prevent keychain access from prompting for password when importing our certificate 112 | security unlock-keychain -p "$KEYCHAINPWD" "$KEYCHAINPATH" 113 | 114 | # certificates need to be decoded before use 115 | echo -n "$APP_CERT_BASE64" | base64 --decode --output $APP_CERT_PATH 116 | echo -n "$INSTALL_CERT_BASE64" | base64 --decode --output $INSTALL_CERT_PATH 117 | 118 | # import certificates into the temp keychain 119 | security import "$APP_CERT_PATH" -P "$APP_CERT_PWD" -t cert -f pkcs12 -k "$KEYCHAINPATH" -A -T /usr/bin/codesign -T /usr/bin/security 120 | security import "$INSTALL_CERT_PATH" -P "$INSTALL_CERT_PWD" -t cert -f pkcs12 -k "$KEYCHAINPATH" -A -T /usr/bin/codesign -T /usr/bin/security 121 | 122 | # allow codesign to access keychain without password prompt 123 | security set-key-partition-list -S apple-tool:,apple:, -k "$KEYCHAINPWD" "$KEYCHAINPATH" 124 | 125 | # Update the keychain list with our newly created temp keychain 126 | security list-keychains -d user -s "$KEYCHAINPATH" login.keychain 127 | 128 | - name: Configure 129 | shell: bash 130 | run: cmake -B ${{ env.BUILD_DIR }} -G Ninja -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" . 131 | 132 | - name: Build 133 | shell: bash 134 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{ env.BUILD_TYPE }} --parallel 4 135 | 136 | - name: Test 137 | working-directory: ${{ env.BUILD_DIR }} 138 | run: ctest --output-on-failure -j4 139 | 140 | - name: Pluginval 141 | working-directory: ${{ env.BUILD_DIR }} 142 | shell: bash 143 | run: | 144 | curl -LO "https://github.com/Tracktion/pluginval/releases/download/v1.0.3/pluginval_${{ matrix.name }}.zip" 145 | 7z x pluginval_${{ matrix.name }}.zip 146 | ${{ matrix.pluginval-binary }} --strictness-level ${{ matrix.pluginval-strictness }} --verbose --validate "${{ env.VST3_PATH }}" 147 | 148 | - name: Codesign (macOS) 149 | working-directory: ${{ env.BUILD_DIR }} 150 | if: ${{ matrix.name == 'macOS' }} 151 | run: | 152 | # Each plugin must be code signed 153 | codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.VST3_PATH }} --deep --strict --options=runtime --timestamp 154 | codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.AU_PATH }} --deep --strict --options=runtime --timestamp 155 | 156 | - name: Create Installer Package (macOS) 157 | if: ${{ matrix.name == 'macOS' }} 158 | shell: bash 159 | env: 160 | INSTALLER_DIR: "${{env.SCRIPTS_DIR}}/installer_mac" 161 | PRODUCT: ${{ env.PROJECT_NAME }} 162 | INPUT_DIR: ${{ env.ARTIFACTS_PATH }} 163 | RESOURCES_DIR: "${{env.SCRIPTS_DIR}}/installer_mac/resources" 164 | OUTPUT_DIR: "${{ runner.temp }}/mac_installer_output" 165 | VERSION: ${{ env.VERSION_NUMBER }} 166 | SIGNING_ID: ${{ secrets.DEVELOPER_ID_INSTALLER}} 167 | run: | 168 | 169 | mkdir -p "$OUTPUT_DIR" 170 | 171 | # creates a signed package and outputs it in a disk image, ready for notarization and staple. 172 | $INSTALLER_DIR/make_installer.sh "$PRODUCT" \ 173 | "$INPUT_DIR" \ 174 | "$RESOURCES_DIR" \ 175 | "$OUTPUT_DIR" \ 176 | "$VERSION" \ 177 | "$SIGNING_ID" 178 | 179 | - name: Create Installer dmg (macOS) 180 | if: ${{ matrix.name == 'macOS' }} 181 | run: | 182 | 183 | hdiutil create /tmp/tmp.dmg -ov -volname "${{ env.OUTPUT_FILENAME }}" -fs HFS+ -srcfolder "${{ runner.temp }}/mac_installer_output/" 184 | hdiutil convert /tmp/tmp.dmg -format UDZO -o "${{ runner.temp }}/${{ env.OUTPUT_FILENAME }}.dmg" 185 | 186 | - name: Notarize and Staple (macOS) 187 | if: ${{ matrix.name == 'macOS' }} 188 | run: | 189 | 190 | codesign -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" --timestamp -i com.ToteBagLabs.Valentine --force "${{ runner.temp }}/${{ env.OUTPUT_FILENAME }}.dmg" 191 | xcrun notarytool submit "${{ runner.temp }}/${{ env.OUTPUT_FILENAME }}.dmg" --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --password ${{ secrets.NOTARIZATION_PASSWORD }} --team-id ${{ secrets.TEAM_ID }} --wait 192 | xcrun stapler staple "${{ runner.temp }}/${{ env.OUTPUT_FILENAME }}.dmg" 193 | 194 | - name: Cleanup Keychain 195 | if: ${{ matrix.name == 'macOS' }} 196 | shell: bash 197 | env: 198 | KEYCHAINPATH: ${{ runner.temp }}/app-signing.keychain-db 199 | run: | 200 | security delete-keychain "$KEYCHAINPATH" 201 | 202 | # default to user login keychain 203 | security list-keychains -d user -s login.keychain 204 | 205 | - name: Zip 206 | if: ${{ matrix.name == 'Linux' }} 207 | working-directory: ${{ env.ARTIFACTS_PATH }} 208 | run: 7z a -tzip ${{ env.OUTPUT_FILENAME }}.zip . 209 | 210 | - name: Generate Installer (Windows) # and Sign with EV cert on Azure (Windows) 211 | if: ${{ matrix.name == 'Windows' }} 212 | shell: bash 213 | env: 214 | INSTALLER_DIR: "scripts/installer_win" 215 | run: | 216 | iscc "$INSTALLER_DIR/installer.iss" 217 | mv "$INSTALLER_DIR/Output/${{ env.OUTPUT_FILENAME }}.exe" "${{ env.ARTIFACTS_PATH }}/" 218 | # dotnet tool install --global AzureSignTool 219 | # AzureSignTool sign -kvu "${{ secrets.AZURE_KEY_VAULT_URI }}" -kvi "${{ secrets.AZURE_CLIENT_ID }}" -kvt "${{ secrets.AZURE_TENANT_ID }}" -kvs "${{ secrets.AZURE_CLIENT_SECRET }}" -kvc ${{ secrets.AZURE_CERT_NAME }} -tr http://timestamp.digicert.com -v "${{ env.ARTIFACTS_PATH }}/${{ env.OUTPUT_FILENA }}.exe" 220 | 221 | - name: Upload Exe (Windows) 222 | if: ${{ matrix.name == 'Windows' }} 223 | uses: actions/upload-artifact@v3 224 | with: 225 | name: ${{ env.OUTPUT_FILENAME }}.exe 226 | path: '${{ env.ARTIFACTS_PATH }}/${{ env.OUTPUT_FILENAME }}.exe' 227 | 228 | - name: Upload Zip (Linux) 229 | if: ${{ matrix.name == 'Linux' }} 230 | uses: actions/upload-artifact@v3 231 | with: 232 | name: ${{ env.OUTPUT_FILENAME }}.zip 233 | path: '${{ env.ARTIFACTS_PATH }}/${{ env.OUTPUT_FILENAME }}.zip' 234 | 235 | - name: Upload DMG (MacOS) 236 | if: ${{ matrix.name == 'macOS' }} 237 | uses: actions/upload-artifact@v3 238 | with: 239 | name: ${{ env.OUTPUT_FILENAME }}.dmg 240 | path: ${{ runner.temp }}/${{ env.OUTPUT_FILENAME }}.dmg 241 | 242 | 243 | release: 244 | if: contains(github.ref, 'tags/v') 245 | runs-on: ubuntu-latest 246 | needs: build_and_test 247 | 248 | steps: 249 | - name: Get Artifacts 250 | uses: actions/download-artifact@v3 251 | 252 | - name: Create Release 253 | uses: softprops/action-gh-release@v0.1.15 254 | with: 255 | prerelease: true 256 | # download-artifact puts these files in their own dirs... 257 | # Using globs sidesteps having to pass the version around 258 | files: | 259 | */*.exe 260 | */*.zip 261 | */*.dmg 262 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Makefile 6 | cmake_install.cmake 7 | install_manifest.txt 8 | compile_commands.json 9 | CTestTestfile.cmake 10 | _deps 11 | **/.DS_Store 12 | .vscode 13 | 14 | # It's nice to have the Builds folder exist, to remind us where it is 15 | Builds/* 16 | !Builds/.gitkeep 17 | Build/* 18 | !Build/.gitkeep 19 | 20 | # This should never exist 21 | Install/* 22 | 23 | # Running CTest makes a bunch o junk 24 | Testing 25 | 26 | # IDE config 27 | .idea 28 | 29 | # clion cmake builds 30 | cmake-build-debug 31 | cmake-build-release 32 | cmake-build-relwithdebinfo 33 | 34 | # lldb cache 35 | .cache/ 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "JUCE"] 2 | path = libs/JUCE 3 | url = https://github.com/juce-framework/JUCE/ 4 | branch = juce8 5 | [submodule "ff_meters"] 6 | path = libs/ff_meters 7 | url = https://github.com/ffAudio/ff_meters.git 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 3.24.1 is bundled in Visual Studio 2022 v17.4 2 | # 3.24.1 is also bundled in CLion as of 2023 3 | cmake_minimum_required(VERSION 3.24.1) 4 | 5 | # Change this to the name of your plugin 6 | # This cannot have spaces (but PRODUCT_NAME can) 7 | set(PROJECT_NAME "Valentine") 8 | 9 | # Set the plugin formats you'll be building here. 10 | # Valid formats: AAX Unity VST AU AUv3 Standalone 11 | set(FORMATS AU VST3 Standalone) 12 | 13 | # This must be set before the project() call 14 | # see: https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html 15 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Support macOS down to High Sierra") 16 | 17 | # Reads in VERSION file and sticks in it CURRENT_VERSION variable 18 | # Be sure the file has no newlines 19 | file(STRINGS VERSION CURRENT_VERSION) 20 | 21 | # Find Git and if possible, get the current commit hash 22 | find_package(Git QUIET) 23 | if(GIT_FOUND) 24 | execute_process( 25 | COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD 26 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 27 | OUTPUT_VARIABLE GIT_COMMIT_HASH 28 | OUTPUT_STRIP_TRAILING_WHITESPACE 29 | ) 30 | else() 31 | set(GIT_COMMIT_HASH "unknown") 32 | endif() 33 | 34 | # Generate a header with version information based on a template 35 | configure_file( 36 | ${CMAKE_SOURCE_DIR}/version.h.in 37 | ${CMAKE_BINARY_DIR}/generated/version.h 38 | ) 39 | 40 | # For simplicity, the name of the project is also the name of the target 41 | project(${PROJECT_NAME} VERSION ${CURRENT_VERSION}) 42 | 43 | # By default we don't want Xcode schemes to be made for modules, etc 44 | set(CMAKE_XCODE_GENERATE_SCHEME OFF) 45 | 46 | # Adds all the module sources so they appear correctly in the IDE 47 | # Must be set before JUCE is added as a sub-dir (or any targets are made) 48 | # https://github.com/juce-framework/JUCE/commit/6b1b4cf7f6b1008db44411f2c8887d71a3348889 49 | set_property(GLOBAL PROPERTY USE_FOLDERS YES) 50 | 51 | # Create a /Modules directory in the IDE with the JUCE Module code 52 | option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Show all module sources in IDE projects" ON) 53 | 54 | # JUCE is setup as a submodule in the /JUCE folder 55 | # Locally, you'll need to run `git submodule update --init --recursive` once 56 | # and `git submodule update --remote --merge` to keep it up to date 57 | # On Github Actions, it's managed by actions/checkout 58 | add_subdirectory(libs/JUCE) 59 | 60 | # Also using Foley's Finest meters 61 | juce_add_module(libs/ff_meters) 62 | 63 | # Check the readme at `docs/CMake API.md` in the JUCE repo for full config 64 | juce_add_plugin("${PROJECT_NAME}" 65 | # VERSION ... # Set this if the plugin version is different to the project version 66 | # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone 67 | # ICON_SMALL ... 68 | COMPANY_NAME "Tote Bag Labs" 69 | BUNDLE_ID com.ToteBagLabs.Valentine 70 | # IS_SYNTH FALSE # Is this a synth or an effect? 71 | # NEEDS_MIDI_INPUT FALSE # Does the plugin need midi input? 72 | # NEEDS_MIDI_OUTPUT FALSE # Does the plugin need midi output? 73 | # IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? 74 | # EDITOR_WANTS_KEYBOARD_FOCUS FALSE # Does the editor need keyboard focus? 75 | COPY_PLUGIN_AFTER_BUILD TRUE # On MacOS, plugin will be copied to /Users/you/Library/Audio/Plug-Ins/ 76 | PLUGIN_MANUFACTURER_CODE Valn # This has to be one uppercase, rest lower for AU formats 77 | PLUGIN_CODE V001 # A unique four-character plugin id with at least one upper-case character 78 | FORMATS "${FORMATS}" 79 | PRODUCT_NAME "${PROJECT_NAME}") # The name of the final executable, which can differ from the target name 80 | 81 | # C++20 please 82 | target_compile_features("${PROJECT_NAME}" PRIVATE cxx_std_20) 83 | 84 | # Manually list all .h and .cpp files for the plugin 85 | set(SourceFiles 86 | src/PluginEditor.h 87 | src/PluginProcessor.h 88 | src/ValentineParameters.h 89 | src/PluginEditor.cpp 90 | src/PluginProcessor.cpp 91 | 92 | src/gui/panels/ValentineBottomRowPanel.h 93 | src/gui/panels/ValentineCenterPanel.h 94 | src/gui/panels/ValentineMainPanel.h 95 | src/gui/panels/ValentineTopRowPanel.h 96 | src/gui/panels/ValentineBottomRowPanel.cpp 97 | src/gui/panels/ValentineCenterPanel.cpp 98 | src/gui/panels/ValentineMainPanel.cpp 99 | src/gui/panels/ValentineTopRowPanel.cpp 100 | 101 | libs/tote_bag/dsp/AudioHelpers.h 102 | libs/tote_bag/dsp/CircularBuffer.h 103 | libs/tote_bag/dsp/Compressor.h 104 | libs/tote_bag/dsp/DigiDegraders.h 105 | libs/tote_bag/dsp/EnvelopeDetector.h 106 | libs/tote_bag/dsp/Saturation.h 107 | libs/tote_bag/dsp/ThiranAllpass.h 108 | libs/tote_bag/dsp/Compressor.cpp 109 | libs/tote_bag/dsp/DigiDegraders.cpp 110 | libs/tote_bag/dsp/EnvelopeDetector.cpp 111 | libs/tote_bag/dsp/Saturation.cpp 112 | 113 | libs/tote_bag/juce_gui/components/panels/InfoPanel.h 114 | libs/tote_bag/juce_gui/components/panels/PresetPanel.h 115 | libs/tote_bag/juce_gui/components/panels/VerticalMeterPanel.h 116 | libs/tote_bag/juce_gui/components/panels/InfoPanel.cpp 117 | libs/tote_bag/juce_gui/components/panels/PresetPanel.cpp 118 | libs/tote_bag/juce_gui/components/panels/VerticalMeterPanel.cpp 119 | 120 | libs/tote_bag/juce_gui/components/widgets/DrawableParameterButton.h 121 | libs/tote_bag/juce_gui/components/widgets/FlatTextButton.h 122 | libs/tote_bag/juce_gui/components/widgets/FlatTextChooser.h 123 | libs/tote_bag/juce_gui/components/widgets/LabelSlider.h 124 | libs/tote_bag/juce_gui/components/widgets/ParameterSlider.h 125 | libs/tote_bag/juce_gui/components/widgets/ParameterTextButton.h 126 | libs/tote_bag/juce_gui/components/widgets/tbl_ToggleButton.h 127 | libs/tote_bag/juce_gui/components/widgets/DrawableParameterButton.cpp 128 | libs/tote_bag/juce_gui/components/widgets/FlatTextButton.cpp 129 | libs/tote_bag/juce_gui/components/widgets/FlatTextChooser.cpp 130 | libs/tote_bag/juce_gui/components/widgets/LabelSlider.cpp 131 | libs/tote_bag/juce_gui/components/widgets/ParameterSlider.cpp 132 | libs/tote_bag/juce_gui/components/widgets/ParameterTextButton.cpp 133 | libs/tote_bag/juce_gui/components/widgets/tbl_ToggleButton.cpp 134 | 135 | libs/tote_bag/juce_gui/lookandfeel/LookAndFeel.h 136 | libs/tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h 137 | libs/tote_bag/juce_gui/lookandfeel/MeterLookAndFeelMethods.h 138 | libs/tote_bag/juce_gui/lookandfeel/LookAndFeel.cpp 139 | 140 | libs/tote_bag/juce_gui/managers/ToteBagPresetManager.h 141 | libs/tote_bag/juce_gui/managers/RadioButtonGroupManager.h 142 | libs/tote_bag/juce_gui/managers/ToteBagPresetManager.cpp 143 | libs/tote_bag/juce_gui/managers/RadioButtonGroupManager.cpp 144 | 145 | libs/tote_bag/juce_gui/utilities/GraphicsUtilities.h 146 | libs/tote_bag/juce_gui/utilities/GraphicsUtilities.cpp 147 | libs/tote_bag/juce_gui/utilities/tbl_font.h 148 | libs/tote_bag/juce_gui/utilities/tbl_font.cpp 149 | 150 | libs/tote_bag/utils/macros.hpp 151 | libs/tote_bag/utils/type_helpers.hpp 152 | libs/tote_bag/utils/tbl_math.hpp 153 | ) 154 | 155 | target_sources("${PROJECT_NAME}" PRIVATE ${SourceFiles}) 156 | 157 | # No, we don't want our source buried in extra nested folders 158 | set_target_properties("${PROJECT_NAME}" PROPERTIES FOLDER "") 159 | 160 | # The Xcode source tree should uhhh, still look like the source tree, yo 161 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${SourceFiles}) 162 | 163 | set(AssetFiles 164 | src/gui/assets/fonts/Roboto-Regular.ttf 165 | src/gui/assets/fonts/RobotoMono-Regular.ttf 166 | src/gui/assets/fonts/RobotoMono-Medium.ttf 167 | src/gui/assets/logos/val_totebag_logo.svg 168 | libs/tote_bag/assets/arrow_left.svg 169 | libs/tote_bag/assets/arrow_right.svg 170 | libs/tote_bag/assets/totie_pink.svg 171 | libs/tote_bag/assets/totie_outline.svg 172 | libs/tote_bag/assets/totie_watermark.svg 173 | ) 174 | 175 | # Setup our binary data as a target 176 | juce_add_binary_data(Assets SOURCES ${AssetFiles}) 177 | 178 | # Required for Linux happiness: 179 | # See https://forum.juce.com/t/loading-pytorch-model-using-binarydata/39997/2 180 | set_target_properties(Assets PROPERTIES POSITION_INDEPENDENT_CODE TRUE) 181 | 182 | # This cleans up the folder organization on Xcode. 183 | # It tucks the Plugin varieties into a "Targets" folder and generate an Xcode Scheme manually 184 | # Xcode scheme generation is turned off globally to limit noise from other targets 185 | # The non-hacky way of doing this is via the global PREDEFINED_TARGETS_FOLDER property 186 | # However that doesn't seem to be working in Xcode 187 | # Not all plugin types (au, vst) available on each build type (win, macos, linux) 188 | foreach(target ${FORMATS} "All") 189 | if(TARGET ${PROJECT_NAME}_${target}) 190 | set_target_properties(${PROJECT_NAME}_${target} PROPERTIES 191 | # Tuck the actual plugin targets into a folder where they won't bother us 192 | FOLDER "Targets" 193 | 194 | # MacOS only: Sets the default executable that Xcode will open on build 195 | # For this exact path to to work, manually build the AudioPluginHost.xcodeproj in the JUCE subdir 196 | XCODE_SCHEME_EXECUTABLE "${CMAKE_CURRENT_SOURCE_DIR}/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app" 197 | 198 | # Let us build the target in Xcode 199 | XCODE_GENERATE_SCHEME ON) 200 | endif() 201 | endforeach() 202 | set_target_properties(Assets PROPERTIES FOLDER "Targets") 203 | 204 | # We'll need to link to these from our plugin as well as our tests 205 | set(JUCE_DEPENDENCIES 206 | juce::juce_audio_utils 207 | juce::juce_dsp) 208 | 209 | target_compile_definitions("${PROJECT_NAME}" 210 | PUBLIC 211 | # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. 212 | JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call 213 | JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call 214 | JUCE_VST3_CAN_REPLACE_VST2=0 215 | JUCE_ENABLE_GPL_MODE=1 216 | JUCE_DISPLAY_SPLASH_SCREEN=0 217 | JUCE_REPORT_APP_USAGE=0 218 | JUCE_MODAL_LOOPS_PERMITTED=1 219 | JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0 220 | ) 221 | 222 | target_include_directories("${PROJECT_NAME}" 223 | PRIVATE 224 | "${CMAKE_CURRENT_SOURCE_DIR}/src" 225 | "${CMAKE_CURRENT_SOURCE_DIR}/libs" 226 | "${CMAKE_BINARY_DIR}") 227 | 228 | target_link_libraries("${PROJECT_NAME}" 229 | PRIVATE 230 | Assets 231 | ${JUCE_DEPENDENCIES} 232 | ff_meters 233 | PUBLIC 234 | juce::juce_recommended_config_flags 235 | juce::juce_recommended_lto_flags 236 | juce::juce_recommended_warning_flags) 237 | 238 | 239 | # Required for ctest (which is just easier for cross-platform CI) 240 | # include(CTest) does this too, but adds tons of targets we don't want 241 | # See: https://github.com/catchorg/Catch2/issues/2026 242 | # You could forgo ctest and call ./tests directly from the build dir 243 | enable_testing() 244 | 245 | # "GLOBS ARE BAD" is brittle and silly UX, sorry CMake! 246 | # CONFIGURE_DEPENDS / Clion's CMake integration makes globbing absolutely fine 247 | file(GLOB_RECURSE TestFiles CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h") 248 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/tests PREFIX "" FILES ${TestFiles}) 249 | 250 | # Use Catch2 v3 on the devel branch 251 | Include(FetchContent) 252 | FetchContent_Declare( 253 | Catch2 254 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 255 | GIT_PROGRESS TRUE 256 | GIT_SHALLOW TRUE 257 | GIT_TAG v3.4.0) 258 | FetchContent_MakeAvailable(Catch2) # find_package equivalent 259 | 260 | # Setup the test executable, again C++ 20 please 261 | add_executable(Tests ${TestFiles}) 262 | target_compile_features(Tests PRIVATE cxx_std_20) 263 | 264 | # Our test executable also wants to know about our plugin code... 265 | target_include_directories(Tests 266 | PRIVATE 267 | "${CMAKE_CURRENT_SOURCE_DIR}/src" 268 | "${CMAKE_CURRENT_SOURCE_DIR}/libs") 269 | 270 | target_link_libraries(Tests PRIVATE Catch2::Catch2WithMain "${PROJECT_NAME}") 271 | 272 | # We can't link again to the shared juce target without ODR violations 273 | # However, we can steal its include dirs and compile definitions to use in tests! 274 | # https://forum.juce.com/t/windows-linker-issue-on-develop/55524/2 275 | target_compile_definitions(Tests PRIVATE $) 276 | target_include_directories(Tests PRIVATE $) 277 | 278 | # Make an Xcode Scheme for the test executable so we can run tests in the IDE 279 | set_target_properties(Tests PROPERTIES XCODE_GENERATE_SCHEME ON) 280 | 281 | # Organize the test source in the tests/ folder in the IDE 282 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/tests PREFIX "" FILES ${TestFiles}) 283 | 284 | # Load and use the .cmake file provided by Catch2 285 | # https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md 286 | # We have to manually provide the source directory here for now 287 | include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) 288 | catch_discover_tests(Tests) 289 | 290 | # Color our warnings and errors 291 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 292 | add_compile_options (-fdiagnostics-color=always) 293 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 294 | add_compile_options (-fcolor-diagnostics) 295 | endif () 296 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Valentine](docs/valentine_screenshot.png) 2 | 3 | [![](https://github.com/Tote-Bag-Labs/valentine/actions/workflows/cmake_ctest.yml/badge.svg)](https://github.com/Tote-Bag-Labs/valentine/actions/workflows/cmake_ctest.yml) 4 | 5 | 6 | V A L E N T I N E 7 | ================== 8 | Valentine is a compressor and distortion processor. It was inspired by the hyper compressed 9 | and crushed textures in the seminal Justice record, †. Using it is easy: turning input up makes the signal 10 | louder, more compressed, and more saturated. Turning crush up adds digital distortion. 11 | 12 | The real fun is in processing signal with some ambience, be it a room or a reverb. With the right input gain 13 | and release settings, you can introduce pumping and breathing artifacts. 14 | 15 | Though designed for aggressive (over) application, Valentine is flexible enough to use in a wide variety of 16 | applications. Try it today! 17 | 18 | I stream and make videos about the development process on [YouTube](https://www.youtube.com/@ToteBagLabs). 19 | I also wrote a few blog posts about it [here](https://josediazrohena.github.io/). 20 | 21 | Getting Valentine 22 | ================= 23 | Download the latest precompiled binaries [here](https://github.com/Tote-Bag-Labs/valentine/releases/latest). 24 | 25 | AU and VST3 on MacOs and VST3 on Windows are currently supported. 26 | 27 | Using Valentine 28 | =============== 29 | 30 | Signal Path 31 | ---------- 32 | Valentine is a non-linear processor. As such, knowing the signal path makes it easier 33 | to get your desired result. Here it is: 34 | 35 | **Crush** -> **Compress** -> **Saturate** -> Soft Clip -> **Output** -> **Mix** 36 | 37 | Parameters 38 | ---------- 39 | - **Crush**: increases the amount of bit crushing. Downsamples the signal to 27.5kHz. Enabled using the Crush button. 40 | - **Compress**: sets the gain applied to signal before compression. Use this to increase the amount of compression and distortion affecting the signal. 41 | - **Saturate**: sets the amount of gain applied to signal before Valentine's waveshaper. Gain is compensated to prevent huge volume boost when you just want more dirt. Enabled using the Crush Enable Button. 42 | - **Ratio**: sets the compression ratio. "Infinity" is more like 1000:1. Increasing this also increases threshold and decreases knee. 43 | - **Attack**: sets compression attack time. 44 | - **Release**: sets compression release time. 45 | - **Output**: sets wet signal output gain before mix. Output clipping can be enabled with the Clip button. 46 | - **Mix**: increases the amount of wet signal in the processor's output. 47 | - **Bypass**: bypasses all processing. 48 | 49 | Building Valentine 50 | ================== 51 | 52 | Xcode 53 | ----- 54 | 55 | Currently, `Xcode 14.2` is supported for building Valentine. 56 | 57 | Here's how to create an Xcode project for building the plugin. 58 | 59 | ``` 60 | git clone https://github.com/Tote-Bag-Labs/valentine.git 61 | cd valentine 62 | git submodule update --init --recursive 63 | cmake -B builds -G Xcode 64 | ``` 65 | 66 | Other (for now) 67 | --------------- 68 | 69 | If you'd rather just build it from the command line, run 70 | `cmake --build Builds --config Release` 71 | 72 | 73 | Contributing to Valentine 74 | ========================= 75 | 76 | Note: I'm putting a pause on pull requests until I've written CI that external contributors can use. 77 | 78 | If you'd like to get involved, take a look at [issues.](https://github.com/tote-bag-labs/valentine/issues) I could use help on anything marked [bug](https://github.com/tote-bag-labs/valentine/labels/bug) or [enhancement](https://github.com/tote-bag-labs/valentine/labels/enhancement). 79 | 80 | I'm currently not taking pull requests for new features. 81 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 2 | -------------------------------------------------------------------------------- /docs/valentine_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tote-bag-labs/valentine/92a839fcdc1600486e4ce80eae3cbc2b8792e80a/docs/valentine_screenshot.png -------------------------------------------------------------------------------- /libs/tote_bag/assets/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libs/tote_bag/assets/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libs/tote_bag/assets/totie_outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /libs/tote_bag/assets/totie_pink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /libs/tote_bag/assets/totie_watermark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/AudioHelpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | AudioHelpers.h 5 | Created: 2 Sep 2019 10:57:34am 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/utils/type_helpers.hpp" 14 | 15 | #include 16 | #include 17 | 18 | namespace tote_bag 19 | { 20 | namespace audio_helpers 21 | { 22 | 23 | inline float linearInterp (float x1, float x2, float frac) 24 | { 25 | return (1.0f - frac) * x1 + frac * x2; 26 | } 27 | 28 | template 29 | inline void checkUnderflow (T& x) 30 | { 31 | if (abs (x) < std::numeric_limits::epsilon()) 32 | x = 0.0f; 33 | } 34 | 35 | template 36 | class SimpleOnePole 37 | { 38 | public: 39 | void reset() { prevValue = 0.0; } 40 | 41 | void prepare (T newCoeff) { coeff = newCoeff; } 42 | 43 | T processSample (T inputSample) 44 | { 45 | // whoops! looks like you forgot to set the coefficient 46 | assert (coeff != -1); 47 | 48 | auto output = coeff * (prevValue - inputSample) + inputSample; 49 | prevValue = output; 50 | return output; 51 | } 52 | 53 | private: 54 | T prevValue, coeff = -1; 55 | }; 56 | 57 | template 58 | FloatType max (FloatType x, FloatType y) 59 | { 60 | x -= y; 61 | x += abs (x); 62 | x *= 0.5; 63 | x += y; 64 | return (x); 65 | } 66 | 67 | template 68 | FloatType min (FloatType x, FloatType y) 69 | { 70 | x = y - x; 71 | x += abs (x); 72 | x *= 0.5; 73 | x = y - x; 74 | return (x); 75 | } 76 | 77 | /** a fast clipping algorithm source: https://www.musicdsp.org/en/latest/Other/81-clipping-without-branching.html 78 | it's noted in the description that it is not so accurate for small numbers 79 | */ 80 | template 81 | FloatType fastClip (FloatType x, FloatType minimumValue, FloatType maximumValue) 82 | { 83 | const FloatType x1 = abs (x - minimumValue); 84 | const FloatType x2 = abs (x - maximumValue); 85 | x = x1 + (minimumValue + maximumValue); 86 | x -= x2; 87 | x *= 0.5; 88 | return (x); 89 | } 90 | 91 | template 92 | constexpr std::pair coshRange() 93 | { 94 | // Copilot spat these ranges out (!) when I started typing them. 95 | // I had loosely figured out the ranges to be 96 | // +- 710.0 and +- 88.0 for double and float respectively. 97 | if constexpr (std::is_same::value) 98 | { 99 | return { -710.0, 710.0 }; 100 | } 101 | else if constexpr (std::is_same::value) 102 | { 103 | return { -88.0f, 88.0f }; 104 | } 105 | else 106 | { 107 | static_assert (type_helpers::dependent_false::value, "ClampedCosh only supports float types"); 108 | } 109 | } 110 | 111 | /** Returns cosh(x), clamping input beforehand to prevent overflow. 112 | The bounds used are found by brute force: e.g. running std::cosh with values 113 | increasing until overflow occurs. I am assuming here that this is well 114 | above the values we would expect to see in "reasonable" use, but 115 | it has yet to be verified. A more sophisticated approach may be needed 116 | if aliasing becomes an issue. 117 | */ 118 | template 119 | inline FloatType clampedCosh (FloatType x) 120 | { 121 | const auto [min, max] = coshRange(); 122 | 123 | return std::cosh (std::clamp (x, min, max)); 124 | } 125 | 126 | /** Returns the the next power of 2 greater than `x`. Returns `x` if it is a power of 2 127 | */ 128 | inline int nextPow2 (int x) 129 | { 130 | return static_cast ((std::pow (2, std::ceil (std::log (x) / std::log (2))))); 131 | } 132 | 133 | } // namespace audio_helpers 134 | } // namespace tote_bag 135 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/CircularBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | CircularBuffer.h 5 | Created: 7 Feb 2020 5:01:51pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/dsp/AudioHelpers.h" 14 | #include 15 | 16 | template 17 | class CircularBuffer 18 | { 19 | public: 20 | CircularBuffer() {} 21 | 22 | void reset() 23 | { 24 | writeIndex = 0; 25 | buffer.clear(); 26 | } 27 | 28 | void setSize (int inBufferLength) 29 | { 30 | bufferLength = tote_bag::audio_helpers::nextPow2 (inBufferLength); 31 | wrapMask = bufferLength - 1; 32 | buffer.setSize (1, bufferLength, false, false, true); 33 | reset(); 34 | } 35 | 36 | void writeBuffer (T input) 37 | { 38 | /** setting a buffer size that is power of two and creating 39 | a mask to ANDOR the read/write index with. a faster way 40 | of implementing the wrap. 41 | 42 | bufferSize = 43 | mask = bufferSize - 1 44 | 45 | */ 46 | buffer.setSample (0, writeIndex++, input); 47 | writeIndex &= wrapMask; 48 | } 49 | 50 | T readBuffer (int delayInSamples, bool readBeforeWrite) 51 | { 52 | // This implementation assumes that data will be read, then written 53 | // Pirkle implementation has a version with bool readBeforeWrite 54 | // commented out. Decided to bring it back here. TODO: remember why. 55 | int readIndex = (writeIndex - (readBeforeWrite ? 1 : 0)) - delayInSamples; 56 | readIndex &= wrapMask; 57 | return buffer.getSample (0, readIndex); 58 | } 59 | 60 | private: 61 | juce::AudioBuffer buffer; 62 | int writeIndex = 0; 63 | int bufferLength = 1024; // power of 2 64 | int wrapMask = bufferLength - 1; 65 | }; 66 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/Compressor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | SFCComp.cpp 5 | Created: 29 Jun 2019 9:12:06pm 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | #include "Compressor.h" 11 | 12 | Compressor::Compressor (bool autoRelease) 13 | : levelDetector (autoRelease) 14 | { 15 | } 16 | 17 | void Compressor::setSampleRate (double inSampleRate) 18 | { 19 | levelDetector.setSampleRate (inSampleRate); 20 | } 21 | 22 | void Compressor::setMeterSource (FFAU::LevelMeterSource* source) 23 | { 24 | if (source != nullptr && meterSource == nullptr) 25 | meterSource = source; 26 | } 27 | 28 | void Compressor::setAttack (float inAttack) 29 | { 30 | levelDetector.setTimeConstant (inAttack, true); 31 | } 32 | 33 | void Compressor::setRelease (float inRelease) 34 | { 35 | levelDetector.setTimeConstant (inRelease, false); 36 | } 37 | 38 | void Compressor::setRatio (float inRatio) 39 | { 40 | ratio.set (inRatio); 41 | } 42 | 43 | void Compressor::setKnee (float inKnee) 44 | { 45 | knee.set (inKnee); 46 | } 47 | 48 | void Compressor::setThreshold (float inThreshold) 49 | { 50 | threshold.set (inThreshold); 51 | } 52 | 53 | void Compressor::reset (int samplesPerBlock) 54 | { 55 | analysisSignal.setSize (1, samplesPerBlock, false, false, true); 56 | analysisSignal.clear(); 57 | levelDetector.reset(); 58 | } 59 | 60 | void Compressor::setOversampleMultiplier (const int o) 61 | { 62 | overSampleMultiplier = o - 1; 63 | } 64 | 65 | inline void Compressor::makeMonoSidechain (const juce::dsp::AudioBlock& inAudio, 66 | juce::AudioBuffer& scSignal) 67 | { 68 | scSignal.clear(); 69 | auto* scWritePointer = scSignal.getWritePointer (0); 70 | 71 | auto numChannels = inAudio.getNumChannels(); 72 | auto numSamples = static_cast (inAudio.getNumSamples()); 73 | 74 | for (size_t channel = 0; channel < numChannels; ++channel) 75 | { 76 | juce::FloatVectorOperations::addWithMultiply (scWritePointer, 77 | inAudio.getChannelPointer (channel), 78 | 1.0f / numChannels, 79 | numSamples); 80 | } 81 | } 82 | 83 | inline float Compressor::calculateGain (float inputSample) 84 | { 85 | auto T = threshold.get(); 86 | jassert (T != -1.0f); 87 | 88 | auto W = knee.get(); 89 | jassert (W != -1.0f); 90 | 91 | auto overshoot = inputSample - T; 92 | auto doubleOvershoot = 2.0f * overshoot; 93 | auto R = ratio.get(); 94 | auto cvOutput = 0.0f; 95 | 96 | if (W > 0.0f) 97 | { 98 | if (doubleOvershoot < -W) 99 | { 100 | cvOutput = inputSample; 101 | } 102 | 103 | else if (abs (doubleOvershoot) <= W) 104 | { 105 | cvOutput = 106 | inputSample 107 | + ((1.0f / R - 1.0f) * powf (overshoot + W / 2.0f, 2)) / (2.0f * W); 108 | } 109 | else 110 | { 111 | cvOutput = T + overshoot / R; 112 | } 113 | } 114 | else 115 | { 116 | cvOutput = inputSample > T ? T + overshoot / R : inputSample; 117 | } 118 | 119 | return -(inputSample - cvOutput); 120 | } 121 | 122 | inline float Compressor::getMakeupGain() 123 | { 124 | return -threshold.get() * (1.0f / ratio.get() - 1.0f) / 2.0f; 125 | } 126 | 127 | void Compressor::process (juce::dsp::AudioBlock& inAudio) 128 | { 129 | makeMonoSidechain (inAudio, analysisSignal); 130 | auto numSamples = inAudio.getNumSamples(); 131 | auto numChannels = inAudio.getNumChannels(); 132 | 133 | auto sidechain = analysisSignal.getWritePointer (0); 134 | for (size_t sample = 0; sample < numSamples; sample++) 135 | { 136 | auto controlVoltage = juce::Decibels::gainToDecibels (abs (sidechain[sample])); 137 | // TODO: level detector methods should be float or templated 138 | controlVoltage = static_cast ( 139 | levelDetector.processSampleDecoupledBranched (controlVoltage)); 140 | 141 | controlVoltage = calculateGain (controlVoltage); 142 | controlVoltage = juce::Decibels::decibelsToGain (controlVoltage); 143 | 144 | meterSource->setReductionLevel (controlVoltage); 145 | 146 | sidechain[sample] = controlVoltage; 147 | } 148 | 149 | for (size_t channel = 0; channel < numChannels; channel++) 150 | { 151 | juce::FloatVectorOperations::multiply (inAudio.getChannelPointer (channel), 152 | inAudio.getChannelPointer (channel), 153 | analysisSignal.getReadPointer (0), 154 | static_cast (numSamples)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/Compressor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | SFCComp.h 5 | Created: 29 Jun 2019 9:12:06pm 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | #pragma once 11 | 12 | #include "EnvelopeDetector.h" 13 | 14 | #include 15 | #include 16 | 17 | class Compressor 18 | { 19 | public: 20 | Compressor (bool autoRelease); 21 | 22 | void reset (int numSamplesPerBlock); 23 | 24 | void setSampleRate (double inSampleRate); 25 | 26 | void setParams (float inAttack, 27 | float inRelease, 28 | float inRatio, 29 | float inThreshold, 30 | float inKnee); 31 | 32 | void makeMonoSidechain (const juce::dsp::AudioBlock& inAudio, 33 | juce::AudioBuffer& scSignal); 34 | 35 | void makeKneeCoeffs(); 36 | 37 | float calculateGain (float analysisSample); 38 | 39 | void process (juce::dsp::AudioBlock& inAudio); 40 | 41 | float getMakeupGain(); 42 | 43 | void setAttack (float inAttack); 44 | void setRelease (float inRelease); 45 | void setRatio (float inRatio); 46 | void setKnee (float inKnee); 47 | void setThreshold (float inThreshold); 48 | 49 | void setMeterSource (FFAU::LevelMeterSource* source); 50 | void setOversampleMultiplier (const int o); 51 | 52 | private: 53 | juce::WeakReference meterSource; 54 | 55 | juce::Atomic ratio {-1.0f}, knee {-1.0f}, msAttack {-1.0f}, msRelease {-1.0f}, 56 | threshold {-1.0}; 57 | 58 | EnvelopeDetector levelDetector; 59 | juce::AudioBuffer analysisSignal; 60 | 61 | int overSampleMultiplier {1}; 62 | 63 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Compressor) 64 | }; 65 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/DigiDegraders.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | SimpleZOH.cpp 5 | Created: 2 Sep 2019 10:40:50am 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "DigiDegraders.h" 12 | #include "tote_bag/dsp/AudioHelpers.h" 13 | 14 | #include 15 | 16 | void SimpleZOH::setResampleOffset (double inHostSr) 17 | { 18 | /* 19 | this allows the ZOH to act the same regardless of 20 | host SR...as long as RefSr is 44100 (most times the lowest possible) 21 | if host sr is larger than refSR, resample coeff will be increased 22 | by the ratio between the two. So if ref is 44100 and session is 88200, 23 | a selected downsample ratio of 2 will result in a downsample ratio of 4 24 | */ 25 | auto currentSR = currentHostSampleRate.get(); 26 | 27 | if (inHostSr != currentSR) 28 | { 29 | resampleOffset = 1.0f; 30 | } 31 | } 32 | 33 | void SimpleZOH::setParams (float inDownsampleCoeff) 34 | { 35 | downsampleCoeff = inDownsampleCoeff; 36 | } 37 | 38 | void SimpleZOH::processBlock (juce::AudioBuffer& inAudio, int samplesPerBlock, int numChannels) 39 | { 40 | auto intDownSampleCoeff = static_cast (downsampleCoeff); 41 | auto frac = downsampleCoeff - intDownSampleCoeff; 42 | 43 | for (int channel = 0; channel < numChannels; ++channel) 44 | { 45 | auto channelData = inAudio.getWritePointer (channel); 46 | 47 | for (int sample = 0; sample < samplesPerBlock; ++sample) 48 | { 49 | auto y1 = getZOHSample (channelData, sample, intDownSampleCoeff); 50 | auto y2 = getZOHSample (channelData, sample, intDownSampleCoeff + 1); 51 | channelData[sample] = tote_bag::audio_helpers::linearInterp (y1, y2, frac); 52 | } 53 | } 54 | } 55 | 56 | inline float SimpleZOH::getZOHSample (const float* channelData, int sampleIndex, int dsCoef) 57 | { 58 | if (dsCoef <= 1) 59 | return channelData[sampleIndex]; 60 | 61 | auto remainder = sampleIndex % dsCoef; 62 | 63 | return remainder == 0 ? channelData[sampleIndex] : channelData[sampleIndex - remainder]; 64 | } 65 | 66 | //===================================================================================== 67 | 68 | void Bitcrusher::setParams (float inBitDepth) 69 | { 70 | // An bit depth of 0 will cause a divide by zero error 71 | assert(inBitDepth > 0); 72 | 73 | bitDepth.set (inBitDepth); 74 | } 75 | 76 | void Bitcrusher::processBlock (juce::AudioBuffer& inAudio, int samplesPerBlock, int numChannels) 77 | { 78 | const auto bits = bitDepth.get(); 79 | 80 | for (int channel = 0; channel < numChannels; ++channel) 81 | { 82 | auto channelData = inAudio.getWritePointer (channel); 83 | 84 | for (int sample = 0; sample < samplesPerBlock; ++sample) 85 | { 86 | const float inputSample = channelData[sample]; 87 | channelData[sample] = asymmetricBitcrush(inputSample, bits); 88 | } 89 | } 90 | } 91 | 92 | inline float Bitcrusher::asymmetricBitcrush (float inputSample, float bits) 93 | { 94 | const auto q = 1.0f / powf (2.0f, bits); 95 | 96 | return q * floorf ((inputSample / q)); 97 | } 98 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/DigiDegraders.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | SimpleZOH.h 5 | Created: 2 Sep 2019 10:40:50am 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class SimpleZOH 22 | { 23 | public: 24 | SimpleZOH() {} 25 | 26 | void setResampleOffset (double inHostSr); 27 | void setParams (float inDownsampleCoeff); 28 | void processBlock (juce::AudioBuffer& inAudio, int samplesPerBlock, int numChannels); 29 | float getZOHSample (const float* channelData, int sampleIndex, int dsCoef); 30 | 31 | private: 32 | float resampleOffset { -1 }, 33 | downsampleCoeff { 1.0f }; 34 | 35 | juce::Atomic currentHostSampleRate; 36 | 37 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleZOH) 38 | }; 39 | 40 | //==================================================================================== 41 | 42 | class Bitcrusher 43 | { 44 | public: 45 | Bitcrusher() {} 46 | 47 | void setParams (float inBitDepth); 48 | void processBlock (juce::AudioBuffer& inAudio, int samplesPerBlock, int numChannels); 49 | 50 | /** Returns a bitcrushed sample—with a twist. 51 | * 52 | * The typical calculation for a uniform bitcrusher is: 53 | * 54 | * quantization = 1 / (2 ^ bits) 55 | * output = quantization * floor((input / quantization) + 0.5) 56 | * 57 | * This makes the quantization (nearest step) the same for negative 58 | * and positive numbers. 59 | * 60 | * Omitting the 0.5 results in quantization (nearest lesser step) that 61 | * effectively makes the signal greater for negative values and smaller 62 | * for positive values. 63 | * 64 | * It also sounds very cool. 65 | * 66 | */ 67 | float asymmetricBitcrush (float inputSample, float bits); 68 | 69 | private: 70 | juce::Atomic bitDepth { 16.0f }; 71 | 72 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Bitcrusher) 73 | }; 74 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/EnvelopeDetector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | EnvelopeDetector.cpp 5 | Created: 16 Jul 2019 10:00:47am 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "EnvelopeDetector.h" 12 | #include "AudioHelpers.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | EnvelopeDetector::EnvelopeDetector (bool autoReleaseFlag) 19 | : autoMode (autoReleaseFlag) 20 | { 21 | } 22 | 23 | void EnvelopeDetector::reset() 24 | { 25 | prevEnv.set (0.0); 26 | currentGain.set (0.0); 27 | } 28 | 29 | void EnvelopeDetector::setSampleRate (double inSampleRate) 30 | { 31 | if (sampleRate.get() != inSampleRate) 32 | { 33 | sampleRate.set (inSampleRate); 34 | 35 | setTimeConstant (msAttack.get(), true); 36 | setTimeConstant (msRelease.get(), false); 37 | } 38 | } 39 | 40 | void EnvelopeDetector::setTimeConstant (double inTime, bool attack) 41 | { 42 | jassert (inTime > 0.0); 43 | 44 | auto sr = sampleRate.get(); 45 | 46 | auto timeCoeff = setCoefficient (inTime, 47 | kEnvelopeTimeConstant, 48 | sr); 49 | if (attack) 50 | { 51 | tauAttack.set (timeCoeff); 52 | msAttack.set (inTime); 53 | } 54 | else 55 | { 56 | tauRelease.set (timeCoeff); 57 | msRelease.set (inTime); 58 | 59 | if (autoMode) 60 | tauSlowRelease.set (setCoefficient (inTime * slowReleaseMultiplier, kEnvelopeTimeConstant, sr)); 61 | } 62 | } 63 | 64 | inline double EnvelopeDetector::setCoefficient (double timeInMilliseconds, 65 | double timeScalar, 66 | double sampleRate) 67 | { 68 | return exp (timeScalar / (0.001 * timeInMilliseconds * sampleRate)); 69 | } 70 | 71 | void EnvelopeDetector::updateCurrentGain (double inGain) { currentGain.set (inGain); } 72 | 73 | inline double EnvelopeDetector::getReleaseCoefficient() 74 | { 75 | if (!autoMode) 76 | return tauRelease.get(); 77 | 78 | return currentGain.get() < slowReleaseThreshold ? tauSlowRelease.get() : tauRelease.get(); 79 | } 80 | 81 | double EnvelopeDetector::processSampleDecoupledBranched (double inputSample) 82 | { 83 | juce::ScopedNoDenormals noDenormals; 84 | 85 | auto prevOutput = prevEnv.get(); 86 | auto coeff = inputSample > prevOutput ? tauAttack.get() : tauRelease.get(); 87 | auto smoothedSample = coeff * (prevOutput - inputSample) + inputSample; 88 | prevEnv.set (smoothedSample); 89 | 90 | return smoothedSample; 91 | } 92 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/EnvelopeDetector.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | EnvelopeDetector.h 5 | Created: 16 Jul 2019 10:00:47am 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | /** 16 | A peak level detector. 17 | */ 18 | class EnvelopeDetector 19 | { 20 | public: 21 | EnvelopeDetector (bool autoReleaseFlag); 22 | 23 | void reset(); 24 | 25 | void setSampleRate (double inSampleRate); 26 | 27 | void setParams (double inAttack, double inRelease); 28 | 29 | void setTimeConstant (double inTime, bool attack); 30 | 31 | /** returns a release coefficient depending on whether or not the envelope 32 | has been set to auto, and then whether or not it meets the threshold 33 | to used the slow attack 34 | */ 35 | double getReleaseCoefficient(); 36 | 37 | double processSampleDecoupledBranched (double inputSample); 38 | 39 | void updateAutoReleaseCounter (bool advance); 40 | 41 | void updateCurrentGain (double inGain); 42 | 43 | /** sets the time coefficients used in envelope detector 44 | */ 45 | static double setCoefficient (double timeInMilliseconds, 46 | double timeScalar, 47 | double sampleRate); 48 | 49 | private: 50 | bool autoMode { false }; 51 | 52 | juce::Atomic sampleRate { 0.0 }, 53 | prevEnv { 0.0 }, 54 | currentGain { 0.0 }, 55 | msAttack { -1 }, 56 | msRelease { -1 }, 57 | tauAttack { 0.0 }, 58 | tauRelease { 0.0 }, 59 | tauSlowRelease { 0.0 }; 60 | 61 | const double slowReleaseThreshold { -12.0 }; 62 | const int slowReleaseMultiplier { 2 }; 63 | 64 | static constexpr double kEnvelopeTimeConstant = -0.99967234081320612357829304641019; 65 | 66 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnvelopeDetector) 67 | }; 68 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/LagrangeInterpolator.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | LagrangeInterp.h 5 | Created: 23 Jan 2020 9:03:40am 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | /** 14 | From Pirkle's FXObjects.h 15 | @doLagrangeInterpolation 16 | \ingroup FX-Functions 17 | 18 | @brief implements n-order Lagrange Interpolation 19 | 20 | \param x - Pointer to an array containing the x-coordinates of the input values 21 | \param y - Pointer to an array containing the y-coordinates of the input values 22 | \param n - the order of the interpolator, this is also the length of the x,y input arrays 23 | \param xbar - The x-coorinates whose y-value we want to interpolate 24 | \return the interpolated value 25 | */ 26 | inline double doLagrangeInterpolation (double* x, double* y, int n, double xbar) 27 | { 28 | int i, j; 29 | double fx = 0.0; 30 | double l = 1.0; 31 | for (i = 0; i < n; i++) 32 | { 33 | l = 1.0; 34 | for (j = 0; j < n; j++) 35 | { 36 | if (j != i) 37 | l *= (xbar - x[j]) / (x[i] - x[j]); 38 | } 39 | fx += l * y[i]; 40 | } 41 | return (fx); 42 | } 43 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/Saturation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | Saturation.cpp 5 | Created: 16 Aug 2019 1:24:47pm 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "Saturation.h" 12 | #include "tote_bag/dsp/AudioHelpers.h" 13 | 14 | Saturation::Saturation (Type sType, float asymm) 15 | : saturationType (sType) 16 | , asymmetry (asymm) 17 | { 18 | // Stereo. TODO: don't use AudioBuffer. Refactor this so 19 | // we have state when we need it. 20 | xState.setSize (2, 1); 21 | } 22 | 23 | void Saturation::setParams (float inSaturation) 24 | { 25 | smoothedSat.setTargetValue (inSaturation); 26 | } 27 | 28 | void Saturation::reset (double sampleRate) 29 | { 30 | smoothedSat.reset (sampleRate, gainRampSec); 31 | clearBuffers(); 32 | } 33 | 34 | void Saturation::clearBuffers() 35 | { 36 | xState.clear(); 37 | antiderivState.fill (0.0); 38 | } 39 | 40 | inline float Saturation::calcGain (float inputSample, float sat) 41 | { 42 | return ((inputSample >= 0.0f && asymmetry > 0.0f) 43 | || (inputSample < 0.0f && asymmetry < 0.0f)) 44 | ? sat * (1.0f + 4.0f * abs (asymmetry)) 45 | : sat; 46 | } 47 | 48 | //=============================================================================== 49 | inline float Saturation::invHypeSineAntiDeriv (float x) 50 | { 51 | return x * std::asinh (x) - sqrt (x * x + 1.0f); 52 | } 53 | 54 | inline float Saturation::inverseHyperbolicSineInterp (float x, size_t channel) 55 | { 56 | auto stateSample = xState.getSample (static_cast (channel), 0); 57 | auto diff = x - stateSample; 58 | 59 | auto antiDeriv = invHypeSineAntiDeriv (x); 60 | auto output = 0.0f; 61 | if (abs (diff) < 1.0e-4f) 62 | { 63 | auto input = (x + stateSample) / 2.f; 64 | output = std::asinh (input); 65 | } 66 | else 67 | { 68 | output = (antiDeriv - antiderivState[channel]) / diff; 69 | } 70 | 71 | xState.setSample (static_cast (channel), 0, x); 72 | antiderivState[channel] = antiDeriv; 73 | 74 | return output; 75 | } 76 | 77 | inline float Saturation::sineArcTangent (float x, float gain) 78 | { 79 | float xScaled = x * gain; 80 | return (xScaled / sqrt (1.f + xScaled * xScaled)) / gain; 81 | } 82 | 83 | float Saturation::hyperTanFirstAntiDeriv (float x) 84 | { 85 | using namespace tote_bag::audio_helpers; 86 | 87 | // Casting to double is necessary to avoid overflow in the exponential function 88 | const auto x1 = clampedCosh (static_cast (x)); 89 | const auto x2 = std::log (x1); 90 | 91 | return static_cast (x2); 92 | } 93 | 94 | inline float Saturation::interpolatedHyperbolicTangent (float x, size_t channel) 95 | 96 | { 97 | auto stateSample = xState.getSample (static_cast (channel), 0); 98 | auto diff = x - stateSample; 99 | auto output = 0.0f; 100 | auto antiDeriv = hyperTanFirstAntiDeriv (x); 101 | 102 | if (abs (diff) < 1.0e-6f) 103 | { 104 | auto input = (x + stateSample) / 2.f; 105 | output = std::tanh (input); 106 | } 107 | else 108 | { 109 | output = (antiDeriv - antiderivState[channel]) / diff; 110 | } 111 | 112 | xState.setSample (static_cast (channel), 0, x); 113 | antiderivState[channel] = antiDeriv; 114 | 115 | return output; 116 | } 117 | 118 | //=============================================================================== 119 | 120 | inline float Saturation::processSample (float inputSample, size_t channel, float sat) 121 | { 122 | auto gain = calcGain (inputSample, sat); 123 | 124 | // mod matrix? 125 | // function pointer? 126 | // branching can prevent optimization 127 | // store a pointer to a processing class 128 | 129 | switch (saturationType) 130 | { 131 | case Type::inverseHyperbolicSine: 132 | return std::asinh (inputSample * gain) 133 | * compensationGain (gain); 134 | 135 | case Type::sineArcTangent: 136 | return sineArcTangent (inputSample, gain); 137 | 138 | case Type::hyperbolicTangent: 139 | return std::tanh (inputSample * gain) 140 | * compensationGain (gain); 141 | 142 | case Type::inverseHyperbolicSineInterp: 143 | return inverseHyperbolicSineInterp (inputSample * gain, channel) 144 | * compensationGain (gain); 145 | 146 | case Type::interpolatedHyperbolicTangent: 147 | return interpolatedHyperbolicTangent (inputSample * gain, channel) 148 | * compensationGain (gain); 149 | 150 | default: 151 | //somehow the distortion type was not set. It must be set! 152 | jassert (false); 153 | } 154 | } 155 | 156 | void Saturation::processBlock (juce::dsp::AudioBlock& inAudio) 157 | { 158 | auto numChannels = inAudio.getNumChannels(); 159 | auto samplesPerBlock = inAudio.getNumSamples(); 160 | 161 | for (size_t i = 0; i < samplesPerBlock; ++i) 162 | { 163 | for (size_t j = 0; j < numChannels; ++j) 164 | { 165 | auto input = inAudio.getChannelPointer (j); 166 | auto x = input[i]; 167 | 168 | auto y = processSample (x, j, smoothedSat.getNextValue()); 169 | 170 | input[i] = y; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/Saturation.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | Saturation.h 5 | Created: 16 Aug 2019 1:24:47pm 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/utils/type_helpers.hpp" 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | // TODO: there is some bounds checking that needs to be done intelligently here 21 | // Basically, some of these algs have diff min maxes. The system blows up if they 22 | // are violated. This whole thing needs a refactor anyway, so i'll just put the 23 | // reminder here instead of making changes thoughtlessly: 24 | // inverseHyperbolicTangent (interpolated) get a min of .001 25 | // the others have a min of probably 1.0 26 | // example: we need a way to specialize the class so all member variables, 27 | // such as upcoming ones that can announce limits to other classes, are customized 28 | // right now, the saturation param starts at 1.0 since that will not blow up for all of them 29 | // but I am initializing the IIHT to .001 in Valentine. Awkward! 30 | 31 | class Saturation 32 | { 33 | public: 34 | enum class Type { 35 | inverseHyperbolicSine, 36 | sineArcTangent, 37 | hyperbolicTangent, 38 | inverseHyperbolicSineInterp, 39 | interpolatedHyperbolicTangent 40 | }; 41 | 42 | Saturation (Type sType, float asymm = 0.0); 43 | 44 | void setParams (float inSaturation); 45 | 46 | void reset (double sampleRate); 47 | 48 | inline float calcGain (float inputSample, float sat); 49 | 50 | inline float processSample (float inputSample, size_t channel, float sat); 51 | 52 | //============================================================== 53 | 54 | inline float invHypeSineAntiDeriv (float x); 55 | 56 | inline float inverseHyperbolicSineInterp (float x, size_t channel); 57 | 58 | inline float sineArcTangent (float x, float gain); 59 | 60 | inline float hyperTanFirstAntiDeriv (float x); 61 | 62 | inline float interpolatedHyperbolicTangent (float x, size_t channel); 63 | 64 | //============================================================== 65 | 66 | void processBlock (juce::dsp::AudioBlock& inAudio); 67 | 68 | void clearBuffers(); 69 | 70 | private: 71 | // tags - first step towards a templated version of this class 72 | struct inverseHyperbolicSineTag 73 | { 74 | }; 75 | struct hyperbolicTangentTag 76 | { 77 | }; 78 | 79 | /** Returns the compensation gain for a given saturation type and input gain. 80 | * This allows us to increase input gain without changing the output level. 81 | * 82 | * Generally, the gain is found using the following: 1.0 / f(inputGain). 83 | * With some functions this will inappropriately boost output level 84 | * for small values. In these cases, we simply return 1.0 / inputGain. 85 | * 86 | * Note: The tolerance point determining when we use this alternate calculation 87 | * may need to be adjusted for different saturation types. 88 | */ 89 | template 90 | auto compensationGain (FloatType inputGain) 91 | { 92 | if constexpr (std::is_same::value) 93 | { 94 | // Tolerance determined by eyballing graphs in desmos 95 | constexpr auto tolerance = static_cast (1.02); 96 | if (inputGain <= tolerance) 97 | { 98 | return static_cast (1.0) / inputGain; 99 | } 100 | return static_cast (1.0) / std::asinh (inputGain); 101 | } 102 | else if constexpr (std::is_same::value) 103 | { 104 | // Tolerance determined by eyballing graphs in desmos 105 | constexpr auto tolerance = static_cast (1.02); 106 | if (inputGain <= tolerance) 107 | { 108 | return static_cast (1.0) / inputGain; 109 | } 110 | return static_cast (1.0) / std::tanh (inputGain); 111 | } 112 | else 113 | { 114 | static_assert (tote_bag::type_helpers::dependent_false::value, 115 | "Unsupported saturation type."); 116 | } 117 | } 118 | 119 | Type saturationType; 120 | 121 | float asymmetry {0.0f}; 122 | 123 | const float gainRampSec {.005f}; 124 | 125 | juce::SmoothedValue smoothedSat {1.0f}; 126 | juce::AudioBuffer xState; 127 | // I guess we're assuming first order ADAA, stereo here. 128 | std::array antiderivState; 129 | 130 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Saturation) 131 | }; 132 | -------------------------------------------------------------------------------- /libs/tote_bag/dsp/ThiranAllpass.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | ThiranAllpass.h 5 | Created: 14 Oct 2019 11:10:18am 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | /** A first order Thiran allpass filter. This is a simple allpass filter that 16 | * has a single coefficient, a1, and a single state variable, accumulator. 17 | * This filter will sound best for delay times around 1 sample. 18 | */ 19 | template 20 | class FirstOrderThiranAllpass 21 | { 22 | public: 23 | /** Resets the filter state to zero. This should be called before processing. 24 | */ 25 | void reset() 26 | { 27 | accumulator = 0.0; 28 | } 29 | 30 | /** Prepares the filter for processing. This should be called before processing. 31 | * @param inDelay The delay time in samples. This should be between 0 and 2. 32 | */ 33 | void prepare (T inDelay) 34 | { 35 | assert (inDelay > 0.0); 36 | 37 | if (delay != inDelay) 38 | { 39 | delay = inDelay; 40 | updateCoefficients(); 41 | } 42 | } 43 | 44 | /** Processes a buffer of samples. This should be called after prepare(). 45 | * @param buffer The buffer to process. 46 | * @param numSamples The number of samples in the buffer. 47 | */ 48 | void process (T* buffer, size_t numSamples) 49 | { 50 | for (size_t sample = 0; sample < numSamples; ++sample) 51 | { 52 | const auto x = buffer[sample]; 53 | const auto y = a1 * x + accumulator; 54 | 55 | buffer[sample] = y; 56 | accumulator = x - (a1 * y); 57 | } 58 | } 59 | 60 | private: 61 | /** Calculates the filter coefficients. This is called automatically by prepare(). 62 | * see Splitting the Unit Delay: Tools for Fractional Delay Filter Design 63 | * T.I. Laakso; V. Valimaki; M. Karjalainen; U.K. Laine; et al. 64 | */ 65 | void updateCoefficients() 66 | { 67 | a1 = (1.0f - delay) / (1.0f + delay); 68 | } 69 | 70 | T delay = 0.0; 71 | T a1 = 0.0; 72 | T accumulator = 0.0; 73 | }; 74 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/InfoPanel.cpp: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #include "InfoPanel.h" 4 | 5 | #include "generated/version.h" 6 | 7 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeel.h" 8 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 9 | 10 | #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR 11 | #include 12 | #endif // JUCE_ENABLE_LIVE_CONSTANT_EDITOR 13 | 14 | namespace tote_bag 15 | { 16 | 17 | namespace detail 18 | { 19 | constexpr int kNumInfoTextLines = 5; 20 | constexpr float kTotieWidth = 2098.55f; 21 | constexpr float kTotieHeight = 2094.79f; 22 | constexpr float kTotieWidthHeightRatio = kTotieWidth / kTotieHeight; 23 | } 24 | 25 | InfoPanel::InfoPanel (std::function mouseUpCallback) 26 | : onMouseUp (mouseUpCallback) 27 | , githubURL ("https://github.com/tote-bag-labs/valentine") 28 | { 29 | totieWatermark = 30 | juce::Drawable::createFromImageData (BinaryData::totie_watermark_svg, 31 | BinaryData::totie_watermark_svgSize); 32 | } 33 | 34 | InfoPanel::~InfoPanel() 35 | { 36 | } 37 | 38 | void InfoPanel::paint (juce::Graphics& g) 39 | { 40 | g.setColour (colours::plainWhite); 41 | g.fillRect (getLocalBounds()); 42 | 43 | drawInfoText (g); 44 | drawWatermark (g); 45 | } 46 | 47 | void InfoPanel::mouseUp (const juce::MouseEvent& e) 48 | { 49 | if (urlBounds.contains (e.getPosition())) 50 | { 51 | githubURL.launchInDefaultBrowser(); 52 | } 53 | else 54 | { 55 | onMouseUp(); 56 | } 57 | } 58 | 59 | void InfoPanel::drawInfoText (juce::Graphics& g) 60 | { 61 | const auto bounds = getLocalBounds(); 62 | const auto boundsHeight = bounds.getHeight(); 63 | 64 | const auto textLineHeight = juce::roundToInt (boundsHeight / 32.0f); 65 | const auto textAreaHeight = 66 | juce::roundToInt (textLineHeight * detail::kNumInfoTextLines); 67 | const auto textAreaY = juce::roundToInt (bounds.getCentreY() - textAreaHeight / 2.0); 68 | 69 | auto textBounds = bounds.withY (textAreaY).withHeight (textAreaHeight); 70 | 71 | g.setColour (colours::plainBlack); 72 | 73 | if (auto* lnf = dynamic_cast (&getLookAndFeel())) 74 | { 75 | g.setFont (lnf->getInfoTextFont().withHeight (textLineHeight)); 76 | } 77 | else 78 | { 79 | // This needs a tote bag look and feel to work correctly 80 | jassertfalse; 81 | } 82 | 83 | const auto placeText = [&] (const juce::String& text) { 84 | const auto placedBounds = textBounds.removeFromTop (textLineHeight); 85 | 86 | g.drawFittedText (text, placedBounds, juce::Justification::centredBottom, 1); 87 | 88 | return placedBounds; 89 | }; 90 | 91 | placeText ("Valentine"); 92 | placeText ("Tote Bag Labs"); 93 | placeText (CURRENT_VERSION); 94 | placeText (juce::String ("Build: " + juce::String (BUILD_ID))); 95 | 96 | g.setColour (colours::valentinePink); 97 | urlBounds = placeText ("Github"); 98 | } 99 | 100 | void InfoPanel::drawWatermark (juce::Graphics& g) 101 | { 102 | if (totieWatermark) 103 | { 104 | const auto bounds = getLocalBounds(); 105 | const auto boundsWidth = bounds.getWidth(); 106 | const auto boundsHeight = bounds.getHeight(); 107 | 108 | const auto totieX = juce::roundToInt (bounds.getX() + (boundsWidth * .28f)); 109 | const auto totieY = juce::roundToInt (bounds.getY() - (boundsHeight * .08f)); 110 | const auto totieHeight = juce::roundToInt (boundsHeight * 1.22f); 111 | const auto totieBounds = bounds.withX (totieX) 112 | .withY (totieY) 113 | .withHeight (totieHeight) 114 | .withWidth (juce::roundToInt ( 115 | totieHeight * detail::kTotieWidthHeightRatio)); 116 | 117 | totieWatermark->drawWithin (g, 118 | totieBounds.toFloat(), 119 | juce::RectanglePlacement::stretchToFit, 120 | 1.0f); 121 | } 122 | } 123 | 124 | } // namespace tote_bag 125 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/InfoPanel.h: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #pragma once 4 | 5 | #include "tote_bag/juce_gui/utilities/tbl_font.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace tote_bag 12 | { 13 | 14 | class InfoPanel : public juce::Component 15 | { 16 | public: 17 | InfoPanel (std::function mouseUpCallback); 18 | 19 | ~InfoPanel() override; 20 | 21 | void paint (juce::Graphics& g) override; 22 | 23 | void mouseUp (const juce::MouseEvent& e) override; 24 | 25 | private: 26 | void drawInfoText (juce::Graphics& g); 27 | void drawWatermark (juce::Graphics& g); 28 | 29 | std::function onMouseUp; 30 | 31 | std::unique_ptr totieWatermark; 32 | 33 | juce::Rectangle urlBounds; 34 | juce::URL githubURL; 35 | 36 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoPanel) 37 | }; 38 | 39 | } // namespace tote_bag 40 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/PresetPanel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | PresetPanel.cpp 5 | Created: 3 Jan 2020 1:10:53pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "PresetPanel.h" 12 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 13 | #include "tote_bag/juce_gui/managers/ToteBagPresetManager.h" 14 | 15 | #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR 16 | #include 17 | #endif // JUCE_ENABLE_LIVE_CONSTANT_EDITOR 18 | 19 | #include 20 | 21 | namespace detail 22 | { 23 | constexpr auto kTotieWidth = 144.22f; 24 | constexpr auto kTotieHeight = 135.51f; 25 | constexpr auto kTotieHWRatio = kTotieHeight / kTotieWidth; 26 | } 27 | 28 | PresetPanel::PresetPanel (ToteBagPresetManager& pManager, 29 | const juce::String& bypassButtonText, 30 | const juce::String& bypassParameterId, 31 | std::function infoButtonCallback, 32 | juce::AudioProcessorValueTreeState& treeState) 33 | : mInfoButton ("ValentineInfo", juce::DrawableButton::ButtonStyle::ImageStretched) 34 | , mPreviousPreset ("PreviousPreset", 35 | juce::DrawableButton::ButtonStyle::ImageStretched) 36 | , mNextPreset ("NextPreset", juce::DrawableButton::ButtonStyle::ImageStretched) 37 | , mBypassButton (bypassButtonText, bypassParameterId, treeState) 38 | , presetManager (pManager) 39 | , minfoButtonCallback (infoButtonCallback) 40 | { 41 | presetManager.setPresetSavedCallback ([this]() { updatePresetDisplay(); }); 42 | 43 | mInfoButton.onClick = [this]() { minfoButtonCallback(); }; 44 | 45 | setupValentineInfoButton(); 46 | setupBypassButton(); 47 | setupSaveAndLoadButtons(); 48 | setupPresetIncrementButtons(); 49 | setupPresetDisplay(); 50 | 51 | startTimerHz (20); 52 | } 53 | 54 | PresetPanel::~PresetPanel() 55 | { 56 | stopTimer(); 57 | } 58 | 59 | void PresetPanel::paint (juce::Graphics& g) 60 | { 61 | g.setColour (tote_bag::colours::offWhite); 62 | g.fillAll(); 63 | 64 | g.setColour (tote_bag::colours::plainBlack); 65 | g.drawRect (getBounds(), mBorderThickness); 66 | } 67 | 68 | void PresetPanel::timerCallback() 69 | { 70 | auto presetNameInManager = presetManager.getCurrentPresetName(); 71 | if (currentPresetName != presetNameInManager) 72 | { 73 | mPresetDisplay.setText (presetNameInManager, juce::dontSendNotification); 74 | currentPresetName = presetNameInManager; 75 | } 76 | } 77 | 78 | void PresetPanel::resized() 79 | { 80 | const auto area = getLocalBounds(); 81 | 82 | mBorderThickness = juce::roundToInt (area.getHeight() * .04f); 83 | 84 | auto presetBounds = area.reduced (mBorderThickness); 85 | 86 | const auto presetBoundsWidth = presetBounds.getWidth(); 87 | const auto presetBoundsHeight = presetBounds.getHeight(); 88 | const auto presetBoundsCentreY = presetBounds.getCentreY(); 89 | 90 | const auto leftInfoButtonGapWidth = juce::roundToInt (presetBoundsWidth * .026f); 91 | presetBounds.removeFromLeft (leftInfoButtonGapWidth); 92 | 93 | const auto infoButtonWidth = juce::roundToInt (presetBoundsWidth * 0.05f); 94 | const auto infoButtonHeight = 95 | juce::roundToInt (infoButtonWidth * detail::kTotieHWRatio); 96 | const auto infoButtonY = presetBoundsCentreY - infoButtonHeight / 2; 97 | 98 | mInfoButton.setBounds (presetBounds.removeFromLeft (infoButtonWidth) 99 | .withY (infoButtonY) 100 | .withHeight (infoButtonHeight)); 101 | 102 | const auto infoButtonBypassGapWidth = juce::roundToInt (presetBoundsWidth * 0.0027f); 103 | presetBounds.removeFromLeft (infoButtonBypassGapWidth); 104 | 105 | const auto bypassSaveLoadButtonHeight = juce::roundToInt (presetBoundsHeight * .8f); 106 | const auto bypassSaveLoadButtonY = 107 | presetBoundsCentreY - bypassSaveLoadButtonHeight / 2; 108 | 109 | const auto bypassButtonWidth = juce::roundToInt (presetBoundsWidth * .134f); 110 | 111 | mBypassButton.setBounds (presetBounds.removeFromLeft (bypassButtonWidth) 112 | .withY (bypassSaveLoadButtonY) 113 | .withHeight (bypassSaveLoadButtonHeight)); 114 | 115 | const auto bypassSaveGapWidth = juce::roundToInt (presetBoundsWidth * .0825f); 116 | presetBounds.removeFromLeft (bypassSaveGapWidth); 117 | 118 | const auto saveLoadButtonWidth = juce::roundToInt (presetBoundsWidth * .157f); 119 | 120 | mSavePresetButton.setBounds (presetBounds.removeFromLeft (saveLoadButtonWidth) 121 | .withY (bypassSaveLoadButtonY) 122 | .withHeight (bypassSaveLoadButtonHeight)); 123 | 124 | const auto saveLoadGapWidth = juce::roundToInt (presetBoundsWidth * .007f); 125 | presetBounds.removeFromLeft (saveLoadGapWidth); 126 | 127 | mLoadPresetButton.setBounds (presetBounds.removeFromLeft (saveLoadButtonWidth) 128 | .withY (bypassSaveLoadButtonY) 129 | .withHeight (bypassSaveLoadButtonHeight)); 130 | 131 | // This is used to set the gap between load button and previous button as well 132 | // as next button and preset display box 133 | const auto prevNextButtonAreaGapWidth = juce::roundToInt (presetBoundsWidth * .016f); 134 | presetBounds.removeFromLeft (prevNextButtonAreaGapWidth); 135 | 136 | const auto prevNextButtonHeight = juce::roundToInt (presetBoundsHeight * .2f); 137 | const auto prevNextButtonY = presetBoundsCentreY - prevNextButtonHeight / 2; 138 | const auto prevNextButtonWidth = juce::roundToInt (presetBoundsWidth * .024f); 139 | 140 | mPreviousPreset.setBounds (presetBounds.removeFromLeft (prevNextButtonWidth) 141 | .withY (prevNextButtonY) 142 | .withHeight (prevNextButtonHeight)); 143 | 144 | const auto prevNextGapWidth = juce::roundToInt (presetBoundsWidth * .0117f); 145 | presetBounds.removeFromLeft (prevNextGapWidth); 146 | 147 | mNextPreset.setBounds (presetBounds.removeFromLeft (prevNextButtonWidth) 148 | .withY (prevNextButtonY) 149 | .withHeight (prevNextButtonHeight)); 150 | 151 | presetBounds.removeFromLeft (prevNextButtonAreaGapWidth); 152 | 153 | const auto presetDisplayHeight = juce::roundToInt (presetBoundsHeight * .85f); 154 | const auto presetDisplayY = presetBoundsCentreY - presetDisplayHeight / 2; 155 | const auto presetBoxWidth = juce::roundToInt (presetBoundsWidth * .27f); 156 | 157 | mPresetDisplay.setBounds (presetBounds.removeFromLeft (presetBoxWidth) 158 | .withY (presetDisplayY) 159 | .withHeight (presetDisplayHeight)); 160 | } 161 | 162 | void PresetPanel::setupValentineInfoButton() 163 | { 164 | mInfoButton.setImages ( 165 | juce::Drawable::createFromImageData (BinaryData::totie_outline_svg, 166 | BinaryData::totie_outline_svgSize) 167 | .get(), 168 | juce::Drawable::createFromImageData (BinaryData::totie_pink_svg, 169 | BinaryData::totie_pink_svgSize) 170 | .get()); 171 | addAndMakeVisible (mInfoButton); 172 | } 173 | 174 | void PresetPanel::setupBypassButton() 175 | { 176 | mBypassButton.setColour (juce::TextButton::ColourIds::buttonColourId, 177 | tote_bag::colours::bypassGrey); 178 | mBypassButton.setColour (juce::TextButton::ColourIds::textColourOffId, 179 | tote_bag::colours::plainWhite); 180 | mBypassButton.setColour (juce::TextButton::ColourIds::buttonOnColourId, 181 | tote_bag::colours::bypassInGrey); 182 | mBypassButton.setColour (juce::TextButton::ColourIds::textColourOnId, 183 | tote_bag::colours::bypassInTextGrey); 184 | 185 | addAndMakeVisible (mBypassButton); 186 | } 187 | 188 | void PresetPanel::setupSaveAndLoadButtons() 189 | { 190 | mSavePresetButton.setButtonText ("SAVE"); 191 | mSavePresetButton.onClick = [this]() { presetManager.savePresetToFile(); }; 192 | addAndMakeVisible (mSavePresetButton); 193 | 194 | mSavePresetButton.setColour (juce::TextButton::ColourIds::buttonColourId, 195 | tote_bag::colours::plainWhite); 196 | mSavePresetButton.setColour (juce::TextButton::ColourIds::buttonOnColourId, 197 | tote_bag::colours::offWhite); 198 | mSavePresetButton.setColour (juce::TextButton::ColourIds::textColourOffId, 199 | tote_bag::colours::plainBlack); 200 | mSavePresetButton.setColour (juce::TextButton::ColourIds::textColourOnId, 201 | tote_bag::colours::plainBlack); 202 | 203 | mLoadPresetButton.setButtonText ("LOAD"); 204 | mLoadPresetButton.onClick = [this]() { presetManager.loadPresetFromFile(); }; 205 | addAndMakeVisible (mLoadPresetButton); 206 | 207 | mLoadPresetButton.setColour (juce::TextButton::ColourIds::buttonColourId, 208 | tote_bag::colours::plainWhite); 209 | mLoadPresetButton.setColour (juce::TextButton::ColourIds::buttonOnColourId, 210 | tote_bag::colours::offWhite); 211 | mLoadPresetButton.setColour (juce::TextButton::ColourIds::textColourOffId, 212 | tote_bag::colours::plainBlack); 213 | mLoadPresetButton.setColour (juce::TextButton::ColourIds::textColourOnId, 214 | tote_bag::colours::plainBlack); 215 | } 216 | 217 | void PresetPanel::setupPresetIncrementButtons() 218 | { 219 | mPreviousPreset.onClick = [this]() { presetManager.loadPreviousPreset(); }; 220 | mPreviousPreset.setImages ( 221 | juce::Drawable::createFromImageData (BinaryData::arrow_left_svg, 222 | BinaryData::arrow_left_svgSize) 223 | .get()); 224 | addAndMakeVisible (mPreviousPreset); 225 | 226 | mNextPreset.onClick = [this]() { presetManager.loadNextPreset(); }; 227 | mNextPreset.setImages ( 228 | juce::Drawable::createFromImageData (BinaryData::arrow_right_svg, 229 | BinaryData::arrow_right_svgSize) 230 | .get()); 231 | addAndMakeVisible (mNextPreset); 232 | } 233 | 234 | void PresetPanel::setupPresetDisplay() 235 | { 236 | // set up preset combo box 237 | mPresetDisplay.setColour (juce::ComboBox::ColourIds::backgroundColourId, 238 | tote_bag::colours::sliderGrey); 239 | mPresetDisplay.setColour (juce::ComboBox::ColourIds::textColourId, 240 | tote_bag::colours::plainWhite); 241 | mPresetDisplay.setColour (juce::ComboBox::ColourIds::outlineColourId, 242 | tote_bag::colours::plainBlack); 243 | 244 | addAndMakeVisible (mPresetDisplay); 245 | mPresetDisplay.onChange = [this]() { handlePresetDisplaySelection(); }; 246 | mPresetDisplay.setText (currentPresetName, juce::dontSendNotification); 247 | mPresetDisplay.setSelectedItemIndex (presetManager.getCurrentPresetIndex(), 248 | juce::dontSendNotification); 249 | 250 | updatePresetDisplay(); 251 | } 252 | 253 | void PresetPanel::handlePresetDisplaySelection() 254 | { 255 | if (mPresetDisplay.getSelectedItemIndex() != -1) 256 | { 257 | presetManager.loadPreset (mPresetDisplay.getSelectedItemIndex()); 258 | } 259 | } 260 | 261 | void PresetPanel::updatePresetDisplay() 262 | { 263 | mPresetDisplay.clear (juce::dontSendNotification); 264 | 265 | const int numPresets = presetManager.getNumberOfPresets(); 266 | 267 | for (int i = 0; i < numPresets; i++) 268 | { 269 | mPresetDisplay.addItem (presetManager.getPresetName (i), (i + 1)); 270 | } 271 | 272 | mPresetDisplay.setText (presetManager.getCurrentPresetName(), 273 | juce::dontSendNotification); 274 | } 275 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/PresetPanel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | PresetPanel.h 5 | Created: 3 Jan 2020 1:10:53pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/juce_gui/components/widgets/ParameterTextButton.h" 14 | 15 | #include 16 | 17 | #include 18 | 19 | class ToteBagPresetManager; 20 | class PresetPanel : public juce::Component, public juce::Timer 21 | { 22 | public: 23 | PresetPanel (ToteBagPresetManager& pManager, 24 | const juce::String& bypassButtonText, 25 | const juce::String& bypassParameterId, 26 | std::function infoButtonCallback, 27 | juce::AudioProcessorValueTreeState& treeState); 28 | ~PresetPanel() override; 29 | 30 | void paint (juce::Graphics& g) override; 31 | 32 | void timerCallback() override; 33 | 34 | void resized() override; 35 | 36 | private: 37 | void setupValentineInfoButton(); 38 | 39 | void setupBypassButton(); 40 | 41 | void setupSaveAndLoadButtons(); 42 | 43 | void setupPresetIncrementButtons(); 44 | 45 | void setupPresetDisplay(); 46 | 47 | void handlePresetDisplaySelection(); 48 | 49 | void updatePresetDisplay(); 50 | 51 | juce::Rectangle outerBorder; 52 | int mBorderThickness = 0; 53 | 54 | juce::DrawableButton mInfoButton; 55 | juce::TextButton mSavePresetButton; 56 | juce::TextButton mLoadPresetButton; 57 | juce::DrawableButton mPreviousPreset; 58 | juce::DrawableButton mNextPreset; 59 | ParameterTextButton mBypassButton; 60 | 61 | juce::ComboBox mPresetDisplay; 62 | 63 | juce::String currentPresetName {"Untitled"}; 64 | 65 | ToteBagPresetManager& presetManager; 66 | 67 | std::function minfoButtonCallback; 68 | 69 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PresetPanel) 70 | }; 71 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/VerticalMeterPanel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VerticalMeterPanel.cpp 5 | Created: 27 Apr 2022 9:47:04pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "VerticalMeterPanel.h" 12 | 13 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 14 | 15 | #include 16 | 17 | //============================================================================== 18 | VerticalMeterPanel::VerticalMeterPanel (ReductionMeterPlacement reductionMeterPlacement, 19 | foleys::LevelMeterSource* levelMeterSource, 20 | foleys::LevelMeterSource* grMeterSource) 21 | : grMeterPlacement (reductionMeterPlacement) 22 | { 23 | using namespace tote_bag::colours; 24 | 25 | levelMeter.setMeterSource (levelMeterSource); 26 | addAndMakeVisible (levelMeter); 27 | 28 | if (grMeterSource) 29 | { 30 | gainReductionMeter = 31 | std::make_unique (FFAU::LevelMeter::MeterFlags::Reduction); 32 | gainReductionMeter->setMeterSource (grMeterSource); 33 | addAndMakeVisible (gainReductionMeter.get()); 34 | } 35 | } 36 | 37 | VerticalMeterPanel::~VerticalMeterPanel() 38 | { 39 | } 40 | 41 | void VerticalMeterPanel::lookAndFeelChanged() 42 | { 43 | using namespace tote_bag::colours; 44 | 45 | auto& lookAndFeel = getLookAndFeel(); 46 | 47 | lookAndFeel.setColour (FFAU::LevelMeter::lmBackgroundColour, valentinePink); 48 | lookAndFeel.setColour (FFAU::LevelMeter::lmMeterGradientLowColour, (grassGreen)); 49 | lookAndFeel.setColour (FFAU::LevelMeter::lmMeterGradientMidColour, avocadoGreen); 50 | lookAndFeel.setColour (FFAU::LevelMeter::lmMeterMaxOverColour, racecarRed); 51 | 52 | levelMeter.setLookAndFeel (&lookAndFeel); 53 | 54 | if (gainReductionMeter.get()) 55 | { 56 | gainReductionMeter->setLookAndFeel (&lookAndFeel); 57 | } 58 | } 59 | 60 | void VerticalMeterPanel::resized() 61 | { 62 | auto area = getLocalBounds(); 63 | 64 | // remove amount for gain reduction no matter what, that way meter will be 65 | // the same size whether or not a gani reduction bar is specified 66 | const auto areaWidth = area.getWidth(); 67 | 68 | // get gr and meters width 69 | const auto grMeterWidth = juce::roundToInt (areaWidth * .25f); 70 | 71 | const auto grMeterBounds = (grMeterPlacement & ReductionMeterPlacement::Left) 72 | ? area.removeFromLeft (grMeterWidth) 73 | : area.removeFromRight (grMeterWidth); 74 | 75 | if (gainReductionMeter.get()) 76 | { 77 | gainReductionMeter->setBounds (grMeterBounds); 78 | } 79 | levelMeter.setBounds (area); 80 | } 81 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/VerticalMeterPanel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VerticalMeterPanel.h 5 | Created: 27 Apr 2022 9:47:04pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeel.h" 14 | 15 | #include 16 | 17 | //============================================================================== 18 | 19 | enum ReductionMeterPlacement { 20 | Left = 1, 21 | Right = 1 << 1, 22 | None = 1 << 2 23 | }; 24 | 25 | class ValentineAudioProcessor; 26 | 27 | class VerticalMeterPanel : public juce::Component 28 | { 29 | public: 30 | VerticalMeterPanel (ReductionMeterPlacement reductionMeterPlacement, 31 | foleys::LevelMeterSource* levelMeterSource, 32 | foleys::LevelMeterSource* grMeterSource = nullptr); 33 | 34 | ~VerticalMeterPanel() override; 35 | 36 | void resized() override; 37 | 38 | void lookAndFeelChanged() override; 39 | 40 | private: 41 | FFAU::LevelMeter levelMeter { FFAU::LevelMeter::MeterFlags::Minimal }; 42 | 43 | std::unique_ptr gainReductionMeter; 44 | 45 | ReductionMeterPlacement grMeterPlacement; 46 | 47 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VerticalMeterPanel) 48 | }; 49 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/panels/tbl_PresetPanelModel.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tote-bag-labs/valentine/92a839fcdc1600486e4ce80eae3cbc2b8792e80a/libs/tote_bag/juce_gui/components/panels/tbl_PresetPanelModel.h -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/DrawableParameterButton.cpp: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #include "DrawableParameterButton.h" 4 | 5 | namespace tote_bag 6 | { 7 | DrawableParameterButton::DrawableParameterButton ( 8 | const juce::Drawable* buttonOnImage, 9 | const juce::Drawable* buttonOffImage, 10 | const juce::String& parameterId, 11 | juce::AudioProcessorValueTreeState& stateToControl) 12 | : juce::DrawableButton (parameterId, ButtonStyle::ImageStretched) 13 | , buttonValue (stateToControl, parameterId, *this) 14 | { 15 | setClickingTogglesState (true); 16 | setImages (buttonOffImage, 17 | nullptr, 18 | nullptr, 19 | nullptr, 20 | buttonOnImage, 21 | nullptr, 22 | nullptr, 23 | nullptr); 24 | } 25 | } // namespace tote_bag 26 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/DrawableParameterButton.h: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace tote_bag 9 | { 10 | class DrawableParameterButton : public juce::DrawableButton 11 | { 12 | public: 13 | DrawableParameterButton (const juce::Drawable* buttonOnImage, 14 | const juce::Drawable* buttonOffImage, 15 | const juce::String& parameterId, 16 | juce::AudioProcessorValueTreeState& stateToControl); 17 | 18 | private: 19 | juce::AudioProcessorValueTreeState::ButtonAttachment buttonValue; 20 | 21 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableParameterButton) 22 | }; 23 | } // namespace tote_bag -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/FlatTextButton.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | FlatButton.cpp 5 | Created: 6 Nov 2022 1:47:36pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "FlatTextButton.h" 12 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeel.h" 13 | 14 | namespace tote_bag 15 | { 16 | 17 | FlatTextButton::FlatTextButton (juce::String name) 18 | : TextButton (name) 19 | { 20 | } 21 | 22 | void FlatTextButton::paintButton (juce::Graphics& g, 23 | bool shouldDrawButtonAsHighlighted, 24 | bool shouldDrawButtonAsDown) 25 | { 26 | if (auto* lnf = dynamic_cast (&getLookAndFeel())) 27 | { 28 | lnf->drawFlatButtonBackground ( 29 | g, 30 | *this, 31 | findColour (getToggleState() ? buttonOnColourId : buttonColourId), 32 | shouldDrawButtonAsHighlighted, 33 | shouldDrawButtonAsDown); 34 | lnf->drawButtonText (g, 35 | *this, 36 | shouldDrawButtonAsHighlighted, 37 | shouldDrawButtonAsDown); 38 | } 39 | else 40 | { 41 | // This button can only be used with our custom look and feel! 42 | // Make sure it is being set by the top level GUI component. 43 | jassertfalse; 44 | } 45 | } 46 | } // namespace totebag 47 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/FlatTextButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | FlatButton.h 5 | Created: 6 Nov 2022 1:47:36pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace tote_bag 16 | { 17 | 18 | class FlatTextButton : public juce::TextButton 19 | { 20 | public: 21 | FlatTextButton (juce::String name); 22 | 23 | protected: 24 | void paintButton (juce::Graphics& g, 25 | bool shouldDrawButtonAsHighlighted, 26 | bool shouldDrawButtonAsDown) override; 27 | }; 28 | } // namespace totebag 29 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/FlatTextChooser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | FlatChooser.cpp 5 | Created: 7 Nov 2022 8:43:30pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "FlatTextChooser.h" 12 | 13 | #include 14 | 15 | //============================================================================== 16 | 17 | namespace tote_bag 18 | { 19 | 20 | FlatTextChooser::FlatTextChooser (const juce::String& labelText, 21 | const std::vector& choices, 22 | int paramGroupId, 23 | juce::AudioParameterChoice* param = nullptr) 24 | : mLabel (labelText + " Chooser", labelText) 25 | { 26 | mLabel.setColour (juce::Label::textColourId, juce::Colours::black); 27 | mLabel.setJustificationType (juce::Justification::centredTop); 28 | 29 | addAndMakeVisible (mLabel); 30 | 31 | for (const auto& choiceLabelText : choices) 32 | { 33 | auto button = mButtons.add (std::make_unique (choiceLabelText)); 34 | 35 | int edgesFlag = 0; 36 | if (choiceLabelText == choices.front()) 37 | { 38 | edgesFlag = juce::Button::ConnectedEdgeFlags::ConnectedOnBottom; 39 | } 40 | else if (choiceLabelText == choices.back()) 41 | { 42 | edgesFlag = juce::Button::ConnectedEdgeFlags::ConnectedOnTop; 43 | } 44 | else 45 | { 46 | edgesFlag = juce::Button::ConnectedEdgeFlags::ConnectedOnTop 47 | | juce::Button::ConnectedEdgeFlags::ConnectedOnBottom; 48 | } 49 | 50 | button->setConnectedEdges (edgesFlag); 51 | addAndMakeVisible (button); 52 | } 53 | 54 | if (param) 55 | { 56 | mButtonState = std::make_unique (*param, paramGroupId); 57 | 58 | for (const auto button : mButtons) 59 | { 60 | mButtonState->attach (button); 61 | } 62 | } 63 | } 64 | 65 | FlatTextChooser::~FlatTextChooser() 66 | { 67 | } 68 | 69 | void FlatTextChooser::resized() 70 | { 71 | const auto mainArea = getLocalBounds(); 72 | const auto margin = juce::roundToInt (mainArea.getHeight() * .01f); 73 | auto buttonArea = getLocalBounds().reduced (margin); 74 | 75 | const auto labelHeight = juce::roundToInt (buttonArea.getHeight() * .140); 76 | const auto labelArea = buttonArea.removeFromTop (labelHeight); 77 | 78 | mLabel.setBounds (labelArea); 79 | 80 | // Make some more space between label and buttons 81 | buttonArea.removeFromTop (juce::roundToInt(labelHeight * .70f)); 82 | 83 | // The buttons themselves shouldn't take up all of the horizontal space 84 | // given to this component 85 | const auto sideMargin = juce::roundToInt (buttonArea.getWidth() * .3f); 86 | buttonArea.reduce (sideMargin, 0); 87 | 88 | const auto numButtons = mButtons.size(); 89 | const auto buttonWidth = buttonArea.getWidth(); 90 | const auto buttonHeight = juce::roundToInt (buttonArea.getHeight() / static_cast (numButtons)); 91 | const auto x = buttonArea.getX(); 92 | auto y = buttonArea.getY(); 93 | 94 | for (auto* button : mButtons) 95 | { 96 | button->setBounds (x, y, buttonWidth, buttonHeight); 97 | 98 | y += buttonHeight; 99 | } 100 | } 101 | 102 | } // namespace tote_bag 103 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/FlatTextChooser.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | FlatChooser.h 5 | Created: 7 Nov 2022 8:43:30pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/juce_gui/components/widgets/FlatTextButton.h" 14 | #include "tote_bag/juce_gui/managers/RadioButtonGroupManager.h" 15 | 16 | #include 17 | 18 | //============================================================================== 19 | 20 | namespace juce 21 | { 22 | class AudioParameterChoice; 23 | } // namespace juce 24 | 25 | namespace tote_bag 26 | { 27 | /* 28 | */ 29 | class FlatTextChooser : public juce::Component 30 | { 31 | public: 32 | FlatTextChooser (const juce::String&, 33 | const std::vector&, 34 | int, 35 | juce::AudioParameterChoice*); 36 | ~FlatTextChooser() override; 37 | 38 | void resized() override; 39 | 40 | private: 41 | juce::Label mLabel; 42 | 43 | juce::OwnedArray mButtons; 44 | 45 | // Depends on mButtons. Must be destroyed first. 46 | std::unique_ptr mButtonState; 47 | 48 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlatTextChooser) 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/LabelSlider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | LabelSlider.cpp 5 | Created: 18 Apr 2022 9:12:40am 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "LabelSlider.h" 12 | 13 | #include 14 | 15 | //============================================================================== 16 | 17 | LabelSlider::LabelSlider (const juce::String& labelText, 18 | const juce::String& parameterId, 19 | juce::AudioProcessorValueTreeState& stateToControl) 20 | : slider (parameterId, stateToControl) 21 | { 22 | label.setText (labelText, juce::dontSendNotification); 23 | label.setColour (juce::Label::textColourId, juce::Colours::black); 24 | label.setJustificationType (juce::Justification::centredTop); 25 | 26 | addAndMakeVisible (label); 27 | addAndMakeVisible (slider); 28 | } 29 | 30 | LabelSlider::~LabelSlider() 31 | { 32 | } 33 | 34 | void LabelSlider::resized() 35 | { 36 | auto sliderArea = getLocalBounds(); 37 | 38 | const auto labelHeight = juce::roundToInt (sliderArea.getHeight() * .1); 39 | label.setBounds (sliderArea.removeFromTop (labelHeight)); 40 | 41 | const auto margin = juce::roundToInt (sliderArea.getHeight() * .07f); 42 | sliderArea.removeFromTop (margin); 43 | 44 | slider.setBounds (sliderArea); 45 | } 46 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/LabelSlider.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | LabelSlider.h 5 | Created: 18 Apr 2022 9:12:40am 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/juce_gui/components/widgets/ParameterSlider.h" 14 | 15 | #include 16 | 17 | //============================================================================== 18 | /* 19 | */ 20 | namespace juce 21 | { 22 | class AudioProcessorValueTreeState; 23 | } // namespace juce 24 | 25 | class LabelSlider : public juce::Component 26 | { 27 | public: 28 | LabelSlider (const juce::String& labelText, 29 | const juce::String& parameterId, 30 | juce::AudioProcessorValueTreeState& stateToControl); 31 | 32 | ~LabelSlider() override; 33 | 34 | void resized() override; 35 | 36 | private: 37 | juce::Label label; 38 | ParameterSlider slider; 39 | 40 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelSlider) 41 | }; 42 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/ParameterSlider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | ParameterSlider.cpp 5 | Created: 4 Aug 2019 4:36:23pm 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "ParameterSlider.h" 12 | 13 | ParameterSlider::ParameterSlider (const juce::String& parameterId, 14 | juce::AudioProcessorValueTreeState& stateToControl) 15 | : sliderValue (std::make_unique (stateToControl, 16 | parameterId, 17 | *this)) 18 | { 19 | setSliderStyle (SliderStyle::RotaryHorizontalVerticalDrag); 20 | setTextValueSuffix (stateToControl.getParameter (parameterId)->label); 21 | setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxBelow, false, 0, 0); 22 | } 23 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/ParameterSlider.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | ParameterSlider.h 5 | Created: 4 Aug 2019 4:36:23pm 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | class ParameterSlider : public juce::Slider 18 | { 19 | public: 20 | ParameterSlider (const juce::String& parameterId, 21 | juce::AudioProcessorValueTreeState& stateToControl); 22 | 23 | private: 24 | std::unique_ptr sliderValue; 25 | 26 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterSlider) 27 | }; 28 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/ParameterTextButton.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | ParameterTextButton.cpp 5 | Created: 23 Jan 2020 9:34:06pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "ParameterTextButton.h" 12 | 13 | ParameterTextButton::ParameterTextButton ( 14 | const juce::String& buttonText, 15 | const juce::String& parameterId, 16 | juce::AudioProcessorValueTreeState& stateToControl) 17 | : buttonValue (stateToControl, parameterId, *this) 18 | { 19 | setButtonText (buttonText); 20 | changeWidthToFitText(); 21 | setClickingTogglesState (true); 22 | } 23 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/ParameterTextButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | ParameterTextButton.h 5 | Created: 23 Jan 2020 9:34:06pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | class ParameterTextButton : public juce::TextButton 17 | { 18 | public: 19 | ParameterTextButton (const juce::String& buttonText, 20 | const juce::String& parameterId, 21 | juce::AudioProcessorValueTreeState& stateToControl); 22 | 23 | private: 24 | juce::AudioProcessorValueTreeState::ButtonAttachment buttonValue; 25 | 26 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterTextButton) 27 | }; 28 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/tbl_ToggleButton.cpp: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #include "tbl_ToggleButton.h" 4 | 5 | namespace tote_bag 6 | { 7 | ToggleButton::ToggleButton (const juce::String& parameterId, 8 | juce::AudioProcessorValueTreeState& stateToControl) 9 | : juce::ToggleButton (parameterId) 10 | , buttonValue (stateToControl, parameterId, *this) 11 | { 12 | setClickingTogglesState (true); 13 | } 14 | } // namespace tote_bag 15 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/components/widgets/tbl_ToggleButton.h: -------------------------------------------------------------------------------- 1 | 2 | // 2023 Tote Bag Labs 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace tote_bag 10 | { 11 | class ToggleButton : public juce::ToggleButton 12 | { 13 | public: 14 | ToggleButton (const juce::String& parameterId, 15 | juce::AudioProcessorValueTreeState& stateToControl); 16 | 17 | enum ColourIds 18 | { 19 | defaultFillColourId = 0x1006504, 20 | activeFillColourId = 0x1006505, 21 | strokeColourId = 0x1006506, 22 | }; 23 | 24 | private: 25 | juce::AudioProcessorValueTreeState::ButtonAttachment buttonValue; 26 | 27 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton) 28 | }; 29 | } // namespace tote_bag 30 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/lookandfeel/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | LookAndFeel.h 5 | Created: 26 Dec 2019 7:02:26pm 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "tote_bag/juce_gui/utilities/tbl_font.h" 14 | 15 | #include 16 | #include 17 | 18 | namespace tote_bag 19 | { 20 | 21 | class FlatTextButton; 22 | 23 | class LookAndFeel : public juce::LookAndFeel_V4, 24 | public FFAU::LevelMeter::LookAndFeelMethods 25 | { 26 | public: 27 | LookAndFeel(); 28 | 29 | /** Draws a drawable knob. Originally used for the knobs in Valentine, this is been refactored 30 | to not rely on member variables. This, along with the fact that this function isn't called 31 | anywhere, effectively orphans it. This is intentional: the way that the drawable was defined 32 | and used didn't make sense for a look and feel class that is supposed to be reuseable. 33 | 34 | TODO: Refactor slider drawing to effectively use this function if desired 35 | */ 36 | void drawDrawableKnob (juce::Graphics& g, 37 | const float radius, 38 | const float drawableWidth, 39 | juce::Drawable& sliderImage, 40 | const float toAngle, 41 | const juce::Rectangle bounds); 42 | 43 | void drawRotarySlider (juce::Graphics& g, 44 | int x, 45 | int y, 46 | int width, 47 | int height, 48 | float sliderPos, 49 | const float rotaryStartAngle, 50 | const float rotaryEndAngle, 51 | juce::Slider&) override; 52 | 53 | juce::Font getTextButtonFont (juce::TextButton&, int buttonHeight) override; 54 | 55 | juce::Font getLabelFont (juce::Label& l) override; 56 | 57 | void drawButtonBackground (juce::Graphics& g, 58 | juce::Button& button, 59 | const juce::Colour& backgroundColour, 60 | bool, 61 | bool shouldDrawButtonAsDown) override; 62 | 63 | void drawFlatButtonBackground (juce::Graphics& g, 64 | tote_bag::FlatTextButton& button, 65 | const juce::Colour& backgroundColour, 66 | bool, 67 | bool isButtonDown); 68 | 69 | void drawButtonText (juce::Graphics& g, juce::TextButton& button, bool, bool) override; 70 | 71 | juce::Font getComboBoxFont (juce::ComboBox&) override; 72 | 73 | void positionComboBoxText (juce::ComboBox& box, juce::Label& label) override; 74 | 75 | void drawComboBox (juce::Graphics& g, 76 | int, 77 | int, 78 | bool, 79 | int, 80 | int, 81 | int, 82 | int, 83 | juce::ComboBox& box) override; 84 | 85 | void drawPopupMenuItem (juce::Graphics& g, 86 | const juce::Rectangle& area, 87 | bool, 88 | bool, 89 | bool isHighlighted, 90 | bool isTicked, 91 | bool, 92 | const juce::String& text, 93 | const juce::String&, 94 | const juce::Drawable*, 95 | const juce::Colour*) override; 96 | 97 | void drawToggleButton (juce::Graphics& g, 98 | juce::ToggleButton& button, 99 | bool, 100 | bool) override; 101 | 102 | juce::Slider::SliderLayout getSliderLayout (juce::Slider& slider) override; 103 | 104 | enum ColourIds 105 | { 106 | knobColourId = 0x1001800, 107 | pointerColourId = 0x1001801 108 | }; 109 | 110 | juce::Font getInfoTextFont(); 111 | 112 | juce::TypefaceMetricsKind getDefaultMetricsKind() const override; 113 | 114 | private: 115 | void drawRotarySliderMeter (juce::Graphics& g, 116 | const juce::Rectangle bounds, 117 | float lineWidth, 118 | float arcRadius, 119 | float rotaryStartAngle, 120 | float rotaryEndAngle, 121 | float toAngle, 122 | juce::Slider& slider); 123 | 124 | void drawRotarySliderBase (juce::Graphics& g, 125 | const float radius, 126 | const float toAngle, 127 | const juce::Rectangle bounds, 128 | juce::Slider&); 129 | 130 | FontHolder fontHolder; 131 | 132 | #include "MeterLookAndFeelMethods.h" 133 | }; 134 | } // namespace tote_bag 135 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | LookAndFeel.h 5 | Created: 2 Feb 2020 10:25:06am 6 | Author: José Díaz Rohena 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace tote_bag 16 | { 17 | namespace colours 18 | { 19 | const juce::Colour plainWhite (0xffffffff); 20 | const juce::Colour valentinePink (0xfff6c7eb); 21 | const juce::Colour valentinePinkDark (0xffe0a2d5); 22 | const juce::Colour grassGreen (0xff43a028); 23 | const juce::Colour avocadoGreen (0xff83a028); 24 | const juce::Colour racecarRed (0xffef202a); 25 | const juce::Colour lightGrey (0xff7d7d7d); 26 | const juce::Colour offWhite (0xffe8e8e8); 27 | const juce::Colour transparentGrey (0x33393838); 28 | const juce::Colour dividerGrey (0xffE0A3D3); 29 | const juce::Colour puttyGrey (0xffe7e7e7); 30 | const juce::Colour slateGrey (0xff383838); 31 | const juce::Colour sliderGrey (0xff2f2f2f); 32 | const juce::Colour mediumGrey (0xff696969); 33 | const juce::Colour bypassGrey (0xff404040); 34 | const juce::Colour bypassInGrey (0xff808080); 35 | const juce::Colour bypassInTextGrey (0xffd2d2d2); 36 | 37 | // This is the colour currently used for our meter 38 | // background. See note about this in MeterLookAndFeelMethods.h 39 | const juce::Colour valentinePinkGrey (0xffe0a3d3); 40 | 41 | const juce::Colour slightlyTransparentBlack (0x99000000); 42 | const juce::Colour plainBlack (0xff000000); 43 | } // colours 44 | } // tote_bag 45 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/managers/RadioButtonGroupManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | RadioButtonStateManager.cpp 5 | Created: 6 Nov 2022 11:22:41pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "RadioButtonGroupManager.h" 12 | 13 | #include 14 | 15 | namespace tote_bag 16 | { 17 | 18 | RadioButtonGroupManager::RadioButtonGroupManager (juce::AudioParameterChoice& parameter, int groupId) 19 | : mParameter (parameter) 20 | , mGroupId (groupId) 21 | { 22 | mCurrentParameterIndex = static_cast (mParameter.getIndex()); 23 | 24 | startTimerHz (20); 25 | } 26 | 27 | RadioButtonGroupManager::~RadioButtonGroupManager() 28 | { 29 | // button->onClick is dependent on this manager, 30 | // causing a crash if it is called after this has 31 | // been destroyed. 32 | for (auto button : mManagedButtons) 33 | { 34 | button->onClick = nullptr; 35 | } 36 | 37 | stopTimer(); 38 | } 39 | 40 | void RadioButtonGroupManager::attach (juce::Button* button) 41 | { 42 | const auto index = mNextButtonIndex; 43 | 44 | button->onClick = [this, button, index] { 45 | buttonOnClickCallback (button, index); 46 | }; 47 | 48 | if (index == mCurrentParameterIndex) 49 | { 50 | button->setToggleState (true, juce::dontSendNotification); 51 | } 52 | 53 | button->setClickingTogglesState (true); 54 | button->setRadioGroupId (mGroupId); 55 | 56 | mManagedButtons.emplace_back (button); 57 | ++mNextButtonIndex; 58 | } 59 | 60 | void RadioButtonGroupManager::buttonOnClickCallback (juce::Button* button, size_t index) 61 | { 62 | auto buttonOn = button->getToggleState(); 63 | 64 | if (buttonOn) 65 | { 66 | mCurrentParameterIndex = index; 67 | 68 | const auto buttonVal = mParameter.convertTo0to1 (index); 69 | 70 | mParameter.beginChangeGesture(); 71 | mParameter.setValueNotifyingHost (buttonVal); 72 | mParameter.endChangeGesture(); 73 | } 74 | } 75 | 76 | void RadioButtonGroupManager::updateGroupState() 77 | { 78 | const auto parameterIndex = static_cast (mParameter.getIndex()); 79 | if (mCurrentParameterIndex != parameterIndex) 80 | { 81 | mManagedButtons[parameterIndex]->setToggleState (true, 82 | juce::dontSendNotification); 83 | mCurrentParameterIndex = parameterIndex; 84 | } 85 | } 86 | 87 | void RadioButtonGroupManager::timerCallback() 88 | { 89 | updateGroupState(); 90 | } 91 | 92 | } // namespace tote_bag 93 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/managers/RadioButtonGroupManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | RadioButtonStateManager.h 5 | Created: 6 Nov 2022 11:22:41pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace juce 16 | { 17 | class AudioParameterChoice; 18 | } // namespace juce 19 | 20 | namespace tote_bag 21 | { 22 | 23 | /** Manages button and parameter state for a radio button group. 24 | */ 25 | class RadioButtonGroupManager : public juce::Timer 26 | { 27 | public: 28 | RadioButtonGroupManager (juce::AudioParameterChoice& parameter, int groupId); 29 | 30 | ~RadioButtonGroupManager() override; 31 | 32 | void timerCallback() override; 33 | 34 | /** Attaches a radio button to be managed by this class. 35 | buttons managed by this class will be indexed in the order they 36 | were attached. 37 | */ 38 | void attach (juce::Button* button); 39 | 40 | private: 41 | /** Called by a managed button's onClick() method to change button and 42 | parameter state. 43 | */ 44 | void buttonOnClickCallback (juce::Button* button, size_t index); 45 | 46 | /** Called periodically to check if param value has changed in a way not driven 47 | by a click on the button itself. e.g. via automation. 48 | */ 49 | void updateGroupState(); 50 | 51 | juce::AudioParameterChoice& mParameter; 52 | std::vector mManagedButtons; 53 | int mGroupId { 0 }; 54 | size_t mCurrentParameterIndex { 0 }; 55 | size_t mNextButtonIndex { 0 }; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RadioButtonGroupManager) 58 | }; 59 | } // namespace tote_bag 60 | 61 | #pragma once 62 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/managers/ToteBagPresetManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VPresetManager.cpp 5 | Created: 3 Jan 2020 1:11:35pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "ToteBagPresetManager.h" 12 | 13 | #include 14 | 15 | ToteBagPresetManager::ToteBagPresetManager (juce::AudioProcessor* inProcessor) 16 | : processor (inProcessor) 17 | { 18 | presetDirectory = juce::File ( 19 | (juce::File::getSpecialLocation (juce::File::userDocumentsDirectory)) 20 | .getFullPathName() 21 | + static_cast (directorySeparator) + "Tote Bag" 22 | + static_cast (directorySeparator) + processor->getName()); 23 | 24 | // checks if preset directory is there—if not, it creates the directory 25 | if (!presetDirectory.exists()) 26 | presetDirectory.createDirectory(); 27 | 28 | updatePresetList(); 29 | } 30 | 31 | void ToteBagPresetManager::savePresetToFile() 32 | { 33 | const juce::String currentPresetPath = presetDirectory.getFullPathName() 34 | + static_cast (directorySeparator) 35 | + currentPresetName; 36 | 37 | juce::FileChooser chooser ("Save a file: ", 38 | juce::File (currentPresetPath), 39 | static_cast (presetFileExtensionWildcard)); 40 | 41 | if (chooser.browseForFileToSave (true)) 42 | { 43 | savePreset (chooser.getResult()); 44 | } 45 | } 46 | 47 | void ToteBagPresetManager::loadPresetFromFile() 48 | { 49 | const juce::String currentPresetDirectory = presetDirectory.getFullPathName(); 50 | 51 | if (currentPresetDirectory.isNotEmpty()) 52 | { 53 | juce::FileChooser chooser ( 54 | "Load a file: ", 55 | juce::File (currentPresetDirectory), 56 | static_cast (presetFileExtensionWildcard)); 57 | 58 | if (chooser.browseForFileToOpen()) 59 | { 60 | loadPreset (chooser.getResult()); 61 | } 62 | } 63 | } 64 | 65 | void ToteBagPresetManager::loadPreset (juce::File presetToLoad) 66 | { 67 | // if preset successfully loads, save its name and index 68 | juce::MemoryBlock presetBinary; 69 | if (presetToLoad.loadFileAsData (presetBinary)) 70 | { 71 | currentPresetName = presetToLoad.getFileNameWithoutExtension(); 72 | 73 | // current preset name is set my set State Info 74 | processor->setStateInformation (presetBinary.getData(), 75 | static_cast (presetBinary.getSize())); 76 | } 77 | } 78 | 79 | void ToteBagPresetManager::loadPreset (int index) 80 | { 81 | auto presetToLoad = juce::File ( 82 | presetDirectory.getFullPathName() + static_cast (directorySeparator) 83 | + getPresetName (index) + static_cast (presetFileExtension)); 84 | loadPreset (presetToLoad); 85 | } 86 | 87 | void ToteBagPresetManager::loadNextPreset() 88 | { 89 | auto newPresetIndex = mCurrentPresetIndex + 1; 90 | if (newPresetIndex > presetList.size() - 1) 91 | { 92 | newPresetIndex = 0; 93 | } 94 | loadPreset (newPresetIndex); 95 | } 96 | 97 | void ToteBagPresetManager::loadPreviousPreset() 98 | { 99 | auto newPresetIndex = mCurrentPresetIndex - 1; 100 | if (newPresetIndex < 0) 101 | { 102 | newPresetIndex = presetList.size() - 1; 103 | } 104 | loadPreset (newPresetIndex); 105 | } 106 | 107 | const juce::String ToteBagPresetManager::getPresetName (int inPresetIndex) 108 | { 109 | return presetList[inPresetIndex]; 110 | } 111 | 112 | const juce::String ToteBagPresetManager::getCurrentPresetName() 113 | { 114 | return currentPresetName; 115 | } 116 | 117 | int ToteBagPresetManager::getCurrentPresetIndex() 118 | { 119 | return mCurrentPresetIndex; 120 | } 121 | 122 | const int ToteBagPresetManager::getNumberOfPresets() 123 | { 124 | return presetList.size(); 125 | } 126 | 127 | void ToteBagPresetManager::setLastChosenPresetName (juce::String newPresetName) 128 | { 129 | currentPresetName = newPresetName; 130 | mCurrentPresetIndex = findPresetIndex (currentPresetName); 131 | } 132 | 133 | void ToteBagPresetManager::createDefaultPreset() 134 | { 135 | auto& parameters = processor->getParameters(); 136 | 137 | for (int i = 0; i < parameters.size(); i++) 138 | { 139 | auto parameter = 140 | (juce::AudioProcessorParameterWithID*) parameters.getUnchecked (i); 141 | 142 | const float defaultValue = parameter->getDefaultValue(); 143 | 144 | parameter->setValueNotifyingHost (defaultValue); 145 | } 146 | 147 | currentPresetName = "Untitled"; 148 | } 149 | 150 | void ToteBagPresetManager::savePreset (juce::File presetFile) 151 | { 152 | // check if the file passed in actually exists. if not, create it 153 | if (!presetFile.exists()) 154 | presetFile.create(); 155 | else 156 | presetFile.deleteFile(); 157 | 158 | // update preset name for GUI display 159 | currentPresetName = presetFile.getFileNameWithoutExtension(); 160 | 161 | // allocate memory on the stack, and fill it with our preset data 162 | juce::MemoryBlock destinationData; 163 | processor->getStateInformation (destinationData); 164 | 165 | // write the preset data to file 166 | presetFile.appendData (destinationData.getData(), destinationData.getSize()); 167 | 168 | updatePresetList(); 169 | 170 | if (onPresetSaved) 171 | { 172 | onPresetSaved(); 173 | } 174 | } 175 | 176 | void ToteBagPresetManager::updatePresetList() 177 | { 178 | presetList.clear(); 179 | 180 | auto directoryIterator = juce::RangedDirectoryIterator ( 181 | juce::File (presetDirectory), 182 | false, 183 | static_cast (presetFileExtensionWildcard), 184 | juce::File::TypesOfFileToFind::findFiles); 185 | 186 | for (auto entry : directoryIterator) 187 | presetList.add (entry.getFile().getFileNameWithoutExtension()); 188 | } 189 | 190 | int ToteBagPresetManager::findPresetIndex (const juce::String& presetName) 191 | { 192 | const auto foundPreset = std::find (presetList.begin(), presetList.end(), presetName); 193 | 194 | if (foundPreset != presetList.end()) 195 | { 196 | return static_cast (foundPreset - presetList.begin()); 197 | } 198 | 199 | return 0; 200 | } 201 | 202 | void ToteBagPresetManager::setPresetSavedCallback (PresetSavedCallback callback) 203 | { 204 | onPresetSaved = callback; 205 | } 206 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/managers/ToteBagPresetManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VPresetManager.h 5 | Created: 3 Jan 2020 1:11:35pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace 16 | { 17 | constexpr std::string_view presetFileExtension = ".tbp"; 18 | constexpr std::string_view presetFileExtensionWildcard = "*.tbp"; 19 | #if JUCE_WINDOWS 20 | constexpr std::string_view directorySeparator = "\\"; 21 | #else 22 | constexpr std::string_view directorySeparator = "/"; 23 | #endif 24 | } 25 | 26 | namespace juce 27 | { 28 | class AudioProcessor; 29 | } 30 | 31 | class ToteBagPresetManager 32 | { 33 | public: 34 | using PresetSavedCallback = std::function; 35 | 36 | ToteBagPresetManager (juce::AudioProcessor* inProcessor); 37 | 38 | /** Returns the number of presets loaded to the local presets array */ 39 | const int getNumberOfPresets(); 40 | 41 | /** Opens a modal window and saves preset to the selected location */ 42 | void savePresetToFile(); 43 | 44 | /** Opens a modal window and loads preset from the selected location */ 45 | void loadPresetFromFile(); 46 | 47 | void loadPreset (int presetIndex); 48 | 49 | void loadNextPreset(); 50 | 51 | void loadPreviousPreset(); 52 | 53 | /** Returns the present name for a given index of the preset directory */ 54 | const juce::String getPresetName (int inPresetIndex); 55 | /** Returns the name of the currently loaded preset */ 56 | const juce::String getCurrentPresetName(); 57 | 58 | int getCurrentPresetIndex(); 59 | 60 | /** Allows caller to set the name of the currently loaded preset. used to facilitate state restore */ 61 | void setLastChosenPresetName (juce::String newPresetName); 62 | 63 | void setPresetSavedCallback (PresetSavedCallback callback); 64 | 65 | private: 66 | /** Resets the processor to its default state, creating an "Untitled" preset */ 67 | void createDefaultPreset(); 68 | 69 | void savePreset (juce::File presetToSave); 70 | 71 | void loadPreset (juce::File presetToLoad); 72 | 73 | /** Iterates over the preset directory and adds the files to localPresets */ 74 | void updatePresetList(); 75 | 76 | int findPresetIndex (const juce::String& presetName); 77 | 78 | juce::File presetDirectory; 79 | juce::String currentPresetName {"Untitled"}; 80 | int mCurrentPresetIndex {0}; 81 | juce::Array presetList; 82 | 83 | juce::AudioProcessor* processor; 84 | 85 | PresetSavedCallback onPresetSaved; 86 | 87 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToteBagPresetManager) 88 | }; 89 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/utilities/GraphicsUtilities.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | GraphicsUtilities.cpp 5 | Created: 23 Jul 2022 2:29:49pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "GraphicsUtilities.h" 12 | 13 | namespace gui_utils 14 | { 15 | 16 | void drawRoundedRect (juce::Graphics& g, 17 | juce::Rectangle bounds, 18 | juce::Colour colour) 19 | { 20 | auto margin = bounds.getHeight() * .025f; 21 | auto croppedBounds = bounds.reduced (margin); 22 | 23 | auto h = croppedBounds.getHeight(); 24 | auto lineThickness = h * .025f; 25 | auto cornerSize = h * .075f; 26 | 27 | g.setColour (colour.darker()); 28 | 29 | g.drawRoundedRectangle (croppedBounds, cornerSize, lineThickness); 30 | 31 | g.setColour (colour); 32 | juce::Path p; 33 | p.addRoundedRectangle (croppedBounds, cornerSize); 34 | g.fillPath (p); 35 | } 36 | 37 | } // namespace gui_utils 38 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/utilities/GraphicsUtilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | GraphicsUtilities.h 5 | Created: 23 Jul 2022 2:29:49pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace gui_utils 16 | { 17 | 18 | void drawRoundedRect (juce::Graphics& g, 19 | juce::Rectangle bounds, 20 | juce::Colour colour); 21 | 22 | } // namespace gui_utils 23 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/utilities/tbl_font.cpp: -------------------------------------------------------------------------------- 1 | // Tote Bag Labs 2023 2 | 3 | #include "tbl_font.h" 4 | 5 | namespace tote_bag 6 | { 7 | 8 | juce::Font FontHolder::getFont(const juce::String& fontName) 9 | { 10 | auto fontIt = fonts.find(fontName); 11 | if (fontIt == fonts.end()) 12 | { 13 | int size = 0; 14 | const auto resource = BinaryData::getNamedResource (fontName.toRawUTF8(), size); 15 | 16 | // Make sure you've passed the correct font name to this function. 17 | // If you're sure you've done that, make sure you've added the font 18 | // to your assets dir and BinaryData files. 19 | jassert(resource != nullptr); 20 | 21 | auto typeface = (juce::Typeface::createSystemTypefaceFor (resource, 22 | static_cast(size))); 23 | 24 | auto font = std::make_unique (typeface); 25 | 26 | const auto[newFontIt, inserted] = fonts.insert({fontName, std::move(font)}); 27 | if(!inserted) 28 | { 29 | jassertfalse; 30 | return juce::Font{}; 31 | } 32 | 33 | fontIt = newFontIt; 34 | } 35 | 36 | return *fontIt->second; 37 | } 38 | 39 | } // namespace tote_bag 40 | -------------------------------------------------------------------------------- /libs/tote_bag/juce_gui/utilities/tbl_font.h: -------------------------------------------------------------------------------- 1 | // Tote Bag Labs 2023 2 | 3 | #include "BinaryData.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | #pragma once 10 | 11 | namespace tote_bag 12 | { 13 | 14 | /** A class that holds our custom fonts. 15 | */ 16 | class FontHolder 17 | { 18 | public: 19 | /** Gets a Font from the map, creating it if it doesn't exist. 20 | */ 21 | juce::Font getFont(const juce::String& fontName); 22 | 23 | private: 24 | std::map> fonts; 25 | }; 26 | 27 | } // namespace tote_bag 28 | -------------------------------------------------------------------------------- /libs/tote_bag/utils/macros.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // macros.hpp 3 | // Assets 4 | // 5 | // Created by Jose Diaz on 25.03.23. 6 | // 7 | 8 | #ifndef macros_hpp 9 | #define macros_hpp 10 | 11 | /** Silences "unused parameter" compiler warnings. Ideally, of course, one would remove the unused 12 | parameter. However, it is sometimes more expedient to (temporarily) use this macro. 13 | */ 14 | #define TBL_Unused(x) (void) (x) 15 | 16 | #endif /* macros_hpp */ 17 | -------------------------------------------------------------------------------- /libs/tote_bag/utils/tbl_math.hpp: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace tote_bag 9 | { 10 | namespace math 11 | { 12 | 13 | /** Returns the indexes for the values in the input range smaller and larger than the input value. 14 | * Input range must be sorted. 15 | */ 16 | template 17 | std::pair 18 | findNearestIndices (const std::array inputRange, 19 | const ValueType inputValue) 20 | { 21 | static_assert (ArraySize > 1, "ArraySize must be greater than 1"); 22 | 23 | auto it = std::upper_bound (inputRange.begin(), inputRange.end(), inputValue); 24 | auto upperIndex = static_cast (std::distance (inputRange.begin(), it)); 25 | 26 | if (upperIndex == 0) 27 | { 28 | return std::make_pair (0, 1); 29 | } 30 | else if (upperIndex == ArraySize) 31 | { 32 | return std::make_pair (ArraySize - 2, ArraySize - 1); 33 | } 34 | else 35 | { 36 | return std::make_pair (upperIndex - 1, upperIndex); 37 | } 38 | } 39 | 40 | /** Returns the values in the input range that are smaller and larger than the input value. 41 | * Input range must be sorted. 42 | */ 43 | template 44 | std::pair 45 | findNearestValues (const std::array inputRange, 46 | const ValueType inputValue) 47 | { 48 | const auto indices = findNearestIndices (inputRange, inputValue); 49 | return std::make_pair (inputRange[indices.first], inputRange[indices.second]); 50 | } 51 | 52 | /** Remaps a value from one range of the other, using sections corresponding to 53 | * the upper and lower bounds of the input value as found in the input range. 54 | */ 55 | template 56 | ValueType piecewiseRemap (const std::array inputSegments, 57 | const std::array outputSegments, 58 | const ValueType input) 59 | { 60 | const auto [lowerBound, upperBound] = findNearestIndices (inputSegments, input); 61 | // clang-format off 62 | const auto normalizedInput = (input - inputSegments[lowerBound]) 63 | / (inputSegments[upperBound] - inputSegments[lowerBound]); 64 | // clang-format on 65 | 66 | return outputSegments[lowerBound] 67 | + (outputSegments[upperBound] - outputSegments[lowerBound]) * normalizedInput; 68 | } 69 | 70 | } // namespace math 71 | } // namespace tote_bag 72 | -------------------------------------------------------------------------------- /libs/tote_bag/utils/type_helpers.hpp: -------------------------------------------------------------------------------- 1 | // Tote Bag Labs 2023 2 | 3 | #include 4 | 5 | #ifndef type_helpers_hpp 6 | #define type_helpers_hpp 7 | 8 | namespace tote_bag 9 | { 10 | namespace type_helpers 11 | { 12 | /** A a type that evaluates to false if instantiated. 13 | * 14 | * This is useful for static asserts that should only trigger based on conditions 15 | * evaluated using template arguments at compile time. 16 | * 17 | * Bool cannot be used in this case, as the resulting expression will always be compiled 18 | * and hence cause an error, no matter whether the conditions for hitting the 19 | * assert have been met or not. 20 | * 21 | * Example: 22 | * 23 | * template 24 | * void functionThatNeedsIntType() 25 | * { 26 | * if constexpr (std::is_same_v) 27 | * { 28 | * // Do something. 29 | * } 30 | * else 31 | * { 32 | * // This will always cause a compilation error. 33 | * //static_assert(false, "This type is not supported"); 34 | * 35 | * // This will only cause an error if the template is instantiated with a type other than int, 36 | * // causing this branch to be evaluated and the dependent_false to be instantiated. 37 | * static_assert(dependent_false::value, "This type is not supported"); 38 | * } 39 | * } 40 | */ 41 | template 42 | struct dependent_false : std::false_type 43 | { 44 | }; 45 | } 46 | } 47 | 48 | #endif /* type_helpers_hpp */ 49 | -------------------------------------------------------------------------------- /scripts/installer_mac/make_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation for pkgbuild and productbuild: https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html 4 | 5 | # preflight check 6 | PRODUCT=$1 # product name 7 | INDIR=$2 # directory where the binaries to be packaged are 8 | RESOURCES_DIR=$3 # directory where readme, icons, etc are 9 | TARGET_DIR=$4 # directory to which installer will be placed 10 | VERSION=$5 # version number 11 | SIGNING_ID=$6 12 | 13 | TMPDIR="./installer-tmp" 14 | mkdir -p $TMPDIR 15 | 16 | echo "MAKE from $INDIR $RESOURCES_DIR into $TARGET_DIR with $VERSION" 17 | 18 | VST3="${PRODUCT}.vst3" 19 | AU="${PRODUCT}.component" 20 | 21 | PRODUCTFILE=`echo $PRODUCT | tr ' ' '-' | tr '[:upper:]' '[:lower:]'` 22 | echo "PRODUCTFILE is ${PRODUCTFILE}" 23 | 24 | if [ "$VERSION" == "" ]; then 25 | echo "You must specify the version you are packaging!" 26 | echo "eg: ./make_installer.sh 1.0.6b4" 27 | exit 1 28 | fi 29 | 30 | OUTPUT_BASE_FILENAME="${PRODUCTFILE}-$VERSION-macOs" 31 | 32 | 33 | build_flavor() 34 | { 35 | flavor=$1 36 | flavorprod=$2 37 | ident=$3 38 | loc=$4 39 | scripts=$5 40 | 41 | echo --- BUILDING ${PRODUCTFILE}_${flavor}.pkg from "$flavorprod" --- 42 | 43 | workdir=$TMPDIR/$flavor 44 | mkdir -p $workdir 45 | 46 | # In the past we would pkgbuild --analyze first to make a plist file, but we are perfectly fine with the 47 | # defaults, so we skip that step here. http://thegreyblog.blogspot.com/2014/06/os-x-creating-packages-from-command_2.html 48 | # was pretty handy in figuring that out and man pkgbuild convinced us to not do it, as did testing. 49 | # 50 | # The defaults only work if a component is a sole entry in a staging directory though, so synthesize that 51 | # by moving the product to a tmp dir 52 | 53 | cp -r "$INDIR/$flavor/$flavorprod" "$workdir" 54 | ls -l $workdir 55 | 56 | # Package the sucker. This will be wrapped by productbuild, so no need to sign here 57 | pkgbuild --root $workdir --identifier $ident --version $VERSION --install-location "$loc" "$TMPDIR/${PRODUCTFILE}_${flavor}.pkg" $sca || exit 1 58 | 59 | rm -rf $workdir 60 | } 61 | 62 | 63 | if [[ -d $INDIR/"VST3"/$VST3 ]]; then 64 | build_flavor "VST3" "$VST3" "com.tote-bag-labs.${PRODUCTFILE}.vst3.pkg" "/Library/Audio/Plug-Ins/VST3" 65 | fi 66 | 67 | if [[ -d $INDIR/"AU"/$AU ]]; then 68 | build_flavor "AU" "$AU" "com.tote-bag-labs.${PRODUCTFILE}.component.pkg" "/Library/Audio/Plug-Ins/Components" 69 | fi 70 | 71 | 72 | echo --- Sub Packages Created --- 73 | ls "${TMPDIR}" 74 | 75 | # create distribution.xml 76 | 77 | if [[ -d $INDIR/"VST3"/$VST3 ]]; then 78 | VST3_PKG_REF="" 79 | VST3_CHOICE="" 80 | VST3_CHOICE_DEF="${PRODUCTFILE}_VST3.pkg" 81 | fi 82 | if [[ -d $INDIR/"AU"/$AU ]]; then 83 | AU_PKG_REF="" 84 | AU_CHOICE="" 85 | AU_CHOICE_DEF="${PRODUCTFILE}_AU.pkg" 86 | fi 87 | 88 | cat > $TMPDIR/distribution.xml << XMLEND 89 | 90 | 91 | ${PRODUCT} ${VERSION} 92 | 93 | 94 | ${VST3_PKG_REF} 95 | ${AU_PKG_REF} 96 | 97 | 98 | ${VST3_CHOICE} 99 | ${AU_CHOICE} 100 | 101 | ${VST3_CHOICE_DEF} 102 | ${AU_CHOICE_DEF} 103 | 104 | XMLEND 105 | 106 | # build installation bundle 107 | echo "Building installation bundle" 108 | 109 | # copy resources into temp dir 110 | mkdir -p "${TMPDIR}/${RESOURCES_DIR}" 111 | cp -r "${RESOURCES_DIR}/Readme.rtf" "${TMPDIR}/${RESOURCES_DIR}" 112 | cp -r "${RESOURCES_DIR}/License.txt" "${TMPDIR}/${RESOURCES_DIR}" 113 | 114 | pushd ${TMPDIR} 115 | 116 | if [[ ! -z "$SIGNING_ID" ]]; then 117 | echo productbuild --sign "$SIGNING_ID" --distribution "distribution.xml" --package-path "." --resources "${RESOURCES_DIR}" "$OUTPUT_BASE_FILENAME.pkg" 118 | productbuild --sign "$SIGNING_ID" --distribution "distribution.xml" --package-path "." --resources ${RESOURCES_DIR} "$OUTPUT_BASE_FILENAME.pkg" 119 | else 120 | echo productbuild ---distribution "distribution.xml" --package-path "." --resources "${RESOURCES_DIR}" "$OUTPUT_BASE_FILENAME.pkg" 121 | productbuild --distribution "distribution.xml" --package-path "." --resources "${RESOURCES_DIR}" "$OUTPUT_BASE_FILENAME.pkg" 122 | fi 123 | 124 | echo --- Main Package Created --- 125 | 126 | popd 127 | 128 | # move completed package to output directory 129 | mv "${TMPDIR}/${OUTPUT_BASE_FILENAME}.pkg" "${TARGET_DIR}" 130 | -------------------------------------------------------------------------------- /scripts/installer_mac/resources/Readme.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf2639 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww18500\viewh13560\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0 7 | 8 | \f0\fs28 \cf0 V A L E N T I N E\ 9 | \ 10 | 11 | \fs24 An aggressive, open source compressor.\ 12 | \ 13 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 14 | \cf0 Hi. Thank you for trying Valentine. I think (I hope) you\'92re going to like it. Before you dive in, I figured I should warn: Valentine is still very much in development. Up until the release of Valentine 1.0.0, updates may dramatically change how the plugin behaves and possibly break mixes or presets.\ 15 | \ 16 | Why even \'93release\'94 this then? If you\'92re reading this, it\'92s very likely that you know me and have agreed to check Valentine out and give feedback. Thank you! It\'92s you who this release is for. I\'92m hoping that, in this trial, I can get a sense of whether Valentine works for people.\ 17 | \ 18 | Any and all feedback is greatly appreciated. That being said, you should know that the GUI definitely needs dialling in. I am also looking for specific feedback on the following topics:\ 19 | \ 20 | - Is \'93Crush\'94 working for you? How does \'93Saturate\'94 feel? \ 21 | \ 22 | - The hidden \'93Nice\'94 parameter. I forgot to totally remove a parameter called \'93Nice\'94 from the plugin. Nice changes Valentine\'92s threshold settings, decreasing the amount it compresses and changing how signal hits the saturator. You can find it by exposing the default plugin parameters in your DAW (or automation). Anyway, I\'92m kind of undecided on \'93Nice\'94. Is it \'93Nice\'94? Do you like it?\ 23 | \ 24 | - Does anything annoy you about using Valentine?\ 25 | \ 26 | - Were you able to make an exciting sound with Valentine?\ 27 | \ 28 | \ 29 | Soon(?) I will set up a formal communication channel to take feedback and talk about Valentine. In the meanwhile, you can find me (Totie) on the Audio Programmer discord.\ 30 | \ 31 | Peace,} -------------------------------------------------------------------------------- /scripts/installer_win/installer.iss: -------------------------------------------------------------------------------- 1 | #define PluginName "Valentine" 2 | #define Publisher "Tote Bag Labs" 3 | #define Version Trim(FileRead(FileOpen("..\..\VERSION"))) 4 | 5 | [Setup] 6 | AppName={#PluginName} 7 | AppPublisher={#Publisher} 8 | AppVersion={#Version} 9 | ArchitecturesInstallIn64BitMode=x64 10 | ArchitecturesAllowed=x64 11 | DefaultDirName="{commoncf64}\VST3\{#PluginName}.vst3\" 12 | DisableDirPage=yes 13 | LicenseFile="..\..\LICENSE" 14 | OutputBaseFilename=valentine-{#Version}-windows 15 | UninstallFilesDir={commonappdata}\{#PluginName}\uninstall 16 | 17 | [UninstallDelete] 18 | Type: filesandordirs; Name: "{commoncf64}\VST3\{#PluginName}Data" 19 | 20 | ; MSVC adds a .ilk when building the plugin. Let's not include that. 21 | [Files] 22 | Source: "..\..\Builds\Valentine_artefacts\Release\VST3\{#PluginName}.vst3\*"; DestDir: "{commoncf64}\VST3\{#PluginName}.vst3\"; Excludes: *.ilk; Flags: ignoreversion recursesubdirs; 23 | 24 | [Run] 25 | Filename: "{cmd}"; \ 26 | WorkingDir: "{commoncf64}\VST3"; \ 27 | Parameters: "/C mklink /D ""{commoncf64}\VST3\{#PluginName}Data"" ""{commonappdata}\{#PluginName}"""; \ 28 | Flags: runascurrentuser; 29 | -------------------------------------------------------------------------------- /src/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | It contains the basic framework code for a JUCE plugin editor. 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "PluginEditor.h" 12 | #include "PluginProcessor.h" 13 | #include "ValentineParameters.h" 14 | //============================================================================== 15 | ValentineAudioProcessorEditor::ValentineAudioProcessorEditor (ValentineAudioProcessor& p) 16 | : AudioProcessorEditor (&p) 17 | , audioProcessor (p) 18 | , mainPanel (audioProcessor) 19 | { 20 | addAndMakeVisible (mainPanel); 21 | 22 | const auto savedWidth = audioProcessor.getSavedGUIwidth(); 23 | const auto w = savedWidth != 0 ? savedWidth : startingWidth; 24 | 25 | setResizable (true, true); 26 | setResizeLimits (minimumWidth, 27 | juce::roundToInt (minimumWidth / ratio), 28 | maximumWidth, 29 | juce::roundToInt (maximumWidth / ratio)); 30 | getConstrainer()->setFixedAspectRatio (ratio); 31 | 32 | setSize (w, juce::roundToInt (w / ratio)); 33 | } 34 | 35 | ValentineAudioProcessorEditor::~ValentineAudioProcessorEditor() 36 | { 37 | audioProcessor.saveGUIwidth (getWidth()); 38 | } 39 | 40 | //============================================================================== 41 | 42 | void ValentineAudioProcessorEditor::resized() 43 | { 44 | mainPanel.setBounds (getLocalBounds()); 45 | } 46 | -------------------------------------------------------------------------------- /src/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | It contains the basic framework code for a JUCE plugin editor. 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "PluginProcessor.h" 14 | 15 | #include "gui/panels/ValentineMainPanel.h" 16 | 17 | #include 18 | #include 19 | 20 | //============================================================================== 21 | 22 | class ValentineAudioProcessorEditor : public juce::AudioProcessorEditor 23 | { 24 | public: 25 | ValentineAudioProcessorEditor (ValentineAudioProcessor&); 26 | ~ValentineAudioProcessorEditor() override; 27 | 28 | //============================================================================== 29 | void resized() override; 30 | 31 | private: 32 | // This reference is provided as a quick way for your editor to 33 | // access the processor object that created it. 34 | ValentineAudioProcessor& audioProcessor; 35 | 36 | VMainPanel mainPanel; 37 | 38 | static constexpr float ratio = 16.0f / 9.0f; 39 | static constexpr int minimumWidth = 700; 40 | static constexpr int startingWidth = 1000; 41 | static constexpr int maximumWidth = 2000; 42 | 43 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValentineAudioProcessorEditor) 44 | }; 45 | -------------------------------------------------------------------------------- /src/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file was auto-generated! 5 | 6 | It contains the basic framework code for a JUCE plugin processor. 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "ValentineParameters.h" 14 | 15 | #include "tote_bag/dsp/CircularBuffer.h" 16 | #include "tote_bag/dsp/Compressor.h" 17 | #include "tote_bag/dsp/DigiDegraders.h" 18 | #include "tote_bag/dsp/Saturation.h" 19 | #include "tote_bag/dsp/ThiranAllpass.h" 20 | #include "tote_bag/juce_gui/managers/ToteBagPresetManager.h" 21 | 22 | #include 23 | 24 | //============================================================================== 25 | /** 26 | */ 27 | 28 | class ValentineAudioProcessor : public juce::AudioProcessor, 29 | public juce::AudioProcessorValueTreeState::Listener 30 | { 31 | public: 32 | //============================================================================== 33 | ValentineAudioProcessor(); 34 | ~ValentineAudioProcessor() override; 35 | 36 | //============================================================================== 37 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 38 | void releaseResources() override; 39 | 40 | #ifndef JucePlugin_PreferredChannelConfigurations 41 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 42 | #endif 43 | 44 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 45 | void processBlockBypassed (juce::AudioBuffer&, juce::MidiBuffer&) override; 46 | 47 | //============================================================================== 48 | juce::AudioProcessorEditor* createEditor() override; 49 | bool hasEditor() const override; 50 | 51 | //============================================================================== 52 | const juce::String getName() const override; 53 | 54 | bool acceptsMidi() const override; 55 | bool producesMidi() const override; 56 | bool isMidiEffect() const override; 57 | double getTailLengthSeconds() const override; 58 | 59 | //============================================================================== 60 | int getNumPrograms() override; 61 | int getCurrentProgram() override; 62 | void setCurrentProgram (int) override; 63 | const juce::String getProgramName (int) override; 64 | void changeProgramName (int, const juce::String&) override; 65 | 66 | //============================================================================== 67 | void parameterChanged (const juce::String& parameter, float newValue) override; 68 | void initializeParams (double sampleRate); 69 | void getStateInformation (juce::MemoryBlock& destData) override; 70 | void setStateInformation (const void* data, int sizeInBytes) override; 71 | void initializeDSP(); 72 | 73 | /** Prepares an input buffer for processing, accounting for possible changes 74 | to number of input or output channels, block size. Also delays the input buffer 75 | by the latency reported by the oversampler. 76 | */ 77 | void prepareInputBuffer (juce::AudioBuffer& buffer, 78 | const int numInputChannels, 79 | const int numOutputChannels, 80 | const int samplesPerBlock); 81 | 82 | /** Returns the saved GUI width. Used by Editor to decide what dimesions 83 | to use during construction. 84 | */ 85 | const int getSavedGUIwidth() 86 | { 87 | return savedWidth.get(); 88 | } 89 | 90 | FFAU::LevelMeterSource& getInputMeterSource() 91 | { 92 | return inputMeterSource; 93 | } 94 | FFAU::LevelMeterSource& getOutputMeterSource() 95 | { 96 | return outputMeterSource; 97 | } 98 | FFAU::LevelMeterSource& getGrMeterSource() 99 | { 100 | return grMeterSource; 101 | } 102 | 103 | /** Called in Editor destructor to save the gui width 104 | */ 105 | void saveGUIwidth (const int w) 106 | { 107 | savedWidth.set (w); 108 | } 109 | 110 | juce::AudioProcessorValueTreeState treeState; 111 | 112 | ToteBagPresetManager& getPresetManager() 113 | { 114 | return presetManager; 115 | } 116 | 117 | private: 118 | ToteBagPresetManager presetManager; 119 | 120 | /** Finds and sets the correct processing delay for compensate for latency 121 | * caused by processing. 122 | * 123 | * On @p init this will find the maximum reported latency for the system, 124 | * report it to the host, and set clean buffer delays to that amount. 125 | * 126 | * In all cases, it will use that maximum reported latency to find the correct 127 | * amount of delay to apply to the processed buffer in order to ensure that the 128 | * delay at output never changes, and is always integral, which can help hosts 129 | * do latency compensation. 130 | */ 131 | void updateLatencyCompensation (bool init); 132 | 133 | /** This is the maximum system latency, rounded up. 134 | * Maximum system latency = oversampling latency + ADAA clipper latency. 135 | * 136 | * Used as both the latency reported to host as well as the target for 137 | * processing delay, which is calculated whenever the graph changes. 138 | * 139 | * During processing, this should never change, as any changes to actual 140 | * latency will reduce processing latency and therefore can be adjusted by 141 | * increasing the processing delay. It may, however, increase if sample 142 | * rate changes. So we need to update it when that happens. 143 | */ 144 | int cleanBufferDelay = 0; 145 | 146 | /** As the name suggests, this is the latency introduced by oversampling. 147 | * this should only change if host sample rate changes, so we cache it here 148 | * as the clean buffer delay is only updated at that time. Otherwise, we risk 149 | * getting the compensation wrong if somehow the oversampler latency changes 150 | * without prepareToPlay() being called. 151 | */ 152 | float overSamplingLatency = 0.0f; 153 | 154 | /** Triggers latency recalculation if true. 155 | * Changes to processing that change latency should set this true as well. 156 | */ 157 | juce::Atomic latencyChanged = false; 158 | 159 | using LatencyCompensation = 160 | juce::dsp::DelayLine; 161 | LatencyCompensation processedDelayLine; 162 | LatencyCompensation cleanDelayLine; 163 | 164 | using SmoothedFloat = juce::SmoothedValue; 165 | SmoothedFloat dryWet; 166 | std::array processedDelayTime; 167 | 168 | FFAU::LevelMeterSource inputMeterSource; 169 | FFAU::LevelMeterSource outputMeterSource; 170 | FFAU::LevelMeterSource grMeterSource; 171 | 172 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 173 | 174 | bool presetIsLoading = false; 175 | 176 | using Oversampling = juce::dsp::Oversampling; 177 | 178 | // used to maintain state for ApplyGainRamp 179 | // compress and makeup are addressed in dB, interface-wise, but handled linearly here 180 | float smoothedGain {1.0f}, 181 | currentGain {juce::Decibels::decibelsToGain ( 182 | FFCompParameterDefaults[getParameterIndex (VParameter::inputGain)])}, 183 | currentMakeup {juce::Decibels::decibelsToGain ( 184 | FFCompParameterDefaults[getParameterIndex (VParameter::makeupGain)])}; 185 | 186 | // compress and makeup are addressed in dB, interface-wise, but handled linearly here 187 | juce::Atomic compressValue {juce::Decibels::decibelsToGain ( 188 | FFCompParameterDefaults[getParameterIndex (VParameter::inputGain)])}, 189 | mixValue {1.0f}, 190 | makeupValue {juce::Decibels::decibelsToGain ( 191 | FFCompParameterDefaults[getParameterIndex (VParameter::makeupGain)])}, 192 | currentWidth {0.0f}, currentHeight {0.0f}; 193 | 194 | juce::Atomic savedWidth {0}; 195 | 196 | juce::Atomic crushOn {false}; 197 | juce::Atomic bypassOn {false}; 198 | juce::Atomic clipOn { 199 | FFCompParameterDefaults[static_cast (VParameter::outputClipEnable)] 200 | > 0.5f}; 201 | bool clipOnState = clipOn.get(); 202 | juce::Atomic saturateOn = 203 | FFCompParameterDefaults[static_cast (VParameter::saturateEnable)] > 0.5f; 204 | bool saturateOnState = saturateOn.get(); 205 | 206 | // This is used for, yes you guessed it, processing 207 | juce::AudioBuffer processBuffer; 208 | 209 | // DSP objects 210 | std::unique_ptr> dryWetFilter; 211 | std::unique_ptr oversampler; 212 | std::unique_ptr ffCompressor; 213 | std::unique_ptr saturator; 214 | std::unique_ptr boundedSaturator; 215 | std::unique_ptr simpleZOH; 216 | std::unique_ptr bitCrush; 217 | 218 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValentineAudioProcessor) 219 | }; 220 | -------------------------------------------------------------------------------- /src/ValentineParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | FFCompParameters.h 5 | Created: 3 Aug 2019 9:54:08pm 6 | Author: dev 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | constexpr int ValentineParameterVersion = 1; 18 | 19 | enum VParameter 20 | { 21 | bitCrush = 0, 22 | crushEnable, 23 | inputGain, 24 | saturation, 25 | saturateEnable, 26 | ratio, 27 | attack, 28 | release, 29 | makeupGain, 30 | dryWet, 31 | bypass, 32 | outputClipEnable, 33 | TOTAL_NUM_PARAMETERS 34 | }; 35 | 36 | namespace 37 | { 38 | const size_t getParameterIndex (VParameter param) 39 | { 40 | return static_cast (param); 41 | } 42 | 43 | inline constexpr float kMinBits = 1.0f; 44 | inline constexpr float kMaxBits = 12.0f; 45 | 46 | inline constexpr float kMinSaturationGain = 0.5f; 47 | inline constexpr float kMaxSaturationGain = 10.0f; 48 | 49 | // The largest the ratio can be as far as the parameter itself is concerned. 50 | // Processing, we actually use a ratio of 1000:1 for this ratio value. 51 | inline constexpr float kRatioParameterMax = 21.0f; 52 | 53 | inline constexpr float kRatioMin = 1.0f; 54 | inline constexpr float kRatioMax = 1000.0f; 55 | 56 | inline constexpr float kKneeMin = 7.0f; 57 | inline constexpr float kKneeMax = 0.0f; 58 | 59 | inline constexpr float kThresholdMin = -15.0f; 60 | inline constexpr float kThresholdMax = -10.0f; 61 | 62 | inline constexpr size_t kNumRatioControlPoints = 6; 63 | inline constexpr std::array kRatioControlPoints = { 64 | kRatioMin, 65 | 4.0f, 66 | 8.0f, 67 | 12.0f, 68 | 20.0, 69 | kRatioMax, 70 | }; 71 | 72 | inline constexpr std::array kKneeControlPoints = { 73 | kKneeMin, 74 | 6.0f, 75 | 3.84f, 76 | 2.16f, 77 | .96f, 78 | kKneeMax, 79 | }; 80 | 81 | inline constexpr std::array kThresholdControlPoints = { 82 | kThresholdMin, 83 | -18.0f, 84 | -14.0f, 85 | -13.0f, 86 | -12.0f, 87 | kThresholdMax, 88 | }; 89 | } // namespace 90 | 91 | static constexpr auto numParams = static_cast (VParameter::TOTAL_NUM_PARAMETERS); 92 | 93 | namespace tote_bag 94 | { 95 | namespace valentine 96 | { 97 | inline const std::array& parameterIDs() 98 | { 99 | static const std::array parameterIDs = { 100 | "Crush", 101 | "CrushEnable", 102 | "Compress", 103 | "Saturate", 104 | "SaturateEnable", 105 | "Ratio", 106 | "AttackTime", 107 | "ReleaseTime", 108 | "Makeup", 109 | "Mix", 110 | "Bypass", 111 | "OutputClipEnable", 112 | }; 113 | 114 | return parameterIDs; 115 | } 116 | 117 | inline const juce::String& parameterID (const size_t index) 118 | { 119 | return parameterIDs()[index]; 120 | } 121 | 122 | inline const juce::String& parameterID (const VParameter parameter) 123 | { 124 | return parameterID (static_cast (parameter)); 125 | } 126 | } // namespace valentine 127 | } // namespace tote_bag 128 | 129 | inline const std::array& FFCompParameterLabel() 130 | { 131 | static const std::array parameterLabels = { 132 | "Crush", 133 | "Crush", 134 | "Compress", 135 | "Saturate", 136 | "Saturate", 137 | "Ratio", 138 | "Attack", 139 | "Release", 140 | "Output", 141 | "Mix", 142 | "Bypass", 143 | "Clip", 144 | }; 145 | 146 | return parameterLabels; 147 | } 148 | 149 | inline const std::array& VParameterUnit() 150 | { 151 | static const std::array unitLabels = { 152 | "", 153 | "", 154 | " dB", 155 | "", 156 | "", 157 | "", 158 | " ms", 159 | " ms", 160 | " dB", 161 | " %", 162 | "", 163 | "", 164 | }; 165 | 166 | return unitLabels; 167 | } 168 | 169 | static constexpr std::array FFCompParameterMin = { 170 | 1.0f, 171 | 0.0f, 172 | -21.0f, 173 | 1.0f, 174 | 0.0f, 175 | kRatioMin, 176 | 0.02f, 177 | 2.0f, 178 | -24.0f, 179 | 0.0f, 180 | 0.0f, 181 | 0.0f, 182 | }; 183 | 184 | static constexpr std::array FFCompParameterMax = { 185 | 10.0f, 186 | 1.0f, 187 | 48.0f, 188 | 10.0f, 189 | 1.0f, 190 | kRatioParameterMax, 191 | 55.0f, 192 | 1100.0f, 193 | 21.0f, 194 | 100.0f, 195 | 1.0f, 196 | 1.0f, 197 | }; 198 | 199 | static constexpr std::array FFCompParameterDefaults = { 200 | 1.0f, 201 | 0.0f, 202 | 0.0f, 203 | 1.0f, 204 | 1.0f, 205 | 4.0f, 206 | 1.3f, 207 | 350.0f, 208 | 0.0f, 209 | 100.0f, 210 | 0.0f, 211 | 1.0f, 212 | }; 213 | 214 | static constexpr std::array FFCompParameterIncrement = { 215 | 0.00001f, 216 | 1.0f, 217 | 0.00001f, 218 | 0.00001f, 219 | 1.0f, 220 | 0.00001f, 221 | 0.00001f, 222 | 0.00001f, 223 | 0.00001f, 224 | 0.00001f, 225 | 1.0f, 226 | 1.0f, 227 | }; 228 | 229 | static constexpr std::array FFCompParamCenter = { 230 | 5.0f, 231 | 0.5f, 232 | 23.0f, 233 | 5.0f, 234 | 0.5f, 235 | 6.0f, 236 | 5.0f, 237 | 300.0f, 238 | -18.0f, 239 | 50.0f, 240 | 0.5f, 241 | 0.5f, 242 | }; 243 | 244 | static constexpr std::array VParamPrecision = { 245 | 2, 246 | 0, 247 | 2, 248 | 2, 249 | 0, 250 | 2, 251 | 2, 252 | 2, 253 | 2, 254 | 0, 255 | 0, 256 | 0, 257 | }; 258 | 259 | //================================================================================== 260 | -------------------------------------------------------------------------------- /src/gui/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tote-bag-labs/valentine/92a839fcdc1600486e4ce80eae3cbc2b8792e80a/src/gui/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/gui/assets/fonts/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tote-bag-labs/valentine/92a839fcdc1600486e4ce80eae3cbc2b8792e80a/src/gui/assets/fonts/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /src/gui/assets/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tote-bag-labs/valentine/92a839fcdc1600486e4ce80eae3cbc2b8792e80a/src/gui/assets/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /src/gui/assets/logos/val_totebag_logo.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineBottomRowPanel.cpp: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #include "ValentineBottomRowPanel.h" 4 | #include "BinaryData.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "ValentineParameters.h" 8 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 9 | #include "tote_bag/juce_gui/utilities/GraphicsUtilities.h" 10 | 11 | #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR 12 | #include 13 | #endif // JUCE_ENABLE_LIVE_CONSTANT_EDITOR 14 | 15 | namespace tote_bag 16 | { 17 | namespace valentine 18 | { 19 | namespace detail 20 | { 21 | inline const juce::String kRatioSliderText = "RATIO"; 22 | inline const juce::String kAttackSliderText = "ATTACK"; 23 | inline const juce::String kReleaseSliderText = "RELEASE"; 24 | inline const juce::String kOutputSliderText = "OUTPUT"; 25 | inline const juce::String kMixSliderText = "MIX"; 26 | } // namespace detail 27 | 28 | BottomRowPanel::BottomRowPanel (ValentineAudioProcessor& processor) 29 | : ratioSlider (detail::kRatioSliderText, 30 | parameterID (VParameter::ratio), 31 | processor.treeState) 32 | , attackSlider (detail::kAttackSliderText, 33 | parameterID (VParameter::attack), 34 | processor.treeState) 35 | , releaseSlider (detail::kReleaseSliderText, 36 | parameterID (VParameter::release), 37 | processor.treeState) 38 | , clipEnableButton (parameterID (VParameter::outputClipEnable), processor.treeState) 39 | , outputSlider (detail::kOutputSliderText, 40 | parameterID (VParameter::makeupGain), 41 | processor.treeState) 42 | , mixSlider (detail::kMixSliderText, 43 | parameterID (VParameter::dryWet), 44 | processor.treeState) 45 | { 46 | addAndMakeVisible (ratioSlider); 47 | addAndMakeVisible (attackSlider); 48 | addAndMakeVisible (releaseSlider); 49 | addAndMakeVisible (clipEnableButton); 50 | addAndMakeVisible (outputSlider); 51 | addAndMakeVisible (mixSlider); 52 | } 53 | 54 | BottomRowPanel::~BottomRowPanel() 55 | { 56 | } 57 | 58 | void BottomRowPanel::paint (juce::Graphics& g) 59 | { 60 | g.setColour (colours::dividerGrey); 61 | 62 | g.fillRect (divider); 63 | } 64 | 65 | void BottomRowPanel::resized() 66 | { 67 | auto bounds = getLocalBounds(); 68 | 69 | // Adjust this to set button width 70 | const auto clipButtonWidth = juce::roundToInt (bounds.getWidth() / 46.0f); 71 | 72 | const auto adjustedComponentWidth = bounds.getWidth() - clipButtonWidth; 73 | const auto sliderWidth = juce::roundToInt (adjustedComponentWidth / 6.0f); 74 | 75 | ratioSlider.setBounds (bounds.removeFromLeft (sliderWidth)); 76 | attackSlider.setBounds (bounds.removeFromLeft (sliderWidth)); 77 | releaseSlider.setBounds (bounds.removeFromLeft (sliderWidth)); 78 | 79 | const auto dividerCentreX = bounds.getX() + juce::roundToInt (sliderWidth / 2.0f); 80 | const auto dividerThickness = juce::roundToInt (bounds.getHeight() * .015f); 81 | const auto dividerTrimAmount = juce::roundToInt (bounds.getHeight() * .08f); 82 | 83 | divider = bounds.removeFromLeft (sliderWidth) 84 | .reduced (0, dividerTrimAmount) 85 | .withX (dividerCentreX) 86 | .withWidth (dividerThickness); 87 | 88 | // Adjust this to set spacing between button and slider 89 | const auto buttonNudge = juce::roundToInt (sliderWidth / 15.0f); 90 | 91 | const auto clipButtonInitialX = bounds.getX(); 92 | const auto clipButtonX = clipButtonInitialX + buttonNudge; 93 | const auto clipButtonY = bounds.getCentreY() - clipButtonWidth / 2; 94 | 95 | clipEnableButton.setBounds (bounds.removeFromLeft (clipButtonWidth) 96 | .withX (clipButtonX) 97 | .withY (clipButtonY) 98 | .withHeight (clipButtonWidth)); 99 | 100 | const auto outputSliderArea = bounds.removeFromLeft (sliderWidth); 101 | 102 | // We have to do this because the slider will otherwise intercept 103 | // button clips. 104 | const auto outputSliderWidth = sliderWidth - juce::roundToInt (buttonNudge * 2.1f); 105 | const auto outputSliderX = outputSliderArea.getCentreX() - outputSliderWidth / 2; 106 | 107 | outputSlider.setBounds ( 108 | outputSliderArea.withX (outputSliderX).withWidth (outputSliderWidth)); 109 | 110 | mixSlider.setBounds (bounds.removeFromLeft (sliderWidth)); 111 | } 112 | 113 | } // namespace valentine 114 | } // namespace tote_bag 115 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineBottomRowPanel.h: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #pragma once 4 | 5 | #include "tote_bag/juce_gui/components/widgets/LabelSlider.h" 6 | #include "tote_bag/juce_gui/components/widgets/tbl_ToggleButton.h" 7 | 8 | #include 9 | 10 | class ValentineAudioProcessor; 11 | 12 | namespace tote_bag 13 | { 14 | namespace valentine 15 | { 16 | class BottomRowPanel : public juce::Component 17 | { 18 | public: 19 | BottomRowPanel (ValentineAudioProcessor& processor); 20 | ~BottomRowPanel() override; 21 | 22 | void paint (juce::Graphics& g) override; 23 | void resized() override; 24 | 25 | private: 26 | juce::Rectangle divider; 27 | 28 | LabelSlider ratioSlider; 29 | LabelSlider attackSlider; 30 | LabelSlider releaseSlider; 31 | 32 | ToggleButton clipEnableButton; 33 | LabelSlider outputSlider; 34 | LabelSlider mixSlider; 35 | }; 36 | } // namespace valentine 37 | } // namespace tote_bag 38 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineCenterPanel.cpp: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #include "ValentineCenterPanel.h" 4 | #include "BinaryData.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "ValentineParameters.h" 8 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 9 | #include "tote_bag/juce_gui/utilities/GraphicsUtilities.h" 10 | 11 | namespace tote_bag 12 | { 13 | namespace valentine 14 | { 15 | 16 | CenterPanel::CenterPanel (ValentineAudioProcessor& processor) 17 | : borderLineThickness (0.0f) 18 | , borderCornerSize (0.0f) 19 | , topRow (processor) 20 | , bottomRow (processor) 21 | { 22 | addAndMakeVisible (topRow); 23 | addAndMakeVisible (bottomRow); 24 | } 25 | 26 | CenterPanel::~CenterPanel() 27 | { 28 | } 29 | 30 | void CenterPanel::paint (juce::Graphics& g) 31 | { 32 | g.setColour (juce::Colours::black); 33 | 34 | g.drawRoundedRectangle (topRowBorder.toFloat(), 35 | borderCornerSize, 36 | borderLineThickness); 37 | 38 | g.drawRoundedRectangle (bottomRowBorder.toFloat(), 39 | borderCornerSize, 40 | borderLineThickness); 41 | } 42 | 43 | void CenterPanel::resized() 44 | { 45 | auto localBounds = getLocalBounds(); 46 | const auto margin = localBounds.getHeight() * .0375f; 47 | localBounds.reduce (juce::roundToInt (margin * 1.6f), 48 | juce::roundToInt (margin * 1.3f)); 49 | 50 | const auto topRowArea = 51 | localBounds.removeFromTop (juce::roundToInt (localBounds.getHeight() * .50f)); 52 | const auto topRowBorderHeight = juce::roundToInt (topRowArea.getHeight() * .90f); 53 | const auto topRowBorderY = topRowArea.getCentreY() - topRowBorderHeight / 2; 54 | 55 | topRowBorder = topRowArea.withY (topRowBorderY).withHeight (topRowBorderHeight); 56 | borderLineThickness = topRowBorder.getHeight() * .01f; 57 | borderCornerSize = topRowBorder.getHeight() * .060f; 58 | 59 | const auto getPanelBounds = [] (const juce::Rectangle borderBounds, 60 | const int borderMargin) { 61 | const auto panelHeight = juce::roundToInt (borderBounds.getHeight() * .7725f); 62 | const auto panelY = borderBounds.getCentreY() - panelHeight / 2; 63 | 64 | return borderBounds.reduced (borderMargin, 0) 65 | .withY (panelY) 66 | .withHeight (panelHeight); 67 | }; 68 | 69 | topRow.setBounds (getPanelBounds (topRowBorder, juce::roundToInt (margin))); 70 | 71 | localBounds.removeFromTop (juce::roundToInt (margin * .50f)); 72 | 73 | const auto bottomRowArea = localBounds; 74 | const auto bottomRowBorderHeight = 75 | juce::roundToInt (bottomRowArea.getHeight() * .84f); 76 | const auto bottomRowBorderY = bottomRowArea.getCentreY() - bottomRowBorderHeight / 2; 77 | 78 | bottomRowBorder = 79 | bottomRowArea.withY (bottomRowBorderY).withHeight (bottomRowBorderHeight); 80 | 81 | bottomRow.setBounds (getPanelBounds (bottomRowBorder, juce::roundToInt (margin))); 82 | } 83 | } // namespace tote_bag 84 | } // namespace valentine 85 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineCenterPanel.h: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #pragma once 4 | 5 | #include "ValentineBottomRowPanel.h" 6 | #include "ValentineTopRowPanel.h" 7 | #include "tote_bag/juce_gui/components/widgets/LabelSlider.h" 8 | #include "tote_bag/juce_gui/components/widgets/tbl_ToggleButton.h" 9 | 10 | #include 11 | 12 | class ValentineAudioProcessor; 13 | 14 | namespace tote_bag 15 | { 16 | namespace valentine 17 | { 18 | class CenterPanel : public juce::Component 19 | { 20 | public: 21 | CenterPanel (ValentineAudioProcessor& processor); 22 | ~CenterPanel() override; 23 | 24 | void paint (juce::Graphics& g) override; 25 | void resized() override; 26 | 27 | private: 28 | juce::Rectangle topRowBorder; 29 | juce::Rectangle bottomRowBorder; 30 | float borderLineThickness; 31 | float borderCornerSize; 32 | 33 | TopRowPanel topRow; 34 | BottomRowPanel bottomRow; 35 | 36 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CenterPanel) 37 | }; 38 | } // namespace valentine 39 | } // namespace tote_bag 40 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineMainPanel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VMainPanel.cpp 5 | Created: 3 Apr 2022 8:53:03pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "ValentineMainPanel.h" 12 | #include "PluginProcessor.h" 13 | #include "ValentineParameters.h" 14 | 15 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 16 | 17 | #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR 18 | #include 19 | #endif // JUCE_ENABLE_LIVE_CONSTANT_EDITOR 20 | 21 | VMainPanel::VMainPanel (ValentineAudioProcessor& processor) 22 | : presetPanel ( 23 | processor.getPresetManager(), 24 | FFCompParameterLabel()[getParameterIndex (VParameter::bypass)], 25 | tote_bag::valentine::parameterID (VParameter::bypass), 26 | [this]() { infoPanel.setVisible (true); }, 27 | processor.treeState) 28 | , infoPanel ([this]() { infoPanel.setVisible (false); }) 29 | , inputMeterPanel (ReductionMeterPlacement::Right, &processor.getInputMeterSource()) 30 | , outputMeterPanel (ReductionMeterPlacement::Left, 31 | &processor.getOutputMeterSource(), 32 | &processor.getGrMeterSource()) 33 | , centerPanel (processor) 34 | { 35 | addAndMakeVisible (presetPanel); 36 | addAndMakeVisible (centerPanel); 37 | addAndMakeVisible (inputMeterPanel); 38 | addAndMakeVisible (outputMeterPanel); 39 | addChildComponent (infoPanel, -1); 40 | 41 | setLookAndFeel (&lookAndFeel); 42 | } 43 | 44 | VMainPanel::~VMainPanel() 45 | { 46 | setLookAndFeel (nullptr); 47 | } 48 | 49 | void VMainPanel::paint (juce::Graphics& g) 50 | { 51 | g.fillAll (tote_bag::colours::valentinePink); 52 | } 53 | 54 | void VMainPanel::resized() 55 | { 56 | auto panelBounds = getLocalBounds(); 57 | 58 | infoPanel.setBounds (panelBounds); 59 | 60 | const auto presetBounds = 61 | panelBounds.removeFromTop (juce::roundToInt (panelBounds.getHeight() * .11f)); 62 | presetPanel.setBounds (presetBounds); 63 | 64 | const auto resizerMargin = juce::roundToInt (panelBounds.getHeight() * .03f); 65 | panelBounds.removeFromLeft (resizerMargin); 66 | panelBounds.removeFromRight (resizerMargin); 67 | panelBounds.removeFromBottom (resizerMargin); 68 | panelBounds.removeFromTop (juce::roundToInt (resizerMargin * .5)); 69 | 70 | const auto meterWidth = juce::roundToInt (panelBounds.getWidth() * .05f); 71 | const auto outMeterBounds = panelBounds.removeFromRight (meterWidth); 72 | const auto inMeterBounds = panelBounds.removeFromLeft (meterWidth); 73 | 74 | // Shift working area left to account for wider output meter 75 | panelBounds.removeFromRight (juce::roundToInt (meterWidth / 4.f)); 76 | 77 | inputMeterPanel.setBounds (inMeterBounds); 78 | outputMeterPanel.setBounds (outMeterBounds); 79 | centerPanel.setBounds (panelBounds); 80 | } 81 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineMainPanel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | VMainPanel.h 5 | Created: 3 Apr 2022 8:53:03pm 6 | Author: Jose Diaz 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "ValentineCenterPanel.h" 14 | 15 | #include "tote_bag/juce_gui/components/panels/InfoPanel.h" 16 | #include "tote_bag/juce_gui/components/panels/PresetPanel.h" 17 | #include "tote_bag/juce_gui/components/panels/VerticalMeterPanel.h" 18 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeel.h" 19 | 20 | #include 21 | 22 | class ValentineAudioProcessor; 23 | 24 | class VMainPanel : public juce::Component 25 | { 26 | public: 27 | VMainPanel (ValentineAudioProcessor& inProcessor); 28 | 29 | ~VMainPanel() override; 30 | 31 | void paint (juce::Graphics& g) override; 32 | 33 | void resized() override; 34 | 35 | private: 36 | tote_bag::LookAndFeel lookAndFeel; 37 | 38 | PresetPanel presetPanel; 39 | tote_bag::InfoPanel infoPanel; 40 | VerticalMeterPanel inputMeterPanel; 41 | VerticalMeterPanel outputMeterPanel; 42 | tote_bag::valentine::CenterPanel centerPanel; 43 | }; 44 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineTopRowPanel.cpp: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #include "ValentineTopRowPanel.h" 4 | #include "BinaryData.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "ValentineParameters.h" 8 | #include "tote_bag/juce_gui/lookandfeel/LookAndFeelConstants.h" 9 | #include "tote_bag/juce_gui/utilities/GraphicsUtilities.h" 10 | 11 | #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR 12 | #include 13 | #endif // JUCE_ENABLE_LIVE_CONSTANT_EDITOR 14 | 15 | namespace tote_bag 16 | { 17 | namespace valentine 18 | { 19 | namespace detail 20 | { 21 | inline const juce::String kCrushSliderText = "CRUSH"; 22 | inline const juce::String kCompressSliderText = "COMPRESS"; 23 | inline const juce::String kSaturateSliderText = "SATURATE"; 24 | } // namespace detail 25 | 26 | TopRowPanel::TopRowPanel (ValentineAudioProcessor& processor) 27 | : crushEnableButton (parameterID (VParameter::crushEnable), processor.treeState) 28 | , crushSlider (detail::kCrushSliderText, 29 | parameterID (VParameter::bitCrush), 30 | processor.treeState) 31 | , compressSlider (detail::kCompressSliderText, 32 | parameterID (VParameter::inputGain), 33 | processor.treeState) 34 | , saturateEnableButton (parameterID (VParameter::saturateEnable), processor.treeState) 35 | , saturateSlider (detail::kSaturateSliderText, 36 | parameterID (VParameter::saturation), 37 | processor.treeState) 38 | , valentineTblLogo ( 39 | juce::Drawable::createFromImageData (BinaryData::val_totebag_logo_svg, 40 | BinaryData::val_totebag_logo_svgSize)) 41 | { 42 | addAndMakeVisible (crushEnableButton); 43 | addAndMakeVisible (crushSlider); 44 | addAndMakeVisible (compressSlider); 45 | addAndMakeVisible (saturateEnableButton); 46 | addAndMakeVisible (saturateSlider); 47 | addAndMakeVisible (valentineTblLogo.get()); 48 | } 49 | 50 | TopRowPanel::~TopRowPanel() 51 | { 52 | } 53 | 54 | void TopRowPanel::resized() 55 | { 56 | auto bounds = getLocalBounds(); 57 | 58 | auto sliders = bounds.removeFromLeft (juce::roundToInt (bounds.getWidth() * .65f)); 59 | 60 | // Adjust this to set button width 61 | const auto buttonWidth = juce::roundToInt (sliders.getWidth() * .035f); 62 | 63 | const auto adjustedComponentWidth = sliders.getWidth() - (buttonWidth * 2.0f); 64 | const auto sliderInitialWidth = juce::roundToInt (adjustedComponentWidth / 3.0f); 65 | 66 | // Adjust this to set spacing between button and slider 67 | const auto buttonNudge = juce::roundToInt (sliderInitialWidth / 9.25f); 68 | 69 | const auto setButtonAndSliderBounds = [&] (auto& button, auto& slider) { 70 | const auto initialButtonX = sliders.getX(); 71 | const auto buttonX = initialButtonX + buttonNudge; 72 | const auto buttonY = sliders.getCentreY() - buttonWidth / 2; 73 | 74 | button.setBounds (sliders.removeFromLeft (buttonWidth) 75 | .withX (buttonX) 76 | .withY (buttonY) 77 | .withHeight (buttonWidth)); 78 | 79 | const auto sliderArea = sliders.removeFromLeft (sliderInitialWidth); 80 | 81 | // We have to do this because the slider will otherwise intercept 82 | // button clips. 83 | const auto sliderWidth = 84 | sliderInitialWidth - juce::roundToInt (buttonNudge * 2.1f); 85 | const auto sliderX = sliderArea.getCentreX() - sliderWidth / 2; 86 | 87 | slider.setBounds (sliderArea.withX (sliderX).withWidth (sliderWidth)); 88 | }; 89 | 90 | setButtonAndSliderBounds (crushEnableButton, crushSlider); 91 | 92 | // This ends up displaying at the same size of the other sliders because 93 | // this is setting the width of the labelSliderComponent. The slider ends 94 | // up being sized according to height, which is the same for all our sliders. 95 | compressSlider.setBounds (sliders.removeFromLeft (sliderInitialWidth)); 96 | setButtonAndSliderBounds (saturateEnableButton, saturateSlider); 97 | 98 | const auto logoHeight = bounds.getHeight() * .25f; 99 | const auto logoWidth = bounds.getWidth() * .75f; 100 | 101 | const auto logoVerticalSpacer = (bounds.getHeight() - logoHeight) / 2.0f; 102 | bounds.removeFromTop (juce::roundToInt (logoVerticalSpacer)); 103 | 104 | const auto logoHorizontalSpacer = (bounds.getWidth() - logoWidth) / 2.0f; 105 | 106 | // logoHorizontalSpacer is the amount we hypothetically should remove from left 107 | // in order to have the logo centred. However, the spacing is fudged here to account 108 | // for the fact that our sliders don't take up all of the horizontal space given 109 | // to them. 110 | const auto horizontalKludgeQuotient = .8f; 111 | bounds.removeFromLeft ( 112 | juce::roundToInt (logoHorizontalSpacer * horizontalKludgeQuotient)); 113 | 114 | const auto valentineLogoBounds = bounds.removeFromLeft (juce::roundToInt (logoWidth)) 115 | .removeFromTop (juce::roundToInt (logoHeight)); 116 | 117 | valentineTblLogo->setTransformToFit ( 118 | valentineLogoBounds.toFloat(), 119 | juce::RectanglePlacement (juce::RectanglePlacement::centred 120 | | juce::RectanglePlacement::fillDestination)); 121 | } 122 | 123 | } // namespace valentine 124 | } // namespace tote_bag 125 | -------------------------------------------------------------------------------- /src/gui/panels/ValentineTopRowPanel.h: -------------------------------------------------------------------------------- 1 | // 2024 Tote Bag Labs 2 | 3 | #pragma once 4 | 5 | #include "tote_bag/juce_gui/components/widgets/LabelSlider.h" 6 | #include "tote_bag/juce_gui/components/widgets/tbl_ToggleButton.h" 7 | 8 | #include 9 | 10 | class ValentineAudioProcessor; 11 | 12 | namespace tote_bag 13 | { 14 | namespace valentine 15 | { 16 | class TopRowPanel : public juce::Component 17 | { 18 | public: 19 | TopRowPanel (ValentineAudioProcessor& processor); 20 | ~TopRowPanel() override; 21 | 22 | void resized() override; 23 | 24 | private: 25 | ToggleButton crushEnableButton; 26 | LabelSlider crushSlider; 27 | LabelSlider compressSlider; 28 | ToggleButton saturateEnableButton; 29 | LabelSlider saturateSlider; 30 | 31 | std::unique_ptr valentineTblLogo; 32 | }; 33 | } // namespace valentine 34 | } // namespace tote_bag 35 | -------------------------------------------------------------------------------- /tests/AudioHelpersTests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // AudioHelpersTests.cpp 3 | // Valentine 4 | // 5 | // Created by Jose Diaz on 19.03.23. 6 | // 7 | 8 | #include "tote_bag/dsp/AudioHelpers.h" 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | TEST_CASE ("Next power of two", "[Audio Helpers]") 15 | { 16 | // Finds the next power of two for given input 17 | const int nextPower = tote_bag::audio_helpers::nextPow2 (200); 18 | REQUIRE (nextPower == 256); 19 | 20 | // Returns the same as input if it is a power of 2 21 | const int nextPowerSame = tote_bag::audio_helpers::nextPow2 (256); 22 | REQUIRE (nextPowerSame == 256); 23 | } 24 | 25 | TEST_CASE ("Clamped cosh won't blow up", "[Audio Helpers]") 26 | { 27 | // cosh will overflow floats at ~71.0f 28 | const auto floatResult = tote_bag::audio_helpers::clampedCosh (750.0f); 29 | 30 | REQUIRE (std::fpclassify (floatResult) == FP_NORMAL); 31 | 32 | // cosh will overflow doubles at ~710.0 33 | const auto doubleResult = tote_bag::audio_helpers::clampedCosh (750.0); 34 | 35 | REQUIRE (std::fpclassify (doubleResult) == FP_NORMAL); 36 | } 37 | -------------------------------------------------------------------------------- /tests/AudioHelpersTests.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // AudioHelpersTests.hpp 3 | // Valentine 4 | // 5 | // Created by Jose Diaz on 19.03.23. 6 | // 7 | 8 | #ifndef AudioHelpersTests_hpp 9 | #define AudioHelpersTests_hpp 10 | 11 | #include 12 | 13 | #endif /* AudioHelpersTests_hpp */ 14 | -------------------------------------------------------------------------------- /tests/MathTests.cpp: -------------------------------------------------------------------------------- 1 | // 2023 Tote Bag Labs 2 | 3 | #include "tote_bag/utils/tbl_math.hpp" 4 | 5 | #include 6 | #include 7 | 8 | TEST_CASE ("findNearestIndices - basics", "[Math]") 9 | { 10 | using namespace tote_bag::math; 11 | 12 | SECTION ("Get nearest indices for int") 13 | { 14 | const std::array array = {0, 1, 2, 3, 5, 7, 12, 100, 120, 130}; 15 | const auto result = findNearestIndices (array, 110); 16 | 17 | REQUIRE (result.first == 7); 18 | REQUIRE (result.second == 8); 19 | } 20 | } 21 | 22 | TEST_CASE ("findNearestIndices - out of Bounds", "[Math]") 23 | { 24 | using namespace tote_bag::math; 25 | 26 | SECTION ("Get nearest indices for out of bounds values") 27 | { 28 | const std::array array = {0.5f, 1.0f, 2.0f, 3.0f, 5.0f}; 29 | 30 | const auto result2 = findNearestIndices (array, 0.0f); 31 | REQUIRE (result2.first == 0); 32 | REQUIRE (result2.second == 1); 33 | 34 | const auto result = findNearestIndices (array, 10.0f); 35 | REQUIRE (result.first == 3); 36 | REQUIRE (result.second == 4); 37 | } 38 | } 39 | 40 | TEST_CASE ("findNearestValues - basics", "[Math]") 41 | { 42 | using namespace tote_bag::math; 43 | 44 | SECTION ("Get nearest values for int") 45 | { 46 | const std::array array = {0, 1, 2, 3, 5, 7, 12, 100, 120, 130}; 47 | const auto result = findNearestValues (array, 110); 48 | 49 | REQUIRE (result.first == 100); 50 | REQUIRE (result.second == 120); 51 | } 52 | 53 | SECTION ("Get nearest values for float") 54 | { 55 | const std::array array = {0.0f, 1.0f, 2.0f, 3.0f, 5.0f}; 56 | const auto result = findNearestValues (array, 1.5f); 57 | 58 | REQUIRE (result.first == 1.0f); 59 | REQUIRE (result.second == 2.0f); 60 | } 61 | } 62 | 63 | TEST_CASE ("findNearestValues - out of bounds", "[Math]") 64 | { 65 | using namespace tote_bag::math; 66 | 67 | SECTION ("Get nearest values for out of bounds values") 68 | { 69 | const std::array array = {0.5f, 1.0f, 2.0f, 3.0f, 5.0f}; 70 | 71 | const auto result = findNearestValues (array, 10.0f); 72 | REQUIRE (result.first == 3.0f); 73 | REQUIRE (result.second == 5.0f); 74 | 75 | const auto result2 = findNearestValues (array, 0.0f); 76 | REQUIRE (result2.first == 0.5f); 77 | REQUIRE (result2.second == 1.0f); 78 | } 79 | } 80 | 81 | TEST_CASE ("piecewiseLinearInterpolation - basics", "[Math]") 82 | { 83 | using namespace tote_bag::math; 84 | 85 | SECTION ("Remap to positive values") 86 | { 87 | const std::array inputRange = {0.0f, 1.0f, 2.0f, 3.0f, 5.0f}; 88 | const std::array outputRange = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f}; 89 | const auto result = piecewiseRemap (inputRange, outputRange, 1.5f); 90 | REQUIRE (result == 25.0f); 91 | } 92 | 93 | SECTION ("Remap to descending values") 94 | { 95 | const std::array inputRange = {0.0f, 1.0f, 2.0f, 3.0f, 5.0f}; 96 | const std::array outputRange = {50.0f, 40.0f, 30.0f, 20.0f, 10.0f}; 97 | const auto result = piecewiseRemap (inputRange, outputRange, 1.5f); 98 | REQUIRE (result == 35.0f); 99 | } 100 | 101 | SECTION ("Remap to negative values") 102 | { 103 | const std::array inputRange = {0.0f, 1.0f, 2.0f, 3.0f, 5.0f}; 104 | const std::array outputRange = {-10.0f, -20.0f, -30.0f, -40.0f, -50.0f}; 105 | const auto result = piecewiseRemap (inputRange, outputRange, 1.5f); 106 | REQUIRE (result == -25.0f); 107 | } 108 | 109 | SECTION ("Remap to control values") 110 | { 111 | const std::array inputRange = {0.0f, 1.0f, 2.0f, 3.0f, 5.0f}; 112 | const std::array outputRange = {-10.0f, -20.0f, -30.0f, -40.0f, -50.0f}; 113 | const auto result = piecewiseRemap (inputRange, outputRange, 3.0f); 114 | REQUIRE (result == -40.0f); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/PluginBasics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TEST_CASE ("one is equal to one", "[dummy]") 6 | { 7 | REQUIRE (1 == 1); 8 | } 9 | 10 | // https://github.com/McMartin/FRUT/issues/490#issuecomment-663544272 11 | 12 | /** 13 | Punting on tests for now. There are several issues that are making this break. 14 | 1. Memory leaks. Wierd. 15 | */ 16 | //ValentineAudioProcessor testPlugin; 17 | //TEST_CASE("Plugin instance name", "[name]") 18 | //{ 19 | // CHECK_THAT(testPlugin.getName().toStdString(), 20 | // Catch::Matchers::Equals("Valentine")); 21 | //} 22 | -------------------------------------------------------------------------------- /version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define BUILD_ID "@GIT_COMMIT_HASH@" 4 | #define CURRENT_VERSION "@CURRENT_VERSION@" 5 | --------------------------------------------------------------------------------