diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..93da340 --- /dev/null +++ b/.clang-format @@ -0,0 +1,86 @@ +# https://forum.juce.com/t/automatic-juce-like-code-formatting-with-clang-format/31624/20 +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: # Allman except for lambdas + AfterClass: true + AfterCaseLabel: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + BeforeElse: true + AfterControlStatement: Always + BeforeLambdaBody: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: false +ColumnLimit: 0 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: 4 +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +MaxEmptyLinesToKeep: 1 +FixNamespaceComments: false +NamespaceIndentation: All +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeParens: NonEmptyParentheses +SpaceInEmptyParentheses: false +SpaceBeforeInheritanceColon: true +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: "c++20" +TabWidth: 4 +UseTab: Never +UseCRLF: false +--- +Language: ObjC +BasedOnStyle: Chromium +BreakBeforeBraces: Allman +ColumnLimit: 0 +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +SpacesBeforeTrailingComments: 1 +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..f63f791 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: sudara diff --git a/.github/workflows/cmake_ctest.yml b/.github/workflows/cmake_ctest.yml new file mode 100644 index 0000000..0b3d333 --- /dev/null +++ b/.github/workflows/cmake_ctest.yml @@ -0,0 +1,243 @@ +name: Pamplejuce + +on: + workflow_dispatch: # lets you run a build from the UI + push: + +# When pushing new commits, cancel any running builds on that branch +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +env: + BUILD_TYPE: Release + BUILD_DIR: Builds + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISPLAY: :0 # linux pluginval needs this + CMAKE_BUILD_PARALLEL_LEVEL: 3 # Use up to 3 cpus to build juceaide, etc + HOMEBREW_NO_INSTALL_CLEANUP: 1 + +# jobs are run in paralell on different machines +# all steps run in series +jobs: + build_and_test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false # show all errors for each platform (vs. cancel jobs on error) + matrix: + include: + - name: Linux + os: ubuntu-22.04 + pluginval-binary: ./pluginval + ccache: ccache + - name: macOS + os: macos-12 + pluginval-binary: pluginval.app/Contents/MacOS/pluginval + ccache: ccache + - name: Windows + os: windows-latest + pluginval-binary: ./pluginval.exe + ccache: sccache + + steps: + + # This is just easier than debugging different compilers on different platforms + - name: Set up Clang + if: ${{ matrix.name != 'macOS' }} + uses: egor-tensin/setup-clang@v1 + + # This also starts up our "fake" display Xvfb, needed for pluginval + - name: Install JUCE's Linux Deps + if: runner.os == 'Linux' + # Thanks to McMartin & co https://forum.juce.com/t/list-of-juce-dependencies-under-linux/15121/44 + run: | + 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 ninja-build + sudo /usr/bin/Xvfb $DISPLAY & + + - name: Cache IPP (Windows) + if: runner.os == 'Windows' + id: cache-ipp + uses: actions/cache@v3 + with: + key: ipp-v1 + path: C:\Program Files (x86)\Intel\oneAPI\ipp + + - name: Install IPP (Windows) + if: (runner.os == 'Windows') && (steps.cache-ipp.outputs.cache-hit != 'true') + shell: bash + run: | + curl --output oneapi.exe https://registrationcenter-download.intel.com/akdlm/irc_nas/19078/w_BaseKit_p_2023.0.0.25940_offline.exe + ./oneapi.exe -s -x -f oneapi + ./oneapi/bootstrapper.exe -s -c --action install --components=intel.oneapi.win.ipp.devel --eula=accept -p=NEED_VS2022_INTEGRATION=1 --log-dir=. + + - name: Save IPP cache even on job fail + if: runner.os == 'Windows' && (steps.cache-ipp.outputs.cache-hit != 'true') + uses: actions/cache/save@v3 + with: + path: C:\Program Files (x86)\Intel\oneAPI\ipp + key: ipp-v1 + + # This lets us use sscache on Windows + # We need to install ccache here for Windows to grab the right version + - name: Install Ninja (Windows) + if: runner.os == 'Windows' + shell: bash + run: choco install ninja ccache + + - name: Install macOS Deps + if: ${{ matrix.name == 'macOS' }} + run: brew install ninja osxutils + + - name: Checkout code + uses: actions/checkout@v3 + with: + submodules: true # Get JUCE populated + + - name: ccache + uses: hendrikmuhs/ccache-action@main + with: + key: v3-${{ matrix.os }}-${{ matrix.type }} + variant: ${{ matrix.ccache }} + + - name: Import Certificates (macOS) + uses: apple-actions/import-codesign-certs@v2 # only exists as a tag right now + if: ${{ matrix.name == 'macOS' }} + with: + p12-file-base64: ${{ secrets.DEV_ID_APP_CERT }} + p12-password: ${{ secrets.DEV_ID_APP_PASSWORD }} + + - name: Configure + shell: bash + run: cmake -B ${{ env.BUILD_DIR }} -G Ninja -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=${{ matrix.ccache }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ matrix.ccache }} -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" . + + - name: Build + shell: bash + run: cmake --build ${{ env.BUILD_DIR }} --config ${{ env.BUILD_TYPE }} --parallel 4 + + - name: Test + working-directory: ${{ env.BUILD_DIR }} + run: ctest --output-on-failure -j4 -VV + + - name: Read in .env from CMake # see GitHubENV.cmake + shell: bash + run: | + cat .env # show us the config + cat .env >> $GITHUB_ENV # pull in our PRODUCT_NAME, etc + + - name: Set additional env vars for next steps + shell: bash + run: | + echo "ARTIFACTS_PATH=${{ env.BUILD_DIR }}/${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}" >> $GITHUB_ENV + echo "VST3_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/VST3/${{ env.PRODUCT_NAME }}.vst3" >> $GITHUB_ENV + echo "AU_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AU/${{ env.PRODUCT_NAME }}.component" >> $GITHUB_ENV + echo "AUV3_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AUv3/${{ env.PRODUCT_NAME }}.appex" >> $GITHUB_ENV + echo "ARTIFACT_NAME=${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-${{ matrix.name }}" >> $GITHUB_ENV + + - name: Pluginval + working-directory: ${{ env.BUILD_DIR }} + shell: bash + run: | + curl -LO "https://github.com/Tracktion/pluginval/releases/download/v1.0.3/pluginval_${{ matrix.name }}.zip" + 7z x pluginval_${{ matrix.name }}.zip + ${{ matrix.pluginval-binary }} --strictness-level 10 --verbose --validate "${{ env.VST3_PATH }}" + + - name: Codesign (macOS) + working-directory: ${{ env.BUILD_DIR }} + if: ${{ matrix.name == 'macOS' }} + run: | + # Each plugin must be code signed + codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v "${{ env.VST3_PATH }}" --deep --strict --options=runtime --timestamp + codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v "${{ env.AU_PATH }}" --deep --strict --options=runtime --timestamp + + - name: Add Custom Icons (macOS) + if: ${{ matrix.name == 'macOS' }} + working-directory: ${{ env.BUILD_DIR }} + run: | + # add the icns as its own icon resource (meta!) + sips -i ../packaging/pamplejuce.icns + + # Grab the resource, put in tempfile + DeRez -only icns ../packaging/pamplejuce.icns > /tmp/icons + + # Stuff the resource into the strange Icon? file's resource fork + Rez -a /tmp/icons -o "${{ env.VST3_PATH }}/Icon"$'\r' + Rez -a /tmp/icons -o "${{ env.AU_PATH }}/Icon"$'\r' + + # Set custom icon attribute + SetFile -a C "${{ env.VST3_PATH }}" + SetFile -a C "${{ env.AU_PATH }}" + + - name: Create DMG, Notarize and Staple (macOS) + if: ${{ matrix.name == 'macOS' }} + run: | + npm install -g appdmg + mkdir -p packaging/dmg + + # Create directories for the dmg symlinks + sudo mkdir -m 755 -p /Library/Audio/Plug-Ins/Components && sudo mkdir -m 755 -p /Library/Audio/Plug-Ins/VST3 + ln -s /Library/Audio/Plug-Ins/Components "packaging/dmg/Your Mac's Component folder" + ln -s /Library/Audio/Plug-Ins/VST3 "packaging/dmg/Your Mac's VST3 folder" + mv "${{ env.ARTIFACTS_PATH }}/VST3/${{ env.PRODUCT_NAME }}.vst3" packaging/dmg + mv "${{ env.ARTIFACTS_PATH }}/AU/${{ env.PRODUCT_NAME }}.component" packaging/dmg + + # Run appdmg to create the .dmg + cd packaging && appdmg dmg.json "${{ env.ARTIFACT_NAME}}.dmg" + codesign -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" --timestamp -i ${{ env.BUNDLE_ID }} --force "${{ env.ARTIFACT_NAME }}.dmg" + xcrun notarytool submit "${{ env.ARTIFACT_NAME }}.dmg" --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --password ${{ secrets.NOTARIZATION_PASSWORD }} --team-id ${{ secrets.TEAM_ID }} --wait + xcrun stapler staple "${{ env.ARTIFACT_NAME }}.dmg" + + - name: Zip + if: ${{ matrix.name == 'Linux' }} + working-directory: ${{ env.ARTIFACTS_PATH }} + run: 7z a -tzip "${{ env.ARTIFACT_NAME }}.zip" . + + - name: Generate Installer and Sign with EV cert on Azure (Windows) + if: ${{ matrix.name == 'Windows' }} + shell: bash + run: | + iscc "packaging\installer.iss" + mv "packaging/Output/${{ env.ARTIFACT_NAME }}.exe" "${{ env.ARTIFACTS_PATH }}/" + dotnet tool install --global AzureSignTool + 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.ARTIFACT_NAME }}.exe" + + - name: Upload Exe (Windows) + if: ${{ matrix.name == 'Windows' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }}.exe + path: '${{ env.ARTIFACTS_PATH }}/${{ env.ARTIFACT_NAME }}.exe' + + - name: Upload Zip (Linux) + if: ${{ matrix.name == 'Linux' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }}.zip + path: '${{ env.ARTIFACTS_PATH }}/${{ env.ARTIFACT_NAME }}.zip' + + - name: Upload DMG (MacOS) + if: ${{ matrix.name == 'macOS' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }}.dmg + path: packaging/${{ env.ARTIFACT_NAME }}.dmg + + release: + if: contains(github.ref, 'tags/v') + runs-on: ubuntu-latest + needs: build_and_test + + steps: + - name: Get Artifacts + uses: actions/download-artifact@v3 + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + prerelease: true + # download-artifact puts these files in their own dirs... + # Using globs sidesteps having to pass the version around + files: | + */*.exe + */*.zip + */*.dmg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59645f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +**/.DS_Store + +# It's nice to have the Builds folder exist, to remind us where it is +Builds/* +!Builds/.gitkeep + +# This should never exist +Install/* + +# Running CTest makes a bunch o junk +Testing + +# IDE config +.idea + +# clion cmake builds +cmake-build-debug +cmake-build-release +cmake-build-relwithdebinfo +packaging/Output/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..62f39a2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "JUCE"] + path = JUCE + url = https://github.com/juce-framework/JUCE/ + branch = develop +[submodule "modules/melatonin_inspector"] + path = modules/melatonin_inspector + url = https://github.com/sudara/melatonin_inspector.git + branch = main +[submodule "cmake"] + path = cmake + url = git@github.com:sudara/cmake-includes.git + branch = main diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0739a88 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,156 @@ +# 3.24.1 is bundled in Visual Studio 2022 v17.4 +# 3.24.1 is also bundled in CLion as of 2023 +cmake_minimum_required(VERSION 3.24.1) + +# This tells cmake we have goodies in the /cmake folder +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +include (PamplejuceVersion) + +# Change me! +# This is the internal name of the project and the name of JUCE's shared code "target" +# Note: This cannot have spaces (it may be 2023, but you can't have it all!) +# Worry not, JUCE's PRODUCT_NAME can have spaces (and is DAWs display) +set(PROJECT_NAME "Pamplejuce") + +# Worry not, JUCE's PRODUCT_NAME can have spaces (and is what DAWs will display) +# You can also just have it be the same thing as PROJECT_NAME +# You may want to append the major version on the end of this (and PROJECT_NAME) ala: +# set(PROJECT_NAME "MyPlugin_v${MAJOR_VERSION}") +# Doing so enables major versions to show up in IDEs and DAWs as separate plugins +# allowing you to change parameters and behavior without breaking existing user projects +set(PRODUCT_NAME "Pamplejuce Demo") + +# Change me! Used for the MacOS bundle name and Installers +set(COMPANY_NAME "Pamplejuce Company") + +# Change me! Used for the MacOS bundle identifier (and signing) +set(BUNDLE_ID "com.pamplejuce.pamplejuce") + +# Change me! Set the plugin formats you want built +# Valid choices: AAX Unity VST VST3 AU AUv3 Standalone +set(FORMATS Standalone AU VST3 AUv3) + +# For simplicity, the name of the CMake project is also the name of the target +project(${PROJECT_NAME} VERSION ${CURRENT_VERSION}) + +# Configures universal binaries in CI +include(PamplejuceMacOS) + +# Couple tweaks that IMO should be JUCE defaults +include(JUCEDefaults) + +# JUCE is setup as a submodule in the /JUCE folder +# Locally, you must run `git submodule update --init --recursive` once +# and later `git submodule update --remote --merge` to keep it up to date +# On Github Actions, this is done as a part of actions/checkout +add_subdirectory(JUCE) + +# Add any other modules you want modules here, before the juce_add_plugin call +# juce_add_module(modules/my_module) + +# This adds the melatonin inspector module +add_subdirectory (modules/melatonin_inspector) + +# See `docs/CMake API.md` in the JUCE repo for all config options +juce_add_plugin("${PROJECT_NAME}" + # Icons for the standalone app + ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/packaging/icon.png" + + # Change me! + COMPANY_NAME "${COMPANY_NAME}" + BUNDLE_ID "${BUNDLE_ID}" + + # On MacOS, plugin is copied to ~/Users/yourname/Library/Audio/Plug-Ins/ + COPY_PLUGIN_AFTER_BUILD TRUE + + # Change me! + # A four-character plugin id + # First character MUST be uppercase for AU formats + PLUGIN_MANUFACTURER_CODE Pamp + + # Change me! + # A unique four-character plugin id + # Note: this must have at least one upper-case character + PLUGIN_CODE P001 + FORMATS "${FORMATS}" + + # The name of your final executable + # This is how it's listed in the DAW + # This can be different from PROJECT_NAME and can have spaces! + # You might want to use v${MAJOR_VERSION} here once you go to v2... + PRODUCT_NAME "${PRODUCT_NAME}") + +# This lets us use our code in both the JUCE targets and our Test target +# Without running into ODR violations +add_library(SharedCode INTERFACE) + +# C++20, please +# Use cxx_std_23 for C++23 (as of CMake v 3.20) +target_compile_features(SharedCode INTERFACE cxx_std_20) + +# Manually list all .h and .cpp files for the plugin +# If you are like me, you'll use globs for your sanity. +# Just ensure you employ CONFIGURE_DEPENDS so the build system picks up changes +# If you want to appease the CMake gods and avoid globs, manually add files like so: +# set(SourceFiles Source/PluginEditor.h Source/PluginProcessor.h Source/PluginEditor.cpp Source/PluginProcessor.cpp) +file(GLOB_RECURSE SourceFiles CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/source/*.h") +target_sources(SharedCode INTERFACE ${SourceFiles}) + +# Adds a BinaryData target for embedding assets into the binary +include(Assets) + +# MacOS only: Cleans up folder and target organization on Xcode. +include(XcodePrettify) + +# This is where you can set preprocessor definitions for JUCE and your plugin +target_compile_definitions(SharedCode + INTERFACE + + # JUCE_WEB_BROWSER and JUCE_USE_CURL off by default + JUCE_WEB_BROWSER=0 # If you set this to 1, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call + JUCE_USE_CURL=0 # If you set this to 1, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call + JUCE_VST3_CAN_REPLACE_VST2=0 + + # Uncomment if you are paying for a an Indie/Pro license or releasing under GPLv3 + # JUCE_DISPLAY_SPLASH_SCREEN=0 + + # lets the app known if we're Debug or Release + CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" + VERSION="${CURRENT_VERSION}" + + # JucePlugin_Name is for some reason doesn't use the nicer PRODUCT_NAME + PRODUCT_NAME_WITHOUT_VERSION="Pamplejuce" +) + +# Link to any other modules you added (with juce_add_module) here! +# Usually JUCE modules must have PRIVATE visibility +# See https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md#juce_add_module +# However, with Pamplejuce, you'll link modules to SharedCode with INTERFACE visibility +# This allows the JUCE plugin targets and the Tests target to link against it +target_link_libraries(SharedCode + INTERFACE + Assets + melatonin_inspector + juce_audio_utils + juce_audio_processors + juce_dsp + juce_gui_basics + juce_gui_extra + juce::juce_recommended_config_flags + juce::juce_recommended_lto_flags + juce::juce_recommended_warning_flags) + +# Link the JUCE plugin targets our SharedCode target +target_link_libraries("${PROJECT_NAME}" PRIVATE SharedCode) + +# IPP support, comment out to disable +include(PamplejuceIPP) + +# Everything related to the tests target +include(Tests) + +# A separate target keeps the Tests target fast! +include(Benchmarks) + +# Pass some config to GA (like our PRODUCT_NAME) +include(GitHubENV) diff --git a/JUCE b/JUCE new file mode 160000 index 0000000..3ed327c --- /dev/null +++ b/JUCE @@ -0,0 +1 @@ +Subproject commit 3ed327cc392acb011b4d1ad890a642615ab82c6f diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1536cc6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sudara Williams + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a25895 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +![PAMPLEJUCE](assets/images/pamplejuce.png) +[![](https://github.com/sudara/pamplejuce/workflows/Pamplejuce/badge.svg)](https://github.com/sudara/pamplejuce/actions) + +Pamplejuce is a ~~template~~ lifestyle for creating and building JUCE plugins in 2023. + +Out of the box, it: + +1. Supports C++20. +2. Uses JUCE 7.x as a submodule tracking develop. +3. Relies on CMake 3.24.1 and higher for cross-platform building. +4. Has [Catch2](https://github.com/catchorg/Catch2) v3.4.0 for the test framework and runner. +5. Includes a Tests target and a Benchmarks target with examples to get started quickly. +6. Has [Melatonin Inspector](https://github.com/sudara/melatonin_inspector) installed as a JUCE module to help relieve headaches when building plugin UI. + +It also has integration with GitHub Actions, specifically: + +1. Building and testing cross-platform (linux, macOS, Windows) binaries +2. Running tests and benchmarks in CI +3. Running [pluginval](http://github.com/tracktion/pluginval) 1.x against the binaries for plugin validation +4. Config for [installing Intel IPP](https://www.intel.com/content/www/us/en/developer/tools/oneapi/ipp.html) +5. [Code signing and notarization on macOS](https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/) +6. [Windows EV/OV code signing via Azure Key Vault](https://melatonin.dev/blog/how-to-code-sign-windows-installers-with-an-ev-cert-on-github-actions/) + +It also contains: + +1. A `.gitignore` for all platforms. +2. A `.clang-format` file for keeping code tidy. +3. A `VERSION` file that will propagate through JUCE and your app. +4. A ton of useful comments and options around the CMake config. + +## How does this all work at a high level? + +If you are new to CMake, I suggest you read up about [JUCE and CMmake on my blog!](https://melatonin.dev/blog/how-to-use-cmake-with-juce/) + + +## Setting up for YOUR project + +This is a template repo! + +That means the easiest thing to do is click "[Use this template](https://github.com/sudara/pamplejuce/generate)" here or at the top of the page to get your own repo with all the code here. + +For an example of a plugin that uses this repo, check out [Load Monster!](https://github.com/sudara/load_monster_plugin). + +After you've created a new repo from the template, you have a checklist of things to do to customize for your project: + +* [ ] `git clone` your new repo (if you make it private, see the warning below about GitHub Actions minutes) + +* [ ] [Download CMAKE](https://cmake.org/download/) if you aren't already using it (Clion and VS2022 both have it bundled, so you can skip this step in those cases). + +* [ ] Populate the JUCE by running `git submodule update --init` in your repository directory. By default, this will track JUCE's `develop` branch, which is a good default until you are at the point of releasing a plugin. It will also pull in the CMake needed and an example module, my component inspector. + +* [ ] Replace `Pamplejuce` with the name of your project in `CMakeLists.txt` where the `PROJECT_NAME` variable is first set. Make this all one word, no spaces. + +* [ ] Adjust which plugin formats you want built as needed (VST3, AU, etc). + +* [ ] Set the correct flags for your plugin `juce_add_plugin`. Check out the API https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md and be sure to change things like `PLUGIN_CODE` and `PLUGIN_MANUFACTURER_CODE` and everything that says `Change me!`. + +* [ ] Build n' Run! If you want to generate an Xcode project, run `cmake -B Builds -G Xcode`. Or just open the project in CLion or VS2022. Running the standalone might be easiest, but you can also build the `AudioPluginHost` that comes with JUCE. Out of the box, Pamplejuce's VST3/AU targets should already be pointing to it's built location. + +* [ ] If you want to package and code sign, you'll want to take a look at the packaging/ directory add assets and config that match your product. Otherwise, you can delete the GitHub Action workflow steps that handle packaging (macOS will need code signing steps to work properly). + +This is what you will see when it's built, the plugin displaying its version number with a button that opens up the [Melatonin Inspector](https://github.com/sudara/melatonin_inspector): + +![Pamplejuce v1 - 2023-08-28 41@2x](https://github.com/sudara/pamplejuce/assets/472/33a9c8d5-fc3f-42e7-bd06-21a1559c7128) + +## Conventions + +1. Your tests go in "/tests", just add .cpp files there. +2. Additional 3rd party JUCE modules go in "/modules." +3. Your binary data target in CMake is called "Assets" (you need to include `BinaryData.h` to access it) +4. GitHub Actions will run against Linux, Windows, and macOS unless modified. + +## Cutting GitHub Releases + +Cut a release with downloadable assets by creating a tag starting with `v` and pushing it to GitHub. Note that you currently *must push the tag along with an actual commit*. + +I recommend the workflow of bumping the VERSION file and then pushing that as a release, like so: + +``` +# edit VERSION +git commit -m "Releasing v0.0.2" +git tag v0.0.2 +git push --tags +``` + +I'll work on making this less awkward... + +Releases are set to `prerelease`, which means that uploaded release assets are visible to other users, but it's not explicitly listed as the latest release until changed in the GitHub UI. + +## Code signing and Notarization + +This repo [codesigns Windows via Azure Key Vault, read more about how to do that on my blog](https://melatonin.dev/blog/how-to-code-sign-windows-installers-with-an-ev-cert-on-github-actions/). + +It also [code signs and notarizes on macOS, again, you can read my article for details](https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/). + + +## A note on GitHub Actions and macOS + +:warning: GitHub gives you 2000 or 3000 free GitHub Actions "minutes" / month for private projects, but [they actually bill 2x the number of minutes you use on Windows and 10x on MacOS](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions#about-billing-for-github-actions). + +If you made the repo private, you might feel disincentivized to push as you would burn through minutes. Note you can push a commit with `[ci skip]` in the message if you are doing things like updating the README. You have a few other big picture options, like doing testing/pluginval only on linux and moving everything else to release only. The tradeoff is you won't be sure everything is happy on all platforms until the time you are releasing, which is the last place you really want friction. By default, multiple commits in quick succession will cancel the earlier builds. + +## How do variables work in GitHub Actions? + +It can be confusing, as the documentation is a big fragmented. + +1. Things in double curly braces like `${{ matrix.name }}` are called ["contexts or expressions"](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions) and can be used to get, set, or perform simple operations. +2. In "if" conditions you can omit the double curly braces, as the whole condition is evaluated as an expression: `if: contains(github.ref, 'tags/v')` +3. You can set variables for the whole workflow to use in ["env"](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#env) +4. Reading those variables is done with the [env context](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#env-context) when you are inside a `with`, `name`, or `if`: `${{ env.SOME_VARIABLE }}` +5. Inside of `run`, you have access to bash ENV variables *in addition* to contexts/expressions. That means `$SOME_VARIABLE` or `${SOME_VARIABLE}` will work but *only when using bash* and [not while using powershell on windows](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell). The version with curly braces (variable expansion) is often used [when the variable is forming part of a larger string to avoid ambiguity](https://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-around-shell-variables). Be sure that the ENV variable was set properly in the workflow/job/step before you use it. And if you need the variable to be os-agnostic, use the env context. + +## How to update a repo based on Pamplejuce + +1. Update with the latest CMake version [listed here](https://github.com/lukka/get-cmake), or the latest version supported by your toolchain like VS or Clion. +2. Update JUCE with `git submodule update --remote --merge JUCE` +3. Update the inspector with `git submodule update --remote --merge modules/melatonin_inspector` +4. Check for an [IPP update from Intel](https://github.com/oneapi-src/oneapi-ci/blob/master/.github/workflows/build_all.yml#L10). +5. If you want to update to the latest CMake config Pamplejuce uses, first check the repository's [CHANGELOG](https://github.com/sudara/cmake-includes/blob/main/CHANGELOG.md) to make sure you are informed of any breaking changes. Then. `git submodule update --remote --merge cmake`. Unfortunately, you'll have to manually compare `CMakeLists.txt`, but it should be pretty easy to see what changed. + +## References & Inspiration + +### CMake + +* [The "Modern CMake" gitbook](https://cliutils.gitlab.io/) which also has a section on [https://cliutils.gitlab.io/modern-cmake/chapters/testing/catch.html](Catch2). +* [Effective Modern CMake](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1) +* JUCE's announcement of [native CMake support](https://forum.juce.com/t/native-built-in-cmake-support-in-juce/38700) +* [Eyalamir Music's JUCE / CMake prototype repository](https://github.com/eyalamirmusic/JUCECmakeRepoPrototype) + +### GitHub Actions + +* [Christian Adam's HelloWorld CMake and ccache repo](https://github.com/cristianadam/HelloWorld) +* [Maxwell Pollack's JUCE CMake + GitHub Actions repo](https://github.com/maxwellpollack/juce-plugin-ci) +* [Oli Larkin's PDSynth iPlug2 template](https://github.com/olilarkin/PDSynth) +* [Running pluginval in CI](https://github.com/Tracktion/pluginval/blob/develop/docs/Adding%20pluginval%20to%20CI.md) + +### Catch2 & CTest + +* [Catch2's docs on CMake integration](https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md) +* [Roman Golyshev's Github Actions & Catch2 repo](https://github.com/fedochet/github-actions-cpp-test) +* [Matt Clarkson's CMakeCatch2 repo](https://github.com/MattClarkson/CMakeCatch2) +* [CMake Cookbook example](https://github.com/dev-cafe/cmake-cookbook/tree/master/chapter-04/recipe-02/cxx-example) +* [Unit Testing With CTest](https://bertvandenbroucke.netlify.app/2019/12/12/unit-testing-with-ctest/) +* [Mark's Catch2 examples from his 2020 ADC talk](https://github.com/Sinecure-Audio/TestsTalk) + +### Packaging, Code Signing and Notarization + +* [iPlug Packages and Inno Setup scripts](https://github.com/olilarkin/wdl-ol/tree/master/IPlugExamples/IPlugEffect/installer) +* [Surge's pkgbuild installer script](https://github.com/kurasu/surge/blob/master/installer_mac/make_installer.sh) +* [Chris Randall's PackageBuilder script](https://forum.juce.com/t/vst-installer/16654/15) +* [David Cramer's GA Workflow for signing and notarizing](https://medium.com/better-programming/indie-mac-app-devops-with-github-actions-b16764a3ebe7) and his [notarize-cli tool](https://github.com/bacongravy/notarize-cli) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/assets/images/pamplejuce.png b/assets/images/pamplejuce.png new file mode 100644 index 0000000..3d573e0 Binary files /dev/null and b/assets/images/pamplejuce.png differ diff --git a/benchmarks/Benchmarks.cpp b/benchmarks/Benchmarks.cpp new file mode 100644 index 0000000..17b9018 --- /dev/null +++ b/benchmarks/Benchmarks.cpp @@ -0,0 +1,40 @@ +#include "PluginEditor.h" +#include "catch2/benchmark/catch_benchmark_all.hpp" +#include "catch2/catch_test_macros.hpp" + +TEST_CASE ("Boot performance") +{ + BENCHMARK_ADVANCED ("Processor constructor") + (Catch::Benchmark::Chronometer meter) + { + auto gui = juce::ScopedJuceInitialiser_GUI {}; + std::vector> storage (size_t (meter.runs())); + meter.measure ([&] (int i) { storage[(size_t) i].construct(); }); + }; + + BENCHMARK_ADVANCED ("Processor destructor") + (Catch::Benchmark::Chronometer meter) + { + auto gui = juce::ScopedJuceInitialiser_GUI {}; + std::vector> storage (size_t (meter.runs())); + for (auto& s : storage) + s.construct(); + meter.measure ([&] (int i) { storage[(size_t) i].destruct(); }); + }; + + BENCHMARK_ADVANCED ("Editor open and close") + (Catch::Benchmark::Chronometer meter) + { + auto gui = juce::ScopedJuceInitialiser_GUI {}; + + PluginProcessor plugin; + + // due to complex construction logic of the editor, let's measure open/close together + meter.measure ([&] (int /* i */) { + auto editor = plugin.createEditorIfNeeded(); + plugin.editorBeingDeleted (editor); + delete editor; + return plugin.getActiveEditor(); + }); + }; +} diff --git a/cmake b/cmake new file mode 160000 index 0000000..739b4ec --- /dev/null +++ b/cmake @@ -0,0 +1 @@ +Subproject commit 739b4ecb45850c317079547a9fbf6ecd2c523b1c diff --git a/docs/Catch2inCtest.jpg b/docs/Catch2inCtest.jpg new file mode 100644 index 0000000..e5a6343 Binary files /dev/null and b/docs/Catch2inCtest.jpg differ diff --git a/docs/Catch2inXcode.jpg b/docs/Catch2inXcode.jpg new file mode 100644 index 0000000..8b86eed Binary files /dev/null and b/docs/Catch2inXcode.jpg differ diff --git a/modules/.gitkeep b/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/melatonin_inspector b/modules/melatonin_inspector new file mode 160000 index 0000000..1421634 --- /dev/null +++ b/modules/melatonin_inspector @@ -0,0 +1 @@ +Subproject commit 142163493ea1e69af76021fbf9685d1141b6b93e diff --git a/packaging/EULA b/packaging/EULA new file mode 100644 index 0000000..b722597 --- /dev/null +++ b/packaging/EULA @@ -0,0 +1,19 @@ +This is a End User License Agreement ("EULA"). + +Please read it carefully before installing this software. By installing and using this software, you agree you have read this EULA and agree to the terms and conditions. If you disagree, do not install. + +1. This software is licensed for use solely by the person who paid for the license, referred to as "Licensee". + +2. Licensee can install this software on multiple computers, but only if they personally own and manage them. +Licensee is allowed to make personal backup copies of the software's materials. + +3. Licensee is prohibited from copying, altering, translating, selling, distributing, or reverse engineering the software. +Trying to access its source code, unless allowed by law, is also forbidden. + +4. Licensee must not change or hide any copyright, trademark, or other ownership notices in the software. + +5. Licensee may not share, publish or distribute the software without prior consent from Pitch Innovations. + +6. This software is provided "as-is" without any warranties, either expressed or implied, including but not limited to implied warranties of merchantability or fitness for a particular purpose. + +7. In no event shall the software's creators or distributors be liable for any damages, including but not limited to, direct, indirect, special, incidental, or consequential damages or other losses arising out of the use of or inability to use the software. diff --git a/packaging/background.jpg b/packaging/background.jpg new file mode 100644 index 0000000..cd48185 Binary files /dev/null and b/packaging/background.jpg differ diff --git a/packaging/background@2x.jpg b/packaging/background@2x.jpg new file mode 100644 index 0000000..01961bf Binary files /dev/null and b/packaging/background@2x.jpg differ diff --git a/packaging/dmg.json b/packaging/dmg.json new file mode 100644 index 0000000..36b5d25 --- /dev/null +++ b/packaging/dmg.json @@ -0,0 +1,22 @@ +{ + "title": "Pamplejuce", + "icon": "pamplejuce.icns", + "background": "background.jpg", + "window": { + "position": { + "x": 100, + "y": 100 + }, + "size": { + "width": 730, + "height": 520 + } + }, + "format": "UDZO", + "contents": [ + { "x": 250, "y": 245, "type": "file", "path": "dmg/Pamplejuce Demo.component" }, + { "x": 480, "y": 245, "type": "file", "path": "dmg/Your Mac's Component Folder" }, + { "x": 250, "y": 405, "type": "file", "path": "dmg/Pamplejuce Demo.vst3" }, + { "x": 480, "y": 405, "type": "file", "path": "dmg/Your Mac's VST3 Folder" } + ] +} diff --git a/packaging/icon.png b/packaging/icon.png new file mode 100644 index 0000000..c6c09f9 Binary files /dev/null and b/packaging/icon.png differ diff --git a/packaging/installer.iss b/packaging/installer.iss new file mode 100644 index 0000000..e2c5332 --- /dev/null +++ b/packaging/installer.iss @@ -0,0 +1,33 @@ +#define Version Trim(FileRead(FileOpen("..\VERSION"))) +#define ProjectName GetEnv('PROJECT_NAME') +#define ProductName GetEnv('PRODUCT_NAME') +#define Publisher GetEnv('COMPANY_NAME') +#define Year GetDateTimeString("yyyy","","") + +[Setup] +ArchitecturesInstallIn64BitMode=x64 +ArchitecturesAllowed=x64 +AppName={#ProductName} +OutputBaseFilename={#ProductName}-{#Version}-Windows +AppCopyright=Copyright (C) {#Year} {#Publisher} +AppPublisher={#Publisher} +AppVersion={#Version} +DefaultDirName="{commoncf64}\VST3\{#ProductName}.vst3" +DisableDirPage=yes + +; MAKE SURE YOU READ THE FOLLOWING! +LicenseFile="EULA" +UninstallFilesDir="{commonappdata}\{#ProductName}\uninstall" + +[UninstallDelete] +Type: filesandordirs; Name: "{commoncf64}\VST3\{#ProductName}Data" + +; MSVC adds a .ilk when building the plugin. Let's not include that. +[Files] +Source: "..\Builds\{#ProjectName}_artefacts\Release\VST3\{#ProductName}.vst3\*"; DestDir: "{commoncf64}\VST3\{#ProductName}.vst3\"; Excludes: *.ilk; Flags: ignoreversion recursesubdirs; + +[Run] +Filename: "{cmd}"; \ + WorkingDir: "{commoncf64}\VST3"; \ + Parameters: "/C mklink /D ""{commoncf64}\VST3\{#ProductName}Data"" ""{commonappdata}\{#ProductName}"""; \ + Flags: runascurrentuser; diff --git a/packaging/pamplejuce.icns b/packaging/pamplejuce.icns new file mode 100644 index 0000000..2e69b31 Binary files /dev/null and b/packaging/pamplejuce.icns differ diff --git a/source/PluginEditor.cpp b/source/PluginEditor.cpp new file mode 100644 index 0000000..b12e1ab --- /dev/null +++ b/source/PluginEditor.cpp @@ -0,0 +1,48 @@ +#include "PluginEditor.h" + +PluginEditor::PluginEditor (PluginProcessor& p) + : AudioProcessorEditor (&p), processorRef (p) +{ + juce::ignoreUnused (processorRef); + + addAndMakeVisible (inspectButton); + + // this chunk of code instantiates and opens the melatonin inspector + inspectButton.onClick = [&] { + if (!inspector) + { + inspector = std::make_unique (*this); + inspector->onClose = [this]() { inspector.reset(); }; + } + + inspector->setVisible (true); + }; + + // Make sure that before the constructor has finished, you've set the + // editor's size to whatever you need it to be. + setSize (400, 300); +} + +PluginEditor::~PluginEditor() +{ +} + +void PluginEditor::paint (juce::Graphics& g) +{ + // (Our component is opaque, so we must completely fill the background with a solid colour) + g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); + + auto area = getLocalBounds(); + g.setColour (juce::Colours::white); + g.setFont (16.0f); + auto helloWorld = juce::String ("Hello from ") + PRODUCT_NAME_WITHOUT_VERSION + " v" VERSION + " running in " + CMAKE_BUILD_TYPE; + g.drawText (helloWorld, area.removeFromTop (150), juce::Justification::centred, false); +} + +void PluginEditor::resized() +{ + // layout the positions of your child components here + auto area = getLocalBounds(); + area.removeFromBottom(50); + inspectButton.setBounds (getLocalBounds().withSizeKeepingCentre(100, 50)); +} diff --git a/source/PluginEditor.h b/source/PluginEditor.h new file mode 100644 index 0000000..1f8e786 --- /dev/null +++ b/source/PluginEditor.h @@ -0,0 +1,25 @@ +#pragma once + +#include "PluginProcessor.h" +#include "BinaryData.h" +#include "melatonin_inspector/melatonin_inspector.h" + +//============================================================================== +class PluginEditor : public juce::AudioProcessorEditor +{ +public: + explicit PluginEditor (PluginProcessor&); + ~PluginEditor() override; + + //============================================================================== + void paint (juce::Graphics&) override; + void resized() override; + +private: + // This reference is provided as a quick way for your editor to + // access the processor object that created it. + PluginProcessor& processorRef; + std::unique_ptr inspector; + juce::TextButton inspectButton { "Inspect the UI" }; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginEditor) +}; diff --git a/source/PluginProcessor.cpp b/source/PluginProcessor.cpp new file mode 100644 index 0000000..16fe17c --- /dev/null +++ b/source/PluginProcessor.cpp @@ -0,0 +1,186 @@ +#include "PluginProcessor.h" +#include "PluginEditor.h" + +//============================================================================== +PluginProcessor::PluginProcessor() + : AudioProcessor (BusesProperties() + #if ! JucePlugin_IsMidiEffect + #if ! JucePlugin_IsSynth + .withInput ("Input", juce::AudioChannelSet::stereo(), true) + #endif + .withOutput ("Output", juce::AudioChannelSet::stereo(), true) + #endif + ) +{ +} + +PluginProcessor::~PluginProcessor() +{ +} + +//============================================================================== +const juce::String PluginProcessor::getName() const +{ + return JucePlugin_Name; +} + +bool PluginProcessor::acceptsMidi() const +{ + #if JucePlugin_WantsMidiInput + return true; + #else + return false; + #endif +} + +bool PluginProcessor::producesMidi() const +{ + #if JucePlugin_ProducesMidiOutput + return true; + #else + return false; + #endif +} + +bool PluginProcessor::isMidiEffect() const +{ + #if JucePlugin_IsMidiEffect + return true; + #else + return false; + #endif +} + +double PluginProcessor::getTailLengthSeconds() const +{ + return 0.0; +} + +int PluginProcessor::getNumPrograms() +{ + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. +} + +int PluginProcessor::getCurrentProgram() +{ + return 0; +} + +void PluginProcessor::setCurrentProgram (int index) +{ + juce::ignoreUnused (index); +} + +const juce::String PluginProcessor::getProgramName (int index) +{ + juce::ignoreUnused (index); + return {}; +} + +void PluginProcessor::changeProgramName (int index, const juce::String& newName) +{ + juce::ignoreUnused (index, newName); +} + +//============================================================================== +void PluginProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +{ + // Use this method as the place to do any pre-playback + // initialisation that you need.. + juce::ignoreUnused (sampleRate, samplesPerBlock); +} + +void PluginProcessor::releaseResources() +{ + // When playback stops, you can use this as an opportunity to free up any + // spare memory, etc. +} + +bool PluginProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const +{ + #if JucePlugin_IsMidiEffect + juce::ignoreUnused (layouts); + return true; + #else + // This is the place where you check if the layout is supported. + // In this template code we only support mono or stereo. + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() + && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + + // This checks if the input layout matches the output layout + #if ! JucePlugin_IsSynth + if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) + return false; + #endif + + return true; + #endif +} + +void PluginProcessor::processBlock (juce::AudioBuffer& buffer, + juce::MidiBuffer& midiMessages) +{ + juce::ignoreUnused (midiMessages); + + juce::ScopedNoDenormals noDenormals; + auto totalNumInputChannels = getTotalNumInputChannels(); + auto totalNumOutputChannels = getTotalNumOutputChannels(); + + // In case we have more outputs than inputs, this code clears any output + // channels that didn't contain input data, (because these aren't + // guaranteed to be empty - they may contain garbage). + // This is here to avoid people getting screaming feedback + // when they first compile a plugin, but obviously you don't need to keep + // this code if your algorithm always overwrites all the output channels. + for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) + buffer.clear (i, 0, buffer.getNumSamples()); + + // This is the place where you'd normally do the guts of your plugin's + // audio processing... + // Make sure to reset the state if your inner loop is processing + // the samples and the outer loop is handling the channels. + // Alternatively, you can process the samples with the channels + // interleaved by keeping the same state. + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* channelData = buffer.getWritePointer (channel); + juce::ignoreUnused (channelData); + // ..do something to the data... + } +} + +//============================================================================== +bool PluginProcessor::hasEditor() const +{ + return true; // (change this to false if you choose to not supply an editor) +} + +juce::AudioProcessorEditor* PluginProcessor::createEditor() +{ + return new PluginEditor (*this); +} + +//============================================================================== +void PluginProcessor::getStateInformation (juce::MemoryBlock& destData) +{ + // You should use this method to store your parameters in the memory block. + // You could do that either as raw data, or use the XML or ValueTree classes + // as intermediaries to make it easy to save and load complex data. + juce::ignoreUnused (destData); +} + +void PluginProcessor::setStateInformation (const void* data, int sizeInBytes) +{ + // You should use this method to restore your parameters from this memory block, + // whose contents will have been created by the getStateInformation() call. + juce::ignoreUnused (data, sizeInBytes); +} + +//============================================================================== +// This creates new instances of the plugin.. +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new PluginProcessor(); +} diff --git a/source/PluginProcessor.h b/source/PluginProcessor.h new file mode 100644 index 0000000..5c95cee --- /dev/null +++ b/source/PluginProcessor.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#if (MSVC) +#include "ipps.h" +#endif + +class PluginProcessor : public juce::AudioProcessor +{ +public: + PluginProcessor(); + ~PluginProcessor() override; + + void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + + bool isBusesLayoutSupported (const BusesLayout& layouts) const override; + + void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; + + juce::AudioProcessorEditor* createEditor() override; + bool hasEditor() const override; + + const juce::String getName() const override; + + bool acceptsMidi() const override; + bool producesMidi() const override; + bool isMidiEffect() const override; + double getTailLengthSeconds() const override; + + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram (int index) override; + const juce::String getProgramName (int index) override; + void changeProgramName (int index, const juce::String& newName) override; + + void getStateInformation (juce::MemoryBlock& destData) override; + void setStateInformation (const void* data, int sizeInBytes) override; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginProcessor) +}; diff --git a/tests/PluginBasics.cpp b/tests/PluginBasics.cpp new file mode 100644 index 0000000..c518339 --- /dev/null +++ b/tests/PluginBasics.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +TEST_CASE("one is equal to one", "[dummy]") +{ + REQUIRE(1 == 1); +} + +// https://github.com/McMartin/FRUT/issues/490#issuecomment-663544272 +PluginProcessor testPlugin; + +TEST_CASE("Plugin instance name", "[name]") +{ + CHECK_THAT(testPlugin.getName().toStdString(), + Catch::Matchers::Equals("Pamplejuce Demo")); +} + +#ifdef PAMPLEJUCE_IPP +#include + +TEST_CASE("IPP version", "[ipp]") +{ + CHECK_THAT(ippsGetLibVersion()->Version, Catch::Matchers::Equals("2021.7 (r0xa954907f)")); +} +#endif