├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── code-checks.yml │ └── pullreq.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── VST3_Info.plist.in ├── auv2_Info.plist.in ├── base_sdks.cmake ├── external │ └── CPM.cmake ├── macBundlePkgInfo ├── make_clapfirst.cmake ├── shared_prologue.cmake ├── top_level_default.cmake ├── wrap_auv2.cmake ├── wrap_clap.cmake ├── wrap_standalone.cmake ├── wrap_vst3.cmake ├── wrap_wclap_emscripten.cmake └── wrapper_functions.cmake ├── include └── clapwrapper │ ├── auv2.h │ └── vst3.h ├── libs └── fmt │ ├── README.md │ └── fmt │ ├── base.h │ ├── chrono.h │ ├── color.h │ ├── format-inl.h │ ├── format.h │ ├── ostream.h │ ├── ranges.h │ └── xchar.h ├── src ├── clap_proxy.cpp ├── clap_proxy.h ├── detail │ ├── ara │ │ └── ara.h │ ├── auv2 │ │ ├── auv2_base_classes.h │ │ ├── auv2_shared.h │ │ ├── auv2_shared.mm │ │ ├── build-helper │ │ │ ├── auv2_infoplist_top.in │ │ │ └── build-helper.cpp │ │ ├── parameter.cpp │ │ ├── parameter.h │ │ ├── process.cpp │ │ ├── process.h │ │ ├── wrappedview.asinclude.mm │ │ └── wrappedview.mm │ ├── clap │ │ ├── automation.h │ │ ├── fsutil.cpp │ │ ├── fsutil.h │ │ └── mac_helpers.mm │ ├── os │ │ ├── fs.h │ │ ├── linux.cpp │ │ ├── log.h │ │ ├── macos.mm │ │ ├── osutil.h │ │ ├── osutil_windows.h │ │ └── windows.cpp │ ├── shared │ │ ├── fixedqueue.h │ │ ├── sha1.cpp │ │ ├── sha1.h │ │ └── spinlock.h │ ├── standalone │ │ ├── entry.cpp │ │ ├── entry.h │ │ ├── linux │ │ │ ├── gtkutils.cpp │ │ │ └── gtkutils.h │ │ ├── macos │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.mm │ │ │ ├── Info.plist.in │ │ │ ├── MainMenu.xib │ │ │ └── StandaloneFunctions.mm │ │ ├── standalone_details.h │ │ ├── standalone_host.cpp │ │ ├── standalone_host.h │ │ ├── standalone_host_audio.cpp │ │ ├── standalone_host_midi.cpp │ │ └── windows │ │ │ ├── windows_standalone.cpp │ │ │ ├── windows_standalone.h │ │ │ └── windows_standalone.manifest │ └── vst3 │ │ ├── aravst3.h │ │ ├── categories.cpp │ │ ├── categories.h │ │ ├── parameter.cpp │ │ ├── parameter.h │ │ ├── plugview.cpp │ │ ├── plugview.h │ │ ├── process.cpp │ │ ├── process.h │ │ └── state.h ├── wrapasauv2.cpp ├── wrapasstandalone.cpp ├── wrapasstandalone.mm ├── wrapasstandalone_windows.cpp ├── wrapasvst3.cpp ├── wrapasvst3.h ├── wrapasvst3_entry.cpp └── wrapasvst3_export_entry.cpp └── tests ├── CMakeLists.txt └── clap-first-example ├── CMakeLists.txt ├── distortion_clap.cpp ├── distortion_clap_entry.cpp └── distortion_clap_entry.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | UseTab: Never 3 | --- 4 | Language: Cpp 5 | ConstructorInitializerIndentWidth: 2 6 | SpaceBeforeParens: ControlStatements 7 | BasedOnStyle: Google 8 | IndentWidth: 2 9 | AlignAfterOpenBracket: Align 10 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 11 | BreakConstructorInitializers: BeforeComma 12 | BreakBeforeBraces: Allman 13 | ColumnLimit: 105 14 | SortIncludes: false 15 | NamespaceIndentation: None 16 | ReflowComments: false 17 | AllowShortFunctionsOnASingleLine: None 18 | --- 19 | Language: ObjC 20 | BasedOnStyle: Google 21 | ConstructorInitializerIndentWidth: 2 22 | SpaceBeforeParens: ControlStatements 23 | IndentWidth: 2 24 | AlignAfterOpenBracket: Align 25 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 26 | BreakConstructorInitializers: BeforeComma 27 | BreakBeforeBraces: Allman 28 | ColumnLimit: 105 29 | SortIncludes: false 30 | NamespaceIndentation: None 31 | ReflowComments: false 32 | AllowShortFunctionsOnASingleLine: None 33 | ... 34 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-*,\ 2 | bugprone-macro-parentheses,\ 3 | misc-unused-parameters,\ 4 | modernize-deprecated-headers,\ 5 | modernize-use-auto,\ 6 | modernize-use-emplace,\ 7 | modernize-use-equals-default,\ 8 | modernize-use-nullptr,\ 9 | readability-container-size-empty,\ 10 | readability-else-after-return,\ 11 | readability-inconsistent-declaration-parameter-name\ 12 | ' 13 | -------------------------------------------------------------------------------- /.github/workflows/code-checks.yml: -------------------------------------------------------------------------------- 1 | name: Clap Wrapper Format and Code Checks 2 | on: [pull_request] 3 | jobs: 4 | formatting-check: 5 | name: Clang Format Check 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | path: 10 | - 'src' 11 | - 'include' 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Run clang-format style check 15 | uses: jidicula/clang-format-action@v4.11.0 16 | with: 17 | clang-format-version: '17' 18 | check-path: ${{ matrix.path }} -------------------------------------------------------------------------------- /.github/workflows/pullreq.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Validation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - next 8 | 9 | jobs: 10 | build_feature: 11 | name: ${{ matrix.name }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | include: 16 | - os: windows-latest 17 | name: Windows External SDK 18 | - os: macos-latest 19 | name: Mac External SDK 20 | - os: ubuntu-latest 21 | name: Linux External SDK 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | with: 26 | submodules: recursive 27 | 28 | - name: Get Dependencies 29 | run: | 30 | mkdir deps 31 | cd deps 32 | git clone https://github.com/free-audio/clap 33 | git clone https://github.com/steinbergmedia/vst3sdk 34 | cd vst3sdk 35 | # temporary workaround, switch to vst3 sdk 3.7.7 36 | git switch --detach v3.7.7_build_19 37 | git submodule update --init --recursive 38 | cd ../.. 39 | 40 | - name: Build project 41 | run: | 42 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCLAP_SDK_ROOT=deps/clap -DVST3_SDK_ROOT=deps/vst3sdk -DCLAP_WRAPPER_OUTPUT_NAME=testplug 43 | cmake --build ./build --config Debug 44 | 45 | - name: Show Build Results 46 | shell: bash 47 | run: | 48 | find build -name testplug.vst3 -print 49 | find build -name testplug.component -print 50 | 51 | 52 | build_feature_cpm_download: 53 | name: ${{ matrix.name }} 54 | runs-on: ${{ matrix.os }} 55 | strategy: 56 | matrix: 57 | include: 58 | - os: windows-latest 59 | cmakeargs: -A x64 60 | install_ninja: false 61 | run_aptget: false 62 | name: Windows 64bit MSVC 63 | 64 | - os: windows-latest 65 | cmakeargs: -A x64 -DCLAP_WRAPPER_CXX_STANDARD=20 66 | install_ninja: false 67 | run_aptget: false 68 | name: Windows 64bit C++20 MSVC 69 | 70 | - os: windows-latest 71 | cmakeargs: -GNinja -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc 72 | install_ninja: true 73 | run_aptget: false 74 | name: Windows gcc/minGW 75 | 76 | #- os: windows-latest 77 | # cmakeargs: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang 78 | # install_ninja: true 79 | # run_aptget: false 80 | # name: Windows clang 81 | 82 | - os: ubuntu-latest 83 | cmakeargs: -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 84 | install_ninja: false 85 | run_aptget: true 86 | name: Linux gcc12 87 | 88 | - os: ubuntu-latest 89 | cmakeargs: -DCMAKE_CXX_COMPILER=g++-13 -DCMAKE_C_COMPILER=gcc-13 90 | install_ninja: false 91 | run_aptget: true 92 | name: Linux gcc13 93 | 94 | - os: ubuntu-24.04 95 | cmakeargs: -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_C_COMPILER=gcc-14 96 | install_ninja: false 97 | run_aptget: true 98 | name: Linux ub-24 gcc14 99 | 100 | - os: ubuntu-latest 101 | cmakeargs: -DCLAP_WRAPPER_CXX_STANDARD=20 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 102 | install_ninja: false 103 | run_aptget: true 104 | name: Linux gcc12 C++20 105 | 106 | - os: ubuntu-latest 107 | cmakeargs: -DCLAP_WRAPPER_CXX_STANDARD=23 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 108 | install_ninja: false 109 | run_aptget: true 110 | name: Linux gcc12 C++23 111 | 112 | - os: macos-latest 113 | cmakeargs: -GNinja 114 | install_ninja: true 115 | run_aptget: false 116 | name: MacOS Ninja 117 | 118 | - os: macos-latest 119 | cmakeargs: -G"Xcode" 120 | install_ninja: false 121 | run_aptget: false 122 | name: MacOS Xcode 123 | 124 | - os: macos-latest 125 | cmakeargs: -G"Ninja Multi-Config" -DCLAP_WRAPPER_CXX_STANDARD=20 126 | install_ninja: true 127 | run_aptget: false 128 | name: MacOS Ninja Multi C++20 129 | 130 | - os: macos-latest 131 | cmakeargs: -G"Unix Makefiles" 132 | install_ninja: false 133 | run_aptget: false 134 | name: MacOS Unix Makefiles 135 | 136 | steps: 137 | - name: Checkout code 138 | uses: actions/checkout@v4 139 | with: 140 | submodules: recursive 141 | 142 | - name: Apt Update 143 | if: ${{ matrix.run_aptget }} 144 | run: sudo apt-get update 145 | 146 | - name: Get Deps 147 | if: ${{ matrix.run_aptget }} 148 | run: sudo apt-get install -y alsa alsa-tools libasound2-dev libjack-dev libgtk-3-dev 149 | 150 | - name: Install Ninja 151 | if: ${{ matrix.install_ninja }} 152 | uses: seanmiddleditch/gha-setup-ninja@master 153 | 154 | - name: Build project 155 | run: | 156 | cmake -S . -B ./build ${{ matrix.cmakeargs }} -DCMAKE_BUILD_TYPE=Debug -DCLAP_WRAPPER_DOWNLOAD_DEPENDENCIES=TRUE -DCLAP_WRAPPER_BUILD_AUV2=TRUE -DCLAP_WRAPPER_BUILD_STANDALONE=TRUE -DCLAP_WRAPPER_OUTPUT_NAME=downloadplug 157 | cmake --build ./build --config Debug 158 | 159 | - name: Show Build Results 160 | shell: bash 161 | run: | 162 | find build -name downloadplug.vst3 -print 163 | find build -name downloadplug.component -print 164 | 165 | 166 | build_feature_clapfirst-test: 167 | name: ${{ matrix.name }} 168 | runs-on: ${{ matrix.os }} 169 | strategy: 170 | matrix: 171 | include: 172 | - os: windows-latest 173 | cmakeargs: -A x64 174 | install_ninja: false 175 | run_aptget: false 176 | name: ClapFirst Windows 64bit MSVC 177 | 178 | - os: ubuntu-latest 179 | cmakeargs: -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 180 | install_ninja: false 181 | run_aptget: true 182 | name: ClapFirst Linux gcc12 183 | 184 | - os: ubuntu-24.04 185 | cmakeargs: -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_C_COMPILER=gcc-14 186 | install_ninja: false 187 | run_aptget: true 188 | name: ClapFirst Linux ub24 gcc14 189 | 190 | - os: macos-latest 191 | cmakeargs: -G"Xcode" 192 | install_ninja: false 193 | run_aptget: false 194 | name: ClapFirst MacOS Xcode 195 | 196 | 197 | steps: 198 | - name: Checkout code 199 | uses: actions/checkout@v4 200 | with: 201 | submodules: recursive 202 | 203 | - name: Apt Update 204 | if: ${{ matrix.run_aptget }} 205 | run: sudo apt-get update 206 | 207 | - name: Get Deps 208 | if: ${{ matrix.run_aptget }} 209 | run: sudo apt-get install -y alsa alsa-tools libasound2-dev libjack-dev libgtk-3-dev 210 | 211 | - name: Install Ninja 212 | if: ${{ matrix.install_ninja }} 213 | uses: seanmiddleditch/gha-setup-ninja@master 214 | 215 | - name: Build project 216 | run: | 217 | cmake -S . -B ./build ${{ matrix.cmakeargs }} -DCMAKE_BUILD_TYPE=Debug -DCLAP_WRAPPER_DOWNLOAD_DEPENDENCIES=TRUE -DCLAP_WRAPPER_BUILD_TESTS=TRUE 218 | cmake --build ./build --config Debug --target clap-first-distortion_all 219 | 220 | - name: Show Build Results 221 | shell: bash 222 | run: | 223 | find build -name "ClapFirst*" -print 224 | 225 | 226 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | #vs 35 | .vs 36 | out 37 | 38 | #clion 39 | .idea 40 | cmake-build-* 41 | 42 | #baconpaul habit 43 | ignore/ 44 | #df habit 45 | build/ 46 | out/ 47 | .DS_Store 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # clap-wrapper project 3 | # 4 | # Copyright (c) 2022-2024 Timo Kaluza (defiantnerd) 5 | # Paul Walker (baconpaul) 6 | # 7 | # There are a variety of advanced options to configure and build this wrapper, but 8 | # for the simple case of single plugin in a single clap, here are some relevant 9 | # cmake-time options which this wrapper will use. See the documentation for a more 10 | # complete description 11 | # 12 | # CLAP_SDK_ROOT The location of the clap library. Defaults to ../clap 13 | # VST3_SDK_ROOT The location of the VST3 sdk. Defaults tp ../vst3sdk 14 | # 15 | # CLAP_WRAPPER_OUTPUT_NAME The name of the resulting .vst3 or .component 16 | # CLAP_SUPPORTS_ALL_NOTE_EXPRESSIONS The wrapper will forward all VST3 note expressions to your CLAP 17 | # CLAP_VST3_TUID_STRING The VST3 component ::iid as a string; absent this wrapper hashes clap id 18 | # CLAP_WRAPPER_BUNDLE_IDENTIFIER the macOS Bundle Identifier. Absent this it is 'org.cleveraudio.wrapper.(name)' 19 | # CLAP_WRAPPER_BUNDLE_VERSION the macOS Bundle Version. Defaults to 1.0 20 | # CLAP_WRAPPER_WINDOWS_SINGLE_FILE if set to TRUE (default) the windows .vst3 is a single file; false a 3.7 spec folder 21 | # CLAP_WRAPPER_DOWNLOAD_DEPENDENCIES if set will download the needed SDKs using CPM from github 22 | # CLAP_WRAPPER_COPY_AFTER_BUILD if included mac and lin will copy to ~/... (lin t/k) 23 | 24 | cmake_minimum_required(VERSION 3.21) 25 | cmake_policy(SET CMP0091 NEW) 26 | cmake_policy(SET CMP0149 NEW) 27 | if (NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) 28 | message(STATUS "[clap-wrapper]: OSX_DEPLOYEMNT_TARGET is undefined. Setting to 10.13") 29 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "Minimum macOS version") 30 | endif() 31 | if (NOT DEFINED CMAKE_OSX_ARCHITECTURES) 32 | message(STATUS "[clap-wrapper]: CMAKE_OSX_ARCHITECTURES is not set. Using native build for clap wrapper") 33 | endif() 34 | if (NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) 35 | if (WIN32) 36 | message(STATUS "CMAKE_MSVC_RUNTIME_LIBRARY not defined. Setting to static link") 37 | endif() 38 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # Statically link the MSVC Runtime 39 | endif() 40 | set(CMAKE_COLOR_DIAGNOSTICS ON) 41 | 42 | # If your clap supports note expressions you *can* implement the wrapper extension here or you 43 | # can just build with this turned on and it will forward all note expressions to your CLAP 44 | option(CLAP_SUPPORTS_ALL_NOTE_EXPRESSIONS "Does the underlying CLAP support note expressions" OFF) 45 | option(CLAP_WRAPPER_WINDOWS_SINGLE_FILE "Build a single fine (rather than folder) on windows" ON) 46 | option(CLAP_WRAPPER_BUILD_TESTS "Build test CLAP wrappers" OFF) 47 | 48 | project(clap-wrapper 49 | LANGUAGES C CXX 50 | VERSION 0.12.1 51 | DESCRIPTION "CLAP-as-X wrappers" 52 | ) 53 | set(CLAP_WRAPPER_VERSION "${PROJECT_VERSION}" CACHE STRING "Version of the wrapper project") 54 | message(STATUS "clap-wrapper: CLAP_WRAPPER_VERSION is ${CLAP_WRAPPER_VERSION}") 55 | 56 | if (APPLE) 57 | enable_language(OBJC) 58 | enable_language(OBJCXX) 59 | endif() 60 | 61 | if (PROJECT_IS_TOP_LEVEL) 62 | if (DEFINED CLAP_WRAPPER_CXX_STANDARD) 63 | set(CMAKE_CXX_STANDARD ${CLAP_WRAPPER_CXX_STANDARD}) 64 | else() 65 | if (NOT DEFINED CMAKE_CXX_STANDARD) 66 | set(CMAKE_CXX_STANDARD 17) 67 | elseif ( ${CMAKE_CXX_STANDARD} VERSION_LESS 17) 68 | message(WARNING "CMAKE_CXX_STANDARD of ${CMAKE_CXX_STANDARD} < 17 not well tested") 69 | endif() 70 | endif() 71 | endif() 72 | message(STATUS "clap-wrapper: Building with C++ standard ${CMAKE_CXX_STANDARD}") 73 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 74 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 75 | set(CMAKE_OBJC_VISIBILITY_PRESET hidden) 76 | set(CMAKE_OBJCXX_VISIBILITY_PRESET hidden) 77 | set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) 78 | 79 | # discover the plugin paths and enable them. The library 80 | # defines functions relative to this, so needs a cache 81 | # variable with the source dir 82 | set(CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "Clap Wrapper Source Location") 83 | include(cmake/wrapper_functions.cmake) 84 | include(cmake/top_level_default.cmake) 85 | 86 | if (${CLAP_WRAPPER_BUILD_TESTS}) 87 | add_subdirectory(tests) 88 | endif() 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 defiantnerd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *"Wrappers to run your CLAP in other plugin formats instantly."* 2 | 3 | # The clap-wrapper Project 4 | 5 | The `clap-wrapper` project allows you to wrap your CLAP plugin 6 | into other plugin or standalone executable formats, letting your 7 | plugin run in hosts which don't support the CLAP format and 8 | allowing you to distribute standalone executable versions of your 9 | CLAP. 10 | 11 | Our [wiki](https://github.com/free-audio/clap-wrapper/wiki) is the root of our developer documentation. 12 | 13 | Currently the `clap-wrapper` supports projecting a CLAP into 14 | 15 | - VST3 16 | - Audio Unit v2 (AUv2) 17 | - A Simple Standalone 18 | 19 | The `clap-wrapper` also provides a variety of deployment, 20 | linkage and build time options to CLAP developers. 21 | 22 | ## The Core Idea 23 | 24 | The core idea of `clap-wrapper` is as follows 25 | 26 | 1. The `clap-wrapper` is an implementation of a clap host which 27 | will load your CLAP using a variety of techniques. 28 | 2. The `clap-wrapper` provides an implementation of a plugin format 29 | or standalone executable which implements the format to the host 30 | or OS, but uses that host to load your clap. 31 | 32 | And with that, voila, your CLAP can appear in a VST3 or AUv2 33 | host or can appear to be a self contained standalone executable. 34 | Available features in CLAP are transposed to equivalent features 35 | in the target format. 36 | 37 | ## Getting Started 38 | 39 | Our [wiki](https://github.com/free-audio/clap-wrapper/wiki) is the root of our developer documentation. 40 | Since building a `clap-wrapper` involves several choices we 41 | strongly suggest you consult it first. 42 | 43 | The simplest form of a clap wrapper, though, is a VST3 44 | which dynamically loads a CLAP of the same name using your 45 | copy of the VST3 and CLAP SDK. To build this, you can use the 46 | simple CMake command 47 | 48 | ```bash 49 | git clone https://github.com/free-audio/clap-wrapper.git 50 | mkdir build 51 | cmake -B build \ 52 | -DCLAP_SDK_ROOT={path to clap sdk} \ 53 | -DVST3_SDK_ROOT={path to vst3 sdk} \ 54 | -DCLAP_WRAPPER_OUTPUT_NAME="The Name of your CLAP" 55 | ``` 56 | If you'd like to also build for AUv2 include `-DCLAP_WRAPPER_BUILD_AUV2=ON` and the SDK in the folder `AudioUnitSDK`. 57 | 58 | ## Licensing 59 | 60 | The `clap-wrapper` project is released under the MIT license. 61 | Please note that using the `clap-wrapper` project to wrap 62 | a VST3 requires either (1) you use the VST3 SDK under the GPL3 63 | license, and as such are open source or (2) you agree to the 64 | VST3 license to generate the wrapper. 65 | -------------------------------------------------------------------------------- /cmake/VST3_Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | BNDL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | NSHumanReadableCopyright 32 | ${MACOSX_BUNDLE_COPYRIGHT} 33 | 34 | 35 | -------------------------------------------------------------------------------- /cmake/auv2_Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${MACOSX_BUNDLE_BUNDLE_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 23 | NSPrincipalClass 24 | 25 | NSHighResolutionCapable 26 | 27 | AudioComponents 28 | 29 | 30 | name 31 | ${AUV2_MANUFACTURER_NAME} : ${MACOSX_BUNDLE_BUNDLE_NAME} 32 | description 33 | ${MACOSX_BUNDLE_BUNDLE_NAME} Clap to AU Wrapper 34 | factoryFunction 35 | wrapAsAUV2Factory 36 | manufacturer 37 | ${AUV2_MANUFACTURER_CODE} 38 | subtype 39 | ${AUV2_SUBTYPE_CODE} 40 | type 41 | ${AUV2_INSTRUMENT_TYPE} 42 | version 43 | 1 44 | resourceUsage 45 | 46 | network.client 47 | 48 | temporary-exception.files.all.read-write 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /cmake/external/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "C8CDC32C03816538CE22781ED72964DC864B2A34A310D3B7104812A5CA2D835D") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /cmake/macBundlePkgInfo: -------------------------------------------------------------------------------- 1 | BNDL???? -------------------------------------------------------------------------------- /cmake/top_level_default.cmake: -------------------------------------------------------------------------------- 1 | # automatically build the wrapper only if this is the top level project 2 | if (PROJECT_IS_TOP_LEVEL) 3 | # provide the CLAP_WRAPPER_OUTPUT_NAME to specify the matching plugin name. 4 | if((NOT CLAP_WRAPPER_OUTPUT_NAME ) OR (CLAP_WRAPPER_OUTPUT_NAME STREQUAL "")) 5 | set(CLAP_WRAPPER_OUTPUT_NAME "clapasvst3") 6 | message(WARNING "clap-wrapper: CLAP_WRAPPER_OUTPUT_NAME not set - continuing with a default name `clapasvst3`") 7 | endif() 8 | 9 | string(MAKE_C_IDENTIFIER ${CLAP_WRAPPER_OUTPUT_NAME} pluginname) 10 | 11 | # Link the actual plugin library 12 | add_library(${pluginname}_as_vst3 MODULE) 13 | target_add_vst3_wrapper( 14 | TARGET ${pluginname}_as_vst3 15 | OUTPUT_NAME "${CLAP_WRAPPER_OUTPUT_NAME}" 16 | SUPPORTS_ALL_NOTE_EXPRESSIONS $ 17 | SINGLE_PLUGIN_TUID "${CLAP_VST3_TUID_STRING}" 18 | BUNDLE_IDENTIFIER "${CLAP_WRAPPER_BUNDLE_IDENTIFIER}" 19 | BUNDLE_VERSION "${CLAP_WRAPPER_BUNDLE_VERSION}" 20 | ) 21 | 22 | if (APPLE) 23 | if (${CLAP_WRAPPER_BUILD_AUV2}) 24 | add_library(${pluginname}_as_auv2 MODULE) 25 | target_add_auv2_wrapper( 26 | TARGET ${pluginname}_as_auv2 27 | OUTPUT_NAME "${CLAP_WRAPPER_OUTPUT_NAME}" 28 | BUNDLE_IDENTIFIER "${CLAP_WRAPPER_BUNDLE_IDENTIFIER}" 29 | BUNDLE_VERSION "${CLAP_WRAPPER_BUNDLE_VERSION}" 30 | 31 | # AUv2 is still a work in progress. For now make this an 32 | # explict option to the single plugin version 33 | INSTRUMENT_TYPE "aumu" 34 | MANUFACTURER_NAME "cleveraudio.org" 35 | MANUFACTURER_CODE "clAd" 36 | SUBTYPE_CODE "gWrp" 37 | ) 38 | endif() 39 | endif() 40 | 41 | if (${CLAP_WRAPPER_BUILD_STANDALONE}) 42 | add_executable(${pluginname}_as_standalone) 43 | target_add_standalone_wrapper(TARGET ${pluginname}_as_standalone 44 | OUTPUT_NAME ${CLAP_WRAPPER_OUTPUT_NAME} 45 | HOSTED_CLAP_NAME ${CLAP_WRAPPER_OUTPUT_NAME} 46 | PLUGIN_INDEX 0) 47 | endif() 48 | endif() 49 | -------------------------------------------------------------------------------- /cmake/wrap_clap.cmake: -------------------------------------------------------------------------------- 1 | # You may wonder why we have a 'wrap_clap.cmake' when the clap wrapper is 2 | # starting with a CLAP. Well, quite a few things to go from a cmake MODULE 3 | # to a clap are required, including copy after build logic, setting up the 4 | # dll, and so forth. 5 | # 6 | # Rather than copy and paste this code around, we provide a convenience function 7 | # for you symmetric with the other wrapper functions 8 | 9 | 10 | function(target_add_clap_configuration) 11 | set(oneValueArgs 12 | TARGET 13 | OUTPUT_NAME 14 | BUNDLE_IDENTIFIER 15 | BUNDLE_VERSION 16 | ) 17 | cmake_parse_arguments(TCLP "" "${oneValueArgs}" "" ${ARGN} ) 18 | 19 | if (NOT DEFINED TCLP_TARGET) 20 | message(FATAL_ERROR "You must define TARGET in target_library_is_clap") 21 | endif() 22 | 23 | if (NOT DEFINED TCLP_OUTPUT_NAME) 24 | message(STATUS "Using target name as clap name in target_libarry_is_clap") 25 | set(TCLP_OUTPUT_NAME TCLP_TARGET) 26 | endif() 27 | 28 | if (NOT DEFINED TCLP_BUNDLE_IDENTIFIER) 29 | message(STATUS "Using sst default macos bundle prefix in target_libarry_is_clap") 30 | set(TCLP_BUNDLE_IDENTIFIER "clap.${TCLP_TARGET}") 31 | endif() 32 | 33 | if (NOT DEFINED TCLP_BUNDLE_VERSION) 34 | message(STATUS "No bundle version in target_library_is_clap; using 0.1") 35 | set(TCLP_BUNDLE_VERSION "0.1") 36 | endif() 37 | 38 | if(APPLE) 39 | set_target_properties(${TCLP_TARGET} PROPERTIES 40 | BUNDLE True 41 | BUNDLE_EXTENSION clap 42 | LIBRARY_OUTPUT_NAME ${TCLP_OUTPUT_NAME} 43 | MACOSX_BUNDLE TRUE 44 | MACOSX_BUNDLE_GUI_IDENTIFIER ${TCLP_BUNDLE_IDENTIFIER} 45 | MACOSX_BUNDLE_BUNDLE_NAME ${TCLP_OUTPUT_NAME} 46 | MACOSX_BUNDLE_BUNDLE_VERSION "${TCLP_BUNDLE_VERSION}" 47 | MACOSX_BUNDLE_SHORT_VERSION_STRING "${TCLP_BUNDLE_VERSION}" 48 | ) 49 | 50 | macos_bundle_flag(TARGET ${TCLP_TARGET}) 51 | 52 | set_target_properties(${TCLP_TARGET} 53 | PROPERTIES 54 | MACOSX_BUNDLE TRUE 55 | XCODE_ATTRIBUTE_GENERATE_PKGINFO_FILE "YES" 56 | ) 57 | elseif(UNIX) 58 | set_target_properties(${TCLP_TARGET} 59 | PROPERTIES 60 | OUTPUT_NAME ${TCLP_OUTPUT_NAME} 61 | SUFFIX ".clap" 62 | PREFIX "") 63 | else() 64 | set_target_properties(${TCLP_TARGET} 65 | PROPERTIES 66 | OUTPUT_NAME ${TCLP_OUTPUT_NAME} 67 | SUFFIX ".clap" 68 | PREFIX "" 69 | LIBRARY_OUTPUT_DIRECTORY CLAP 70 | ) 71 | endif() 72 | 73 | if (${CLAP_WRAPPER_COPY_AFTER_BUILD}) 74 | target_copy_after_build(TARGET ${TCLP_TARGET} FLAVOR clap) 75 | endif() 76 | endfunction(target_add_clap_configuration) 77 | -------------------------------------------------------------------------------- /cmake/wrap_standalone.cmake: -------------------------------------------------------------------------------- 1 | 2 | function(target_add_standalone_wrapper) 3 | set(oneValueArgs 4 | TARGET 5 | OUTPUT_NAME 6 | BUNDLE_IDENTIFIER 7 | BUNDLE_VERSION 8 | 9 | PLUGIN_INDEX 10 | PLUGIN_ID 11 | STATICALLY_LINKED_CLAP_ENTRY 12 | HOSTED_CLAP_NAME 13 | 14 | WINDOWS_ICON 15 | MACOS_ICON 16 | 17 | MACOS_EMBEDDED_CLAP_LOCATION 18 | ) 19 | cmake_parse_arguments(SA "" "${oneValueArgs}" "" ${ARGN} ) 20 | 21 | if (NOT DEFINED SA_TARGET) 22 | message(FATAL_ERROR "clap-wrapper: target_add_standalone_wrapper requires a target") 23 | endif() 24 | 25 | if (NOT TARGET ${SA_TARGET}) 26 | message(FATAL_ERROR "clap-wrapper: standalone-target must be a target") 27 | endif() 28 | 29 | if (NOT DEFINED SA_PLUGIN_ID) 30 | set(SA_PLUGIN_ID "") 31 | endif() 32 | 33 | if (NOT DEFINED SA_PLUGIN_INDEX) 34 | set(SA_PLUGIN_INDEX 0) 35 | endif() 36 | 37 | if (NOT DEFINED SA_OUTPUT_NAME) 38 | set(SA_OUTPUT_NAME ${SA_TARGET}) 39 | endif() 40 | 41 | if (NOT DEFINED SA_WINDOWS_ICON) 42 | set(SA_WINDOWS_ICON "") 43 | endif() 44 | 45 | if (NOT DEFINED SA_MACOS_ICON) 46 | set(SA_MACOS_ICON "") 47 | endif() 48 | 49 | guarantee_rtaudio() 50 | guarantee_rtmidi() 51 | 52 | set(salib ${SA_TARGET}-clap-wrapper-standalone-lib) 53 | add_library(${salib} STATIC 54 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/entry.cpp 55 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/standalone_host.cpp 56 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/standalone_host_audio.cpp 57 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/standalone_host_midi.cpp 58 | ) 59 | target_link_libraries(${salib} 60 | PUBLIC 61 | clap-wrapper-shared-detail 62 | base-sdk-rtmidi 63 | base-sdk-rtaudio 64 | ) 65 | target_link_libraries(${salib} PRIVATE clap-wrapper-compile-options) 66 | 67 | if (APPLE) 68 | target_sources(${salib} PRIVATE) 69 | target_link_libraries(${salib} 70 | PUBLIC "-framework AVFoundation" "-framework Foundation" "-framework CoreFoundation" "-framework AppKit") 71 | 72 | endif() 73 | 74 | if (APPLE) 75 | set(MAIN_XIB "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/macos/MainMenu.xib") 76 | set(GEN_XIB "${CMAKE_BINARY_DIR}/generated_xib/${SA_TARGET}/MainMenu.xib") 77 | configure_file(${MAIN_XIB} ${GEN_XIB}) 78 | 79 | target_sources(${SA_TARGET} PRIVATE 80 | "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone.mm" 81 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/macos/AppDelegate.mm 82 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/macos/StandaloneFunctions.mm 83 | ${GEN_XIB} 84 | ) 85 | 86 | set_target_properties(${SA_TARGET} PROPERTIES 87 | BUNDLE TRUE 88 | BUNDLE_NAME ${SA_OUTPUT_NAME} 89 | BUNDLE_EXTENSION app 90 | OUTPUT_NAME ${SA_OUTPUT_NAME} 91 | MACOSX_BUNDLE_BUNDLE_NAME ${SA_OUTPUT_NAME} 92 | MACOSX_BUNDLE TRUE 93 | MACOSX_BUNDLE_INFO_PLIST ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/macos/Info.plist.in 94 | RESOURCE "${GEN_XIB}" 95 | ) 96 | 97 | if (NOT ${CMAKE_GENERATOR} STREQUAL "Xcode") 98 | message(STATUS "cmake-wrapper: ejecting xib->nib rules manually for ${CMAKE_GENERATOR} on ${SA_TARGET}") 99 | find_program(IBTOOL ibtool REQUIRED) 100 | add_custom_command(TARGET ${SA_TARGET} PRE_BUILD 101 | COMMAND ${CMAKE_COMMAND} -E echo ${IBTOOL} --compile "$/../Resources/MainMenu.nib" ${GEN_XIB} 102 | COMMAND ${IBTOOL} --compile "$/../Resources/MainMenu.nib" ${GEN_XIB} 103 | ) 104 | endif() 105 | 106 | if(NOT "${SA_MACOS_ICON}" STREQUAL "") 107 | add_custom_command(TARGET ${SA_TARGET} POST_BUILD 108 | COMMAND ${CMAKE_COMMAND} -E copy ${SA_MACOS_ICON} "$/../Resources/Icon.icns" 109 | ) 110 | endif() 111 | 112 | macos_include_clap_in_bundle(TARGET ${SA_TARGET} 113 | MACOS_EMBEDDED_CLAP_LOCATION ${SA_MACOS_EMBEDDED_CLAP_LOCATION}) 114 | 115 | elseif(WIN32 AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) 116 | guarantee_wil() 117 | 118 | if(NOT "${SA_WINDOWS_ICON}" STREQUAL "") 119 | file(WRITE "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.rc" "1 ICON \"windows_standalone_${SA_TARGET}.ico\"") 120 | file(COPY_FILE ${SA_WINDOWS_ICON} "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.ico") 121 | target_sources(${SA_TARGET} PRIVATE 122 | "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.rc" 123 | ) 124 | endif() 125 | 126 | target_sources(${SA_TARGET} PRIVATE 127 | "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone_windows.cpp" 128 | "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/windows_standalone.cpp" 129 | "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/windows_standalone.manifest" 130 | ) 131 | 132 | target_compile_definitions(${salib} PUBLIC 133 | CLAP_WRAPPER_HAS_WIN32 134 | UNICODE 135 | NOMINMAX 136 | ) 137 | 138 | set_target_properties(${SA_TARGET} PROPERTIES 139 | WIN32_EXECUTABLE TRUE 140 | OUTPUT_NAME ${SA_OUTPUT_NAME} 141 | ) 142 | 143 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 144 | target_compile_definitions(${SA_TARGET} PRIVATE _SILENCE_CLANG_COROUTINE_MESSAGE) 145 | endif() 146 | 147 | if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") 148 | target_link_options( 149 | ${SA_TARGET} 150 | PRIVATE 151 | /entry:mainCRTStartup 152 | ) 153 | elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") 154 | target_link_options( 155 | ${SA_TARGET} 156 | PRIVATE 157 | -Wl,/entry:mainCRTStartup 158 | ) 159 | endif() 160 | 161 | target_link_libraries(${SA_TARGET} PRIVATE base-sdk-wil ComCtl32.Lib) 162 | 163 | elseif(UNIX) 164 | target_sources(${SA_TARGET} PRIVATE 165 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone.cpp) 166 | 167 | find_package(PkgConfig REQUIRED) 168 | pkg_check_modules(GTK gtkmm-3.0) 169 | if (${GTK_FOUND}) 170 | message(STATUS "clap-wrapper: using gtkmm-3.0 for standalone") 171 | target_compile_options(${salib} PUBLIC ${GTK_CFLAGS}) 172 | target_include_directories(${salib} PUBLIC ${GTK_INCLUDE_DIRS}) 173 | target_link_libraries(${salib} PUBLIC ${GTK_LINK_LIBRARIES}) 174 | target_compile_definitions(${salib} PUBLIC CLAP_WRAPPER_HAS_GTK3) 175 | target_sources(${salib} PRIVATE ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/linux/gtkutils.cpp) 176 | else() 177 | message(STATUS "clap-wrapper: can't find gtkmm-3.0; no ui in standalone") 178 | endif() 179 | 180 | set_target_properties(${SA_TARGET} PROPERTIES OUTPUT_NAME ${SA_OUTPUT_NAME}) 181 | 182 | else() 183 | target_sources(${SA_TARGET} PRIVATE 184 | ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone.cpp) 185 | endif() 186 | 187 | if (DEFINED SA_HOSTED_CLAP_NAME) 188 | set(hasclapname TRUE) 189 | endif() 190 | target_compile_definitions(${SA_TARGET} PRIVATE 191 | PLUGIN_ID="${SA_PLUGIN_ID}" 192 | PLUGIN_INDEX=${SA_PLUGIN_INDEX} 193 | $<$:STATICALLY_LINKED_CLAP_ENTRY=1> 194 | $<$:HOSTED_CLAP_NAME="${SA_HOSTED_CLAP_NAME}"> 195 | OUTPUT_NAME="${SA_OUTPUT_NAME}" 196 | ) 197 | 198 | target_link_libraries(${SA_TARGET} PRIVATE 199 | ${salib} 200 | ) 201 | endfunction(target_add_standalone_wrapper) 202 | -------------------------------------------------------------------------------- /cmake/wrap_wclap_emscripten.cmake: -------------------------------------------------------------------------------- 1 | # This adds a WCLAP target, but must only be called when using Emscripten's toolchain. 2 | 3 | function(target_add_wclap_configuration) 4 | set(oneValueArgs 5 | TARGET 6 | OUTPUT_NAME 7 | ) 8 | cmake_parse_arguments(TCLP "" "${oneValueArgs}" "" ${ARGN} ) 9 | 10 | if (NOT EMSCRIPTEN) 11 | message(FATAL_ERROR "Do not call this outside the Emscripten toolchain") 12 | endif() 13 | 14 | if (NOT DEFINED TCLP_TARGET) 15 | message(FATAL_ERROR "You must define TARGET in target_library_is_clap") 16 | endif() 17 | 18 | if (NOT DEFINED TCLP_OUTPUT_NAME) 19 | message(STATUS "Using target name as clap name in target_library_is_clap") 20 | set(TCLP_OUTPUT_NAME TCLP_TARGET) 21 | endif() 22 | 23 | set_target_properties(${TCLP_TARGET} 24 | PROPERTIES 25 | OUTPUT_NAME "${TCLP_OUTPUT_NAME}" 26 | SUFFIX ".wclap/module.wasm" 27 | PREFIX "" 28 | ) 29 | # Make sure directory exists 30 | add_custom_command(TARGET ${TCLP_TARGET} PRE_BUILD 31 | COMMAND ${CMAKE_COMMAND} -E make_directory "$" 32 | ) 33 | 34 | if (${CLAP_WRAPPER_COPY_AFTER_BUILD}) 35 | target_copy_after_build(TARGET ${TCLP_TARGET} FLAVOR wclap) 36 | endif() 37 | endfunction(target_add_wclap_configuration) 38 | -------------------------------------------------------------------------------- /cmake/wrapper_functions.cmake: -------------------------------------------------------------------------------- 1 | # This is the meat of the clap wrapper cmake (which does quite a lot) 2 | # 3 | # The library is broken into several chunks 4 | # 5 | # shared_prologue defines things like platform, utility macros, and compile options interface. 6 | # It also provides a function to eject the shared clap wrapper base targets 7 | # 8 | # base sdks provides mechanisms to resolve our code dependencies, CLAP, VST3, AudioUnitSDK, 9 | # RtAudio and RtMidi providing a library like `base-sdk-vst3` target after you call the 10 | # enablement function `guarantee_vst3sdk()`. Our pattern is to require a call to guarantee 11 | # to create the target, so dependencies are lazy / as needed 12 | # 13 | # We then make sure that clap and clap shared are guaranteed and then load each of the wrap 14 | # functions 15 | # 16 | # wrap_xyz.cmake provides a function target_add_xyz_wrapper. The arguments to these are relatively 17 | # uniform (each takes a TARGET and OUTPUT_NAME) but are varied based on the build modes and requirements 18 | # of each format XYZ. 19 | 20 | include(cmake/shared_prologue.cmake) 21 | include(cmake/base_sdks.cmake) 22 | 23 | guarantee_clap() 24 | guarantee_clap_wrapper_shared() 25 | 26 | 27 | include(cmake/wrap_auv2.cmake) 28 | include(cmake/wrap_vst3.cmake) 29 | include(cmake/wrap_standalone.cmake) 30 | include(cmake/wrap_clap.cmake) 31 | include(cmake/wrap_wclap_emscripten.cmake) 32 | 33 | include(cmake/make_clapfirst.cmake) 34 | -------------------------------------------------------------------------------- /include/clapwrapper/auv2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "clap/private/macros.h" 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" 8 | { 9 | #endif 10 | 11 | static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_INFO_AUV2[] = 12 | "clap.plugin-factory-info-as-auv2.draft0"; 13 | 14 | typedef struct clap_plugin_info_as_auv2 15 | { 16 | char au_type[5]; // the au_type. If empty (best choice) use the features[0] to aumu aufx aumi 17 | char au_subt[5]; // the subtype. If empty (worst choice) we try a bad 32 bit hash of the id 18 | } clap_plugin_info_as_auv2_t; 19 | 20 | typedef struct clap_plugin_factory_as_auv2 21 | { 22 | // optional values for the Steinberg::PFactoryInfo structure 23 | const char *manufacturer_code; // your 'manu' field 24 | const char *manufacturer_name; // your manufacturer display name 25 | 26 | // populate information about this particular auv2. If this method returns 27 | // false, the CLAP Plugin at the given index will not be exported into the 28 | // resulting AUv2 29 | bool(CLAP_ABI *get_auv2_info)(const clap_plugin_factory_as_auv2 *factory, uint32_t index, 30 | clap_plugin_info_as_auv2_t *info); 31 | } clap_plugin_factory_as_auv2_t; 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /include/clapwrapper/vst3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "clap/private/macros.h" 4 | 5 | /* 6 | Some information for the VST3 factory/plugin structures can not 7 | be derived from the clap headers or structures. While in a pristine 8 | CLAP those information can be generated, you will need those informations 9 | fixed when maintaining compatibility with previously published 10 | versions. 11 | */ 12 | 13 | // CLAP_ABI was introduced in CLAP 1.1.2, for older versions we make it transparent 14 | #ifndef CLAP_ABI 15 | #define CLAP_ABI 16 | #endif 17 | 18 | #ifdef __cplusplus 19 | extern "C" 20 | { 21 | #endif 22 | 23 | // the factory extension 24 | static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_INFO_VST3[] = 25 | "clap.plugin-factory-info-as-vst3/0"; 26 | 27 | // the plugin extension 28 | static const CLAP_CONSTEXPR char CLAP_PLUGIN_AS_VST3[] = "clap.plugin-info-as-vst3/0"; 29 | 30 | typedef uint8_t array_of_16_bytes[16]; 31 | 32 | // clang-format off 33 | // COMPONENT allows you to provide the 4 uint32_t parts of the GUID and transforms them to the 16 byte array 34 | // To use this to port an existing VST3 35 | // 1. Run the vst3 validator 36 | // 2. Find the line cid = 30D2C648CCAABA57976D20DEFF9B93C1 in the output 37 | // 3. Implement this extension to provide an info with (for example) 38 | // static array_of_16_bytes cid COMPONENT_ID(0x30D2C648, 0xCCAABA57, 0x976D20DE, 0xFF9B93C1); 39 | // static clap_plugin_info_as_vst3 info { 40 | // "Vendor", 41 | // &cid, 42 | // "" 43 | // }; 44 | // return &info; 45 | #define COMPONENT_ID(g1, g2, g3, g4) \ 46 | { \ 47 | (uint8_t)((g1 & 0x000000FF) ), \ 48 | (uint8_t)((g1 & 0x0000FF00) >> 8), \ 49 | (uint8_t)((g1 & 0x00FF0000) >> 16), \ 50 | (uint8_t)((g1 & 0xFF000000) >> 24), \ 51 | (uint8_t)((g2 & 0x00FF0000) >> 16), \ 52 | (uint8_t)((g2 & 0xFF000000) >> 24), \ 53 | (uint8_t)((g2 & 0x000000FF) ), \ 54 | (uint8_t)((g2 & 0x0000FF00) >> 8), \ 55 | (uint8_t)((g3 & 0xFF000000) >> 24), \ 56 | (uint8_t)((g3 & 0x00FF0000) >> 16), \ 57 | (uint8_t)((g3 & 0x0000FF00) >> 8), \ 58 | (uint8_t)((g3 & 0x000000FF) ), \ 59 | (uint8_t)((g4 & 0xFF000000) >> 24), \ 60 | (uint8_t)((g4 & 0x00FF0000) >> 16), \ 61 | (uint8_t)((g4 & 0x0000FF00) >> 8), \ 62 | (uint8_t)((g4 & 0x000000FF) ), \ 63 | } 64 | 65 | // clang-format on 66 | 67 | /* 68 | clap_plugin_as_vst3 69 | 70 | all members are optional when set to nullptr 71 | if not provided, the wrapper code will use/generate appropriate values 72 | 73 | this struct is being returned by the plugin in clap_plugin_factory_as_vst3_t::get_vst3_info() 74 | */ 75 | 76 | typedef struct clap_plugin_info_as_vst3 77 | { 78 | const char* vendor; // vendor 79 | const array_of_16_bytes* componentId; // compatibility GUID 80 | const char* features; // feature string for SubCategories 81 | } clap_plugin_info_as_vst3_t; 82 | 83 | /* 84 | clap_plugin_factory_as_vst3 85 | 86 | all members are optional and can be set to nullptr 87 | if not provided, the wrapper code will use/generate appropriate values 88 | 89 | retrieved when asking for factory CLAP_PLUGIN_FACTORY_INFO_VST3 by clap_entry::get_factory() 90 | */ 91 | 92 | typedef struct clap_plugin_factory_as_vst3 93 | { 94 | // optional values for the Steinberg::PFactoryInfo structure 95 | const char* vendor; // if not nullptr, the vendor string in the 96 | const char* vendor_url; 97 | const char* email_contact; 98 | 99 | // retrieve additional information for the Steinberg::PClassInfo2 struct by pointer to clap_plugin_as_vst3 100 | // returns nullptr if no additional information is provided or can be a nullptr itself 101 | const clap_plugin_info_as_vst3_t*(CLAP_ABI* get_vst3_info)( 102 | const clap_plugin_factory_as_vst3* factory, uint32_t index); 103 | } clap_plugin_factory_as_vst3_t; 104 | 105 | enum clap_supported_note_expressions 106 | { 107 | AS_VST3_NOTE_EXPRESSION_VOLUME = 1 << 0, 108 | AS_VST3_NOTE_EXPRESSION_PAN = 1 << 1, 109 | AS_VST3_NOTE_EXPRESSION_TUNING = 1 << 2, 110 | AS_VST3_NOTE_EXPRESSION_VIBRATO = 1 << 3, 111 | AS_VST3_NOTE_EXPRESSION_EXPRESSION = 1 << 4, 112 | AS_VST3_NOTE_EXPRESSION_BRIGHTNESS = 1 << 5, 113 | AS_VST3_NOTE_EXPRESSION_PRESSURE = 1 << 6, 114 | 115 | AS_VST3_NOTE_EXPRESSION_ALL = (1 << 7) - 1 // just the and of the above 116 | 117 | }; 118 | 119 | /* 120 | retrieve additional information for the plugin itself, if note expressions are being supported and if there 121 | is a limit in MIDI channels (to reduce the offered controllers etc. in the VST3 host) 122 | 123 | This extension is optionally returned by the plugin when asked for extension CLAP_PLUGIN_AS_VST3 124 | */ 125 | typedef struct clap_plugin_as_vst3 126 | { 127 | uint32_t(CLAP_ABI* getNumMIDIChannels)(const clap_plugin* plugin, 128 | uint32_t note_port); // return 1-16 129 | uint32_t(CLAP_ABI* supportedNoteExpressions)( 130 | const clap_plugin* plugin); // returns a bitmap of clap_supported_note_expressions 131 | } clap_plugin_as_vst3_t; 132 | 133 | #ifdef __cplusplus 134 | } 135 | #endif 136 | -------------------------------------------------------------------------------- /libs/fmt/README.md: -------------------------------------------------------------------------------- 1 | This is a copy of the fmt:: library from github. License is in header files in 2 | the "fmt" directory 3 | 4 | -------------------------------------------------------------------------------- /libs/fmt/fmt/ostream.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - std::ostream support 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_OSTREAM_H_ 9 | #define FMT_OSTREAM_H_ 10 | 11 | #ifndef FMT_MODULE 12 | # include // std::filebuf 13 | #endif 14 | 15 | #ifdef _WIN32 16 | # ifdef __GLIBCXX__ 17 | # include 18 | # include 19 | # endif 20 | # include 21 | #endif 22 | 23 | #include "chrono.h" // formatbuf 24 | 25 | #ifdef _MSVC_STL_UPDATE 26 | # define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE 27 | #elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 28 | # define FMT_MSVC_STL_UPDATE _MSVC_LANG 29 | #else 30 | # define FMT_MSVC_STL_UPDATE 0 31 | #endif 32 | 33 | FMT_BEGIN_NAMESPACE 34 | namespace detail { 35 | 36 | // Generate a unique explicit instantion in every translation unit using a tag 37 | // type in an anonymous namespace. 38 | namespace { 39 | struct file_access_tag {}; 40 | } // namespace 41 | template 42 | class file_access { 43 | friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } 44 | }; 45 | 46 | #if FMT_MSVC_STL_UPDATE 47 | template class file_access; 49 | auto get_file(std::filebuf&) -> FILE*; 50 | #endif 51 | 52 | // Write the content of buf to os. 53 | // It is a separate function rather than a part of vprint to simplify testing. 54 | template 55 | void write_buffer(std::basic_ostream& os, buffer& buf) { 56 | const Char* buf_data = buf.data(); 57 | using unsigned_streamsize = make_unsigned_t; 58 | unsigned_streamsize size = buf.size(); 59 | unsigned_streamsize max_size = to_unsigned(max_value()); 60 | do { 61 | unsigned_streamsize n = size <= max_size ? size : max_size; 62 | os.write(buf_data, static_cast(n)); 63 | buf_data += n; 64 | size -= n; 65 | } while (size != 0); 66 | } 67 | 68 | template struct streamed_view { 69 | const T& value; 70 | }; 71 | } // namespace detail 72 | 73 | // Formats an object of type T that has an overloaded ostream operator<<. 74 | template 75 | struct basic_ostream_formatter : formatter, Char> { 76 | void set_debug_format() = delete; 77 | 78 | template 79 | auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { 80 | auto buffer = basic_memory_buffer(); 81 | auto&& formatbuf = detail::formatbuf>(buffer); 82 | auto&& output = std::basic_ostream(&formatbuf); 83 | output.imbue(std::locale::classic()); // The default is always unlocalized. 84 | output << value; 85 | output.exceptions(std::ios_base::failbit | std::ios_base::badbit); 86 | return formatter, Char>::format( 87 | {buffer.data(), buffer.size()}, ctx); 88 | } 89 | }; 90 | 91 | using ostream_formatter = basic_ostream_formatter; 92 | 93 | template 94 | struct formatter, Char> 95 | : basic_ostream_formatter { 96 | template 97 | auto format(detail::streamed_view view, Context& ctx) const 98 | -> decltype(ctx.out()) { 99 | return basic_ostream_formatter::format(view.value, ctx); 100 | } 101 | }; 102 | 103 | /** 104 | * Returns a view that formats `value` via an ostream `operator<<`. 105 | * 106 | * **Example**: 107 | * 108 | * fmt::print("Current thread id: {}\n", 109 | * fmt::streamed(std::this_thread::get_id())); 110 | */ 111 | template 112 | constexpr auto streamed(const T& value) -> detail::streamed_view { 113 | return {value}; 114 | } 115 | 116 | inline void vprint(std::ostream& os, string_view fmt, format_args args) { 117 | auto buffer = memory_buffer(); 118 | detail::vformat_to(buffer, fmt, args); 119 | FILE* f = nullptr; 120 | #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI 121 | if (auto* buf = dynamic_cast(os.rdbuf())) 122 | f = detail::get_file(*buf); 123 | #elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI 124 | auto* rdbuf = os.rdbuf(); 125 | if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) 126 | f = sfbuf->file(); 127 | else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) 128 | f = fbuf->file(); 129 | #endif 130 | #ifdef _WIN32 131 | if (f) { 132 | int fd = _fileno(f); 133 | if (_isatty(fd)) { 134 | os.flush(); 135 | if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; 136 | } 137 | } 138 | #endif 139 | detail::ignore_unused(f); 140 | detail::write_buffer(os, buffer); 141 | } 142 | 143 | /** 144 | * Prints formatted data to the stream `os`. 145 | * 146 | * **Example**: 147 | * 148 | * fmt::print(cerr, "Don't {}!", "panic"); 149 | */ 150 | FMT_EXPORT template 151 | void print(std::ostream& os, format_string fmt, T&&... args) { 152 | fmt::vargs vargs = {{args...}}; 153 | if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs); 154 | auto buffer = memory_buffer(); 155 | detail::vformat_to(buffer, fmt.str, vargs); 156 | detail::write_buffer(os, buffer); 157 | } 158 | 159 | FMT_EXPORT template 160 | void println(std::ostream& os, format_string fmt, T&&... args) { 161 | fmt::print(os, "{}\n", fmt::format(fmt, std::forward(args)...)); 162 | } 163 | 164 | FMT_END_NAMESPACE 165 | 166 | #endif // FMT_OSTREAM_H_ 167 | -------------------------------------------------------------------------------- /src/clap_proxy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | CLAP Proxy 5 | 6 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 7 | 8 | This file is part of the clap-wrappers project which is released under MIT License. 9 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 10 | 11 | The CLAP Proxy class interfaces the actual CLAP plugin and the serves the IHost interface which 12 | is implemented by the different wrapper flavors. 13 | 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #if WIN 24 | #include 25 | #endif 26 | 27 | #include "detail/clap/fsutil.h" 28 | 29 | namespace Clap 30 | { 31 | class Plugin; 32 | class Raise; 33 | 34 | // the IHost interface is being implemented by the actual wrapper class 35 | class IHost 36 | { 37 | public: 38 | virtual ~IHost() = default; 39 | 40 | virtual void mark_dirty() = 0; 41 | virtual void restartPlugin() = 0; 42 | virtual void request_callback() = 0; 43 | 44 | virtual void setupWrapperSpecifics( 45 | const clap_plugin_t* plugin) = 0; // called when a wrapper could scan for wrapper specific plugins 46 | 47 | virtual void setupAudioBusses( 48 | const clap_plugin_t* plugin, 49 | const clap_plugin_audio_ports_t* 50 | audioports) = 0; // called from initialize() to allow the setup of audio ports 51 | virtual void setupMIDIBusses( 52 | const clap_plugin_t* plugin, 53 | const clap_plugin_note_ports_t* 54 | noteports) = 0; // called from initialize() to allow the setup of MIDI ports 55 | virtual void setupParameters(const clap_plugin_t* plugin, const clap_plugin_params_t* params) = 0; 56 | 57 | virtual void param_rescan(clap_param_rescan_flags flags) = 0; // ext_host_params 58 | virtual void param_clear(clap_id param, clap_param_clear_flags flags) = 0; 59 | virtual void param_request_flush() = 0; 60 | 61 | virtual void latency_changed() = 0; 62 | virtual void tail_changed() = 0; 63 | 64 | virtual bool gui_can_resize() = 0; 65 | virtual bool gui_request_resize(uint32_t width, uint32_t height) = 0; 66 | virtual bool gui_request_show() = 0; 67 | virtual bool gui_request_hide() = 0; 68 | 69 | virtual bool register_timer(uint32_t period_ms, clap_id* timer_id) = 0; 70 | virtual bool unregister_timer(clap_id timer_id) = 0; 71 | 72 | virtual const char* host_get_name() = 0; 73 | 74 | // context menu 75 | 76 | // actually, everything here should be virtual only, but until all wrappers are updated, 77 | // IHost provides default implementations. 78 | 79 | virtual bool supportsContextMenu() const = 0; 80 | virtual bool context_menu_populate(const clap_context_menu_target_t* target, 81 | const clap_context_menu_builder_t* builder) = 0; 82 | virtual bool context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id) = 0; 83 | virtual bool context_menu_can_popup() = 0; 84 | virtual bool context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, 85 | int32_t x, int32_t y) = 0; 86 | 87 | #if LIN 88 | virtual bool register_fd(int fd, clap_posix_fd_flags_t flags) = 0; 89 | virtual bool modify_fd(int fd, clap_posix_fd_flags_t flags) = 0; 90 | virtual bool unregister_fd(int fd) = 0; 91 | #endif 92 | }; 93 | 94 | struct ClapPluginExtensions; 95 | 96 | struct AudioSetup 97 | { 98 | double sampleRate = 44100.; 99 | uint32_t minFrames = 0; 100 | uint32_t maxFrames = 0; 101 | }; 102 | 103 | struct ClapPluginExtensions 104 | { 105 | const clap_plugin_state_t* _state = nullptr; 106 | const clap_plugin_params_t* _params = nullptr; 107 | const clap_plugin_audio_ports_t* _audioports = nullptr; 108 | const clap_plugin_gui_t* _gui = nullptr; 109 | const clap_plugin_note_ports_t* _noteports = nullptr; 110 | const clap_plugin_latency_t* _latency = nullptr; 111 | const clap_plugin_render_t* _render = nullptr; 112 | const clap_plugin_tail_t* _tail = nullptr; 113 | const clap_plugin_timer_support_t* _timer = nullptr; 114 | const clap_plugin_context_menu_t* _contextmenu = nullptr; 115 | const clap_ara_plugin_extension_t* _ara = nullptr; 116 | #if LIN 117 | const clap_plugin_posix_fd_support* _posixfd = nullptr; 118 | #endif 119 | }; 120 | 121 | class Raise 122 | { 123 | public: 124 | Raise(std::atomic& counter) : ctx(counter) 125 | { 126 | ++ctx; 127 | } 128 | ~Raise() 129 | { 130 | ctx--; 131 | } 132 | 133 | private: 134 | std::atomic& ctx; 135 | }; 136 | 137 | /// 138 | /// Plugin is the `host` for the CLAP plugin instance 139 | /// and the interface for the VST3 plugin wrapper 140 | /// 141 | class Plugin 142 | { 143 | public: 144 | static std::shared_ptr createInstance(const clap_plugin_factory*, const std::string& id, 145 | IHost* host); 146 | static std::shared_ptr createInstance(const clap_plugin_factory*, size_t idx, IHost* host); 147 | static std::shared_ptr createInstance(Clap::Library& library, size_t index, IHost* host); 148 | 149 | protected: 150 | // only the Clap::Library is allowed to create instances 151 | Plugin(IHost* host); 152 | const clap_host_t* getClapHostInterface() 153 | { 154 | return &_host; 155 | } 156 | void connectClap(const clap_plugin_t* clap); 157 | 158 | public: 159 | Plugin(const Plugin&) = delete; 160 | Plugin(Plugin&&) = delete; 161 | Plugin& operator=(const Plugin&) = delete; 162 | Plugin& operator=(Plugin&&) = delete; 163 | ~Plugin(); 164 | 165 | void schnick(); 166 | bool initialize(); 167 | void terminate(); 168 | void setSampleRate(double sampleRate); 169 | double getSampleRate() const 170 | { 171 | return _audioSetup.sampleRate; 172 | } 173 | void setBlockSizes(uint32_t minFrames, uint32_t maxFrames); 174 | 175 | bool load(const clap_istream_t* stream) const; 176 | bool save(const clap_ostream_t* stream) const; 177 | bool activate() const; 178 | void deactivate() const; 179 | bool start_processing(); 180 | void stop_processing(); 181 | void reset(); 182 | // void process(const clap_process_t* data); 183 | const clap_plugin_gui_t* getUI() const; 184 | 185 | ClapPluginExtensions _ext; 186 | const clap_plugin_t* _plugin = nullptr; 187 | void log(clap_log_severity severity, const char* msg); 188 | 189 | // threadcheck 190 | bool is_main_thread() const; 191 | bool is_audio_thread() const; 192 | 193 | // param 194 | void param_rescan(clap_param_rescan_flags flags); 195 | void param_clear(clap_id param, clap_param_clear_flags flags); 196 | void param_request_flush(); 197 | 198 | // state 199 | void mark_dirty(); 200 | 201 | // latency 202 | void latency_changed(); 203 | 204 | // tail 205 | void tail_changed(); 206 | 207 | // context_menu 208 | bool context_menu_populate(const clap_context_menu_target_t* target, 209 | const clap_context_menu_builder_t* builder); 210 | bool context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id); 211 | bool context_menu_can_popup(); 212 | bool context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, int32_t x, 213 | int32_t y); 214 | 215 | // hostgui 216 | void resize_hints_changed() 217 | { 218 | } 219 | bool request_resize(uint32_t width, uint32_t height) 220 | { 221 | return _parentHost->gui_request_resize(width, height); 222 | } 223 | bool request_show() 224 | { 225 | return _parentHost->gui_request_show(); 226 | } 227 | bool request_hide() 228 | { 229 | return false; 230 | } 231 | void closed(bool was_destroyed) 232 | { 233 | } 234 | 235 | // clap_timer support 236 | bool register_timer(uint32_t period_ms, clap_id* timer_id); 237 | bool unregister_timer(clap_id timer_id); 238 | 239 | #if LIN 240 | bool register_fd(int fd, clap_posix_fd_flags_t flags); 241 | bool modify_fd(int fd, clap_posix_fd_flags_t flags); 242 | bool unregister_fd(int fd); 243 | 244 | #endif 245 | CLAP_NODISCARD Raise AlwaysAudioThread(); 246 | CLAP_NODISCARD Raise AlwaysMainThread(); 247 | 248 | private: 249 | static const void* clapExtension(const clap_host* host, const char* extension); 250 | static void clapRequestCallback(const clap_host* host); 251 | static void clapRequestRestart(const clap_host* host); 252 | static void clapRequestProcess(const clap_host* host); 253 | 254 | //static bool clapIsMainThread(const clap_host* host); 255 | //static bool clapIsAudioThread(const clap_host* host); 256 | 257 | clap_host_t _host; // the host_t structure for the proxy 258 | IHost* _parentHost = nullptr; 259 | const std::thread::id _main_thread_id = std::this_thread::get_id(); 260 | std::atomic _audio_thread_override = 0; 261 | std::atomic _main_thread_override = 0; 262 | 263 | AudioSetup _audioSetup; 264 | }; 265 | 266 | class StateMemento 267 | { 268 | public: 269 | void clear() 270 | { 271 | _mem.clear(); 272 | } 273 | void rewind() 274 | { 275 | _readoffset = 0; 276 | } 277 | const uint8_t* data() 278 | { 279 | return _mem.data(); 280 | } 281 | size_t size() 282 | { 283 | return _mem.size(); 284 | } 285 | void setData(const uint8_t* data, size_t size); 286 | operator const clap_ostream_t*() 287 | { 288 | return &_outstream; 289 | } 290 | operator const clap_istream_t*() 291 | { 292 | return &_instream; 293 | } 294 | 295 | private: 296 | uint64_t _readoffset = 0; 297 | static int64_t CLAP_ABI _read(const struct clap_istream* stream, void* buffer, uint64_t size); 298 | static int64_t CLAP_ABI _write(const struct clap_ostream* stream, const void* buffer, uint64_t size); 299 | clap_ostream_t _outstream = {this, _write}; 300 | clap_istream_t _instream = {this, _read}; 301 | std::vector _mem; 302 | }; 303 | } // namespace Clap 304 | -------------------------------------------------------------------------------- /src/detail/ara/ara.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // ARA support for clap-wrapper 6 | // 7 | // the structs and identifiers are a copy from the ARA SDK (file ARA_API/ARACLAP.h from https://github.com/Celemony/ARA_API). 8 | // ARA is Copyright (c) 2022-2024, Celemony Software GmbH, All Rights Reserved. 9 | // 10 | // The ARA SDK is available under Apache-2.0 license here: 11 | // 12 | // https://github.com/Celemony/ARA_SDK 13 | // 14 | // More information about ARA can be found here: https://vwww.celemony.com/ara 15 | // 16 | // the clap-wrapper itself does not require the ARA SDK because CLAP is a shell API only and 17 | // just passes interface pointers through extensions between the foreign host and the ARA plugin core. 18 | // 19 | // do not use this file in your clap plugin, use the ARA SDK! 20 | // 21 | 22 | //! Factory ID for retrieving the clap_ara_factory_t extension from clap_plugin_entry_t.get_factory() 23 | static CLAP_CONSTEXPR const char CLAP_EXT_ARA_FACTORY[] = "org.ara-audio.ara.factory/2"; 24 | 25 | //! Extension ID for retrieving the clap_ara_plugin_extension_t from clap_plugin_t.get_extension() 26 | static CLAP_CONSTEXPR const char CLAP_EXT_ARA_PLUGINEXTENSION[] = "org.ara-audio.ara.pluginextension/2"; 27 | 28 | //! Add this feature if your plugin supports ARA. 29 | //! This allows hosts to detect ARA early on in the setup phase. 30 | #define CLAP_PLUGIN_FEATURE_ARA_SUPPORTED "ara:supported" 31 | 32 | //! Add this feature if your plugin requires ARA to operate (will not work as normal insert plug-in). 33 | //! This allows non-ARA CLAP hosts to suppress the plug-in since it cannot be used there. 34 | #define CLAP_PLUGIN_FEATURE_ARA_REQUIRED "ara:required" 35 | 36 | // VST3 class category name 37 | #define kARAMainFactoryClass "ARA Main Factory Class" 38 | 39 | #ifdef __cplusplus 40 | extern "C" 41 | { 42 | #endif 43 | 44 | // substitute types so we don't need to include the ARA SDK in full 45 | typedef void *ARAFactoryPtr; 46 | typedef void *ARAPlugInExtensionInstancePtr; 47 | typedef void *ARADocumentControllerRef; 48 | typedef int32_t ARAPlugInInstanceRoleFlags; 49 | 50 | /***************************************************************************************************/ 51 | //! Extension interface to connect to ARA at CLAP factory level. 52 | //! The host can pass CLAP_EXT_ARA_FACTORY to clap_plugin_entry_t.get_factory() to directly obtain an 53 | //! ARAFactory, which allows for creating and maintaining the model independently of any clap_plugin_t 54 | //! instances, enabling tasks such as automatic tempo detection or audio-to-MIDI conversion. 55 | //! For rendering and editing the model however, there must be an associated clap_plugin_t provided in 56 | //! the same binary - the descriptor for which is returned at the same index as the related ARAFactory. 57 | 58 | typedef struct clap_ara_factory 59 | { 60 | //! Get the number of ARA factories (i.e. ARA-capable plug.-ins) available. 61 | //! Note that the regular clap_plugin_factory can contain more plug-ins if these do not support 62 | //! ARA - make no assumption about items returned here being related to the items returned there 63 | //! in terms of count or order. 64 | uint32_t(CLAP_ABI *get_factory_count)(const struct clap_ara_factory *factory); 65 | 66 | //! Get the ARA factory for the plug-in at the given index. 67 | //! The returned pointer must remain valid until clap_plugin_entry_t.deinit() is called. 68 | //! The returned ARAFactory must be equal to the ARAFactory returned from instances of the 69 | //! associated CLAP plug-in through their clap_ara_plugin_extension_t.get_factory(). 70 | ARAFactoryPtr(CLAP_ABI *get_ara_factory)(const struct clap_ara_factory *factory, uint32_t index); 71 | 72 | //! Get the ID of the CLAP plug-in associated with the ARA factory for the given index. 73 | //! The plug-in must be in the same binary. 74 | //! The returned pointer must remain valid until clap_plugin_entry_t.deinit is called. 75 | const char *(CLAP_ABI *get_plugin_id)(const struct clap_ara_factory *factory, uint32_t index); 76 | } clap_ara_factory_t; 77 | 78 | /***************************************************************************************************/ 79 | //! Extension interface to connect to ARA at CLAP plug-in level. 80 | //! This interface provides access to the ARA specific extension of a CLAP plug-in. 81 | //! Return an pointer to a clap_ara_plugin_extension_t when clap_plugin_t.get_extension() is called 82 | //! with CLAP_EXT_ARA_PLUGINEXTENSION. 83 | 84 | typedef struct clap_ara_plugin_extension 85 | { 86 | //! Access the ARAFactory associated with this plug-in. 87 | ARAFactoryPtr(CLAP_ABI *get_factory)(const clap_plugin_t *plugin); 88 | 89 | //! Bind the CLAP instance to an ARA document controller, switching it from "normal" operation 90 | //! to ARA mode with the assigned roles, and exposing the ARA plug-in extension. 91 | //! \p knownRoles encodes all roles that the host considered in its implementation and will explicitly 92 | //! assign to some plug-in instance(s), while \p assignedRoles describes the roles that this specific 93 | //! instance will fulfill. 94 | //! This may be called only once during the lifetime of the CLAP plug-in, before the first call 95 | //! to clap_plugin_t.activate() or clap_host_state_t.load() or other processing related extensions 96 | //! or the creation of the GUI. 97 | //! The ARA document controller must remain valid as long as the plug-in is in use - rendering, 98 | //! showing its UI, etc. However, when tearing down the plug-in, the actual order for deleting 99 | //! the clap_plugin_t instance and for deleting ARA document controller is undefined. 100 | //! Plug-ins must handle both potential destruction orders to allow for a simpler reference 101 | //! counting implementation on the host side. 102 | ARAPlugInExtensionInstancePtr(CLAP_ABI *bind_to_document_controller)( 103 | const clap_plugin_t *plugin, ARADocumentControllerRef documentControllerRef, 104 | ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles); 105 | } clap_ara_plugin_extension_t; 106 | 107 | #ifdef __cplusplus 108 | } 109 | #endif 110 | -------------------------------------------------------------------------------- /src/detail/auv2/auv2_shared.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * this is a shared header to connect the wrapped view with the actual AU 5 | * 6 | * the connection will be created when the wrapped View (cocoa) is asked to connect 7 | * via the uiViewForAudioUnit call which provides an instance of the audiounit component. 8 | * this (private) property is being asked then to know the actual instance the view 9 | * is connected to. 10 | * 11 | * With the hinting, the view can access all necessary structures in the 12 | * audiounit connected. 13 | */ 14 | 15 | #include 16 | #include 17 | #include "clap_proxy.h" 18 | #include 19 | 20 | static const AudioUnitPropertyID kAudioUnitProperty_ClapWrapper_UIConnection_id = 0x00010911; 21 | 22 | namespace free_audio::auv2_wrapper 23 | { 24 | 25 | typedef struct ui_connection 26 | { 27 | uint32_t identifier = kAudioUnitProperty_ClapWrapper_UIConnection_id; 28 | Clap::Plugin *_plugin = nullptr; 29 | clap_window_t *_window = nullptr; 30 | std::function _registerWindow = nullptr; 31 | } ui_connection; 32 | 33 | } // namespace free_audio::auv2_wrapper 34 | -------------------------------------------------------------------------------- /src/detail/auv2/auv2_shared.mm: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace free_audio::auv2_wrapper 9 | { 10 | bool auv2shared_mm_request_resize(const clap_window_t* win, uint32_t w, uint32_t h) 11 | { 12 | if (!win) return false; 13 | 14 | auto* nsv = (NSView*)win; 15 | [nsv setFrame:NSMakeRect(0, 0, w, h)]; 16 | 17 | return false; 18 | } 19 | } // namespace free_audio::auv2_wrapper 20 | -------------------------------------------------------------------------------- /src/detail/auv2/build-helper/auv2_infoplist_top.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${AUV2_OUTPUT_NAME} 9 | CFBundleIdentifier 10 | ${AUV2_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${AUV2_OUTPUT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | ${AUV2_BUNDLE_VERSION} 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${AUV2_BUNDLE_VERSION} 23 | NSPrincipalClass 24 | 25 | NSHighResolutionCapable 26 | 27 | -------------------------------------------------------------------------------- /src/detail/auv2/parameter.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "parameter.h" 3 | 4 | namespace Clap::AUv2 5 | { 6 | 7 | Parameter::Parameter(clap_param_info_t &clap_param) 8 | { 9 | _info = clap_param; 10 | _cfstring = CFStringCreateWithCString(NULL, _info.name, kCFStringEncodingUTF8); 11 | } 12 | 13 | void Parameter::resetInfo(const clap_param_info_t &i) 14 | { 15 | _info = i; 16 | CFRelease(_cfstring); 17 | _cfstring = CFStringCreateWithCString(NULL, _info.name, kCFStringEncodingUTF8); 18 | } 19 | Parameter::~Parameter() 20 | { 21 | CFRelease(_cfstring); 22 | } 23 | 24 | } // namespace Clap::AUv2 25 | -------------------------------------------------------------------------------- /src/detail/auv2/parameter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | AUv2 process Adapter 5 | 6 | Copyright (c) 2023 Timo Kaluza (defiantnerd) 7 | 8 | This file i spart of the clap-wrappers project which is released under MIT License 9 | 10 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 11 | 12 | 13 | The process adapter is responsible to translate events, timing information 14 | 15 | */ 16 | 17 | #include 18 | 19 | #ifdef __GNUC__ 20 | #pragma GCC diagnostic push 21 | #pragma GCC diagnostic ignored "-Wextra" 22 | #endif 23 | 24 | #include 25 | 26 | // TODO: check if additional AU headers are needed 27 | #include 28 | #include 29 | #include 30 | 31 | namespace Clap::AUv2 32 | { 33 | 34 | class Parameter 35 | { 36 | public: 37 | Parameter(clap_param_info_t& clap_param); 38 | ~Parameter(); 39 | const clap_param_info_t& info() const 40 | { 41 | return _info; 42 | } 43 | const CFStringRef CFString() const 44 | { 45 | return _cfstring; 46 | } 47 | 48 | void resetInfo(const clap_param_info_t& i); 49 | 50 | private: 51 | clap_param_info_t _info; 52 | CFStringRef _cfstring; 53 | }; 54 | 55 | } // namespace Clap::AUv2 56 | -------------------------------------------------------------------------------- /src/detail/auv2/process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | AUv2 process Adapter 5 | 6 | Copyright (c) 2023 Timo Kaluza (defiantnerd) 7 | 8 | This file i spart of the clap-wrappers project which is released under MIT License 9 | 10 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 11 | 12 | 13 | The process adapter is responsible to translate events, timing information 14 | 15 | */ 16 | 17 | #include 18 | 19 | #ifdef __GNUC__ 20 | #pragma GCC diagnostic push 21 | #pragma GCC diagnostic ignored "-Wextra" 22 | #endif 23 | 24 | #include 25 | 26 | // TODO: check if additional AU headers are needed 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "../clap/automation.h" 32 | #include "parameter.h" 33 | #include 34 | 35 | namespace Clap::AUv2 36 | { 37 | using ParameterTree = std::map>; 38 | 39 | struct ProcessData 40 | { 41 | AudioUnitRenderActionFlags& flags; 42 | const AudioTimeStamp& timestamp; 43 | uint32_t numSamples = 0; 44 | void* audioUnit = nullptr; 45 | 46 | // ------------- 47 | bool _AUtransportValid; // true if: 48 | // information from the AU Host 49 | Float64 _cycleStart; 50 | Float64 _cycleEnd; 51 | Float64 _currentSongPosInSeconds; // outCurrentSampleInTimeLine 52 | 53 | Boolean _isPlaying; 54 | Boolean _transportChanged; 55 | Boolean _isLooping; 56 | Boolean _isRecording; 57 | 58 | // -------------- 59 | bool _AUbeatAndTempoValid; // true if: 60 | // information from the AU host 61 | Float64 _beat; 62 | Float64 _tempo; 63 | 64 | // -------------- 65 | bool _AUmusicalTimeValid; // true if: 66 | // information from the AU host 67 | UInt32 _offsetToNextBeat; 68 | Float32 _musicalNumerator; 69 | UInt32 _musicalDenominator; 70 | Float64 _currentDownBeat; 71 | }; 72 | 73 | typedef union clap_multi_event 74 | { 75 | clap_event_header_t header; 76 | clap_event_note_t note; 77 | clap_event_midi_t midi; 78 | clap_event_midi_sysex_t sysex; 79 | clap_event_param_value_t param; 80 | clap_event_note_expression_t noteexpression; 81 | } clap_multi_event_t; 82 | 83 | class IMIDIOutputs 84 | { 85 | public: 86 | virtual ~IMIDIOutputs(){}; 87 | virtual void send(const clap_multi_event_t& event) = 0; 88 | }; 89 | 90 | class ProcessAdapter 91 | { 92 | public: 93 | void setupProcessing(ausdk::AUScope& audioInputs, ausdk::AUScope& audioOutputs, 94 | const clap_plugin_t* plugin, const clap_plugin_params_t* ext_params, 95 | Clap::IAutomation* automationInterface, ParameterTree* parameters, 96 | IMIDIOutputs* midiouts, uint32_t numMaxSamples, uint32_t preferredMIDIDialect); 97 | 98 | void process(ProcessData& data); // AU Data 99 | void flush(); 100 | 101 | // interface for AUv2 wrapper: 102 | void addMIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame); 103 | void addParameterEvent(const clap_param_info_t& info, double value, uint32_t inOffsetSampleFrame); 104 | // void startNote() 105 | ~ProcessAdapter(); 106 | 107 | private: 108 | // necessary C callbacks: 109 | static uint32_t input_events_size(const struct clap_input_events* list); 110 | static const clap_event_header_t* input_events_get(const struct clap_input_events* list, 111 | uint32_t index); 112 | 113 | static bool output_events_try_push(const struct clap_output_events* list, 114 | const clap_event_header_t* event); 115 | 116 | void sortEventIndices(); 117 | 118 | bool enqueueOutputEvent(const clap_event_header_t* event); 119 | 120 | void processOutputEvents(); 121 | 122 | void addToActiveNotes(const clap_event_note* note); 123 | void removeFromActiveNotes(const clap_event_note* note); 124 | 125 | // the plugin 126 | const clap_plugin_t* _plugin = nullptr; 127 | const clap_plugin_params_t* _ext_params = nullptr; 128 | 129 | // for automation gestures 130 | std::vector _gesturedParameters; 131 | 132 | // for INoteExpression 133 | struct ActiveNote 134 | { 135 | bool used = false; 136 | int32_t note_id; // -1 if unspecified, otherwise >=0 137 | int16_t port_index; 138 | int16_t channel; // 0..15 139 | int16_t key; // 0..127 140 | }; 141 | std::vector _activeNotes; 142 | 143 | uint32_t _numInputs = 0; 144 | uint32_t _numOutputs = 0; 145 | 146 | clap_audio_buffer_t* _input_ports = nullptr; 147 | clap_audio_buffer_t* _output_ports = nullptr; 148 | clap_event_transport_t _transport = {}; 149 | clap_input_events_t _in_events = {}; 150 | clap_output_events_t _out_events = {}; 151 | 152 | float* _silent_input = nullptr; 153 | float* _silent_output = nullptr; 154 | 155 | clap_process_t _processData = {-1, 0, &_transport, nullptr, nullptr, 0, 0, &_in_events, &_out_events}; 156 | 157 | std::vector _events; 158 | std::vector _eventindices; 159 | 160 | std::vector _outevents; 161 | 162 | uint32_t _preferred_midi_dialect = CLAP_NOTE_DIALECT_CLAP; 163 | 164 | Clap::IAutomation* _automation = nullptr; 165 | ParameterTree* _parameters = nullptr; 166 | 167 | IMIDIOutputs* _midiouts = nullptr; 168 | 169 | // AU Process Data? 170 | ausdk::AUScope* _audioInputScope = nullptr; 171 | ausdk::AUScope* _audioOutputScope = nullptr; 172 | }; 173 | } // namespace Clap::AUv2 174 | -------------------------------------------------------------------------------- /src/detail/auv2/wrappedview.asinclude.mm: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // wrapped view for clap-wrapper 4 | // 5 | // created by Paul Walker (baconpaul) and Timo Kaluza (defiantnerd) 6 | // 7 | 8 | #include 9 | #import 10 | #import 11 | #import 12 | 13 | #include "auv2_shared.h" 14 | #include 15 | 16 | //#define CLAP_WRAPPER_UI_CLASSNAME_NSVIEW CLAP_WRAPPER_COCOA_CLASS_NSVIEW 17 | //#define CLAP_WRAPPER_UI_CLASSNAME_COCOAUI CLAP_WRAPPER_COCOA_CLASS 18 | 19 | @interface CLAP_WRAPPER_COCOA_CLASS_NSVIEW : NSView 20 | { 21 | free_audio::auv2_wrapper::ui_connection ui; 22 | CFRunLoopTimerRef idleTimer; 23 | float lastScale; 24 | NSSize underlyingUISize; 25 | bool setSizeByZoom; // use this flag to see if resize comes from here or from external 26 | } 27 | 28 | - (id)initWithAUv2:(free_audio::auv2_wrapper::ui_connection *)cont preferredSize:(NSSize)size; 29 | - (void)doIdle; 30 | - (void)dealloc; 31 | - (void)setFrame:(NSRect)newSize; 32 | 33 | @end 34 | 35 | @interface CLAP_WRAPPER_COCOA_CLASS : NSObject 36 | { 37 | } 38 | - (NSString *)description; 39 | @end 40 | 41 | @implementation CLAP_WRAPPER_COCOA_CLASS 42 | 43 | - (NSView *)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize 44 | { 45 | static free_audio::auv2_wrapper::ui_connection uiconn; 46 | 47 | // free_audio::auv2_wrapper::ui_connection connection; 48 | // Remember we end up being called here because that's what AUCocoaUIView does in the initiation 49 | // collaboration with hosts 50 | 51 | UInt32 size = sizeof(free_audio::auv2_wrapper::ui_connection); 52 | if (AudioUnitGetProperty(inAudioUnit, kAudioUnitProperty_ClapWrapper_UIConnection_id, 53 | kAudioUnitScope_Global, 0, &uiconn, &size) != noErr) 54 | return nil; 55 | 56 | return [[[CLAP_WRAPPER_COCOA_CLASS_NSVIEW alloc] initWithAUv2:&uiconn 57 | preferredSize:inPreferredSize] autorelease]; 58 | LOGINFO("[clap-wrapper] get ui View for AudioUnit"); 59 | // return nil; 60 | } 61 | 62 | - (unsigned int)interfaceVersion 63 | { 64 | LOGINFO("[clap-wrapper] get interface version"); 65 | return 0; 66 | } 67 | 68 | - (NSString *)description 69 | { 70 | LOGINFO("[clap-wrapper] get description"); 71 | return [NSString stringWithUTF8String:"Wrap Window"]; // TODO: get name from plugin 72 | } 73 | 74 | @end 75 | 76 | void CLAP_WRAPPER_TIMER_CALLBACK(CFRunLoopTimerRef timer, void *info) 77 | { 78 | CLAP_WRAPPER_COCOA_CLASS_NSVIEW *view = (CLAP_WRAPPER_COCOA_CLASS_NSVIEW *)info; 79 | [view doIdle]; 80 | } 81 | 82 | @implementation CLAP_WRAPPER_COCOA_CLASS_NSVIEW 83 | 84 | - (id)initWithAUv2:(free_audio::auv2_wrapper::ui_connection *)cont preferredSize:(NSSize)size 85 | { 86 | LOGINFO("[clap-wrapper] creating NSView"); 87 | 88 | ui = *cont; 89 | if (ui._registerWindow) 90 | { 91 | ui._registerWindow((clap_window_t *)self); 92 | } 93 | ui._plugin->_ext._gui->create(ui._plugin->_plugin, CLAP_WINDOW_API_COCOA, false); 94 | auto gui = ui._plugin->_ext._gui; 95 | 96 | // actually, the host should send an appropriate size, 97 | // yet, they actually just send utter garbage, so: don't care 98 | // if (size.width == 0 || size.height == 0) 99 | { 100 | // gui->get_size(ui._plugin->_plugin,) 101 | uint32_t w, h; 102 | if (gui->get_size(ui._plugin->_plugin, &w, &h)) 103 | { 104 | size = {(double)w, (double)h}; 105 | } 106 | } 107 | self = [super initWithFrame:NSMakeRect(0, 0, size.width, size.height)]; 108 | 109 | // gui->show(ui._plugin->_plugin); 110 | 111 | clap_window_t m; 112 | m.api = CLAP_WINDOW_API_COCOA; 113 | m.ptr = self; 114 | 115 | gui->set_parent(ui._plugin->_plugin, &m); 116 | gui->set_scale(ui._plugin->_plugin, 1.0); 117 | 118 | if (gui->can_resize(ui._plugin->_plugin)) 119 | { 120 | clap_gui_resize_hints_t resize_hints; 121 | gui->get_resize_hints(ui._plugin->_plugin, &resize_hints); 122 | NSAutoresizingMaskOptions mask = 0; 123 | 124 | if (resize_hints.can_resize_horizontally) mask |= NSViewWidthSizable; 125 | if (resize_hints.can_resize_vertically) mask |= NSViewHeightSizable; 126 | 127 | [self setAutoresizingMask:mask]; 128 | gui->set_size(ui._plugin->_plugin, size.width, size.height); 129 | } 130 | 131 | idleTimer = nil; 132 | CFTimeInterval TIMER_INTERVAL = .05; // In SurgeGUISynthesizer.h it uses 50 ms 133 | CFRunLoopTimerContext TimerContext = {0, self, NULL, NULL, NULL}; 134 | CFAbsoluteTime FireTime = CFAbsoluteTimeGetCurrent() + TIMER_INTERVAL; 135 | idleTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, FireTime, TIMER_INTERVAL, 0, 0, 136 | CLAP_WRAPPER_TIMER_CALLBACK, &TimerContext); 137 | if (idleTimer) CFRunLoopAddTimer(CFRunLoopGetMain(), idleTimer, kCFRunLoopCommonModes); 138 | 139 | return self; 140 | } 141 | 142 | - (void)doIdle 143 | { 144 | // auto gui = ui._plugin->_ext._gui; 145 | } 146 | - (void)dealloc 147 | { 148 | LOGINFO("[clap-wrapper] NS View dealloc"); 149 | if (idleTimer) 150 | { 151 | CFRunLoopTimerInvalidate(idleTimer); 152 | } 153 | auto gui = ui._plugin->_ext._gui; 154 | gui->destroy(ui._plugin->_plugin); 155 | 156 | [super dealloc]; 157 | } 158 | - (void)setFrame:(NSRect)newSize 159 | { 160 | [super setFrame:newSize]; 161 | auto gui = ui._plugin->_ext._gui; 162 | gui->set_scale(ui._plugin->_plugin, 1.0); 163 | gui->set_size(ui._plugin->_plugin, newSize.size.width, newSize.size.height); 164 | 165 | // gui->show(ui._plugin->_plugin); 166 | } 167 | 168 | @end 169 | 170 | bool CLAP_WRAPPER_FILL_AUCV(AudioUnitCocoaViewInfo *viewInfo) 171 | { 172 | // now we are in m&m land.. 173 | auto bundle = [NSBundle bundleForClass:[CLAP_WRAPPER_COCOA_CLASS class]]; 174 | 175 | if (bundle) 176 | { 177 | // Get the URL for the main bundle 178 | NSURL *url = [bundle bundleURL]; 179 | CFURLRef cfUrl = (__bridge CFURLRef)url; 180 | CFRetain(cfUrl); 181 | 182 | #define ascf(x) CFSTR(#x) 183 | #define tocf(x) ascf(x) 184 | CFStringRef className = tocf(CLAP_WRAPPER_COCOA_CLASS); 185 | #undef tocf 186 | #undef ascf 187 | 188 | *viewInfo = {cfUrl, {className}}; 189 | LOGINFO("[clap-wrapper] created AudioUnitCocoaView: - class is \"{}\"", 190 | CFStringGetCStringPtr(className, kCFStringEncodingUTF8)); 191 | return true; 192 | } 193 | LOGINFO("[clap-wrapper] create AudioUnitCocoaView failed: {}", __func__); 194 | return false; 195 | } 196 | -------------------------------------------------------------------------------- /src/detail/auv2/wrappedview.mm: -------------------------------------------------------------------------------- 1 | 2 | #include "generated_cocoaclasses.hxx" -------------------------------------------------------------------------------- /src/detail/clap/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Clap 6 | { 7 | class IAutomation 8 | { 9 | public: 10 | virtual void onBeginEdit(clap_id id) = 0; 11 | virtual void onPerformEdit(const clap_event_param_value_t* value) = 0; 12 | virtual void onEndEdit(clap_id id) = 0; 13 | virtual ~IAutomation() 14 | { 15 | } 16 | }; 17 | } // namespace Clap 18 | -------------------------------------------------------------------------------- /src/detail/clap/fsutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | 5 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 6 | Paul Walker 7 | 8 | This file is part of the clap-wrappers project which is released under MIT License. 9 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 10 | 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #if WIN 17 | #include 18 | #endif 19 | 20 | #include "clapwrapper/vst3.h" 21 | #include "clapwrapper/auv2.h" 22 | #include "../ara/ara.h" 23 | #include "detail/os/fs.h" 24 | 25 | #if MAC 26 | #include 27 | #endif 28 | 29 | namespace Clap 30 | { 31 | 32 | std::vector getValidCLAPSearchPaths(); 33 | class Plugin; 34 | class IHost; 35 | 36 | class Library 37 | { 38 | public: 39 | Library(); 40 | ~Library(); 41 | bool load(const fs::path&); 42 | 43 | const clap_plugin_entry_t* _pluginEntry = nullptr; 44 | const clap_plugin_factory_t* _pluginFactory = nullptr; 45 | const clap_plugin_factory_as_vst3* _pluginFactoryVst3Info = nullptr; 46 | const clap_plugin_factory_as_auv2* _pluginFactoryAUv2Info = nullptr; 47 | const clap_ara_factory_t* _pluginFactoryARAInfo = nullptr; 48 | std::vector plugins; 49 | const clap_plugin_info_as_vst3_t* get_vst3_info(uint32_t index) const; 50 | 51 | #if MAC 52 | CFBundleRef getBundleRef() 53 | { 54 | return _bundle; 55 | } 56 | #endif 57 | 58 | bool hasEntryPoint() const 59 | { 60 | #if WIN 61 | return _handle != 0 || _selfcontained; 62 | #endif 63 | 64 | #if MAC 65 | return _bundle != nullptr || _selfcontained; 66 | #endif 67 | 68 | #if LIN 69 | return _handle != nullptr || _selfcontained; 70 | #endif 71 | } 72 | 73 | private: 74 | #if MAC 75 | CFBundleRef _bundle{nullptr}; 76 | #endif 77 | 78 | #if LIN 79 | void* _handle{nullptr}; 80 | #endif 81 | 82 | #if WIN 83 | HMODULE _handle = 0; 84 | bool getEntryFunction(HMODULE handle, const char* path); 85 | #endif 86 | 87 | void setupPluginsFromPluginEntry(const char* p); 88 | bool _selfcontained = false; 89 | }; 90 | 91 | } // namespace Clap 92 | -------------------------------------------------------------------------------- /src/detail/clap/mac_helpers.mm: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022 Paul Walker 3 | Timo Kaluza (defiantnerd) 4 | 5 | 6 | This file is part of the clap-wrappers project which is released under MIT License. 7 | See file LICENSE or go to https://github.com/defiantnerd/clap-wrapper for full license details. 8 | 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "detail/os/fs.h" 17 | 18 | namespace Clap 19 | { 20 | fs::path sharedLibraryBundlePath() 21 | { 22 | Dl_info info; 23 | if (!dladdr(reinterpret_cast(&sharedLibraryBundlePath), &info) || !info.dli_fname[0]) 24 | { 25 | // If dladdr(3) returns zero, dlerror(3) won't know why either 26 | return {}; 27 | } 28 | try 29 | { 30 | auto res = fs::path{info.dli_fname}; 31 | res = res.parent_path().parent_path(); // this might throw if not in bundle so catch 32 | if (res.filename().u8string() == "Contents" && res.has_parent_path()) 33 | { 34 | // We are properly situated in a bundle in either a MacOS ir an iOS location 35 | return res.parent_path(); 36 | } 37 | } 38 | catch (const fs::filesystem_error &) 39 | { 40 | // oh well 41 | } 42 | return {}; 43 | } 44 | 45 | std::vector getMacCLAPSearchPaths() 46 | { 47 | auto res = std::vector(); 48 | 49 | auto bundlePath = sharedLibraryBundlePath(); 50 | if (!bundlePath.empty()) 51 | { 52 | std::string name = bundlePath.u8string(); 53 | CFURLRef bundleUrl = CFURLCreateFromFileSystemRepresentation( 54 | nullptr, (const unsigned char *)name.c_str(), name.size(), true); 55 | if (bundleUrl) 56 | { 57 | auto pluginBundle = CFBundleCreate(nullptr, bundleUrl); 58 | CFRelease(bundleUrl); 59 | 60 | if (pluginBundle) 61 | { 62 | auto pluginFoldersUrl = CFBundleCopyBuiltInPlugInsURL(pluginBundle); 63 | 64 | if (pluginFoldersUrl) 65 | { 66 | // Remember CFURL and NSURL are toll free bridged 67 | auto *ns = (NSURL *)pluginFoldersUrl; 68 | auto pp = fs::path{[ns fileSystemRepresentation]}; 69 | res.push_back(pp); 70 | CFRelease(pluginFoldersUrl); 71 | } 72 | CFRelease(pluginBundle); 73 | } 74 | } 75 | } 76 | 77 | auto *fileManager = [NSFileManager defaultManager]; 78 | auto *userLibURLs = [fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]; 79 | auto *sysLibURLs = [fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSLocalDomainMask]; 80 | 81 | if (userLibURLs) 82 | { 83 | auto *u = [userLibURLs objectAtIndex:0]; 84 | auto p = fs::path{[u fileSystemRepresentation]} / "Audio" / "Plug-Ins" / "CLAP"; 85 | res.push_back(p); 86 | } 87 | 88 | if (sysLibURLs) 89 | { 90 | auto *u = [sysLibURLs objectAtIndex:0]; 91 | auto p = fs::path{[u fileSystemRepresentation]} / "Audio" / "Plug-Ins" / "CLAP"; 92 | res.push_back(p); 93 | } 94 | return res; 95 | } 96 | std::string toString(const CFStringRef aString) 97 | { 98 | if (aString == NULL) 99 | { 100 | return std::string(); 101 | } 102 | 103 | std::string result; 104 | 105 | CFIndex length = CFStringGetLength(aString); 106 | CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; 107 | result.reserve(maxSize); 108 | 109 | if (CFStringGetCString(aString, result.data(), maxSize, kCFStringEncodingUTF8)) 110 | { 111 | return result; 112 | } 113 | 114 | return std::string(); 115 | } 116 | } // namespace Clap 117 | -------------------------------------------------------------------------------- /src/detail/os/fs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if MAC && !MACOS_USE_STD_FILESYSTEM 4 | #include "ghc/filesystem.hpp" 5 | namespace fs = ghc::filesystem; 6 | #else 7 | #include 8 | namespace fs = std::filesystem; 9 | #endif 10 | -------------------------------------------------------------------------------- /src/detail/os/linux.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | the Linux helper 3 | 4 | provides services for all plugin instances regarding Linux 5 | - global timer object 6 | - dispatch to UI thread 7 | - get binary name 8 | */ 9 | 10 | #include "public.sdk/source/main/moduleinit.h" 11 | #include "osutil.h" 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace os 18 | { 19 | class LinuxHelper 20 | { 21 | public: 22 | void init(); 23 | void terminate(); 24 | void attach(IPlugObject* plugobject); 25 | void detach(IPlugObject* plugobject); 26 | 27 | private: 28 | void executeDefered(); 29 | std::vector _plugs; 30 | } gLinuxHelper; 31 | 32 | #if 0 33 | class WindowsHelper 34 | { 35 | public: 36 | void init(); 37 | void terminate(); 38 | void attach(IPlugObject* plugobject); 39 | void detach(IPlugObject* plugobject); 40 | private: 41 | void executeDefered(); 42 | static LRESULT Wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 43 | HWND _msgWin = 0; 44 | UINT_PTR _timer = 0; 45 | std::vector _plugs; 46 | } gWindowsHelper; 47 | #endif 48 | 49 | static Steinberg::ModuleInitializer createMessageWindow([] { gLinuxHelper.init(); }); 50 | static Steinberg::ModuleTerminator dropMessageWindow([] { gLinuxHelper.terminate(); }); 51 | 52 | #if 0 53 | static char* getModuleNameA() 54 | { 55 | static char modulename[2048]; 56 | HMODULE selfmodule; 57 | if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)getModuleNameA, &selfmodule)) 58 | { 59 | auto size = GetModuleFileNameA(selfmodule, modulename, 2048); 60 | } 61 | return modulename; 62 | } 63 | 64 | static TCHAR* getModuleName() 65 | { 66 | static TCHAR modulename[2048]; 67 | HMODULE selfmodule; 68 | if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)getModuleName, &selfmodule)) 69 | { 70 | auto size = GetModuleFileName(selfmodule, modulename, 2048); 71 | } 72 | return modulename; 73 | } 74 | #endif 75 | 76 | fs::path getPluginPath() 77 | { 78 | Dl_info info; 79 | if (dladdr((void*)getPluginPath, &info)) 80 | { 81 | return info.dli_fname; 82 | } 83 | return {}; 84 | } 85 | 86 | std::string getParentFolderName() 87 | { 88 | fs::path n = getPluginPath(); 89 | if (n.has_parent_path()) 90 | { 91 | auto p = n.parent_path(); 92 | if (p.has_filename()) 93 | { 94 | return p.filename().u8string(); 95 | } 96 | } 97 | 98 | return {}; 99 | } 100 | 101 | std::string getBinaryName() 102 | { 103 | fs::path n = getPluginPath(); 104 | if (n.has_filename()) 105 | { 106 | return n.stem().u8string(); 107 | } 108 | return {}; 109 | } 110 | 111 | #if 0 112 | LRESULT WindowsHelper::Wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 113 | { 114 | switch (msg) 115 | { 116 | case WM_USER + 1: 117 | return 1; 118 | break; 119 | case WM_TIMER: 120 | gWindowsHelper.executeDefered(); 121 | return 1; 122 | break; 123 | default: 124 | return ::DefWindowProc(hwnd, msg, wParam, lParam); 125 | } 126 | } 127 | 128 | void WindowsHelper::init() 129 | { 130 | auto modulename = getModuleName(); 131 | WNDCLASSEX wc; 132 | memset(&wc, 0, sizeof(wc)); 133 | wc.cbSize = sizeof(wc); 134 | wc.hInstance = ghInst; 135 | wc.lpfnWndProc = &Wndproc; 136 | wc.lpszClassName = modulename; 137 | auto a = RegisterClassEx(&wc); 138 | 139 | _msgWin = ::CreateWindowEx(0, modulename, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0); 140 | ::SetWindowLongW(_msgWin, GWLP_WNDPROC, (LONG_PTR)&Wndproc); 141 | _timer = ::SetTimer(_msgWin, 0, 20, NULL); 142 | } 143 | 144 | void WindowsHelper::terminate() 145 | { 146 | ::KillTimer(_msgWin, _timer); 147 | ::DestroyWindow(_msgWin); 148 | ::UnregisterClass(getModuleName(), ghInst); 149 | } 150 | 151 | #endif 152 | void LinuxHelper::init() 153 | { 154 | } 155 | 156 | void LinuxHelper::terminate() 157 | { 158 | } 159 | 160 | void LinuxHelper::executeDefered() 161 | { 162 | for (auto p : _plugs) 163 | { 164 | if (p) p->onIdle(); 165 | } 166 | } 167 | void LinuxHelper::attach(IPlugObject* plugobject) 168 | { 169 | _plugs.push_back(plugobject); 170 | } 171 | 172 | void LinuxHelper::detach(IPlugObject* plugobject) 173 | { 174 | _plugs.erase(std::remove(_plugs.begin(), _plugs.end(), plugobject), _plugs.end()); 175 | } 176 | 177 | } // namespace os 178 | 179 | namespace os 180 | { 181 | // [UI Thread] 182 | void attach(IPlugObject* plugobject) 183 | { 184 | gLinuxHelper.attach(plugobject); 185 | } 186 | 187 | // [UI Thread] 188 | void detach(IPlugObject* plugobject) 189 | { 190 | gLinuxHelper.detach(plugobject); 191 | } 192 | 193 | uint64_t getTickInMS() 194 | { 195 | return clock(); 196 | } 197 | } // namespace os 198 | -------------------------------------------------------------------------------- /src/detail/os/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define FMT_HEADER_ONLY 1 4 | #include "fmt/format.h" 5 | #include "fmt/ranges.h" 6 | 7 | #ifndef CLAP_WRAPPER_LOGLEVEL 8 | #if NDEBUG 9 | #define CLAP_WRAPPER_LOGLEVEL 0 10 | #else 11 | #define CLAP_WRAPPER_LOGLEVEL 2 12 | #endif 13 | #endif 14 | 15 | #if CLAP_WRAPPER_LOGLEVEL == 0 16 | #define LOGINFO(...) (void(0)) 17 | #define LOGDETAIL(...) (void(0)) 18 | 19 | #else 20 | 21 | #if WIN 22 | #include 23 | #endif 24 | #include 25 | 26 | namespace os 27 | { 28 | 29 | inline void log(const char* text) 30 | { 31 | #if WIN 32 | OutputDebugStringA(text); 33 | OutputDebugStringA("\n"); 34 | #else 35 | fprintf(stdout, "%s\n", text); 36 | fflush(stdout); 37 | #endif 38 | } 39 | 40 | template 41 | void log(fmt::string_view format_str, Args&&... args) 42 | { 43 | fmt::memory_buffer buf; 44 | fmt::vformat_to(std::back_inserter(buf), format_str, fmt::make_format_args(args...)); 45 | buf.push_back(0); 46 | log((const char*)buf.data()); 47 | } 48 | 49 | template 50 | void logWithLocation(const std::string& file, uint32_t line, const std::string func, 51 | fmt::string_view format_str, Args&&... args) 52 | { 53 | fmt::memory_buffer buf; 54 | fmt::vformat_to(std::back_inserter(buf), "{}:{} ({}) ", fmt::make_format_args(file, line, func)); 55 | fmt::vformat_to(std::back_inserter(buf), format_str, fmt::make_format_args(args...)); 56 | buf.push_back(0); 57 | log((const char*)buf.data()); 58 | } 59 | } // namespace os 60 | 61 | #if (CLAP_WRAPPER_LOGLEVEL == 0) 62 | #define LOGINFO(...) (void(0)) 63 | #define LOGDETAIL(...) (void(0)) 64 | #endif 65 | 66 | #if (CLAP_WRAPPER_LOGLEVEL == 1) 67 | #define LOGINFO os::logWithLocation(__FILE__, __LINE__, __func__, __VA_ARGS__) 68 | #define LOGDETAIL(...) (void(0)) 69 | #endif 70 | 71 | #if (CLAP_WRAPPER_LOGLEVEL == 2) 72 | #define LOGINFO(...) os::logWithLocation(__FILE__, __LINE__, __func__, __VA_ARGS__) 73 | #define LOGDETAIL(...) os::logWithLocation(__FILE__, __LINE__, __func__, __VA_ARGS__) 74 | #endif 75 | #endif 76 | -------------------------------------------------------------------------------- /src/detail/os/macos.mm: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 1 2 | 3 | /* 4 | the macos helper 5 | 6 | provides services for all plugin instances regarding macos 7 | - global timer object 8 | - dispatch to UI thread 9 | - get the bundle name 10 | 11 | */ 12 | 13 | #include 14 | #ifdef CLAP_WRAPPER_BUILD_FOR_VST3 15 | #include "public.sdk/source/main/moduleinit.h" 16 | #endif 17 | #include "osutil.h" 18 | #include 19 | #include 20 | #include 21 | 22 | #include "detail/os/fs.h" 23 | 24 | namespace os 25 | { 26 | 27 | class MacOSHelper 28 | { 29 | public: 30 | void init(); 31 | void terminate(); 32 | void attach(IPlugObject* plugobject); 33 | void detach(IPlugObject* plugobject); 34 | 35 | private: 36 | static void timerCallback(CFRunLoopTimerRef t, void* info); 37 | void executeDefered(); 38 | CFRunLoopTimerRef _timer = nullptr; 39 | std::vector _plugs; 40 | } gMacOSHelper; 41 | 42 | // standard specific extensions 43 | // ---------------------------------------------------------- 44 | #ifdef CLAP_WRAPPER_BUILD_FOR_VST3 45 | static Steinberg::ModuleInitializer createMessageWindow([] { gMacOSHelper.init(); }); 46 | static Steinberg::ModuleTerminator dropMessageWindow([] { gMacOSHelper.terminate(); }); 47 | #endif 48 | // ---------------------------------------------------------- 49 | 50 | void MacOSHelper::init() 51 | { 52 | } 53 | 54 | void MacOSHelper::terminate() 55 | { 56 | } 57 | 58 | void MacOSHelper::executeDefered() 59 | { 60 | for (auto p : _plugs) 61 | { 62 | if (p) p->onIdle(); 63 | } 64 | } 65 | 66 | void MacOSHelper::timerCallback(CFRunLoopTimerRef /*t*/, void* info) 67 | { 68 | auto self = static_cast(info); 69 | self->executeDefered(); 70 | } 71 | 72 | static float kIntervall = 10.f; 73 | 74 | void MacOSHelper::attach(IPlugObject* plugobject) 75 | { 76 | if (_plugs.empty()) 77 | { 78 | CFRunLoopTimerContext context = {}; 79 | context.info = this; 80 | _timer = 81 | CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + (kIntervall * 0.001f), 82 | kIntervall * 0.001f, 0, 0, timerCallback, &context); 83 | if (_timer) CFRunLoopAddTimer(CFRunLoopGetCurrent(), _timer, kCFRunLoopCommonModes); 84 | } 85 | _plugs.push_back(plugobject); 86 | } 87 | 88 | void MacOSHelper::detach(IPlugObject* plugobject) 89 | { 90 | _plugs.erase(std::remove(_plugs.begin(), _plugs.end(), plugobject), _plugs.end()); 91 | if (_plugs.empty()) 92 | { 93 | if (_timer) 94 | { 95 | CFRunLoopTimerInvalidate(_timer); 96 | CFRelease(_timer); 97 | } 98 | _timer = nullptr; 99 | } 100 | } 101 | 102 | } // namespace os 103 | 104 | namespace os 105 | { 106 | // [UI Thread] 107 | void attach(IPlugObject* plugobject) 108 | { 109 | gMacOSHelper.attach(plugobject); 110 | } 111 | 112 | // [UI Thread] 113 | void detach(IPlugObject* plugobject) 114 | { 115 | gMacOSHelper.detach(plugobject); 116 | } 117 | 118 | uint64_t getTickInMS() 119 | { 120 | return (::clock() * 1000) / CLOCKS_PER_SEC; 121 | } 122 | 123 | fs::path getPluginPath() 124 | { 125 | Dl_info info; 126 | if (dladdr((void*)getPluginPath, &info)) 127 | { 128 | fs::path binaryPath = info.dli_fname; 129 | return binaryPath.parent_path().parent_path().parent_path(); 130 | } 131 | 132 | return {}; 133 | } 134 | 135 | std::string getParentFolderName() 136 | { 137 | return getPluginPath().parent_path().stem(); 138 | } 139 | 140 | std::string getBinaryName() 141 | { 142 | return getPluginPath().stem(); 143 | } 144 | 145 | } // namespace os 146 | -------------------------------------------------------------------------------- /src/detail/os/osutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | a minimalistic OS layer 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "log.h" 12 | #include "fs.h" 13 | 14 | namespace os 15 | { 16 | class State 17 | { 18 | // the State class ensures that a specific function pair (like init/terminate) is only called in pairs. 19 | // the bool _state reflects if the _on lambda has already been called 20 | // on destruction, it makes sure that the _off lambda is definitely called. 21 | // 22 | // the construct should only be applied to when no state machine can cover this properly 23 | // or your own code depends on correct calling sequences of an external code base and should 24 | // help to implement defensive programming. 25 | 26 | public: 27 | State(std::function on, std::function off) : _on(on), _off(off) 28 | { 29 | } 30 | ~State() 31 | { 32 | off(); 33 | } 34 | void on() 35 | { 36 | if (!_state) 37 | { 38 | _on(); 39 | } 40 | _state = true; 41 | } 42 | void off() 43 | { 44 | if (_state) 45 | { 46 | _off(); 47 | } 48 | _state = false; 49 | } 50 | 51 | private: 52 | bool _state = false; 53 | std::function _on, _off; 54 | }; 55 | 56 | class IPlugObject 57 | { 58 | public: 59 | virtual void onIdle() = 0; 60 | virtual ~IPlugObject() 61 | { 62 | } 63 | }; 64 | void attach(IPlugObject* plugobject); 65 | void detach(IPlugObject* plugobject); 66 | uint64_t getTickInMS(); 67 | 68 | // Used for clap_plugin_entry.init(). Path to DSO (Linux, Windows), or the bundle (macOS). 69 | fs::path getPluginPath(); 70 | std::string getParentFolderName(); 71 | std::string getBinaryName(); 72 | } // namespace os 73 | -------------------------------------------------------------------------------- /src/detail/os/osutil_windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fs.h" 4 | #include 5 | 6 | namespace os 7 | { 8 | 9 | static fs::path getModulePath(HMODULE mod) 10 | { 11 | fs::path modulePath{}; 12 | DWORD bufferSize = 150; // Start off with a reasonably large size 13 | std::wstring buffer(bufferSize, L'\0'); 14 | 15 | DWORD length = GetModuleFileNameW(mod, buffer.data(), bufferSize); 16 | 17 | constexpr size_t maxExtendedPath = 0x7FFF - 24; // From Windows Implementation Library 18 | 19 | while (length == bufferSize && GetLastError() == ERROR_INSUFFICIENT_BUFFER && 20 | bufferSize < maxExtendedPath) 21 | { 22 | bufferSize *= 2; 23 | buffer.resize(bufferSize); 24 | length = GetModuleFileNameW(mod, buffer.data(), bufferSize); 25 | } 26 | buffer.resize(length); 27 | modulePath = std::move(buffer); 28 | return modulePath; 29 | } 30 | 31 | } // namespace os 32 | -------------------------------------------------------------------------------- /src/detail/os/windows.cpp: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 1 2 | 3 | /* 4 | the windows helper 5 | 6 | provides services for all plugin instances regarding Windows 7 | - global timer object 8 | - dispatch to UI thread 9 | 10 | */ 11 | 12 | #include 13 | #include 14 | #include "public.sdk/source/main/moduleinit.h" 15 | #include "osutil.h" 16 | #include 17 | #include "osutil_windows.h" 18 | 19 | // from dllmain.cpp of the VST3 SDK 20 | extern HINSTANCE ghInst; 21 | 22 | namespace os 23 | { 24 | 25 | class WindowsHelper 26 | { 27 | public: 28 | void init(); 29 | void terminate(); 30 | void attach(IPlugObject* plugobject); 31 | void detach(IPlugObject* plugobject); 32 | 33 | private: 34 | void executeDefered(); 35 | static LRESULT Wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 36 | HWND _msgWin = 0; 37 | UINT_PTR _timer = 0; 38 | std::vector _plugs; 39 | } gWindowsHelper; 40 | 41 | static Steinberg::ModuleInitializer createMessageWindow([] { gWindowsHelper.init(); }); 42 | static Steinberg::ModuleTerminator dropMessageWindow([] { gWindowsHelper.terminate(); }); 43 | 44 | fs::path getPluginPath() 45 | { 46 | HMODULE selfmodule; 47 | if (GetModuleHandleExW( 48 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 49 | (LPCWSTR)getPluginPath, &selfmodule)) 50 | { 51 | return os::getModulePath(selfmodule); 52 | } 53 | return {}; 54 | } 55 | 56 | std::string getParentFolderName() 57 | { 58 | fs::path n = getPluginPath(); 59 | if (n.has_parent_path()) 60 | { 61 | auto p = n.parent_path(); 62 | if (p.has_filename()) 63 | { 64 | return p.filename().u8string(); 65 | } 66 | } 67 | 68 | return std::string(); 69 | } 70 | 71 | std::string getBinaryName() 72 | { 73 | fs::path n = getPluginPath(); 74 | if (n.has_filename()) 75 | { 76 | return n.stem().u8string(); 77 | } 78 | return std::string(); 79 | } 80 | 81 | LRESULT WindowsHelper::Wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 82 | { 83 | switch (msg) 84 | { 85 | case WM_USER + 1: 86 | return 1; 87 | break; 88 | case WM_TIMER: 89 | gWindowsHelper.executeDefered(); 90 | return 1; 91 | break; 92 | default: 93 | return DefWindowProc(hwnd, msg, wParam, lParam); 94 | } 95 | } 96 | 97 | static std::wstring helperClassName(HINSTANCE hinst) 98 | { 99 | return fmt::format(L"clapwrapper{}", (void*)hinst); 100 | } 101 | 102 | void WindowsHelper::init() 103 | { 104 | auto className = helperClassName(ghInst); 105 | WNDCLASSEXW wc; 106 | memset(&wc, 0, sizeof(wc)); 107 | wc.cbSize = sizeof(wc); 108 | wc.hInstance = ghInst; 109 | wc.lpfnWndProc = (WNDPROC)&Wndproc; 110 | wc.lpszClassName = className.c_str(); 111 | RegisterClassExW(&wc); 112 | 113 | _msgWin = CreateWindowExW(0, className.c_str(), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0); 114 | SetWindowLongW(_msgWin, GWLP_WNDPROC, (LONG_PTR)&Wndproc); 115 | _timer = SetTimer(_msgWin, 0, 20, NULL); 116 | } 117 | 118 | void WindowsHelper::terminate() 119 | { 120 | KillTimer(_msgWin, _timer); 121 | DestroyWindow(_msgWin); 122 | UnregisterClassW(helperClassName(ghInst).c_str(), ghInst); 123 | } 124 | 125 | void WindowsHelper::executeDefered() 126 | { 127 | for (auto&& p : _plugs) 128 | { 129 | if (p) p->onIdle(); 130 | } 131 | } 132 | 133 | void WindowsHelper::attach(IPlugObject* plugobject) 134 | { 135 | _plugs.push_back(plugobject); 136 | } 137 | 138 | void WindowsHelper::detach(IPlugObject* plugobject) 139 | { 140 | _plugs.erase(std::remove(_plugs.begin(), _plugs.end(), plugobject), _plugs.end()); 141 | } 142 | 143 | // [UI Thread] 144 | void attach(IPlugObject* plugobject) 145 | { 146 | gWindowsHelper.attach(plugobject); 147 | } 148 | 149 | // [UI Thread] 150 | void detach(IPlugObject* plugobject) 151 | { 152 | gWindowsHelper.detach(plugobject); 153 | } 154 | 155 | uint64_t getTickInMS() 156 | { 157 | return GetTickCount64(); 158 | } 159 | } // namespace os 160 | -------------------------------------------------------------------------------- /src/detail/shared/fixedqueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ClapWrapper::detail::shared 7 | { 8 | 9 | template 10 | class fixedqueue 11 | { 12 | public: 13 | inline void push(const T& val) 14 | { 15 | push(&val); 16 | } 17 | inline void push(const T* val) 18 | { 19 | _elements[_head] = *val; 20 | _head = (_head + 1) & _wrapMask; 21 | } 22 | inline bool pop(T& out) 23 | { 24 | if (_head == _tail) 25 | { 26 | return false; 27 | } 28 | out = _elements[_tail]; 29 | _tail = (_tail + 1) & _wrapMask; 30 | return true; 31 | } 32 | 33 | private: 34 | T _elements[Q] = {}; 35 | std::atomic_uint32_t _head = 0u; 36 | std::atomic_uint32_t _tail = 0u; 37 | 38 | static constexpr uint32_t _wrapMask = Q - 1; 39 | static_assert((Q & _wrapMask) == 0, "Q needs to be a multiple of 2"); 40 | }; 41 | } // namespace ClapWrapper::detail::shared 42 | -------------------------------------------------------------------------------- /src/detail/shared/sha1.cpp: -------------------------------------------------------------------------------- 1 | #include "sha1.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static constexpr bool isBigEndian = false; 8 | 9 | namespace Crypto 10 | { 11 | class Sha1 12 | { 13 | public: 14 | Sha1() 15 | { 16 | reset(); 17 | } 18 | Sha1(const unsigned char* message_array, size_t length) 19 | { 20 | reset(); 21 | input(message_array, length); 22 | } 23 | void input(const unsigned char* message_array, size_t length); 24 | struct sha1hash hash(); 25 | 26 | private: 27 | void reset(); 28 | void processMessageBlock(); 29 | void padmessage(); 30 | inline uint32_t circularShift(int bits, uint32_t word) 31 | { 32 | return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32 - bits)); 33 | } 34 | 35 | unsigned H[5] = // Message digest buffers 36 | {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}; 37 | 38 | uint32_t _lengthLow = 0; // Message length in bits 39 | uint32_t _length_high = 0; // Message length in bits 40 | 41 | unsigned char _messageBlock[64] = {0}; // 512-bit message blocks 42 | int _messageBlockIndex = 0; // Index into message block array 43 | 44 | bool _computed = false; // Is the digest computed? 45 | bool _corrupted = false; // Is the message digest corruped? 46 | }; 47 | 48 | void Sha1::reset() 49 | { 50 | _lengthLow = 0; 51 | _length_high = 0; 52 | _messageBlockIndex = 0; 53 | 54 | H[0] = 0x67452301; 55 | H[1] = 0xEFCDAB89; 56 | H[2] = 0x98BADCFE; 57 | H[3] = 0x10325476; 58 | H[4] = 0xC3D2E1F0; 59 | 60 | _computed = false; 61 | _corrupted = false; 62 | } 63 | 64 | void Sha1::input(const unsigned char* message_array, size_t length) 65 | { 66 | if (length == 0) 67 | { 68 | return; 69 | } 70 | 71 | if (_computed || _corrupted) 72 | { 73 | _corrupted = true; 74 | return; 75 | } 76 | 77 | if (_messageBlockIndex >= 64) 78 | { 79 | return; 80 | } 81 | 82 | while (length-- && !_corrupted) 83 | { 84 | _messageBlock[_messageBlockIndex++] = (*message_array & 0xFF); 85 | 86 | _lengthLow += 8; 87 | _lengthLow &= 0xFFFFFFFF; // Force it to 32 bits 88 | if (_lengthLow == 0) 89 | { 90 | _length_high++; 91 | _length_high &= 0xFFFFFFFF; // Force it to 32 bits 92 | if (_length_high == 0) 93 | { 94 | _corrupted = true; // Message is too long 95 | } 96 | } 97 | 98 | if (_messageBlockIndex == 64) 99 | { 100 | processMessageBlock(); 101 | } 102 | 103 | message_array++; 104 | } 105 | } 106 | 107 | void Sha1::processMessageBlock() 108 | { 109 | const unsigned K[] = {// Constants defined for SHA-1 110 | 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; 111 | int t; // Loop counter 112 | unsigned temp; // Temporary word value 113 | unsigned W[80]; // Word sequence 114 | unsigned A, B, C, D, E; // Word buffers 115 | 116 | /* 117 | * Initialize the first 16 words in the array W 118 | */ 119 | for (t = 0; t < 16; t++) 120 | { 121 | W[t] = ((unsigned)_messageBlock[t * 4]) << 24; 122 | W[t] |= ((unsigned)_messageBlock[t * 4 + 1]) << 16; 123 | W[t] |= ((unsigned)_messageBlock[t * 4 + 2]) << 8; 124 | W[t] |= ((unsigned)_messageBlock[t * 4 + 3]); 125 | } 126 | 127 | for (t = 16; t < 80; t++) 128 | { 129 | W[t] = circularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); 130 | } 131 | 132 | A = H[0]; 133 | B = H[1]; 134 | C = H[2]; 135 | D = H[3]; 136 | E = H[4]; 137 | 138 | for (t = 0; t < 20; t++) 139 | { 140 | temp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; 141 | temp &= 0xFFFFFFFF; 142 | E = D; 143 | D = C; 144 | C = circularShift(30, B); 145 | B = A; 146 | A = temp; 147 | } 148 | 149 | for (t = 20; t < 40; t++) 150 | { 151 | temp = circularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1]; 152 | temp &= 0xFFFFFFFF; 153 | E = D; 154 | D = C; 155 | C = circularShift(30, B); 156 | B = A; 157 | A = temp; 158 | } 159 | 160 | for (t = 40; t < 60; t++) 161 | { 162 | temp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; 163 | temp &= 0xFFFFFFFF; 164 | E = D; 165 | D = C; 166 | C = circularShift(30, B); 167 | B = A; 168 | A = temp; 169 | } 170 | 171 | for (t = 60; t < 80; t++) 172 | { 173 | temp = circularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3]; 174 | temp &= 0xFFFFFFFF; 175 | E = D; 176 | D = C; 177 | C = circularShift(30, B); 178 | B = A; 179 | A = temp; 180 | } 181 | 182 | H[0] = (H[0] + A) & 0xFFFFFFFF; 183 | H[1] = (H[1] + B) & 0xFFFFFFFF; 184 | H[2] = (H[2] + C) & 0xFFFFFFFF; 185 | H[3] = (H[3] + D) & 0xFFFFFFFF; 186 | H[4] = (H[4] + E) & 0xFFFFFFFF; 187 | 188 | _messageBlockIndex = 0; 189 | } 190 | 191 | void Sha1::padmessage() 192 | { 193 | /* 194 | * Check to see if the current message block is too small to hold 195 | * the initial padding bits and length. If so, we will pad the 196 | * block, process it, and then continue padding into a second block. 197 | */ 198 | if (_messageBlockIndex > 55) 199 | { 200 | if (_messageBlockIndex < 64) 201 | { 202 | _messageBlock[_messageBlockIndex++] = 0x80; 203 | } 204 | while (_messageBlockIndex < 64) 205 | { 206 | _messageBlock[_messageBlockIndex++] = 0; 207 | } 208 | 209 | processMessageBlock(); 210 | 211 | while (_messageBlockIndex < 56) 212 | { 213 | _messageBlock[_messageBlockIndex++] = 0; 214 | } 215 | } 216 | else 217 | { 218 | if (_messageBlockIndex < 64) 219 | { 220 | _messageBlock[_messageBlockIndex++] = 0x80; 221 | } 222 | while (_messageBlockIndex < 56) 223 | { 224 | _messageBlock[_messageBlockIndex++] = 0; 225 | } 226 | } 227 | 228 | /* 229 | * Store the message length as the last 8 octets 230 | */ 231 | _messageBlock[56] = (_length_high >> 24) & 0xFF; 232 | _messageBlock[57] = (_length_high >> 16) & 0xFF; 233 | _messageBlock[58] = (_length_high >> 8) & 0xFF; 234 | _messageBlock[59] = (_length_high) & 0xFF; 235 | _messageBlock[60] = (_lengthLow >> 24) & 0xFF; 236 | _messageBlock[61] = (_lengthLow >> 16) & 0xFF; 237 | _messageBlock[62] = (_lengthLow >> 8) & 0xFF; 238 | _messageBlock[63] = (_lengthLow) & 0xFF; 239 | 240 | processMessageBlock(); 241 | } 242 | 243 | struct sha1hash Sha1::hash() 244 | { 245 | padmessage(); 246 | 247 | struct sha1hash r; 248 | int i = 0; 249 | 250 | r.bytes[i++] = (H[0] >> 24) & 0xFF; 251 | r.bytes[i++] = (H[0] >> 16) & 0xFF; 252 | r.bytes[i++] = (H[0] >> 8) & 0xFF; 253 | r.bytes[i++] = (H[0]) & 0xFF; 254 | 255 | r.bytes[i++] = (H[1] >> 24) & 0xFF; 256 | r.bytes[i++] = (H[1] >> 16) & 0xFF; 257 | r.bytes[i++] = (H[1] >> 8) & 0xFF; 258 | r.bytes[i++] = (H[1]) & 0xFF; 259 | 260 | r.bytes[i++] = (H[2] >> 24) & 0xFF; 261 | r.bytes[i++] = (H[2] >> 16) & 0xFF; 262 | r.bytes[i++] = (H[2] >> 8) & 0xFF; 263 | r.bytes[i++] = (H[2]) & 0xFF; 264 | 265 | r.bytes[i++] = (H[3] >> 24) & 0xFF; 266 | r.bytes[i++] = (H[3] >> 16) & 0xFF; 267 | r.bytes[i++] = (H[3] >> 8) & 0xFF; 268 | r.bytes[i++] = (H[3]) & 0xFF; 269 | 270 | r.bytes[i++] = (H[4] >> 24) & 0xFF; 271 | r.bytes[i++] = (H[4] >> 16) & 0xFF; 272 | r.bytes[i++] = (H[4] >> 8) & 0xFF; 273 | r.bytes[i++] = (H[4]) & 0xFF; 274 | 275 | reset(); 276 | 277 | return r; 278 | } 279 | 280 | struct sha1hash sha1(const char* text, size_t len) 281 | { 282 | Sha1 x((const unsigned char*)(text), len); 283 | return x.hash(); 284 | } 285 | 286 | uint32_t swapOrder32(uint32_t n) 287 | { 288 | if (!isBigEndian) 289 | { 290 | return ((n >> 24) & 0x000000FF) | ((n >> 8) & 0x0000FF00) | ((n << 8) & 0x00FF0000) | 291 | ((n << 24) & 0xFF000000); 292 | } 293 | 294 | return n; 295 | } 296 | 297 | uint16_t swapOrder16(uint16_t n) 298 | { 299 | if (!isBigEndian) 300 | { 301 | return ((n >> 8) & 0x00FF) | ((n << 8) & 0xFF00); 302 | } 303 | return n; 304 | } 305 | 306 | /* Name string is a fully-qualified domain name */ 307 | uuid_object NameSpace_DNS = {/* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 */ 308 | 0x6ba7b810, 0x9dad, 0x11d1, 309 | 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; 310 | 311 | uuid_object create_sha1_guid_from_name(const char* name, size_t namelen) 312 | { 313 | uuid_object uuid; 314 | 315 | /* put name space ID in network byte order so it hashes the same no matter what endian machine we're on */ 316 | 317 | // namespace DNS 318 | uuid_object net_nsid = {/* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 */ 319 | 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; 320 | 321 | net_nsid.time_low = swapOrder32(net_nsid.time_low); 322 | net_nsid.time_mid = swapOrder16(net_nsid.time_mid); 323 | net_nsid.time_hi_and_version = swapOrder16(net_nsid.time_hi_and_version); 324 | 325 | Sha1 c; 326 | c.input((const uint8_t*)&net_nsid, sizeof(net_nsid)); 327 | c.input((const uint8_t*)name, namelen); 328 | auto hash = c.hash(); 329 | 330 | /* convert UUID to local byte order */ 331 | memcpy(&uuid, hash.bytes, sizeof(uuid)); 332 | uuid.time_low = swapOrder32(uuid.time_low); 333 | uuid.time_mid = swapOrder16(uuid.time_mid); 334 | uuid.time_hi_and_version = swapOrder16(uuid.time_hi_and_version); 335 | 336 | const auto v = 5; 337 | /* put in the variant and version bits */ 338 | uuid.time_hi_and_version &= 0x0FFF; 339 | uuid.time_hi_and_version |= (v << 12); 340 | uuid.clock_seq_hi_and_reserved &= 0x3F; 341 | uuid.clock_seq_hi_and_reserved |= 0x80; 342 | 343 | return uuid; 344 | } 345 | } // namespace Crypto 346 | -------------------------------------------------------------------------------- /src/detail/shared/sha1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Crypto 8 | { 9 | 10 | struct sha1hash 11 | { 12 | uint8_t bytes[20]; 13 | }; 14 | 15 | struct sha1hash sha1(const char* text, size_t len); 16 | 17 | typedef struct uuid_object_ 18 | { 19 | uint32_t time_low; 20 | uint16_t time_mid; 21 | uint16_t time_hi_and_version; 22 | uint8_t clock_seq_hi_and_reserved; 23 | uint8_t clock_seq_low; 24 | uint8_t node[6]; 25 | } uuid_object; 26 | 27 | uuid_object create_sha1_guid_from_name(const char* name, size_t namelen); 28 | 29 | } // namespace Crypto 30 | -------------------------------------------------------------------------------- /src/detail/shared/spinlock.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | namespace ClapWrapper::detail::shared 5 | { 6 | struct SpinLock 7 | { 8 | private: 9 | std::atomic locked_{false}; 10 | 11 | public: 12 | SpinLock() : locked_(false) 13 | { 14 | } 15 | 16 | void lock() 17 | { 18 | while (locked_.exchange(true, std::memory_order_acquire)) 19 | { 20 | } 21 | } 22 | 23 | void unlock() 24 | { 25 | locked_.store(false, std::memory_order_release); 26 | } 27 | }; 28 | 29 | struct SpinLockGuard 30 | { 31 | SpinLock &lock; 32 | SpinLockGuard(SpinLock &l) : lock(l) 33 | { 34 | lock.lock(); 35 | }; 36 | ~SpinLockGuard() 37 | { 38 | lock.unlock(); 39 | } 40 | }; 41 | } // namespace ClapWrapper::detail::shared -------------------------------------------------------------------------------- /src/detail/standalone/entry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "standalone_details.h" 4 | #include "standalone_host.h" 5 | #include "entry.h" 6 | 7 | namespace freeaudio::clap_wrapper::standalone 8 | { 9 | 10 | static std::unique_ptr standaloneHost; 11 | std::shared_ptr plugin; 12 | const clap_plugin_entry *entry{nullptr}; 13 | 14 | std::shared_ptr mainCreatePlugin(const clap_plugin_entry *ee, const std::string &clapId, 15 | uint32_t clapIndex, int argc, char **argv) 16 | { 17 | entry = ee; 18 | LOGINFO("Standalone starting : {}", argv[0]); 19 | 20 | LOGINFO("CLAP Version : {}.{}.{}", entry->clap_version.major, entry->clap_version.major, 21 | entry->clap_version.revision); 22 | 23 | entry->init(argv[0]); 24 | 25 | auto fac = (const clap_plugin_factory *)entry->get_factory(CLAP_PLUGIN_FACTORY_ID); 26 | if (!fac) 27 | { 28 | LOGINFO("[ERROR] entry->get_factory return nullptr"); 29 | return nullptr; 30 | } 31 | 32 | if (!standaloneHost) 33 | { 34 | standaloneHost = std::make_unique(); 35 | } 36 | 37 | if (clapId.empty()) 38 | { 39 | LOGINFO("Loading CLAP by index {}", clapIndex); 40 | plugin = Clap::Plugin::createInstance(fac, clapIndex, standaloneHost.get()); 41 | } 42 | else 43 | { 44 | LOGINFO("Loading CLAP by id '{}'", clapId); 45 | plugin = Clap::Plugin::createInstance(fac, clapId, standaloneHost.get()); 46 | } 47 | 48 | if (!plugin) 49 | { 50 | LOGINFO("[ERROR] Unable to create plugin"); 51 | return nullptr; 52 | } 53 | 54 | standaloneHost->setPlugin(plugin); 55 | 56 | plugin->initialize(); 57 | 58 | auto pt = getStandaloneSettingsPath(); 59 | if (pt.has_value()) 60 | { 61 | auto loadPath = *pt / plugin->_plugin->desc->id; 62 | 63 | try 64 | { 65 | fs::create_directories(loadPath); 66 | standaloneHost->saveStandaloneAndPluginSettings(loadPath, "defaults.clapwrapper"); 67 | } 68 | catch (const fs::filesystem_error &e) 69 | { 70 | // Oh well - whatcha gonna do? 71 | } 72 | 73 | try 74 | { 75 | if (fs::exists(loadPath / "settings.clapwrapper")) 76 | { 77 | standaloneHost->tryLoadStandaloneAndPluginSettings(loadPath, "settings.clapwrapper"); 78 | } 79 | } 80 | catch (const fs::filesystem_error &e) 81 | { 82 | // Oh well - whatcha gonna do? 83 | } 84 | } 85 | 86 | return plugin; 87 | } 88 | 89 | void mainStartAudio() 90 | { 91 | standaloneHost->startMIDIThread(); 92 | standaloneHost->startAudioThread(); 93 | } 94 | 95 | std::shared_ptr getMainPlugin() 96 | { 97 | return plugin; 98 | } 99 | 100 | StandaloneHost *getStandaloneHost() 101 | { 102 | if (!standaloneHost) 103 | { 104 | standaloneHost = std::make_unique(); 105 | } 106 | return standaloneHost.get(); 107 | } 108 | 109 | int mainWait() 110 | { 111 | while (standaloneHost->running) 112 | { 113 | using namespace std::chrono_literals; 114 | std::this_thread::sleep_for(1s); 115 | } 116 | return 0; 117 | } 118 | 119 | int mainFinish() 120 | { 121 | LOGINFO("Shutting down"); 122 | 123 | if (standaloneHost && plugin) 124 | { 125 | standaloneHost->stopAudioThread(); 126 | standaloneHost->stopMIDIThread(); 127 | 128 | auto pt = getStandaloneSettingsPath(); 129 | if (pt.has_value()) 130 | { 131 | auto savePath = *pt / plugin->_plugin->desc->id; 132 | LOGINFO("Saving settings to '{}'", savePath.u8string()); 133 | try 134 | { 135 | fs::create_directories(savePath); 136 | standaloneHost->saveStandaloneAndPluginSettings(savePath, "settings.clapwrapper"); 137 | } 138 | catch (const fs::filesystem_error &e) 139 | { 140 | // Oh well - whatcha gonna do? 141 | } 142 | } 143 | else 144 | { 145 | LOGINFO("[WARNING] No Standalone Settings Path; not streaming"); 146 | } 147 | 148 | plugin->deactivate(); 149 | } 150 | plugin.reset(); 151 | standaloneHost.reset(); 152 | 153 | if (entry) 154 | { 155 | entry->deinit(); 156 | } 157 | return 0; 158 | } 159 | } // namespace freeaudio::clap_wrapper::standalone 160 | -------------------------------------------------------------------------------- /src/detail/standalone/entry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace freeaudio::clap_wrapper::standalone 8 | { 9 | std::shared_ptr mainCreatePlugin(const clap_plugin_entry *entry, const std::string &clapId, 10 | uint32_t clapIndex, int argc, char **argv); 11 | void mainStartAudio(); 12 | 13 | std::shared_ptr getMainPlugin(); 14 | 15 | struct StandaloneHost; 16 | StandaloneHost *getStandaloneHost(); 17 | 18 | int mainWait(); 19 | int mainFinish(); 20 | } // namespace freeaudio::clap_wrapper::standalone -------------------------------------------------------------------------------- /src/detail/standalone/linux/gtkutils.cpp: -------------------------------------------------------------------------------- 1 | #if CLAP_WRAPPER_HAS_GTK3 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "gtkutils.h" 9 | #include "detail/standalone/standalone_details.h" 10 | #include "detail/standalone/standalone_host.h" 11 | #include "detail/standalone/entry.h" 12 | 13 | #include 14 | 15 | namespace freeaudio::clap_wrapper::standalone::linux_standalone 16 | { 17 | 18 | static void activate(GtkApplication *app, gpointer user_data) 19 | { 20 | GdkDisplay *display = gdk_display_get_default(); 21 | 22 | if (!GDK_IS_X11_DISPLAY(display)) 23 | { 24 | std::cout << "clap-wrapper standalone requires GDK X11 backend" << std::endl; 25 | std::terminate(); 26 | } 27 | 28 | auto g = (GtkGui *)user_data; 29 | g->setupPlugin(app); 30 | } 31 | 32 | static gboolean onResize(GtkWidget *wid, GdkEventConfigure *event, gpointer user_data) 33 | { 34 | auto g = (GtkGui *)user_data; 35 | return g->resizePlugin(wid, event->width, event->height); 36 | } 37 | 38 | bool GtkGui::resizePlugin(GtkWidget *wid, uint32_t w, uint32_t h) 39 | { 40 | if (plugin->_ext._gui) 41 | { 42 | auto gui = plugin->_ext._gui; 43 | auto p = plugin->_plugin; 44 | 45 | if (!gui->can_resize(p)) 46 | { 47 | gui->get_size(p, &w, &h); 48 | gtk_window_resize(GTK_WINDOW(wid), w, h); 49 | return TRUE; 50 | } 51 | 52 | #if 1 53 | gui->set_size(p, w, h); 54 | #else 55 | // For some reason, this freaks out with drags in surge on gtk on linux. 56 | auto adj = false; 57 | auto aw = w, ah = h; 58 | gui->adjust_size(p, &aw, &ah); 59 | gui->set_size(p, aw, ah); 60 | 61 | if (aw != w || ah != h) adj = true; 62 | 63 | w = aw; 64 | h = ah; 65 | 66 | gtk_window_resize(GTK_WINDOW(wid), w, h); 67 | 68 | return adj; 69 | #endif 70 | } 71 | return FALSE; 72 | } 73 | 74 | void GtkGui::setupPlugin(_GtkApplication *app) 75 | { 76 | GtkWidget *window; 77 | 78 | if (plugin->_ext._gui) 79 | { 80 | auto ui = plugin->_ext._gui; 81 | auto p = plugin->_plugin; 82 | if (!ui->is_api_supported(p, CLAP_WINDOW_API_X11, false)) 83 | LOGINFO("[ERROR] CLAP does not support X11"); 84 | 85 | ui->create(p, CLAP_WINDOW_API_X11, false); 86 | ui->set_scale(p, 1); 87 | 88 | uint32_t w, h; 89 | ui->get_size(p, &w, &h); 90 | ui->adjust_size(p, &w, &h); 91 | 92 | window = gtk_application_window_new(app); 93 | gtk_window_set_title(GTK_WINDOW(window), p->desc->name); 94 | gtk_window_set_default_size(GTK_WINDOW(window), w, h); 95 | 96 | GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 97 | gtk_container_add(GTK_CONTAINER(window), vbox); 98 | 99 | // Create the 'inner window' 100 | GtkWidget *frame = gtk_frame_new("Inner 'Window'"); 101 | gtk_widget_set_size_request(frame, w, h); 102 | gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); 103 | 104 | g_signal_connect(window, "configure-event", G_CALLBACK(onResize), this); 105 | 106 | gtk_widget_show_all(window); 107 | 108 | clap_window win; 109 | win.api = CLAP_WINDOW_API_X11; 110 | auto gw = gtk_widget_get_window(GTK_WIDGET(frame)); 111 | win.x11 = GDK_WINDOW_XID(gw); 112 | ui->set_parent(p, &win); 113 | ui->show(p); 114 | } 115 | } 116 | 117 | void GtkGui::initialize(freeaudio::clap_wrapper::standalone::StandaloneHost *sah) 118 | { 119 | sah->gtkGui = this; 120 | app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); 121 | g_signal_connect(app, "activate", G_CALLBACK(activate), this); 122 | } 123 | 124 | void GtkGui::setPlugin(std::shared_ptr p) 125 | { 126 | plugin = p; 127 | } 128 | 129 | void GtkGui::runloop(int argc, char **argv) 130 | { 131 | g_application_run(G_APPLICATION(app), argc, argv); 132 | } 133 | 134 | void GtkGui::shutdown() 135 | { 136 | g_object_unref(app); 137 | } 138 | 139 | int gtimercb(void *ud) 140 | { 141 | auto tcb = (GtkGui::TimerCB *)ud; 142 | return tcb->that->runTimerFn(tcb->id); 143 | } 144 | 145 | bool GtkGui::register_timer(uint32_t period_ms, clap_id *timer_id) 146 | { 147 | std::lock_guard g{cbMutex}; 148 | 149 | auto nid = currTimer++; 150 | *timer_id = nid; 151 | 152 | auto nto = std::make_unique(); 153 | nto->id = nid; 154 | nto->that = this; 155 | 156 | g_timeout_add(period_ms, gtimercb, nto.get()); 157 | 158 | timerCbs.insert(std::move(nto)); 159 | return true; 160 | } 161 | 162 | bool GtkGui::unregister_timer(clap_id timer_id) 163 | { 164 | std::lock_guard g{cbMutex}; 165 | terminatedTimers.insert(timer_id); 166 | return true; 167 | } 168 | 169 | int GtkGui::runTimerFn(clap_id id) 170 | { 171 | std::lock_guard g{cbMutex}; 172 | if (terminatedTimers.find(id) != terminatedTimers.end()) return false; 173 | 174 | if (plugin->_ext._timer) plugin->_ext._timer->on_timer(plugin->_plugin, id); 175 | return true; 176 | } 177 | 178 | int gfdcb(int fd, GIOCondition cond, void *ud) 179 | { 180 | auto that = (GtkGui::FDCB *)ud; 181 | 182 | assert(fd == that->fd); 183 | 184 | return that->that->runFD(fd, that->flags); 185 | } 186 | 187 | bool GtkGui::register_fd(int fd, clap_posix_fd_flags_t flags) 188 | { 189 | std::lock_guard g{cbMutex}; 190 | 191 | auto nto = std::make_unique(); 192 | nto->fd = fd; 193 | nto->that = this; 194 | nto->flags = flags; 195 | 196 | int cd{0}; 197 | if (flags & CLAP_POSIX_FD_READ) cd = cd | G_IO_IN | G_IO_PRI; 198 | if (flags & CLAP_POSIX_FD_WRITE) cd = cd | G_IO_OUT; 199 | if (flags & CLAP_POSIX_FD_ERROR) cd = cd | G_IO_ERR; 200 | 201 | nto->ghandle = g_unix_fd_add(fd, (GIOCondition)cd, gfdcb, nto.get()); 202 | 203 | fdCbs.insert(std::move(nto)); 204 | 205 | return true; 206 | } 207 | bool GtkGui::unregister_fd(int fd) 208 | { 209 | std::lock_guard g{cbMutex}; 210 | for (const auto &f : fdCbs) 211 | { 212 | if (f->fd == fd && f->ghandle) 213 | { 214 | g_source_remove(f->ghandle); 215 | f->ghandle = 0; 216 | } 217 | } 218 | return true; 219 | } 220 | 221 | int GtkGui::runFD(int fd, clap_posix_fd_flags_t flags) 222 | { 223 | if (plugin->_ext._posixfd) 224 | { 225 | plugin->_ext._posixfd->on_fd(plugin->_plugin, fd, flags); 226 | } 227 | return true; 228 | } 229 | 230 | bool GtkGui::parseCommandLine(int argc, char **argv) 231 | { 232 | auto sah = freeaudio::clap_wrapper::standalone::getStandaloneHost(); 233 | 234 | auto [i, o, s] = sah->getDefaultAudioInOutSampleRate(); 235 | 236 | bool list_devices{false}; 237 | int sampleRate{s}; 238 | unsigned int inId{i}, outId{o}; 239 | 240 | #ifdef __GNUC__ 241 | #pragma GCC diagnostic push 242 | #pragma GCC diagnostic ignored \ 243 | "-Wmissing-field-initializers" // other peoples errors are outside my scope 244 | #endif 245 | const GOptionEntry entries[] = { 246 | {"list-devices", 'l', 0, G_OPTION_ARG_NONE, &list_devices, "List Input Output and MIDI Devices", 247 | nullptr}, 248 | {"sample-rate", 's', 0, G_OPTION_ARG_INT, &sampleRate, "Sample Rate", nullptr}, 249 | {"input-device", 'i', 0, G_OPTION_ARG_INT, &inId, "Input Device (0 for no input)", nullptr}, 250 | {"output-device", 'o', 0, G_OPTION_ARG_INT, &outId, "Output Device (0 for no input)", nullptr}, 251 | {NULL}}; 252 | #ifdef __GNUC__ 253 | #pragma GCC diagnostic pop 254 | #endif 255 | 256 | GOptionContext *context; 257 | GError *error = nullptr; 258 | 259 | context = g_option_context_new("Clap Wrapper Standalone"); 260 | g_option_context_add_main_entries(context, entries, NULL); 261 | 262 | g_option_context_add_group(context, gtk_get_option_group(TRUE)); 263 | if (!g_option_context_parse(context, &argc, &argv, &error)) 264 | { 265 | g_print("Failed to parse options: %s\n", error->message); 266 | g_error_free(error); 267 | return false; 268 | } 269 | 270 | if (list_devices) 271 | { 272 | std::cout << "\n\nAvailable Audio Interfaces:\n\nOutput:\n"; 273 | auto outD = sah->getOutputAudioDevices(); 274 | for (auto &d : outD) 275 | { 276 | std::cout << " - " << d.name << " (id=" << d.ID << " channels=" << d.outputChannels << ")" 277 | << std::endl; 278 | } 279 | 280 | std::cout << "\nInput:\n"; 281 | auto inD = sah->getInputAudioDevices(); 282 | for (auto &d : inD) 283 | { 284 | std::cout << " - " << d.name << " (id=" << d.ID << " channels=" << d.outputChannels << ")" 285 | << std::endl; 286 | } 287 | return false; 288 | } 289 | 290 | LOGINFO("Post Argument Parse: inId={} outId={} sampleRate={}", inId, outId, sampleRate); 291 | sah->setStartupAudio(inId, outId, sampleRate); 292 | 293 | return true; 294 | } 295 | 296 | } // namespace freeaudio::clap_wrapper::standalone::linux_standalone 297 | 298 | #endif 299 | -------------------------------------------------------------------------------- /src/detail/standalone/linux/gtkutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "detail/standalone/standalone_host.h" 6 | 7 | struct _GtkApplication; // sigh their typedef screws up forward decls 8 | struct _GtkWidget; 9 | 10 | namespace freeaudio::clap_wrapper::standalone::linux_standalone 11 | { 12 | struct GtkGui 13 | { 14 | _GtkApplication *app{nullptr}; 15 | std::shared_ptr plugin; 16 | 17 | bool parseCommandLine(int argc, char **argv); 18 | 19 | void initialize(freeaudio::clap_wrapper::standalone::StandaloneHost *); 20 | void setPlugin(std::shared_ptr); 21 | void runloop(int argc, char **argv); 22 | void shutdown(); 23 | 24 | void setupPlugin(_GtkApplication *app); 25 | bool resizePlugin(_GtkWidget *wid, uint32_t w, uint32_t h); 26 | 27 | clap_id currTimer{8675309}; 28 | std::mutex cbMutex{}; 29 | 30 | struct TimerCB 31 | { 32 | clap_id id; 33 | GtkGui *that; 34 | }; 35 | std::unordered_set> timerCbs; 36 | std::unordered_set terminatedTimers; 37 | int runTimerFn(clap_id id); 38 | bool timersStarted{false}; 39 | bool register_timer(uint32_t period_ms, clap_id *timer_id); 40 | bool unregister_timer(clap_id timer_id); 41 | 42 | struct FDCB 43 | { 44 | uint32_t ghandle; 45 | int fd; 46 | clap_posix_fd_flags_t flags; 47 | GtkGui *that; 48 | }; 49 | std::unordered_set> fdCbs; 50 | bool register_fd(int fd, clap_posix_fd_flags_t flags); 51 | // todo g_source_modify_unix_fd 52 | bool unregister_fd(int fd); 53 | int runFD(int fd, clap_posix_fd_flags_t flags); 54 | }; 55 | } // namespace freeaudio::clap_wrapper::standalone::linux_standalone -------------------------------------------------------------------------------- /src/detail/standalone/macos/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | // @class AudioSettingsWindowDelegate; 4 | 5 | @interface ClapWrapperAppDelegate : NSObject 6 | { 7 | // AudioSettingsWindowDelegate *audioSettingsWindowDelegate; 8 | } 9 | 10 | @property(assign) NSTimer* requestCallbackTimer; 11 | @property(assign) IBOutlet NSWindow* window; 12 | 13 | - (IBAction)openAudioSettingsWindow:(id)sender; 14 | 15 | - (IBAction)streamWrapperFileAs:(id)sender; 16 | - (IBAction)openWrapperFile:(id)sender; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /src/detail/standalone/macos/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | ${MACOSX_BUNDLE_BUNDLE_NAME} would like microphone access. 7 | 8 | CFBundleDevelopmentRegion 9 | en 10 | CFBundleExecutable 11 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 12 | CFBundleIconFile 13 | 14 | CFBundleIdentifier 15 | ${MACOSX_BUNDLE_BUNDLE_NAME}.standalone 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | ${MACOSX_BUNDLE_BUNDLE_NAME} 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | NSMainNibFile 29 | MainMenu 30 | NSPrincipalClass 31 | NSApplication 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/detail/standalone/macos/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/detail/standalone/macos/StandaloneFunctions.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include "detail/standalone/standalone_host.h" 5 | 6 | namespace freeaudio::clap_wrapper::standalone 7 | { 8 | std::optional getStandaloneSettingsPath() 9 | { 10 | auto *fileManager = [NSFileManager defaultManager]; 11 | auto *resultURLs = [fileManager URLsForDirectory:NSApplicationSupportDirectory 12 | inDomains:NSUserDomainMask]; 13 | if (resultURLs) 14 | { 15 | auto *u = [resultURLs objectAtIndex:0]; 16 | return fs::path{[u fileSystemRepresentation]} / "clap-wrapper-standalone"; 17 | } 18 | return std::nullopt; 19 | } 20 | } // namespace freeaudio::clap_wrapper::standalone 21 | -------------------------------------------------------------------------------- /src/detail/standalone/standalone_details.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "detail/os/osutil.h" 7 | 8 | #define TRACE LOGDETAIL("") 9 | -------------------------------------------------------------------------------- /src/detail/standalone/standalone_host.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "standalone_details.h" 12 | 13 | #include "detail/clap/fsutil.h" 14 | 15 | #ifdef __GNUC__ 16 | #pragma GCC diagnostic push 17 | #pragma GCC diagnostic ignored "-Wall" // other peoples errors are outside my scope 18 | #endif 19 | 20 | #include "RtAudio.h" 21 | #include "RtMidi.h" 22 | 23 | #ifdef __GNUC__ 24 | #pragma GCC diagnostic pop 25 | #endif 26 | 27 | #include "clap_proxy.h" 28 | #include "detail/shared/fixedqueue.h" 29 | 30 | namespace freeaudio::clap_wrapper::standalone 31 | { 32 | #if LIN 33 | #if CLAP_WRAPPER_HAS_GTK3 34 | namespace linux_standalone 35 | { 36 | struct GtkGui; 37 | } 38 | #endif 39 | #endif 40 | 41 | std::optional getStandaloneSettingsPath(); 42 | 43 | struct StandaloneHost : Clap::IHost 44 | { 45 | StandaloneHost() 46 | { 47 | inputEvents.ctx = this; 48 | inputEvents.size = ie_getsize; 49 | inputEvents.get = ie_get; 50 | outputEvents.ctx = this; 51 | outputEvents.try_push = oe_try_push; 52 | } 53 | virtual ~StandaloneHost(); 54 | 55 | static bool oe_try_push(const struct clap_output_events *oe, const clap_event_header_t *evt) 56 | { 57 | return true; 58 | } 59 | 60 | static uint32_t ie_getsize(const struct clap_input_events *ie) 61 | { 62 | auto sh = (StandaloneHost *)ie->ctx; 63 | return sh->inputEventSize(); 64 | } 65 | static const clap_event_header_t *ie_get(const struct clap_input_events *ie, uint32_t idx) 66 | { 67 | auto sh = (StandaloneHost *)ie->ctx; 68 | return sh->inputEvent(idx); 69 | } 70 | 71 | static constexpr int maxEventsPerCycle{256}; 72 | static constexpr int eventSize{1024}; 73 | static constexpr int queueSize{eventSize * maxEventsPerCycle}; 74 | const unsigned char eventQueue[queueSize]{}; 75 | 76 | int currInput{0}; 77 | void clearInputEvents() 78 | { 79 | currInput = 0; 80 | } 81 | bool pushInputEvent(clap_event_header_t *event) 82 | { 83 | if (event->size > eventSize) 84 | { 85 | return false; 86 | } 87 | if (currInput >= maxEventsPerCycle) 88 | { 89 | return false; 90 | } 91 | memcpy((void *)(eventQueue + currInput * eventSize), (const void *)event, event->size); 92 | currInput++; 93 | return true; 94 | } 95 | uint32_t inputEventSize() 96 | { 97 | return currInput; 98 | } 99 | const clap_event_header_t *inputEvent(uint32_t idx) 100 | { 101 | return (const clap_event_header_t *)(eventQueue + idx * eventSize); 102 | } 103 | 104 | std::shared_ptr clapPlugin; 105 | void setPlugin(std::shared_ptr plugin) 106 | { 107 | this->clapPlugin = plugin; 108 | } 109 | 110 | void mark_dirty() override 111 | { 112 | TRACE; 113 | } 114 | void restartPlugin() override 115 | { 116 | TRACE; 117 | } 118 | std::atomic callbackRequested{false}; 119 | void request_callback() override 120 | { 121 | callbackRequested = true; 122 | } 123 | void setupWrapperSpecifics(const clap_plugin_t *plugin) override 124 | { 125 | TRACE; 126 | } 127 | 128 | bool saveStandaloneAndPluginSettings(const fs::path &intoDir, const fs::path &withName); 129 | bool tryLoadStandaloneAndPluginSettings(const fs::path &fromDir, const fs::path &withName); 130 | 131 | uint32_t numAudioInputs{0}, numAudioOutputs{0}; 132 | std::vector inputChannelByBus; 133 | std::vector outputChannelByBus; 134 | uint32_t mainInput{0}, mainOutput{0}; 135 | uint32_t totalInputChannels{0}, totalOutputChannels{0}; 136 | void setupAudioBusses(const clap_plugin_t *plugin, 137 | const clap_plugin_audio_ports_t *audioports) override; 138 | 139 | bool hasMIDIInput{false}, hasClapNoteInput{false}, createsMidiOutput{false}; 140 | void setupMIDIBusses(const clap_plugin_t *plugin, const clap_plugin_note_ports_t *noteports) override; 141 | 142 | void setupParameters(const clap_plugin_t *plugin, const clap_plugin_params_t *params) override 143 | { 144 | TRACE; 145 | } 146 | void param_rescan(clap_param_rescan_flags flags) override 147 | { 148 | TRACE; 149 | } 150 | void param_clear(clap_id param, clap_param_clear_flags flags) override 151 | { 152 | TRACE; 153 | } 154 | void param_request_flush() override 155 | { 156 | } 157 | 158 | bool gui_can_resize() override; 159 | 160 | std::function onRequestResize = [](auto, auto) { return false; }; 161 | bool gui_request_resize(uint32_t width, uint32_t height) override; 162 | 163 | bool gui_request_show() override 164 | { 165 | TRACE; 166 | return false; 167 | } 168 | bool gui_request_hide() override 169 | { 170 | TRACE; 171 | return false; 172 | } 173 | 174 | #if LIN 175 | #if CLAP_WRAPPER_HAS_GTK3 176 | freeaudio::clap_wrapper::standalone::linux_standalone::GtkGui *gtkGui{nullptr}; 177 | #endif 178 | #endif 179 | 180 | bool register_timer(uint32_t period_ms, clap_id *timer_id) override; 181 | bool unregister_timer(clap_id timer_id) override; 182 | 183 | const char *host_get_name() override; 184 | 185 | // context menu extension 186 | bool supportsContextMenu() const override 187 | { 188 | return false; 189 | } 190 | bool context_menu_populate(const clap_context_menu_target_t *target, 191 | const clap_context_menu_builder_t *builder) override 192 | { 193 | return false; 194 | } 195 | bool context_menu_perform(const clap_context_menu_target_t *target, clap_id action_id) override 196 | { 197 | return false; 198 | } 199 | bool context_menu_can_popup() override 200 | { 201 | return false; 202 | } 203 | bool context_menu_popup(const clap_context_menu_target_t *target, int32_t screen_index, int32_t x, 204 | int32_t y) override 205 | { 206 | return false; 207 | } 208 | 209 | #if LIN 210 | bool register_fd(int fd, clap_posix_fd_flags_t flags) override; 211 | bool modify_fd(int fd, clap_posix_fd_flags_t flags) override; 212 | bool unregister_fd(int fd) override; 213 | 214 | #endif 215 | 216 | void latency_changed() override 217 | { 218 | TRACE; 219 | } 220 | void tail_changed() override 221 | { 222 | TRACE; 223 | } 224 | 225 | // Implementation in standalone_host_midi.cpp 226 | struct midiChunk 227 | { 228 | char dat[3]{}; 229 | midiChunk() 230 | { 231 | memset(dat, 0, sizeof(dat)); 232 | } 233 | }; 234 | ClapWrapper::detail::shared::fixedqueue midiToAudioQueue; 235 | std::vector> midiIns; 236 | uint32_t numMidiPorts{0}; 237 | std::vector currentMidiPorts; 238 | void startMIDIThread(); 239 | void stopMIDIThread(); 240 | void processMIDIEvents(double deltatime, std::vector *message); 241 | static void midiCallback(double deltatime, std::vector *message, void *userData); 242 | 243 | // in standalone_host.cpp 244 | void clapProcess(void *pOutput, const void *pInoput, uint32_t frameCount); 245 | 246 | // Actual audio IO In standalone_host_audio.cpp 247 | std::unique_ptr rtaDac; 248 | std::function displayAudioError{nullptr}; 249 | RtAudio::Api audioApi{RtAudio::Api::UNSPECIFIED}; 250 | std::string audioApiName{RtAudio::getApiName(RtAudio::Api::UNSPECIFIED)}; 251 | std::string audioApiDisplayName{RtAudio::getApiDisplayName(RtAudio::Api::UNSPECIFIED)}; 252 | unsigned int audioInputDeviceID{0}, audioOutputDeviceID{0}; 253 | bool audioInputUsed{true}, audioOutputUsed{true}; 254 | int32_t currentSampleRate{0}; 255 | uint32_t currentBufferSize{0}; 256 | uint32_t currentInputChannels{0}, currentOutputChannels{0}; 257 | void guaranteeRtAudioDAC(); 258 | void setAudioApi(RtAudio::Api api); 259 | std::tuple getDefaultAudioInOutSampleRate(); 260 | void startAudioThread(); 261 | void startAudioThreadOn(unsigned int inputDeviceID, uint32_t inputChannels, bool useInput, 262 | unsigned int outputDeviceID, uint32_t outputChannels, bool useOutput, 263 | int32_t sampleRate); 264 | void stopAudioThread(); 265 | 266 | bool startupAudioSet{false}; 267 | unsigned int startAudioIn{0}, startAudioOut{0}; 268 | int startSampleRate{0}; 269 | void setStartupAudio(unsigned int in, unsigned int out, int sr) 270 | { 271 | startupAudioSet = true; 272 | startAudioIn = in; 273 | startAudioOut = out; 274 | startSampleRate = sr; 275 | } 276 | 277 | void activatePlugin(int32_t sr, int32_t minBlock, int32_t maxBlock); 278 | bool isActive{false}; 279 | 280 | std::vector getCompiledApi(); 281 | std::vector getInputAudioDevices(); 282 | std::vector getOutputAudioDevices(); 283 | std::vector getSampleRates(); 284 | std::vector getBufferSizes(); 285 | 286 | clap_input_events inputEvents{}; 287 | clap_output_events outputEvents{}; 288 | 289 | std::atomic running{true}, finishedRunning{false}; 290 | 291 | // We need to have play buffers for the clap. For now lets assume 292 | // (1) the standalone is never more than 64 total ins and outs and 293 | // (2) the block size is less that 4096 * 16 and 294 | // (3) memory in the standalone is pretty cheap 295 | static constexpr int utilityBufferSize{4096 * 16}; 296 | static constexpr int utilityBufferMaxChannels{64}; 297 | float utilityBuffer[utilityBufferMaxChannels][utilityBufferSize]{}; 298 | }; 299 | } // namespace freeaudio::clap_wrapper::standalone 300 | -------------------------------------------------------------------------------- /src/detail/standalone/standalone_host_audio.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifdef __GNUC__ 4 | #pragma GCC diagnostic push 5 | #pragma GCC diagnostic ignored "-Wall" // other peoples errors are outside my scope 6 | #endif 7 | 8 | //#define MINIAUDIO_IMPLEMENTATION 9 | //#include "miniaudio.h" 10 | #include "RtAudio.h" 11 | 12 | #ifdef __GNUC__ 13 | #pragma GCC diagnostic pop 14 | #endif 15 | 16 | #include "standalone_host.h" 17 | #include "entry.h" 18 | 19 | namespace freeaudio::clap_wrapper::standalone 20 | { 21 | int rtaCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 22 | double /* streamTime */, RtAudioStreamStatus status, void *data) 23 | { 24 | if (status) 25 | { 26 | } 27 | auto sh = (StandaloneHost *)data; 28 | sh->clapProcess(outputBuffer, inputBuffer, nBufferFrames); 29 | 30 | return 0; 31 | } 32 | 33 | void rtaErrorCallback(RtAudioErrorType errorType, const std::string &errorText) 34 | { 35 | if (errorType != RTAUDIO_OUTPUT_UNDERFLOW && errorType != RTAUDIO_INPUT_OVERFLOW) 36 | { 37 | LOGINFO("[ERROR] RtAudio reports '{}' [{}]", errorText, (int)errorType); 38 | auto ae = getStandaloneHost()->displayAudioError; 39 | if (ae) 40 | { 41 | ae(errorText); 42 | } 43 | } 44 | else 45 | { 46 | static bool reported = false; 47 | if (!reported) 48 | { 49 | LOGINFO("[ERROR] RtAudio reports under/overflow '{}' [{}]", errorText, (int)errorType); 50 | reported = true; 51 | } 52 | } 53 | } 54 | 55 | void StandaloneHost::guaranteeRtAudioDAC() 56 | { 57 | if (!rtaDac) 58 | { 59 | LOGDETAIL("Creating Standalone RtAudioDAC"); 60 | setAudioApi(RtAudio::Api::UNSPECIFIED); 61 | } 62 | } 63 | 64 | void StandaloneHost::setAudioApi(RtAudio::Api api) 65 | { 66 | rtaDac = std::make_unique(api, &rtaErrorCallback); 67 | audioApi = api; 68 | audioApiName = rtaDac->getApiName(api); 69 | audioApiDisplayName = rtaDac->getApiDisplayName(api); 70 | rtaDac->showWarnings(true); 71 | } 72 | 73 | std::tuple StandaloneHost::getDefaultAudioInOutSampleRate() 74 | { 75 | guaranteeRtAudioDAC(); 76 | auto iid = rtaDac->getDefaultInputDevice(); 77 | auto oid = rtaDac->getDefaultOutputDevice(); 78 | auto outInfo = rtaDac->getDeviceInfo(oid); 79 | auto sr = outInfo.currentSampleRate; 80 | if (sr < 1) 81 | { 82 | sr = outInfo.preferredSampleRate; 83 | } 84 | 85 | return {iid, oid, (int32_t)sr}; 86 | } 87 | void StandaloneHost::startAudioThread() 88 | { 89 | guaranteeRtAudioDAC(); 90 | 91 | if (startupAudioSet) 92 | { 93 | auto in = startAudioIn; 94 | auto out = startAudioOut; 95 | auto sr = startSampleRate; 96 | startAudioThreadOn(in, 2, in > 0 && numAudioInputs > 0, out, 2, out > 0 && numAudioOutputs > 0, sr); 97 | } 98 | else 99 | { 100 | auto [in, out, sr] = getDefaultAudioInOutSampleRate(); 101 | startAudioThreadOn(in, 2, numAudioInputs > 0, out, 2, numAudioOutputs > 0, sr); 102 | } 103 | } 104 | 105 | std::vector filterDevicesBy(const std::unique_ptr &rtaDac, 106 | std::function f) 107 | { 108 | std::vector res; 109 | auto dids = rtaDac->getDeviceIds(); 110 | auto dnms = rtaDac->getDeviceNames(); 111 | for (auto d : dids) 112 | { 113 | auto inf = rtaDac->getDeviceInfo(d); 114 | if (f(inf)) 115 | { 116 | res.push_back(inf); 117 | } 118 | } 119 | return res; 120 | } 121 | 122 | std::vector StandaloneHost::getCompiledApi() 123 | { 124 | guaranteeRtAudioDAC(); 125 | 126 | std::vector compiledApi; 127 | rtaDac->getCompiledApi(compiledApi); 128 | 129 | return compiledApi; 130 | } 131 | 132 | std::vector StandaloneHost::getInputAudioDevices() 133 | { 134 | guaranteeRtAudioDAC(); 135 | return filterDevicesBy(rtaDac, [](auto &a) { return a.inputChannels > 0; }); 136 | } 137 | 138 | std::vector StandaloneHost::getOutputAudioDevices() 139 | { 140 | guaranteeRtAudioDAC(); 141 | return filterDevicesBy(rtaDac, [](auto &a) { return a.outputChannels > 0; }); 142 | } 143 | 144 | std::vector StandaloneHost::getSampleRates() 145 | { 146 | guaranteeRtAudioDAC(); 147 | 148 | std::vector res; 149 | 150 | auto samplesRates{rtaDac->getDeviceInfo(audioInputDeviceID).sampleRates}; 151 | 152 | for (auto &sampleRate : rtaDac->getDeviceInfo(audioInputDeviceID).sampleRates) 153 | { 154 | res.push_back(sampleRate); 155 | } 156 | 157 | return res; 158 | } 159 | 160 | std::vector StandaloneHost::getBufferSizes() 161 | { 162 | guaranteeRtAudioDAC(); 163 | 164 | std::vector res{16, 32, 48, 64, 96, 128, 144, 160, 165 | 192, 224, 256, 480, 512, 1024, 2048, 4096}; 166 | return res; 167 | } 168 | 169 | void StandaloneHost::startAudioThreadOn(unsigned int inputDeviceID, uint32_t inputChannels, 170 | bool useInput, unsigned int outputDeviceID, 171 | uint32_t outputChannels, bool useOutput, int32_t reqSampleRate) 172 | { 173 | guaranteeRtAudioDAC(); 174 | 175 | if (rtaDac->isStreamRunning()) 176 | { 177 | stopAudioThread(); 178 | running = true; 179 | finishedRunning = false; 180 | } 181 | 182 | audioInputDeviceID = inputDeviceID; 183 | audioInputUsed = useInput; 184 | audioOutputDeviceID = outputDeviceID; 185 | audioOutputUsed = useOutput; 186 | 187 | auto dids = rtaDac->getDeviceIds(); 188 | auto dnms = rtaDac->getDeviceNames(); 189 | 190 | RtAudio::StreamParameters oParams; 191 | int32_t sampleRate{reqSampleRate}; 192 | 193 | if (useOutput) 194 | { 195 | oParams.deviceId = outputDeviceID; 196 | auto outInfo = rtaDac->getDeviceInfo(oParams.deviceId); 197 | oParams.nChannels = std::min(outputChannels, outInfo.outputChannels); 198 | oParams.firstChannel = 0; 199 | if (sampleRate < 0) 200 | { 201 | sampleRate = outInfo.preferredSampleRate; 202 | } 203 | else 204 | { 205 | // Mkae sure this sample rate is available 206 | bool isPossible{false}; 207 | for (auto sr : outInfo.sampleRates) 208 | { 209 | isPossible = isPossible || ((int)sr == (int)sampleRate); 210 | } 211 | if (!isPossible) 212 | { 213 | sampleRate = outInfo.preferredSampleRate; 214 | } 215 | } 216 | } 217 | 218 | RtAudio::StreamParameters iParams; 219 | if (useInput) 220 | { 221 | iParams.deviceId = inputDeviceID; 222 | auto inInfo = rtaDac->getDeviceInfo(iParams.deviceId); 223 | iParams.nChannels = std::min(inputChannels, inInfo.inputChannels); 224 | iParams.firstChannel = 0; 225 | if (sampleRate < 0) sampleRate = inInfo.preferredSampleRate; 226 | } 227 | 228 | if (sampleRate < 0) 229 | { 230 | LOGINFO("[WARNING] No preferred sample rate detected; using 48k"); 231 | sampleRate = 48000; 232 | } 233 | 234 | currentSampleRate = sampleRate; 235 | 236 | RtAudio::StreamOptions options; 237 | options.flags = RTAUDIO_SCHEDULE_REALTIME; 238 | 239 | /* 240 | * RTAudio doesn't tell you what the possible frame sizes are but instead 241 | * just tells you to try open stream with power of twos you want. So leave 242 | * this for now at 256 and return to it shortly. 243 | */ 244 | LOGINFO("[WARNING] Hardcoding frame size to 256 samples for now"); 245 | 246 | if (currentBufferSize == 0) 247 | { 248 | currentBufferSize = 256; 249 | } 250 | 251 | if (rtaDac->openStream((useOutput) ? &oParams : nullptr, (useInput) ? &iParams : nullptr, 252 | RTAUDIO_FLOAT32, sampleRate, ¤tBufferSize, &rtaCallback, (void *)this, 253 | &options)) 254 | { 255 | LOGINFO("[ERROR] Error opening rta stream '{}'", rtaDac->getErrorText()); 256 | rtaDac->closeStream(); 257 | return; 258 | } 259 | 260 | activatePlugin(sampleRate, 1, currentBufferSize * 2); 261 | 262 | LOGDETAIL("RtAudio Attached Devices"); 263 | if (useOutput) 264 | { 265 | for (auto i = 0U; i < dids.size(); ++i) 266 | { 267 | if (oParams.deviceId == dids[i]) LOGDETAIL(" - Output : '{}'", dnms[i]); 268 | } 269 | currentOutputChannels = oParams.nChannels; 270 | LOGDETAIL("RtAudio Output Stream Channels {}", oParams.nChannels); 271 | } 272 | if (useInput) 273 | { 274 | for (auto i = 0U; i < dids.size(); ++i) 275 | { 276 | if (iParams.deviceId == dids[i]) LOGDETAIL(" - Input : '{}'", dnms[i]); 277 | } 278 | currentInputChannels = iParams.nChannels; 279 | LOGDETAIL("RtAudio Input Stream Channels {}", iParams.nChannels); 280 | } 281 | 282 | if (!rtaDac->isStreamOpen()) 283 | { 284 | LOGINFO("[ERROR] Stream failed to open : {}", rtaDac->getErrorText()); 285 | return; 286 | } 287 | 288 | if (rtaDac->startStream()) 289 | { 290 | LOGINFO("[ERROR] startStream failed : {}", rtaDac->getErrorText()); 291 | return; 292 | } 293 | } 294 | 295 | void StandaloneHost::stopAudioThread() 296 | { 297 | LOGINFO("Shutting down audio"); 298 | if (!rtaDac->isStreamRunning()) 299 | { 300 | } 301 | else 302 | { 303 | running = false; 304 | 305 | // bit of a hack. Wait until we get an ack from audio callback 306 | for (auto i = 0; i < 10000 && !finishedRunning; ++i) 307 | { 308 | using namespace std::chrono_literals; 309 | std::this_thread::sleep_for(1ms); 310 | } 311 | 312 | if (rtaDac && rtaDac->isStreamRunning()) 313 | { 314 | rtaDac->stopStream(); 315 | rtaDac->closeStream(); 316 | } 317 | } 318 | return; 319 | } 320 | } // namespace freeaudio::clap_wrapper::standalone 321 | -------------------------------------------------------------------------------- /src/detail/standalone/standalone_host_midi.cpp: -------------------------------------------------------------------------------- 1 | #include "standalone_host.h" 2 | #include "standalone_details.h" 3 | 4 | #ifdef __GNUC__ 5 | #pragma GCC diagnostic push 6 | #pragma GCC diagnostic ignored "-Wall" // other peoples errors are outside my scope 7 | #endif 8 | 9 | #include "RtMidi.h" 10 | 11 | #ifdef __GNUC__ 12 | #pragma GCC diagnostic pop 13 | #endif 14 | 15 | namespace freeaudio::clap_wrapper::standalone 16 | { 17 | void StandaloneHost::startMIDIThread() 18 | { 19 | try 20 | { 21 | LOGINFO("Initializing Midi"); 22 | auto midiIn = std::make_unique(); 23 | numMidiPorts = midiIn->getPortCount(); 24 | } 25 | catch (RtMidiError &error) 26 | { 27 | error.printMessage(); 28 | exit(EXIT_FAILURE); 29 | } 30 | 31 | LOGDETAIL("MIDI: There are {} MIDI input sources available. Binding all.", numMidiPorts); 32 | for (unsigned int i = 0; i < numMidiPorts; i++) 33 | { 34 | try 35 | { 36 | auto midiIn = std::make_unique(); 37 | LOGDETAIL(" - '{}'", midiIn->getPortName(i)); 38 | midiIn->openPort(i); 39 | midiIn->setCallback(midiCallback, this); 40 | midiIns.push_back(std::move(midiIn)); 41 | } 42 | catch (RtMidiError &error) 43 | { 44 | error.printMessage(); 45 | } 46 | } 47 | } 48 | 49 | void StandaloneHost::processMIDIEvents(double deltatime, std::vector *message) 50 | { 51 | auto nBytes = message->size(); 52 | 53 | if (nBytes <= 3) 54 | { 55 | midiChunk ck; 56 | memset(ck.dat, 0, sizeof(ck.dat)); 57 | memcpy(ck.dat, message->data(), nBytes); 58 | midiToAudioQueue.push(ck); 59 | } 60 | } 61 | 62 | void StandaloneHost::midiCallback(double deltatime, std::vector *message, void *userData) 63 | { 64 | auto sh = (StandaloneHost *)userData; 65 | sh->processMIDIEvents(deltatime, message); 66 | } 67 | 68 | void StandaloneHost::stopMIDIThread() 69 | { 70 | for (auto &m : midiIns) 71 | { 72 | m.reset(); 73 | } 74 | } 75 | 76 | } // namespace freeaudio::clap_wrapper::standalone 77 | -------------------------------------------------------------------------------- /src/detail/standalone/windows/windows_standalone.manifest: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | true/pm 15 | 16 | 17 | PerMonitorV2 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/detail/vst3/aravst3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ara/ara.h" 4 | 5 | #include "pluginterfaces/base/funknown.h" 6 | #include "pluginterfaces/base/falignpush.h" 7 | 8 | // these interfaces are copied from the ARA SDK to maintain compatibility 9 | // without the need of the full ARA SDK 10 | // 11 | // the types handled here are just void* for the intercommunication between 12 | // VST3 Host and CLAP plugin. 13 | 14 | #define ARA_DEPRECATED(x) 15 | #define ARA_ADDENDUM(x) 16 | 17 | namespace ARA 18 | { 19 | /***************************************************************************************************/ 20 | //! Interface class to be implemented by an object provided by the VST3 factory. 21 | //! The host can use the VST3 factory to directly obtain the ARA factory, which allows for creating 22 | //! and maintaining the model independently of any IAudioProcessor instances, enabling tasks such as 23 | //! automatic tempo detection or audio-to-MIDI conversion. 24 | //! For rendering and editing the model however, there must be an associated IAudioProcessor class 25 | //! provided in the same binary. 26 | //! This match is usually trivial because there typically is only one such class in the binary, but 27 | //! there are cases such as WaveShell where multiple plug-ins live in the same binary, and only a 28 | //! subset of those plug-ins support ARA. In this scenario, the plug-in must use the same class name 29 | //! for the matching pair of ARA::IMainFactory and IAudioProcessor classes - this enables the host 30 | //! to quickly identify the matching pairs without having to create instances of all the 31 | //! IAudioProcessor classes to query their IPlugInEntryPoint::getFactory ()->factoryID to perform 32 | //! the matching. 33 | class IMainFactory : public Steinberg::FUnknown 34 | { 35 | public: 36 | //! Get the ARA factory. 37 | //! The returned pointer must remain valid throughout the lifetime of the object that provided it. 38 | //! The returned ARAFactory must be equal to the ARAFactory provided by the associated 39 | //! IAudioProcessor class through its IPlugInEntryPoint. 40 | virtual ARAFactoryPtr PLUGIN_API getFactory() = 0; 41 | virtual ~IMainFactory() 42 | { 43 | } 44 | static const Steinberg::FUID iid; 45 | }; 46 | 47 | //! Class category name for the ARA::IMainFactory. 48 | #if !defined(kARAMainFactoryClass) 49 | #define kARAMainFactoryClass "ARA Main Factory Class" 50 | #endif 51 | 52 | DECLARE_CLASS_IID(IMainFactory, 0xDB2A1669, 0xFAFD42A5, 0xA82F864F, 0x7B6872EA) 53 | 54 | /***************************************************************************************************/ 55 | //! Interface class to be implemented by the VST3 IAudioProcessor component (kVstAudioEffectClass). 56 | class IPlugInEntryPoint : public Steinberg::FUnknown 57 | { 58 | public: 59 | //! Get the ARA factory. 60 | //! The returned pointer must remain valid throughout the lifetime of the object that provided it. 61 | //! The returned ARAFactory must be equal to the ARAFactory provided by the associated IMainFactory. 62 | //! To prevent ambiguities, the name of the plug-in as stored in the PClassInfo.name of this 63 | //! class must match the ARAFactory.plugInName returned here. 64 | virtual ARAFactoryPtr PLUGIN_API getFactory() = 0; 65 | 66 | //! Bind the VST3 instance to an ARA document controller, switching it from "normal" operation 67 | //! to ARA mode, and exposing the ARA plug-in extension. 68 | //! Note that since ARA 2.0, this call has been deprecated and replaced with 69 | //! bindToDocumentControllerWithRoles (). 70 | //! This deprecated call is equivalent to the new call with no known roles set, however all 71 | //! ARA 1.x hosts are in fact using all instances with playback renderer, edit renderer and 72 | //! editor view role enabled, so plug-ins implementing ARA 1 backwards compatibility can 73 | //! safely assume those three roles to be enabled if this call was made. 74 | //! Same call order rules as bindToDocumentControllerWithRoles () apply. 75 | ARA_DEPRECATED(2_0_Draft) 76 | virtual ARAPlugInExtensionInstancePtr PLUGIN_API 77 | bindToDocumentController(ARADocumentControllerRef documentControllerRef) = 0; 78 | 79 | static const Steinberg::FUID iid; 80 | }; 81 | 82 | DECLARE_CLASS_IID(IPlugInEntryPoint, 0x12814E54, 0xA1CE4076, 0x82B96813, 0x16950BD6) 83 | 84 | //! ARA 2 extension of IPlugInEntryPoint, from the ARA SDK 85 | class ARA_ADDENDUM(2_0_Draft) IPlugInEntryPoint2 : public Steinberg::FUnknown 86 | { 87 | public: 88 | //! Extended version of bindToDocumentController (): 89 | //! bind the VST3 instance to an ARA document controller, switching it from "normal" operation 90 | //! to ARA mode with the assigned roles, and exposing the ARA plug-in extension. 91 | //! \p knownRoles encodes all roles that the host considered in its implementation and will explicitly 92 | //! assign to some plug-in instance(s), while \p assignedRoles describes the roles that this specific 93 | //! instance will fulfill. 94 | //! This may be called only once during the lifetime of the IAudioProcessor component, before 95 | //! the first call to setActive () or setState () or getProcessContextRequirements () or the 96 | //! creation of the GUI (see IPlugView). 97 | //! The ARA document controller must remain valid as long as the plug-in is in use - rendering, 98 | //! showing its UI, etc. However, when tearing down the plug-in, the actual order for deleting 99 | //! the IAudioProcessor instance and for deleting ARA document controller is undefined. 100 | //! Plug-ins must handle both potential destruction orders to allow for a simpler reference 101 | //! counting implementation on the host side. 102 | virtual ARAPlugInExtensionInstancePtr PLUGIN_API bindToDocumentControllerWithRoles( 103 | ARADocumentControllerRef documentControllerRef, ARAPlugInInstanceRoleFlags knownRoles, 104 | ARAPlugInInstanceRoleFlags assignedRoles) = 0; 105 | static const Steinberg::FUID iid; 106 | }; 107 | 108 | DECLARE_CLASS_IID(IPlugInEntryPoint2, 0xCD9A5913, 0xC9EB46D7, 0x96CA53AD, 0xD1DB89F5) 109 | 110 | } // namespace ARA 111 | 112 | #include "pluginterfaces/base/falignpop.h" 113 | -------------------------------------------------------------------------------- /src/detail/vst3/categories.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | converting CLAP categories to VST3 categories 3 | 4 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 5 | 6 | This file is part of the clap-wrappers project which is released under MIT License. 7 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 8 | 9 | 10 | both, CLAP and VST3, use strings to describe the plugin type. 11 | CLAP uses 4 main categories and a bunch of sub categories, where there is no limit of that list. 12 | 13 | VST3 uses a string where the categories are separated by '|'. The translation table below 14 | allows to match CLAP categories to VST3 categories. It is intended that a CLAP feature string 15 | could appear twice in the list with a different, additional VST3 string, so a Flanger would provide 16 | this list: 17 | 18 | audio-effect 19 | flanger 20 | 21 | which could result in VST3 as 22 | 23 | Fx|Modulation|Flanger 24 | 25 | Also the "Fx" could be added multiple times, duplicates will be automatically removed before creating 26 | the VST3 category string without changing the order. 27 | 28 | Note that most VST3 hosts will use the first token (like `Fx` or `Instrument`) to locate the plugin 29 | in a specific location and the second token (like `Synth` as a sub menu category in selection menus). 30 | 31 | Additionally, the Steinberg::PClassInfo struct reserves 128 bytes for the subcategory string, so any 32 | category string that exceeds this limit won't be added anymore and no further strings will be added. 33 | Make sure that the two most important categories are always at the beginning of your CLAP descriptor. 34 | 35 | Note: If you as a plugin developer want to set the VST3 categories explicitely, you can use the 36 | CLAP_PLUGIN_AS_VST3 extension (see clap-wrapper/include/clapwrapper/vst3.h) to explicitely set 37 | the category string. 38 | 39 | */ 40 | 41 | #include "categories.h" 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include "../ara/ara.h" 48 | 49 | using namespace Steinberg; 50 | using namespace Steinberg::Vst; 51 | 52 | // clang-format off 53 | static const struct _translate 54 | { 55 | const char* clapattribute; 56 | const char* vst3attribute; 57 | } translationTable[] = 58 | { 59 | // CLAP main categories 60 | { CLAP_PLUGIN_FEATURE_INSTRUMENT , PlugType::kInstrument }, 61 | { CLAP_PLUGIN_FEATURE_AUDIO_EFFECT , PlugType::kFx}, 62 | { CLAP_PLUGIN_FEATURE_NOTE_EFFECT , PlugType::kInstrumentSynth}, // it seems there is no type for a sequencer etc 63 | { CLAP_PLUGIN_FEATURE_DRUM , PlugType::kInstrumentDrum}, 64 | { CLAP_PLUGIN_FEATURE_ANALYZER , PlugType::kAnalyzer}, 65 | 66 | // CLAP sub categories 67 | { CLAP_PLUGIN_FEATURE_SYNTHESIZER , "Synth"}, 68 | { CLAP_PLUGIN_FEATURE_SAMPLER , "Sampler"}, 69 | { CLAP_PLUGIN_FEATURE_DRUM , "Drum"}, 70 | { CLAP_PLUGIN_FEATURE_DRUM_MACHINE , "Drum"}, 71 | 72 | { CLAP_PLUGIN_FEATURE_FILTER , "Filter"}, 73 | { CLAP_PLUGIN_FEATURE_PHASER , "Modulation" }, 74 | { CLAP_PLUGIN_FEATURE_EQUALIZER , "EQ"}, 75 | { CLAP_PLUGIN_FEATURE_DEESSER , "Restoration"}, 76 | { CLAP_PLUGIN_FEATURE_PHASE_VOCODER , "Modulation"}, 77 | { CLAP_PLUGIN_FEATURE_GRANULAR , "Synth"}, 78 | { CLAP_PLUGIN_FEATURE_FREQUENCY_SHIFTER , "Modulator"}, 79 | { CLAP_PLUGIN_FEATURE_PITCH_SHIFTER , "Pitch Shifter"}, 80 | 81 | { CLAP_PLUGIN_FEATURE_DISTORTION , "Distortion"}, 82 | { CLAP_PLUGIN_FEATURE_TRANSIENT_SHAPER , "Distortion"}, 83 | { CLAP_PLUGIN_FEATURE_COMPRESSOR , "Dynamics"}, 84 | { CLAP_PLUGIN_FEATURE_LIMITER , "Dynamics"}, 85 | 86 | { CLAP_PLUGIN_FEATURE_FLANGER , "Modulation"}, 87 | // { CLAP_PLUGIN_FEATURE_FLANGER , "Flanger"}, 88 | { CLAP_PLUGIN_FEATURE_CHORUS , "Modulation"}, 89 | // { CLAP_PLUGIN_FEATURE_CHORUS , "Chorus"}, 90 | { CLAP_PLUGIN_FEATURE_DELAY , "Delay"}, 91 | { CLAP_PLUGIN_FEATURE_REVERB , "Reverb"}, 92 | 93 | { CLAP_PLUGIN_FEATURE_TREMOLO , "Modulation"}, 94 | { CLAP_PLUGIN_FEATURE_GLITCH , "Modulation"}, 95 | 96 | { CLAP_PLUGIN_FEATURE_UTILITY , "Tools"}, 97 | { CLAP_PLUGIN_FEATURE_PITCH_CORRECTION , "Pitch Shift"}, 98 | { CLAP_PLUGIN_FEATURE_RESTORATION , "Restoration"}, 99 | 100 | { CLAP_PLUGIN_FEATURE_MULTI_EFFECTS , "Tools"}, 101 | 102 | { CLAP_PLUGIN_FEATURE_MIXING , "Mixing"}, 103 | { CLAP_PLUGIN_FEATURE_MASTERING , "Mastering"}, 104 | 105 | /*{ CLAP_PLUGIN_FEATURE_ARA_SUPPORTED , "OnlyARA" }, this is indicated by a missing factory in VST3 */ 106 | { CLAP_PLUGIN_FEATURE_ARA_REQUIRED , "OnlyARA" }, 107 | 108 | { "external" , "External"}, 109 | 110 | {nullptr, nullptr} 111 | }; 112 | // clang-format on 113 | 114 | std::string clapCategoriesToVST3(const char* const* clap_categories) 115 | { 116 | std::vector r; 117 | for (auto f = clap_categories; f && *f; ++f) 118 | { 119 | auto it = std::find_if(std::begin(translationTable), std::end(translationTable), 120 | [&](const auto& entry) 121 | { return entry.clapattribute && !strcmp(entry.clapattribute, *f); }); 122 | 123 | if (it != std::end(translationTable)) 124 | { 125 | r.push_back(it->vst3attribute); 126 | } 127 | } 128 | 129 | // Sort and remove duplicates 130 | std::sort(r.begin(), r.end()); 131 | r.erase(std::unique(r.begin(), r.end()), r.end()); 132 | 133 | std::string result; 134 | for (auto& i : r) 135 | { 136 | if (result.size() + i.size() <= Steinberg::PClassInfo2::kSubCategoriesSize) 137 | { 138 | result.append(i); 139 | result.append("|"); 140 | } 141 | else 142 | { 143 | result.append("*"); 144 | break; 145 | } 146 | } 147 | result.pop_back(); 148 | return result; 149 | } 150 | -------------------------------------------------------------------------------- /src/detail/vst3/categories.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // see categories.cpp for details 4 | 5 | #include 6 | std::string clapCategoriesToVST3(const char* const* clap_categories); 7 | -------------------------------------------------------------------------------- /src/detail/vst3/parameter.cpp: -------------------------------------------------------------------------------- 1 | #include "parameter.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #if CLAP_VERSION_LT(1, 2, 0) 7 | static_assert(false, "the CLAP-as-VST3 wrapper requires at least CLAP 1.2.0"); 8 | /* 9 | * CLAP_PARAM_IS_ENUM is available with CLAP 1.2.0 10 | * 11 | * This is only a requirement to compile the wrapper properly, it will still wrap CLAPs compiled with earlier versions of CLAP. 12 | */ 13 | #endif 14 | 15 | using namespace Steinberg; 16 | 17 | Vst3Parameter::Vst3Parameter(const Steinberg::Vst::ParameterInfo& vst3info, 18 | const clap_param_info_t* clapinfo) 19 | : Steinberg::Vst::Parameter(vst3info) 20 | , id(clapinfo->id) 21 | , cookie(clapinfo->cookie) 22 | , min_value(clapinfo->min_value) 23 | , max_value(clapinfo->max_value) 24 | { 25 | // 26 | } 27 | 28 | Vst3Parameter::Vst3Parameter(const Steinberg::Vst::ParameterInfo& vst3info, uint8_t /*bus*/, 29 | uint8_t channel, uint8_t cc) 30 | : Steinberg::Vst::Parameter(vst3info) 31 | , id(vst3info.id) 32 | , cookie(nullptr) 33 | , min_value(0) 34 | , max_value(127) 35 | , isMidi(true) 36 | , channel(channel) 37 | , controller(cc) 38 | { 39 | if (cc == Vst::ControllerNumbers::kPitchBend) 40 | { 41 | max_value = 16383; 42 | } 43 | } 44 | Vst3Parameter::~Vst3Parameter() = default; 45 | 46 | bool Vst3Parameter::setNormalized(Steinberg::Vst::ParamValue v) 47 | { 48 | return super::setNormalized(v); 49 | } 50 | 51 | #if 0 52 | void Vst3Parameter::toString(Steinberg::Vst::ParamValue valueNormalized, Steinberg::Vst::String128 string) const 53 | { 54 | super::toString(valueNormalized, string); 55 | } 56 | /** Converts a string to a normalized value. */ 57 | bool Vst3Parameter::fromString(const Steinberg::Vst::TChar* string, Steinberg::Vst::ParamValue& valueNormalized) const 58 | { 59 | return super::fromString(string, valueNormalized); 60 | } 61 | #endif 62 | 63 | Vst3Parameter* Vst3Parameter::create( 64 | const clap_param_info_t* info, 65 | std::function getUnitId) 66 | { 67 | Vst::ParameterInfo v; 68 | 69 | v.id = info->id & 0x7FFFFFFF; // why ever SMTG does not want the highest bit to be set 70 | 71 | // the long name might contain the module name 72 | // this will change when we split the module to units 73 | std::string fullname; 74 | Vst::UnitID unit = 0; 75 | 76 | // if there is a module name and a lambda 77 | if (info->module[0] != 0 && getUnitId) 78 | { 79 | unit = getUnitId(info->module); 80 | fullname = info->name; 81 | } 82 | else 83 | { 84 | if (!fullname.empty()) 85 | { 86 | fullname.append("/"); 87 | } 88 | fullname.append(info->name); 89 | if (fullname.size() >= str16BufferSize(v.title)) 90 | { 91 | fullname = info->name; 92 | } 93 | } 94 | 95 | // str8ToStr16(v.title, fullname.c_str(), str16BufferSize(v.title)); 96 | utf8_to_utf16l(fullname.c_str(), (uint16_t*)(v.title), str16BufferSize(v.title)); 97 | // TODO: string shrink algorithm shortening the string a bit 98 | // str8ToStr16(v.shortTitle, info->name, str16BufferSize(v.shortTitle)); 99 | utf8_to_utf16l(info->name, (uint16_t*)v.shortTitle, str16BufferSize(v.shortTitle)); 100 | v.units[0] = 0; // unfortunately, CLAP has no unit for parameter values 101 | v.unitId = unit; 102 | 103 | /* 104 | In the VST3 SDK the normalized value [0, 1] to discrete value and its inverse function discrete value to normalized value is defined like this: 105 | 106 | Normalize: 107 | double normalized = discreteValue / (double) stepCount; 108 | 109 | Denormalize : 110 | int discreteValue = min (stepCount, normalized * (stepCount + 1)); 111 | */ 112 | 113 | v.flags = Vst::ParameterInfo::kNoFlags | 114 | ((info->flags & CLAP_PARAM_IS_HIDDEN) ? Vst::ParameterInfo::kIsHidden : 0) | 115 | ((info->flags & CLAP_PARAM_IS_BYPASS) ? Vst::ParameterInfo::kIsBypass : 0) | 116 | ((info->flags & CLAP_PARAM_IS_AUTOMATABLE) ? Vst::ParameterInfo::kCanAutomate : 0) | 117 | ((info->flags & CLAP_PARAM_IS_READONLY) ? Vst::ParameterInfo::kIsReadOnly : 0) | 118 | ((info->flags & CLAP_PARAM_IS_ENUM) ? Vst::ParameterInfo::kIsList : 0) 119 | 120 | // | ((info->flags & CLAP_PARAM_IS_READONLY) ? Vst::ParameterInfo::kIsReadOnly : 0) 121 | ; 122 | 123 | auto param_range = (info->max_value - info->min_value); 124 | 125 | v.defaultNormalizedValue = (info->default_value - info->min_value) / param_range; 126 | if ((info->flags & CLAP_PARAM_IS_STEPPED) || (info->flags & CLAP_PARAM_IS_ENUM)) 127 | { 128 | auto steps = param_range; 129 | v.stepCount = steps; 130 | } 131 | else 132 | v.stepCount = 0; 133 | 134 | auto result = new Vst3Parameter(v, info); 135 | result->addRef(); // ParameterContainer doesn't add the ref -> but we don't have copies 136 | return result; 137 | } 138 | 139 | Vst3Parameter* Vst3Parameter::create(uint8_t bus, uint8_t channel, uint8_t cc, Vst::ParamID id) 140 | { 141 | Vst::ParameterInfo v; 142 | 143 | v.id = id; 144 | 145 | auto name = "controller"; 146 | // the long name might contain the module name 147 | // this will change when we split the module to units 148 | std::string fullname("MIDI"); 149 | if (!fullname.empty()) 150 | { 151 | fullname.append("/"); 152 | } 153 | fullname.append("controller"); 154 | if (fullname.size() >= str16BufferSize(v.title)) 155 | { 156 | fullname = "controller"; 157 | } 158 | 159 | utf8_to_utf16l(fullname.c_str(), (uint16_t*)(v.title), str16BufferSize(v.title)); 160 | utf8_to_utf16l(name, (uint16_t*)v.shortTitle, str16BufferSize(v.shortTitle)); 161 | 162 | // str8ToStr16(v.title, fullname.c_str(), str16BufferSize(v.title)); 163 | // TODO: string shrink algorithm shortening the string a bit 164 | // str8ToStr16(v.shortTitle, name, str16BufferSize(v.shortTitle)); 165 | 166 | v.units[0] = 0; // nothing in the "unit" field 167 | // the unit will not be set here, but outside 168 | // v.unitId = channel + 1; 169 | 170 | v.defaultNormalizedValue = 0; 171 | v.flags = Vst::ParameterInfo::kNoFlags; 172 | if (cc == Vst::ControllerNumbers::kCtrlProgramChange) 173 | { 174 | v.flags |= Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; 175 | v.stepCount = 127; 176 | } 177 | 178 | v.defaultNormalizedValue = 0; 179 | v.stepCount = 127; 180 | 181 | if (cc == Vst::ControllerNumbers::kPitchBend) 182 | { 183 | v.stepCount = 16383; 184 | } 185 | 186 | auto result = new Vst3Parameter(v, bus, channel, cc); 187 | result->addRef(); // ParameterContainer doesn't add the ref -> but we don't have copies 188 | return result; 189 | } 190 | -------------------------------------------------------------------------------- /src/detail/vst3/parameter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Vst3Parameter 5 | 6 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 7 | 8 | This file is part of the clap-wrappers project which is released under MIT License. 9 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 10 | 11 | is being derived from Steinberg::Vst::Parameter and additionally stores 12 | information about the CLAP info (like the unique id of the CLAP parameter 13 | as well as the cookie pointer which is needed to address a parameter change 14 | properly). 15 | 16 | Still, the wrapper will use the ParameterContainer (std::vector>) 17 | to communicate with the VST3 host. 18 | 19 | call Vst3Parameter::create(clapinfo) to create a heap based instance of it. 20 | 21 | The create function will apply everything necessary to the Vst::Parameter object in terms 22 | of value range conversion. 23 | 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | void utf8_to_utf16l(const char* utf8string, uint16_t* target, size_t targetsize); 32 | 33 | class Vst3Parameter : public Steinberg::Vst::Parameter 34 | { 35 | using super = Steinberg::Vst::Parameter; 36 | 37 | protected: 38 | Vst3Parameter(const Steinberg::Vst::ParameterInfo& vst3info, const clap_param_info_t* clapinfo); 39 | Vst3Parameter(const Steinberg::Vst::ParameterInfo& vst3info, uint8_t bus, uint8_t channel, uint8_t cc); 40 | 41 | public: 42 | virtual ~Vst3Parameter(); 43 | bool setNormalized(Steinberg::Vst::ParamValue v) override; 44 | 45 | #if 0 46 | // toString/fromString from VST3 parameter should actually be overloaded, but the wrapper will not call them 47 | // for the conversion, the _plugin instance is needed and we don't want to keep a copy in each parameter 48 | // the IEditController will be called anyway from the host, therefore the conversion will take place there. 49 | 50 | /** Converts a normalized value to a string. */ 51 | void toString(Steinberg::Vst::ParamValue valueNormalized, Steinberg::Vst::String128 string) const override; 52 | /** Converts a string to a normalized value. */ 53 | bool fromString(const Steinberg::Vst::TChar* string, Steinberg::Vst::ParamValue& valueNormalized) const override; 54 | #endif 55 | 56 | // for asClapValue() and asVst3Value() 57 | // regarding conversion and the meaning of stepCount take a look here: 58 | // https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Parameters+Automation/Index.html#conversion-of-normalized-values 59 | 60 | inline double asClapValue(double vst3value) const 61 | { 62 | if (info.stepCount > 0) 63 | { 64 | return (vst3value * info.stepCount) + min_value; 65 | } 66 | return (vst3value * (max_value - min_value)) + min_value; 67 | } 68 | inline double asVst3Value(double clapvalue) const 69 | { 70 | auto& info = this->getInfo(); 71 | if (info.stepCount > 0) 72 | { 73 | return floor(clapvalue - min_value) / float(info.stepCount); 74 | } 75 | return (clapvalue - min_value) / (max_value - min_value); 76 | } 77 | static Vst3Parameter* create(const clap_param_info_t* info, 78 | std::function getUnitId); 79 | static Vst3Parameter* create(uint8_t bus, uint8_t channel, uint8_t cc, Steinberg::Vst::ParamID id); 80 | // copies from the clap_param_info_t 81 | uint32_t param_index_for_clap_get_info = 0; 82 | clap_id id = 0; 83 | 84 | void* cookie = nullptr; 85 | double min_value; // minimum plain value 86 | double max_value; // maximum plain value 87 | // or it was MIDI 88 | bool isMidi = false; 89 | uint8_t channel = 0; 90 | uint8_t controller = 0; 91 | }; 92 | -------------------------------------------------------------------------------- /src/detail/vst3/plugview.cpp: -------------------------------------------------------------------------------- 1 | #include "plugview.h" 2 | #include 3 | #include 4 | #include 5 | 6 | WrappedView::WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, 7 | std::function onReleaseAdditionalReferences, 8 | std::function onDestroy, std::function onRunLoopAvailable) 9 | : IPlugView() 10 | , FObject() 11 | , _plugin(plugin) 12 | , _extgui(gui) 13 | , _onReleaseAdditionalReferences(onReleaseAdditionalReferences) 14 | , _onRunLoopAvailable(onRunLoopAvailable) 15 | , _onDestroy(onDestroy) 16 | { 17 | } 18 | 19 | WrappedView::~WrappedView() 20 | { 21 | drop_ui(); 22 | } 23 | 24 | void WrappedView::ensure_ui() 25 | { 26 | if (!_created) 27 | { 28 | const char* api{nullptr}; 29 | #if MAC 30 | api = CLAP_WINDOW_API_COCOA; 31 | #endif 32 | #if WIN 33 | api = CLAP_WINDOW_API_WIN32; 34 | #endif 35 | #if LIN 36 | api = CLAP_WINDOW_API_X11; 37 | #endif 38 | 39 | if (_extgui->is_api_supported(_plugin, api, false)) _extgui->create(_plugin, api, false); 40 | 41 | _created = true; 42 | } 43 | } 44 | 45 | void WrappedView::drop_ui() 46 | { 47 | if (_created) 48 | { 49 | releaseAdditionalReferences(); 50 | _attached = false; 51 | if (_onDestroy) 52 | { 53 | _onDestroy(true); 54 | } 55 | _extgui->destroy(_plugin); 56 | _created = false; 57 | } 58 | else 59 | { 60 | if (_onDestroy) 61 | { 62 | _onDestroy(false); 63 | } 64 | } 65 | } 66 | 67 | void WrappedView::releaseAdditionalReferences() 68 | { 69 | // releases things like IContextMenu when the wrapper provided entries, but the user chose 70 | // a plugin-owned entry. 71 | 72 | if (_onReleaseAdditionalReferences) 73 | { 74 | _onReleaseAdditionalReferences(); 75 | } 76 | } 77 | 78 | tresult PLUGIN_API WrappedView::isPlatformTypeSupported(FIDString type) 79 | { 80 | static struct vst3_and_clap_match_types_t 81 | { 82 | const char* VST3; 83 | const char* CLAP; 84 | } platformTypeMatches[] = {{kPlatformTypeHWND, CLAP_WINDOW_API_WIN32}, 85 | {kPlatformTypeNSView, CLAP_WINDOW_API_COCOA}, 86 | {kPlatformTypeX11EmbedWindowID, CLAP_WINDOW_API_X11}, 87 | {nullptr, nullptr}}; 88 | auto* n = platformTypeMatches; 89 | while (n->VST3 && n->CLAP) 90 | { 91 | if (!strcmp(type, n->VST3)) 92 | { 93 | if (_extgui->is_api_supported(_plugin, n->CLAP, false)) 94 | { 95 | return kResultOk; 96 | } 97 | } 98 | ++n; 99 | } 100 | 101 | return kResultFalse; 102 | } 103 | 104 | tresult PLUGIN_API WrappedView::attached(void* parent, FIDString /*type*/) 105 | { 106 | #if WIN 107 | _window = {CLAP_WINDOW_API_WIN32, {parent}}; 108 | #endif 109 | 110 | #if MAC 111 | _window = {CLAP_WINDOW_API_COCOA, {parent}}; 112 | #endif 113 | 114 | #if LIN 115 | _window = {CLAP_WINDOW_API_X11, {parent}}; 116 | #endif 117 | 118 | ensure_ui(); 119 | _extgui->set_parent(_plugin, &_window); 120 | _attached = true; 121 | if (_extgui->can_resize(_plugin)) 122 | { 123 | uint32_t w = _rect.getWidth(); 124 | uint32_t h = _rect.getHeight(); 125 | if (_extgui->adjust_size(_plugin, &w, &h)) 126 | { 127 | _rect.right = _rect.left + w + 1; 128 | _rect.bottom = _rect.top + h + 1; 129 | } 130 | _extgui->set_size(_plugin, w, h); 131 | } 132 | _extgui->show(_plugin); 133 | return kResultOk; 134 | } 135 | 136 | tresult PLUGIN_API WrappedView::removed() 137 | { 138 | releaseAdditionalReferences(); 139 | _attached = false; 140 | _window.ptr = nullptr; 141 | return kResultOk; 142 | } 143 | 144 | tresult PLUGIN_API WrappedView::onWheel(float /*distance*/) 145 | { 146 | return kResultFalse; 147 | } 148 | 149 | tresult PLUGIN_API WrappedView::onKeyDown(char16 /*key*/, int16 /*keyCode*/, int16 /*modifiers*/) 150 | { 151 | return kResultFalse; 152 | } 153 | 154 | tresult PLUGIN_API WrappedView::onKeyUp(char16 /*key*/, int16 /*keyCode*/, int16 /*modifiers*/) 155 | { 156 | return kResultFalse; 157 | } 158 | 159 | tresult PLUGIN_API WrappedView::getSize(ViewRect* size) 160 | { 161 | ensure_ui(); 162 | if (size) 163 | { 164 | uint32_t w, h; 165 | if (_extgui->get_size(_plugin, &w, &h)) 166 | { 167 | size->right = size->left + w; 168 | size->bottom = size->top + h; 169 | _rect = *size; 170 | return kResultOk; 171 | } 172 | return kResultFalse; 173 | } 174 | return kInvalidArgument; 175 | } 176 | 177 | tresult PLUGIN_API WrappedView::onSize(ViewRect* newSize) 178 | { 179 | // TODO: discussion took place if this call should be ignored completely 180 | // since it seems not to match the CLAP UI scheme. 181 | // for now, it is left in and might be removed in the future. 182 | if (!newSize) return kResultFalse; 183 | 184 | _rect = *newSize; 185 | if (_created && _attached) 186 | { 187 | if (_extgui->can_resize(_plugin)) 188 | { 189 | uint32_t w = _rect.getWidth(); 190 | uint32_t h = _rect.getHeight(); 191 | if (_extgui->adjust_size(_plugin, &w, &h)) 192 | { 193 | _rect.right = _rect.left + w; 194 | _rect.bottom = _rect.top + h; 195 | } 196 | if (_extgui->set_size(_plugin, w, h)) 197 | { 198 | return kResultOk; 199 | } 200 | } 201 | else 202 | { 203 | return kResultFalse; 204 | } 205 | } 206 | return kResultOk; 207 | } 208 | 209 | tresult PLUGIN_API WrappedView::onFocus(TBool state) 210 | { 211 | // TODO: this might be something for the wrapperhost API 212 | // to notify the plugin about a focus change 213 | if (state == false) 214 | { 215 | releaseAdditionalReferences(); 216 | } 217 | return kResultOk; 218 | } 219 | 220 | tresult PLUGIN_API WrappedView::setFrame(IPlugFrame* frame) 221 | { 222 | releaseAdditionalReferences(); 223 | 224 | _plugFrame = frame; 225 | 226 | #if LIN 227 | if (_plugFrame) 228 | { 229 | if (_plugFrame->queryInterface(Steinberg::Linux::IRunLoop::iid, (void**)&_runLoop) == 230 | Steinberg::kResultOk && 231 | _onRunLoopAvailable) 232 | { 233 | _onRunLoopAvailable(); 234 | } 235 | } 236 | #endif 237 | return kResultOk; 238 | } 239 | 240 | tresult PLUGIN_API WrappedView::canResize() 241 | { 242 | ensure_ui(); 243 | return _extgui->can_resize(_plugin) ? kResultOk : kResultFalse; 244 | } 245 | 246 | tresult PLUGIN_API WrappedView::checkSizeConstraint(ViewRect* rect) 247 | { 248 | ensure_ui(); 249 | uint32_t w = rect->getWidth(); 250 | uint32_t h = rect->getHeight(); 251 | if (_extgui->adjust_size(_plugin, &w, &h)) 252 | { 253 | rect->right = rect->left + w; 254 | rect->bottom = rect->top + h; 255 | return kResultOk; 256 | } 257 | return kResultFalse; 258 | } 259 | 260 | bool WrappedView::request_resize(uint32_t width, uint32_t height) 261 | { 262 | auto oldrect = _rect; 263 | _rect.right = _rect.left + (int32)width; 264 | _rect.bottom = _rect.top + (int32)height; 265 | 266 | if (_plugFrame && !_plugFrame->resizeView(this, &_rect)) 267 | { 268 | _rect = oldrect; 269 | return false; 270 | } 271 | return true; 272 | } 273 | tresult WrappedView::setContentScaleFactor(IPlugViewContentScaleSupport::ScaleFactor factor) 274 | { 275 | ensure_ui(); 276 | if (_extgui->set_scale(_plugin, factor)) 277 | { 278 | return kResultOk; 279 | } 280 | return kResultFalse; 281 | } 282 | -------------------------------------------------------------------------------- /src/detail/vst3/plugview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 5 | 6 | This file is part of the clap-wrappers project which is released under MIT License. 7 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 8 | 9 | */ 10 | 11 | #include "base/source/fobject.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace Steinberg; 18 | 19 | class WrappedView : public Steinberg::IPlugView, 20 | public Steinberg::IPlugViewContentScaleSupport, 21 | public Steinberg::FObject 22 | { 23 | public: 24 | WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, 25 | std::function onReleaseAdditionalReferences, std::function onDestroy, 26 | std::function onRunLoopAvailable); 27 | ~WrappedView(); 28 | 29 | // IPlugView interface 30 | tresult PLUGIN_API isPlatformTypeSupported(FIDString type) override; 31 | 32 | /** The parent window of the view has been created, the (platform) representation of the view 33 | should now be created as well. 34 | Note that the parent is owned by the caller and you are not allowed to alter it in any way 35 | other than adding your own views. 36 | Note that in this call the plug-in could call a IPlugFrame::resizeView ()! 37 | \param parent : platform handle of the parent window or view 38 | \param type : \ref platformUIType which should be created */ 39 | tresult PLUGIN_API attached(void* parent, FIDString type) override; 40 | 41 | /** The parent window of the view is about to be destroyed. 42 | You have to remove all your own views from the parent window or view. */ 43 | tresult PLUGIN_API removed() override; 44 | 45 | /** Handling of mouse wheel. */ 46 | tresult PLUGIN_API onWheel(float distance) override; 47 | 48 | /** Handling of keyboard events : Key Down. 49 | \param key : unicode code of key 50 | \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h 51 | \param modifiers : any combination of modifiers - see \ref KeyModifier in keycodes.h 52 | \return kResultTrue if the key is handled, otherwise kResultFalse. \n 53 | Please note that kResultTrue must only be returned if the key has really been 54 | handled. Otherwise key command handling of the host might be blocked! */ 55 | tresult PLUGIN_API onKeyDown(char16 key, int16 keyCode, int16 modifiers) override; 56 | 57 | /** Handling of keyboard events : Key Up. 58 | \param key : unicode code of key 59 | \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h 60 | \param modifiers : any combination of KeyModifier - see \ref KeyModifier in keycodes.h 61 | \return kResultTrue if the key is handled, otherwise return kResultFalse. */ 62 | tresult PLUGIN_API onKeyUp(char16 key, int16 keyCode, int16 modifiers) override; 63 | 64 | /** Returns the size of the platform representation of the view. */ 65 | tresult PLUGIN_API getSize(ViewRect* size) override; 66 | 67 | /** Resizes the platform representation of the view to the given rect. Note that if the plug-in 68 | * requests a resize (IPlugFrame::resizeView ()) onSize has to be called afterward. */ 69 | tresult PLUGIN_API onSize(ViewRect* newSize) override; 70 | 71 | /** Focus changed message. */ 72 | tresult PLUGIN_API onFocus(TBool state) override; 73 | 74 | /** Sets IPlugFrame object to allow the plug-in to inform the host about resizing. */ 75 | tresult PLUGIN_API setFrame(IPlugFrame* frame) override; 76 | 77 | /** Is view sizable by user. */ 78 | tresult PLUGIN_API canResize() override; 79 | 80 | /** On live resize this is called to check if the view can be resized to the given rect, if not 81 | * adjust the rect to the allowed size. */ 82 | tresult PLUGIN_API checkSizeConstraint(ViewRect* rect) override; 83 | 84 | tresult setContentScaleFactor(ScaleFactor factor) override; 85 | 86 | //---Interface------ 87 | OBJ_METHODS(WrappedView, FObject) 88 | DEFINE_INTERFACES 89 | DEF_INTERFACE(IPlugView) 90 | DEF_INTERFACE(IPlugViewContentScaleSupport) 91 | END_DEFINE_INTERFACES(FObject) 92 | REFCOUNT_METHODS(FObject) 93 | 94 | // wrapper needed interfaces 95 | bool request_resize(uint32_t width, uint32_t height); 96 | 97 | private: 98 | void ensure_ui(); 99 | void drop_ui(); 100 | void releaseAdditionalReferences(); 101 | const clap_plugin_t* _plugin = nullptr; 102 | const clap_plugin_gui_t* _extgui = nullptr; 103 | std::function _onReleaseAdditionalReferences = nullptr, _onRunLoopAvailable = nullptr; 104 | std::function _onDestroy = nullptr; 105 | clap_window_t _window = {nullptr, {nullptr}}; 106 | IPlugFrame* _plugFrame = nullptr; 107 | ViewRect _rect = {0, 0, 0, 0}; 108 | bool _created = false; 109 | bool _attached = false; 110 | 111 | #if LIN 112 | public: 113 | Steinberg::Linux::IRunLoop* getRunLoop() 114 | { 115 | return _runLoop; 116 | } 117 | 118 | private: 119 | Steinberg::Linux::IRunLoop* _runLoop = nullptr; 120 | #endif 121 | }; 122 | -------------------------------------------------------------------------------- /src/detail/vst3/process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | VST3 process adapter 5 | 6 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 7 | 8 | This file is part of the clap-wrappers project which is released under MIT License. 9 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 10 | 11 | 12 | The process adapter is responible to translate events, timing information 13 | 14 | */ 15 | 16 | #include 17 | 18 | #ifdef __GNUC__ 19 | #pragma GCC diagnostic push 20 | #pragma GCC diagnostic ignored "-Wextra" 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef __GNUC__ 29 | #pragma GCC diagnostic pop 30 | #endif 31 | 32 | #include 33 | #include 34 | 35 | #include "../clap/automation.h" 36 | 37 | namespace Clap 38 | { 39 | class ProcessAdapter 40 | { 41 | public: 42 | typedef union clap_multi_event 43 | { 44 | clap_event_header_t header; 45 | clap_event_note_t note; 46 | clap_event_midi_t midi; 47 | clap_event_midi_sysex_t sysex; 48 | clap_event_param_value_t param; 49 | clap_event_note_expression_t noteexpression; 50 | } clap_multi_event_t; 51 | 52 | #if 0 53 | // the bitly helpers. These names conflict with macOS params.h but are 54 | // unused, so for now comment out 55 | static void setbit(uint64_t& val, int bit, bool value) 56 | { 57 | if (value) val |= ((uint64_t)1 << bit); else val &= ~((uint64_t)1 << bit); 58 | } 59 | static void setbits(uint64_t& val, int pos, int nbits, bool value) 60 | { 61 | uint64_t mask = (((uint64_t)1 << nbits) - 1); 62 | val &= ~(mask << pos); 63 | if (value) val |= mask << pos; 64 | } 65 | #endif 66 | 67 | void setupProcessing(const clap_plugin_t* plugin, const clap_plugin_params_t* ext_params, 68 | Steinberg::Vst::BusList& audioinputs, Steinberg::Vst::BusList& audiooutputs, 69 | uint32_t numSamples, size_t numEventInputs, size_t numEventOutputs, 70 | Steinberg::Vst::ParameterContainer& params, 71 | Steinberg::Vst::IComponentHandler* componenthandler, IAutomation* automation, 72 | bool enablePolyPressure, bool supportsTuningNoteExpression); 73 | void process(Steinberg::Vst::ProcessData& data); 74 | void flush(); 75 | void processOutputParams(Steinberg::Vst::ProcessData& data); 76 | void activateAudioBus(Steinberg::Vst::BusDirection dir, Steinberg::int32 index, 77 | Steinberg::TBool state); 78 | 79 | // C callbacks 80 | static uint32_t input_events_size(const struct clap_input_events* list); 81 | static const clap_event_header_t* input_events_get(const struct clap_input_events* list, 82 | uint32_t index); 83 | 84 | static bool output_events_try_push(const struct clap_output_events* list, 85 | const clap_event_header_t* event); 86 | 87 | private: 88 | void sortEventIndices(); 89 | void processInputEvents(Steinberg::Vst::IEventList* eventlist); 90 | 91 | bool enqueueOutputEvent(const clap_event_header_t* event); 92 | void addToActiveNotes(const clap_event_note* note); 93 | void removeFromActiveNotes(const clap_event_note* note); 94 | 95 | // the plugin 96 | const clap_plugin_t* _plugin = nullptr; 97 | const clap_plugin_params_t* _ext_params = nullptr; 98 | 99 | Steinberg::Vst::ParameterContainer* parameters = nullptr; 100 | Steinberg::Vst::IComponentHandler* _componentHandler = nullptr; 101 | IAutomation* _automation = nullptr; 102 | Steinberg::Vst::BusList* _audioinputs = nullptr; 103 | Steinberg::Vst::BusList* _audiooutputs = nullptr; 104 | 105 | // for automation gestures 106 | std::vector _gesturedParameters; 107 | 108 | // for INoteExpression 109 | struct ActiveNote 110 | { 111 | bool used = false; 112 | int32_t note_id; // -1 if unspecified, otherwise >=0 113 | int16_t port_index; 114 | int16_t channel; // 0..15 115 | int16_t key; // 0..127 116 | }; 117 | std::vector _activeNotes; 118 | 119 | clap_audio_buffer_t* _input_ports = nullptr; 120 | clap_audio_buffer_t* _output_ports = nullptr; 121 | clap_event_transport_t _transport = {}; 122 | clap_input_events_t _in_events = {}; 123 | clap_output_events_t _out_events = {}; 124 | 125 | float* _silent_input = nullptr; 126 | float* _silent_output = nullptr; 127 | 128 | clap_process_t _processData = {-1, 0, &_transport, nullptr, nullptr, 0, 0, &_in_events, &_out_events}; 129 | 130 | Steinberg::Vst::ProcessData* _vstdata = nullptr; 131 | 132 | std::vector _events; 133 | std::vector _eventindices; 134 | 135 | bool _supportsPolyPressure = false; 136 | bool _supportsTuningNoteExpression = false; 137 | }; 138 | 139 | } // namespace Clap 140 | -------------------------------------------------------------------------------- /src/detail/vst3/state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Copyright (c) 2022 Timo Kaluza (defiantnerd) 5 | 6 | This file is part of the clap-wrappers project which is released under MIT License. 7 | See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. 8 | 9 | */ 10 | 11 | class CLAPVST3StreamAdapter 12 | { 13 | public: 14 | CLAPVST3StreamAdapter(Steinberg::IBStream* stream) : vst_stream(stream) 15 | { 16 | } 17 | operator const clap_istream_t*() const 18 | { 19 | return ∈ 20 | } 21 | operator const clap_ostream_t*() const 22 | { 23 | return &out; 24 | } 25 | 26 | static int64_t read(const struct clap_istream* stream, void* buffer, uint64_t size) 27 | { 28 | auto self = static_cast(stream->ctx); 29 | Steinberg::int32 bytesRead = 0; 30 | if (kResultOk == self->vst_stream->read(buffer, (int32)size, &bytesRead)) return bytesRead; 31 | return -1; 32 | } 33 | static int64_t write(const struct clap_ostream* stream, const void* buffer, uint64_t size) 34 | { 35 | auto self = static_cast(stream->ctx); 36 | Steinberg::int32 bytesWritten = 0; 37 | if (kResultOk == self->vst_stream->write(const_cast(buffer), (int32)size, &bytesWritten)) 38 | return bytesWritten; 39 | return -1; 40 | } 41 | 42 | private: 43 | Steinberg::IBStream* vst_stream = nullptr; 44 | clap_istream_t in = {this, read}; 45 | clap_ostream_t out = {this, write}; 46 | }; 47 | -------------------------------------------------------------------------------- /src/wrapasstandalone.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "detail/standalone/standalone_details.h" 4 | #include "detail/standalone/entry.h" 5 | 6 | #if LIN 7 | #if CLAP_WRAPPER_HAS_GTK3 8 | #include "detail/standalone/linux/gtkutils.h" 9 | #endif 10 | #endif 11 | 12 | // For now just a simple main. In the future this will branch out to 13 | // an [NSApplicationMain ] and so on depending on platform 14 | int main(int argc, char **argv) 15 | { 16 | const clap_plugin_entry *entry{nullptr}; 17 | #ifdef STATICALLY_LINKED_CLAP_ENTRY 18 | extern const clap_plugin_entry clap_entry; 19 | entry = &clap_entry; 20 | #else 21 | // Library shenanigans t/k 22 | std::string clapName{HOSTED_CLAP_NAME}; 23 | LOGINFO("Loading '{}'", clapName); 24 | 25 | auto pts = Clap::getValidCLAPSearchPaths(); 26 | 27 | auto lib = Clap::Library(); 28 | 29 | for (const auto &searchPaths : pts) 30 | { 31 | auto clapPath = searchPaths / (clapName + ".clap"); 32 | 33 | if (fs::exists(clapPath) && !entry) 34 | { 35 | lib.load(clapPath); 36 | entry = lib._pluginEntry; 37 | } 38 | } 39 | 40 | #endif 41 | 42 | #if LIN 43 | #if CLAP_WRAPPER_HAS_GTK3 44 | freeaudio::clap_wrapper::standalone::linux_standalone::GtkGui gtkGui{}; 45 | 46 | if (!gtkGui.parseCommandLine(argc, argv)) 47 | { 48 | return 1; 49 | } 50 | gtkGui.initialize(freeaudio::clap_wrapper::standalone::getStandaloneHost()); 51 | #endif 52 | #endif 53 | 54 | if (!entry) 55 | { 56 | std::cerr << "Clap Standalone: No Entry as configured" << std::endl; 57 | return 3; 58 | } 59 | 60 | std::string pid{PLUGIN_ID}; 61 | int pindex{PLUGIN_INDEX}; 62 | 63 | auto plugin = 64 | freeaudio::clap_wrapper::standalone::mainCreatePlugin(entry, pid, pindex, 1, (char **)argv); 65 | freeaudio::clap_wrapper::standalone::mainStartAudio(); 66 | 67 | #if LIN 68 | #if CLAP_WRAPPER_HAS_GTK3 69 | gtkGui.setPlugin(plugin); 70 | gtkGui.runloop(argc, argv); 71 | gtkGui.shutdown(); 72 | gtkGui.setPlugin(nullptr); 73 | #else 74 | freeaudio::clap_wrapper::standalone::mainWait(); 75 | #endif 76 | #else 77 | freeaudio::clap_wrapper::standalone::mainWait(); 78 | #endif 79 | 80 | plugin = nullptr; 81 | freeaudio::clap_wrapper::standalone::mainFinish(); 82 | } 83 | -------------------------------------------------------------------------------- /src/wrapasstandalone.mm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main(int argc, const char* argv[]) 4 | { 5 | return NSApplicationMain(argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /src/wrapasstandalone_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "detail/standalone/windows/windows_standalone.h" 2 | 3 | int main(int argc, char** argv) 4 | { 5 | auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; 6 | 7 | const clap_plugin_entry* entry{nullptr}; 8 | #ifdef STATICALLY_LINKED_CLAP_ENTRY 9 | extern const clap_plugin_entry clap_entry; 10 | entry = &clap_entry; 11 | #else 12 | std::string clapName{HOSTED_CLAP_NAME}; 13 | freeaudio::clap_wrapper::standalone::windows_standalone::log("Loading {}", clapName); 14 | 15 | auto lib{Clap::Library()}; 16 | 17 | for (const auto& searchPaths : Clap::getValidCLAPSearchPaths()) 18 | { 19 | auto clapPath{searchPaths / (clapName + ".clap")}; 20 | 21 | if (fs::exists(clapPath) && !entry) 22 | { 23 | lib.load(clapPath); 24 | entry = lib._pluginEntry; 25 | } 26 | } 27 | #endif 28 | 29 | if (!entry) 30 | { 31 | freeaudio::clap_wrapper::standalone::windows_standalone::log("No entry as configured"); 32 | 33 | return 3; 34 | } 35 | 36 | freeaudio::clap_wrapper::standalone::windows_standalone::Plugin plugin{entry, argc, argv}; 37 | 38 | return freeaudio::clap_wrapper::standalone::windows_standalone::run(); 39 | } 40 | -------------------------------------------------------------------------------- /src/wrapasvst3_export_entry.cpp: -------------------------------------------------------------------------------- 1 | #include "public.sdk/source/main/pluginfactory.h" 2 | 3 | SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() 4 | { 5 | extern Steinberg::IPluginFactory* GetPluginFactoryEntryPoint(); 6 | return GetPluginFactoryEntryPoint(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(clap-first-example) 2 | -------------------------------------------------------------------------------- /tests/clap-first-example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is a test of our clap-first makefiles we use in CI, and as such serves as 2 | # an example of doign this without helpers, with the absolute smalles cmake, and 3 | # so forth. A more fulsome example, using C++, setting up compiler options for 4 | # wider deployment, and so forth can be found at 5 | # 6 | # https://github.com/baconpaul/six-sines 7 | 8 | project(clap-first-distortion) 9 | 10 | set(PRODUCT_NAME "ClapFirst Bad Distortion") 11 | 12 | # Step one is to create a static 'impl' library which contains a static 13 | # implementation of clap_init, clap_deinit and clap_get_factor. Here those 14 | # are all in one cpp file, but a common idiom is to have an -entry-impl file 15 | # separate from the rest of your clap, especially in multi-plugin claps. 16 | add_library(${PROJECT_NAME}-impl STATIC distortion_clap.cpp) 17 | target_link_libraries(${PROJECT_NAME}-impl PUBLIC clap) 18 | 19 | # Then we use make_clapfirst_plugin, provided by the wrappers, to create the 20 | # project out of the impl and the cpp file which exports the entry 21 | make_clapfirst_plugins( 22 | TARGET_NAME ${PROJECT_NAME} 23 | IMPL_TARGET ${PROJECT_NAME}-impl 24 | 25 | OUTPUT_NAME "${PRODUCT_NAME}" 26 | 27 | ENTRY_SOURCE "distortion_clap_entry.cpp" 28 | 29 | BUNDLE_IDENTIFER "org.free-audio.clap-first-bad-distortion" 30 | BUNDLE_VERSION ${PROJECT_VERSION} 31 | 32 | COPY_AFTER_BUILD FALSE 33 | 34 | PLUGIN_FORMATS CLAP VST3 AUV2 WCLAP 35 | 36 | ASSET_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}_assets 37 | 38 | # You can set up the AUv2 for a single plugin here, or you can 39 | # set it up with the auv2 extension in your clap 40 | AUV2_MANUFACTURER_NAME "Free Audio" 41 | AUV2_MANUFACTURER_CODE "FrAD" 42 | AUV2_SUBTYPE_CODE "BdDt" 43 | AUV2_INSTRUMENT_TYPE "aufx" 44 | 45 | # You can add a target-per-standalone you want. Syntax here is 46 | # target-postfix output-name clap-id 47 | # This allows you to make multiple standalones from a multi-plugin clap 48 | # In this case, the standalone has no UI and is an audio to audio effect 49 | # so it isn't very useful 50 | # STANDALONE_CONFIGURATIONS 51 | # standalone "${PRODUCT_NAME}" "org.free-audio.clap-first-bad-distortion" 52 | ) 53 | 54 | -------------------------------------------------------------------------------- /tests/clap-first-example/distortion_clap_entry.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * distortion_clap_entry 3 | * 4 | * This uses extern versions of the entry methods from a static library to create 5 | * a working exported C linkage clap entry point 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "distortion_clap_entry.h" 12 | 13 | extern "C" 14 | { 15 | #ifdef __GNUC__ 16 | #pragma GCC diagnostic push 17 | #pragma GCC diagnostic ignored "-Wattributes" // other peoples errors are outside my scope 18 | #endif 19 | 20 | const CLAP_EXPORT struct clap_plugin_entry clap_entry = {CLAP_VERSION, dist_entry_init, 21 | dist_entry_deinit, dist_entry_get_factory}; 22 | 23 | #ifdef __GNUC__ 24 | #pragma GCC diagnostic pop 25 | #endif 26 | } -------------------------------------------------------------------------------- /tests/clap-first-example/distortion_clap_entry.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Walker on 2/21/25. 3 | // 4 | 5 | #pragma once 6 | 7 | extern bool dist_entry_init(const char *plugin_path); 8 | extern void dist_entry_deinit(void); 9 | extern const void *dist_entry_get_factory(const char *factory_id); --------------------------------------------------------------------------------