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/workflows/cmake_ctest.yml b/.github/workflows/cmake_ctest.yml new file mode 100644 index 0000000..5e2ebd7 --- /dev/null +++ b/.github/workflows/cmake_ctest.yml @@ -0,0 +1,240 @@ +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: + PROJECT_NAME: Pamplejuce + 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: Setup Environment Variables + shell: bash + run: | + VERSION=$(cat VERSION) + 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.PROJECT_NAME }}.vst3" >> $GITHUB_ENV + echo "AU_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AU/${{ env.PROJECT_NAME }}.component" >> $GITHUB_ENV + echo "AUV3_PATH=${{ env.PROJECT_NAME }}_artefacts/${{ env.BUILD_TYPE }}/AUv3/${{ env.PROJECT_NAME }}.appex" >> $GITHUB_ENV + echo "PRODUCT_NAME=${{ env.PROJECT_NAME }}-$VERSION-${{ matrix.name }}" >> $GITHUB_ENV + + - 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@v1 + 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: 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.PROJECT_NAME }}.vst3" packaging/dmg + mv "${{ env.ARTIFACTS_PATH }}/AU/${{ env.PROJECT_NAME }}.component" packaging/dmg + + # Run appdmg to create the .dmg + cd packaging && appdmg dmg.json ${{ env.PRODUCT_NAME}}.dmg + codesign -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" --timestamp -i com.pamplejuce.pamplejuce --force ${{ env.PRODUCT_NAME }}.dmg + xcrun notarytool submit ${{ env.PRODUCT_NAME }}.dmg --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --password ${{ secrets.NOTARIZATION_PASSWORD }} --team-id ${{ secrets.TEAM_ID }} --wait + xcrun stapler staple ${{ env.PRODUCT_NAME }}.dmg + + - name: Zip + if: ${{ matrix.name == 'Linux' }} + working-directory: ${{ env.ARTIFACTS_PATH }} + run: 7z a -tzip ${{ env.PRODUCT_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.PRODUCT_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.PRODUCT_NAME }}.exe" + + - name: Upload Exe (Windows) + if: ${{ matrix.name == 'Windows' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.PRODUCT_NAME }}.exe + path: '${{ env.ARTIFACTS_PATH }}/${{ env.PRODUCT_NAME }}.exe' + + - name: Upload Zip (Linux) + if: ${{ matrix.name == 'Linux' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.PRODUCT_NAME }}.zip + path: '${{ env.ARTIFACTS_PATH }}/${{ env.PRODUCT_NAME }}.zip' + + - name: Upload DMG (MacOS) + if: ${{ matrix.name == 'macOS' }} + uses: actions/upload-artifact@v3 + with: + name: ${{ env.PRODUCT_NAME }}.dmg + path: packaging/${{ env.PRODUCT_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..3584032 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "JUCE"] + path = JUCE + url = https://github.com/juce-framework/JUCE/ + branch = develop diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cf77bbf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,193 @@ +# 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 is the name of your plugin +# This cannot have spaces (but PRODUCT_NAME can) +set(PROJECT_NAME "Pamplejuce") + +# Set the plugin formats you'll be building here. +# Valid formats: AAX Unity VST AU AUv3 Standalone +set(FORMATS AU VST3 AUv3) + +# This must be set before the project() call +# see: https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_DEPLOYMENT_TARGET.html +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Support macOS down to High Sierra") + +# Reads in our VERSION file and sticks in it CURRENT_VERSION variable +# Be sure the file has no newlines! +file(STRINGS VERSION CURRENT_VERSION) + +# For simplicity, the name of the project is also the name of the target +project(${PROJECT_NAME} VERSION ${CURRENT_VERSION}) + +# By default we don't want Xcode schemes to be made for modules, etc +set(CMAKE_XCODE_GENERATE_SCHEME OFF) + +# Building universal binaries on macOS increases build time +# This is set on CI but not during local dev +if ((DEFINED ENV{CI}) AND (CMAKE_BUILD_TYPE STREQUAL "Release")) + message("Building for Apple Silicon and x86_64") + set(CMAKE_OSX_ARCHITECTURES arm64 x86_64) +endif () + +# Adds all the module sources so they appear correctly in the IDE +# Must be set before JUCE is added as a sub-dir (or any targets are made) +# https://github.com/juce-framework/JUCE/commit/6b1b4cf7f6b1008db44411f2c8887d71a3348889 +set_property(GLOBAL PROPERTY USE_FOLDERS YES) + +# Create a /Modules directory in the IDE with the JUCE Module code +option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Show all module sources in IDE projects" ON) + +# JUCE is setup as a submodule in the /JUCE folder +# Locally, you'll need to run `git submodule update --init --recursive` once +# and `git submodule update --remote --merge` to keep it up to date +# On Github Actions, it's managed by actions/checkout +add_subdirectory(JUCE) + +# Check the readme at `docs/CMake API.md` in the JUCE repo for full config +juce_add_plugin("${PROJECT_NAME}" + # VERSION ... # Set this if the plugin version is different to the project version + # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone + # ICON_SMALL ... + COMPANY_NAME Pamplejuce + BUNDLE_ID com.pamplejuce.pamplejuce + # IS_SYNTH TRUE/FALSE # Is this a synth or an effect? + # NEEDS_MIDI_INPUT TRUE/FALSE # Does the plugin need midi input? + # NEEDS_MIDI_OUTPUT TRUE/FALSE # Does the plugin need midi output? + # IS_MIDI_EFFECT TRUE/FALSE # Is this plugin a MIDI effect? + # EDITOR_WANTS_KEYBOARD_FOCUS TRUE/FALSE # Does the editor need keyboard focus? + COPY_PLUGIN_AFTER_BUILD TRUE # On MacOS, plugin will be copied to /Users/you/Library/Audio/Plug-Ins/ + PLUGIN_MANUFACTURER_CODE Pamp # This has to be one uppercase, rest lower for AU formats + PLUGIN_CODE P001 # A unique four-character plugin id with at least one upper-case character + FORMATS "${FORMATS}" + PRODUCT_NAME "${PROJECT_NAME}") # The name of the final executable, which can differ from the target name + +# C++20, please +target_compile_features("${PROJECT_NAME}" PRIVATE cxx_std_20) + +# Manually list all .h and .cpp files for the plugin +set(SourceFiles + Source/PluginEditor.h + Source/PluginProcessor.h + Source/PluginEditor.cpp + Source/PluginProcessor.cpp) +target_sources("${PROJECT_NAME}" PRIVATE ${SourceFiles}) + +# No, we don't want our source buried in extra nested folders +set_target_properties("${PROJECT_NAME}" PROPERTIES FOLDER "") + +# The Xcode source tree should uhhh, still look like the source tree, yo +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Source PREFIX "" FILES ${SourceFiles}) + +# Setup our binary data as a target +juce_add_binary_data(Assets SOURCES pamplejuce.png) + +# Required for Linux happiness: +# See https://forum.juce.com/t/loading-pytorch-model-using-binarydata/39997/2 +set_target_properties(Assets PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + +# Clean up folder organization on Xcode. +# It tucks the Plugin varieties into a "Targets" folder and generate an Xcode Scheme manually +# Xcode scheme generation is turned off globally to limit noise from other targets +# The non-hacky way of doing this is via the global PREDEFINED_TARGETS_FOLDER property +# However that doesn't seem to be working in Xcode +# Not all plugin types (au, vst) available on each build type (win, macos, linux) +foreach(target ${FORMATS} "All") + if(TARGET ${PROJECT_NAME}_${target}) + set_target_properties(${PROJECT_NAME}_${target} PROPERTIES + # Tuck the actual plugin targets into a folder where they won't bother us + FOLDER "Targets" + + # MacOS only: Sets the default executable that Xcode will open on build + # For this exact path to to work, manually build the AudioPluginHost.xcodeproj in the JUCE subdir + XCODE_SCHEME_EXECUTABLE "${CMAKE_CURRENT_SOURCE_DIR}/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app" + + # Let us build the target in Xcode + XCODE_GENERATE_SCHEME ON) + endif() +endforeach() +set_target_properties(Assets PROPERTIES FOLDER "Targets") + + +target_compile_definitions("${PROJECT_NAME}" + PUBLIC + # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. + JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call + JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call + JUCE_VST3_CAN_REPLACE_VST2=0) + +target_link_libraries("${PROJECT_NAME}" + PRIVATE + Assets + PUBLIC + juce::juce_audio_utils + juce::juce_audio_processors + juce::juce_recommended_config_flags + juce::juce_recommended_lto_flags + juce::juce_recommended_warning_flags) + +# When present, use Intel IPP for performance on Windows +if(WIN32) # Can't use MSVC here, as it won't catch Clang on Windows + find_package(IPP) + if(IPP_FOUND) + target_link_libraries("${PROJECT_NAME}" PUBLIC IPP::ipps IPP::ippcore IPP::ippi IPP::ippcv) + message("IPP LIBRARIES FOUND") + target_compile_definitions("${PROJECT_NAME}" PUBLIC PAMPLEJUCE_IPP=1) + else() + message("IPP LIBRARIES *NOT* FOUND") + endif() +endif() + +# Required for ctest (which is just easier for cross-platform CI) +# include(CTest) does this too, but adds tons of targets we don't want +# See: https://github.com/catchorg/Catch2/issues/2026 +# You also could forgo ctest and call ./Tests directly from the build dir +enable_testing() + +# "GLOBS ARE BAD" is brittle and silly dev UX, sorry CMake! +# CONFIGURE_DEPENDS / Clion's CMake integration makes globbing absolutely fine +file(GLOB_RECURSE TestFiles CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Tests/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Tests/*.h") +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Tests PREFIX "" FILES ${TestFiles}) + +# Use Catch2 v3 on the devel branch +Include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE + GIT_TAG v3.3.2) +FetchContent_MakeAvailable(Catch2) # find_package equivalent + +# Setup the test executable, again C++ 20 please +add_executable(Tests ${TestFiles}) +target_compile_features(Tests PRIVATE cxx_std_20) + +# Our test executable also wants to know about our plugin code... +target_include_directories(Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source) +target_link_libraries(Tests PRIVATE Catch2::Catch2WithMain "${PROJECT_NAME}") + +# We can't link again to the shared juce target without ODL violations +# However, we can steal its include dirs and compile definitions to use in tests! +target_compile_definitions(Tests INTERFACE $) +target_include_directories(Tests INTERFACE $) + +# Make an Xcode Scheme for the test executable so we can run tests in the IDE +set_target_properties(Tests PROPERTIES XCODE_GENERATE_SCHEME ON) + +# Organize the test source in the Tests/ folder in the IDE +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Tests PREFIX "" FILES ${TestFiles}) + +# Load and use the .cmake file provided by Catch2 +# https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md +# We have to manually provide the source directory here for now +include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) +catch_discover_tests(Tests) + +# Color our warnings and errors +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options (-fdiagnostics-color=always) +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options (-fcolor-diagnostics) +endif () 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/JUCE b/JUCE new file mode 160000 index 0000000..69795dc --- /dev/null +++ b/JUCE @@ -0,0 +1 @@ +Subproject commit 69795dc8e589a9eb5df251b6dd994859bf7b3fab 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..d0b3ed4 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +![PAMPLEJUCE](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 supports: + +1. C++20 +2. JUCE 7.x as a submodule tracking develop +3. CMake 3.24.1 and higher for building cross-platform +4. [Catch2](https://github.com/catchorg/Catch2) v3.3.2 as the test framework and runner +5. [pluginval](http://github.com/tracktion/pluginval) 1.x for plugin validation +6. GitHub Actions config for [installing Intel IPP](https://www.intel.com/content/www/us/en/developer/tools/oneapi/ipp.html), building binaries, running Catch2 tests and pluginval, artifact building on the Windows, Linux and macOS platforms, including [code signing and notarization on macOS](https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/) and [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. Proper `.gitignore` for all platforms +2. A `.clang-format` file +3. A `VERSION` file that will propagate through to JUCE and your app. + +## How does this all work at a high level? + +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: + +0. `git clone` your new repo (if you make it private, see the warning below about GitHub Actions minutes) + +1. [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). + +2. Populate the latest JUCE by running `git submodule update --init` in your repository directory. By default, this will track JUCE's `develop` branch, which IMO is what you want until you are at the point of releasing a plugin. + +3. Replace `Pamplejuce` with the name of your project in CMakeLists.txt line 7, where the `PROJECT_NAME` variable is set. Make this all one word, no spaces. + +4. Pick which formats you want built on line 11. + +5. Set the correct flags for your plugin under `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`. + +7. If you are packaging and code signing, you'll want to take a look at the packaging/ directory and add assets and config that match your product. Otherwise you can delete the steps which do this. + +## Conventions + +1. Your tests will be in "Tests" and you can just add new .cpp files there. +2. Your binary data target is called "Assets" + +## Releases + +You can 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 macOS, blog article coming soon, but there are many more examples of this in the wild. + + +## Tips n' Tricks + +1. :warning: GitHub gives you 2000 or 3000 free GitHub Actions "minutes" 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). + +2. There's a `VERSION` file in the root that you can treat as the main place to bump the version. + +3. You might feel disincentivized to push to a private repo due to burning minutes. 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. 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` +3. Check for an [IPP update from Intel](https://github.com/oneapi-src/oneapi-ci/blob/master/.github/workflows/build_all.yml#L10). +4. You'll have to manually compare CMakeLists.txt, as I assume you made a changes. In the future, I may move most of the cmake magic into helpers to keep the main CMakesList.txt cleaner. + +## 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/Source/PluginEditor.cpp b/Source/PluginEditor.cpp new file mode 100644 index 0000000..4e2e21c --- /dev/null +++ b/Source/PluginEditor.cpp @@ -0,0 +1,34 @@ +#include "PluginEditor.h" +#include "PluginProcessor.h" + +//============================================================================== +PluginEditor::PluginEditor (PluginProcessor& p) + : AudioProcessorEditor (&p), processorRef (p) +{ + juce::ignoreUnused (processorRef); + + // 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)); + + g.setColour (juce::Colours::white); + g.setFont (15.0f); + g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); +} + +void PluginEditor::resized() +{ + // lay out the positions of your components +} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h new file mode 100644 index 0000000..6dbc769 --- /dev/null +++ b/Source/PluginEditor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "PluginProcessor.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; + + 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/Benchmarks.cpp b/Tests/Benchmarks.cpp new file mode 100644 index 0000000..782edc9 --- /dev/null +++ b/Tests/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/Tests/PluginBasics.cpp b/Tests/PluginBasics.cpp new file mode 100644 index 0000000..c448683 --- /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")); +} + +#ifdef PAMPLEJUCE_IPP +#include + +TEST_CASE("IPP version", "[ipp]") +{ + CHECK_THAT(ippsGetLibVersion()->Version, Catch::Matchers::Equals("2021.7 (r0xa954907f)")); +} +#endif diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file 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..ae2b635 --- /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.component" }, + { "x": 480, "y": 245, "type": "file", "path": "dmg/Your Mac's Component Folder" }, + { "x": 250, "y": 405, "type": "file", "path": "dmg/Pamplejuce.vst3" }, + { "x": 480, "y": 405, "type": "file", "path": "dmg/Your Mac's VST3 Folder" } + ] +} \ No newline at end of file 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..4514049 --- /dev/null +++ b/packaging/installer.iss @@ -0,0 +1,30 @@ +#define Version Trim(FileRead(FileOpen("..\VERSION"))) +#define PluginName "Pamplejuce" +#define Publisher "Melatonin" +#define Year GetDateTimeString("yyyy","","") + +[Setup] +ArchitecturesInstallIn64BitMode=x64 +ArchitecturesAllowed=x64 +AppName={#PluginName} +OutputBaseFilename={#PluginName}-{#Version}-Windows +AppCopyright=Copyright (C) {#Year} {#Publisher} +AppPublisher={#Publisher} +AppVersion={#Version} +DefaultDirName="{commoncf64}\VST3\{#PluginName}.vst3" +DisableDirPage=yes +LicenseFile="..\LICENSE" +UninstallFilesDir="{commonappdata}\{#PluginName}\uninstall" + +[UninstallDelete] +Type: filesandordirs; Name: "{commoncf64}\VST3\{#PluginName}Data" + +; MSVC adds a .ilk when building the plugin. Let's not include that. +[Files] +Source: "..\Builds\Pamplejuce_artefacts\Release\VST3\{#PluginName}.vst3\*"; DestDir: "{commoncf64}\VST3\{#PluginName}.vst3\"; Excludes: *.ilk; Flags: ignoreversion recursesubdirs; + +[Run] +Filename: "{cmd}"; \ + WorkingDir: "{commoncf64}\VST3"; \ + Parameters: "/C mklink /D ""{commoncf64}\VST3\{#PluginName}Data"" ""{commonappdata}\{#PluginName}"""; \ + 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/pamplejuce.png b/pamplejuce.png new file mode 100644 index 0000000..3d573e0 Binary files /dev/null and b/pamplejuce.png differ