├── libs ├── CLI11 │ ├── CMakeLists.txt │ ├── README.md │ └── LICENSE.txt └── CMakeLists.txt ├── .gitmodules ├── LICENSE.md ├── src ├── clap-info │ ├── clap-info-host.h │ ├── info.h │ ├── clap-info-host.cpp │ ├── info-params.cpp │ ├── info-other-extensions.cpp │ ├── info-ports.cpp │ └── main.cpp └── clap-scanner │ ├── resolve_macosLocations.mm │ ├── scanner.cpp │ └── resolve_entrypoint.cpp ├── .clang-format ├── README.md ├── .gitignore ├── include └── clap-scanner │ └── scanner.h ├── .github └── workflows │ ├── feature.yml │ └── release.yml └── CMakeLists.txt /libs/CLI11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(CLI11 INTERFACE) 2 | target_include_directories(CLI11 INTERFACE include) 3 | 4 | -------------------------------------------------------------------------------- /libs/CLI11/README.md: -------------------------------------------------------------------------------- 1 | This is the header-only CLI11 2.2 release from https://github.com/CLIUtils/CLI11/releases 2 | 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/clap"] 2 | path = libs/clap 3 | url = https://github.com/free-audio/clap 4 | [submodule "libs/jsoncpp"] 5 | path = libs/jsoncpp 6 | url = https://github.com/open-source-parsers/jsoncpp.git 7 | -------------------------------------------------------------------------------- /libs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(clap-info-libs VERSION 1.0.0 LANGUAGES C CXX) 2 | 3 | add_subdirectory(CLI11) 4 | add_library(jsoncpp 5 | jsoncpp/src/lib_json/json_reader.cpp 6 | jsoncpp/src/lib_json/json_value.cpp 7 | jsoncpp/src/lib_json/json_writer.cpp 8 | ) 9 | target_include_directories(jsoncpp PUBLIC jsoncpp/include) 10 | target_compile_options(jsoncpp PRIVATE $<$:-Wno-deprecated-declarations>) 11 | 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022, Paul Walker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/clap-info/clap-info-host.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #ifndef CLAP_INFO_HOST_CLAP_INFO_HOST_H 14 | #define CLAP_INFO_HOST_CLAP_INFO_HOST_H 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "clap/entry.h" 23 | #include "clap/host.h" 24 | #include "clap/events.h" 25 | 26 | #include "clap/ext/params.h" 27 | #include "clap/ext/audio-ports.h" 28 | #include 29 | 30 | namespace clap_info_host 31 | { 32 | struct HostConfig 33 | { 34 | public: 35 | bool announceQueriedExtensions{true}; 36 | }; 37 | 38 | clap_host_t *createCLAPInfoHost(); 39 | HostConfig *getHostConfig(); 40 | 41 | } // namespace clap_info_host 42 | 43 | #endif // CLAP_INFO_HOST_CLAP_INFO_HOST_H 44 | -------------------------------------------------------------------------------- /src/clap-info/info.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #ifndef CLAP_INFO_INFO_TYPES_H 14 | #define CLAP_INFO_INFO_TYPES_H 15 | 16 | #include "clap/clap.h" 17 | 18 | #include "json/json.h" 19 | 20 | namespace clap_info_host 21 | { 22 | Json::Value createParamsJson(const clap_plugin *inst); 23 | Json::Value createAudioPortsJson(const clap_plugin *inst); 24 | Json::Value createNotePortsJson(const clap_plugin *inst); 25 | Json::Value createLatencyJson(const clap_plugin *inst); 26 | Json::Value createTailJson(const clap_plugin *inst); 27 | Json::Value createGuiJson(const clap_plugin *inst); 28 | Json::Value createStateJson(const clap_plugin *inst); 29 | Json::Value createNoteNameJson(const clap_plugin *inst); 30 | Json::Value createAudioPortsConfigJson(const clap_plugin *inst); 31 | 32 | } // namespace clap_info_host 33 | #endif // CLAP_INFO_INFO_TYPES_H 34 | -------------------------------------------------------------------------------- /src/clap-scanner/resolve_macosLocations.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace clap_scanner 20 | { 21 | 22 | void getSystemPaths(std::vector &res) 23 | { 24 | auto *fileManager = [NSFileManager defaultManager]; 25 | auto *userLibURLs = [fileManager URLsForDirectory:NSLibraryDirectory 26 | inDomains:NSUserDomainMask]; 27 | auto *sysLibURLs = [fileManager URLsForDirectory:NSLibraryDirectory 28 | inDomains:NSLocalDomainMask]; 29 | 30 | if (userLibURLs) 31 | { 32 | auto *u = [userLibURLs objectAtIndex:0]; 33 | auto p = 34 | std::filesystem::path{[u fileSystemRepresentation]} / "Audio" / "Plug-Ins" / "CLAP"; 35 | res.push_back(p); 36 | } 37 | 38 | if (sysLibURLs) 39 | { 40 | auto *u = [sysLibURLs objectAtIndex:0]; 41 | auto p = 42 | std::filesystem::path{[u fileSystemRepresentation]} / "Audio" / "Plug-Ins" / "CLAP"; 43 | res.push_back(p); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | --- 5 | Language: Cpp 6 | BasedOnStyle: LLVM 7 | IndentWidth: 4 8 | AlignAfterOpenBracket: Align 9 | BreakBeforeBraces: Custom 10 | BraceWrapping: 11 | AfterCaseLabel: true 12 | AfterClass: true 13 | AfterControlStatement: Always 14 | AfterEnum: true 15 | AfterFunction: true 16 | AfterNamespace: true 17 | AfterObjCDeclaration: true 18 | AfterStruct: true 19 | AfterUnion: true 20 | AfterExternBlock: true 21 | BeforeCatch: true 22 | BeforeElse: true 23 | BeforeLambdaBody: false 24 | BeforeWhile: false 25 | IndentBraces: false 26 | SplitEmptyFunction: true 27 | SplitEmptyRecord: true 28 | SplitEmptyNamespace: true 29 | ColumnLimit: 100 30 | SortIncludes: false 31 | --- 32 | Language: ObjC 33 | BasedOnStyle: LLVM 34 | IndentWidth: 4 35 | AlignAfterOpenBracket: Align 36 | BreakBeforeBraces: Custom 37 | BraceWrapping: 38 | AfterCaseLabel: true 39 | AfterClass: true 40 | AfterControlStatement: Always 41 | AfterEnum: true 42 | AfterFunction: true 43 | AfterNamespace: true 44 | AfterObjCDeclaration: true 45 | AfterStruct: true 46 | AfterUnion: true 47 | AfterExternBlock: true 48 | BeforeCatch: true 49 | BeforeElse: true 50 | BeforeLambdaBody: false 51 | BeforeWhile: false 52 | IndentBraces: false 53 | SplitEmptyFunction: true 54 | SplitEmptyRecord: true 55 | SplitEmptyNamespace: true 56 | ColumnLimit: 100 57 | SortIncludes: false 58 | --- 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clap-info: A simple command line CLAP information tool 2 | 3 | This is a CLAP information tool which simply loads a clap and allows you to 4 | print a variety of information about the plugin. It prints the information in 5 | machine-readable (JSON) format. 6 | 7 | `clap-info` can perform the following 8 | 9 | - Traverse your system finding .clap files, and scanning them for information 10 | - Load a single CLAP and display the descriptors 11 | - For a given CLAP and descriptor, create an instance of a plugin and print 12 | extension information, including ports, parameters, and more 13 | 14 | We occasionally tag releases with versions and make binary releases available, 15 | but the program is designed to be easy to build, and when starting a CLAP development 16 | effort, can be a useful tiny host to debug the first stages of CLAP development. 17 | 18 | ## To build 19 | 20 | ```bash 21 | git clone --recurse-submodules https://github.com/free-audio/clap-info 22 | cd clap-info 23 | cmake -Bbuild 24 | cmake --build build 25 | ``` 26 | 27 | ## To use as a scanner 28 | 29 | `clap-info -l` will list all installed CLAPs on your system, using the CLAP 30 | locations documented in `entry.h` 31 | 32 | `clap-info -s` will load each clap and print the descriptors for each CLAP, 33 | and as such implements a CLAP scanner. 34 | 35 | ## To use on a single CLAP 36 | 37 | ```bash 38 | clap-info "/path/to/YourCLAP.clap" 39 | ``` 40 | 41 | this usage will print complete information on plugin 0 in your clap. 42 | 43 | You can configure the information with various degrees of probes, verbosity, and more. 44 | Use `clap-info --help` to get full information on the options. 45 | 46 | -------------------------------------------------------------------------------- /libs/CLI11/LICENSE.txt: -------------------------------------------------------------------------------- 1 | CLI11 2.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry 2 | Schreiner under NSF AWARD 1414736. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms of CLI11, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 3. Neither the name of the copyright holder nor the names of its contributors 13 | may be used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /.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 | # Visual Studio 35 | obj/ 36 | *.sln 37 | *.vcxproj 38 | *.vcxproj.filters 39 | *.vcxproj.user 40 | .vs/ 41 | packages/ 42 | target/ 43 | *.pdb 44 | packages.config 45 | CMakeSettings.json 46 | .vscode 47 | .cache 48 | 49 | # XCode 50 | Surge.xcworkspace/ 51 | surge-au.xcodeproj/ 52 | surge-vst2.xcodeproj/ 53 | surge-vst3.xcodeproj/ 54 | surge-headless.xcodeproj/ 55 | products/ 56 | installer_mac/installer 57 | installer_mac/*.dmg 58 | installer_osx/installer 59 | installer_osx/Install_Surge_*.dmg 60 | build_logs/ 61 | fxbuild/ 62 | .DS_Store 63 | 64 | # IntelliJ IDEA 65 | .idea 66 | 67 | # Linux 68 | Makefile 69 | surge-*.make 70 | premake-stamp 71 | cmake-stamp 72 | /Debug 73 | *.deb 74 | 75 | # Qt Creator 76 | *.txt.user 77 | *.txt.user.* 78 | 79 | # CMake 80 | build/ 81 | build32/ 82 | buildlin/ 83 | buildlin-*/ 84 | buildmac/ 85 | buildwin/ 86 | buildxt/ 87 | build-arm/ 88 | build_lv2 89 | buildiwyu/ 90 | cmake-build-*/ 91 | bwd/ 92 | CMakeUserPresets.json 93 | 94 | # Reaper 95 | *.RPP-bak 96 | 97 | VERSION_GIT_INFO 98 | .clang-tidy 99 | 100 | # Juce until we add a submodule 101 | libs/juce-* 102 | 103 | # A place for BP to store stuff 104 | ignore/* 105 | __pycache__ 106 | minst.sh 107 | -------------------------------------------------------------------------------- /include/clap-scanner/scanner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #ifndef CLAP_INFO_SCANNER_H 14 | #define CLAP_INFO_SCANNER_H 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | namespace clap_scanner 23 | { 24 | /* 25 | * Return a list of the valid CLAP search paths, per the spec 26 | * in entry.h, including parsing the appropriate environment. 27 | */ 28 | std::vector validCLAPSearchPaths(); 29 | 30 | /* 31 | * Traverse the valid CLAP search paths looking for OS specific 32 | * paths to .clap instances 33 | */ 34 | std::vector installedCLAPs(); 35 | 36 | /* 37 | * Given the path of a CLAP instance, undertake the OS specific code 38 | * to open the clap and resolve the clap_plugin_entry_t *, which can return 39 | * null in the case of an invalid CLAP file 40 | */ 41 | const clap_plugin_entry_t *entryFromCLAPPath(const std::filesystem::path &p); 42 | 43 | /* 44 | * Given a clap_plugin_entry_t, visit each clap description available 45 | * inside the CLAP with a std::function 46 | */ 47 | bool foreachCLAPDescription(const clap_plugin_entry_t *, std::function ); 48 | 49 | /* 50 | * Given a path to a CLAP, visit each clap description available inside the 51 | * CLAP with a std::function 52 | */ 53 | inline bool foreachCLAPDescription(const std::filesystem::path &p, std::function cb) 54 | { 55 | auto e = entryFromCLAPPath(p); 56 | if (e) 57 | { 58 | return foreachCLAPDescription(e, cb); 59 | } 60 | return false; 61 | } 62 | 63 | 64 | } 65 | 66 | #endif // CLAP_INFO_SCANNER_H 67 | -------------------------------------------------------------------------------- /src/clap-info/clap-info-host.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include 14 | #include "clap-info-host.h" 15 | 16 | namespace clap_info_host 17 | { 18 | 19 | static std::unique_ptr static_host_config; 20 | const void *get_extension(const struct clap_host *host, const char *eid) 21 | { 22 | if (static_host_config->announceQueriedExtensions) 23 | std::cout << " Plugin->Host : Requesting Extension " << eid << std::endl; 24 | return nullptr; 25 | } 26 | 27 | void request_restart(const struct clap_host *h) {} 28 | 29 | void request_process(const struct clap_host *h) {} 30 | 31 | void request_callback(const struct clap_host *h) {} 32 | 33 | static clap_host clap_info_host_static{CLAP_VERSION_INIT, 34 | nullptr, 35 | "clap-info", 36 | "CLAP team", 37 | "https://github.com/free-audio/clap-info", 38 | "1.0.0", 39 | get_extension, 40 | request_restart, 41 | request_process, 42 | request_callback}; 43 | 44 | clap_host_t *createCLAPInfoHost() 45 | { 46 | if (!static_host_config) 47 | static_host_config = std::make_unique(); 48 | return &clap_info_host_static; 49 | } 50 | 51 | HostConfig *getHostConfig() 52 | { 53 | if (!static_host_config) 54 | { 55 | std::cout << "Please call createCLAPInfoHost before getHostConfig()"; 56 | return nullptr; 57 | } 58 | return static_host_config.get(); 59 | } 60 | 61 | } // namespace clap_info_host -------------------------------------------------------------------------------- /src/clap-scanner/scanner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include "clap-scanner/scanner.h" 14 | 15 | namespace clap_scanner 16 | { 17 | std::vector installedCLAPs() 18 | { 19 | auto sp = validCLAPSearchPaths(); 20 | std::vector claps; 21 | for (const auto &p : sp) 22 | { 23 | try 24 | { 25 | for (auto const &dir_entry : std::filesystem::recursive_directory_iterator(p)) 26 | { 27 | if (dir_entry.path().extension().u8string() == ".clap") 28 | { 29 | #if MAC 30 | if (std::filesystem::is_directory(dir_entry.path())) 31 | { 32 | claps.emplace_back(dir_entry.path()); 33 | } 34 | #else 35 | if (!std::filesystem::is_directory(dir_entry.path())) 36 | { 37 | claps.emplace_back(dir_entry.path()); 38 | } 39 | #endif 40 | } 41 | } 42 | } 43 | catch (const std::filesystem::filesystem_error &) 44 | { 45 | } 46 | } 47 | return claps; 48 | } 49 | 50 | bool foreachCLAPDescription(const clap_plugin_entry_t *entry, 51 | std::function cb) 52 | { 53 | auto fac = (clap_plugin_factory_t *)entry->get_factory(CLAP_PLUGIN_FACTORY_ID); 54 | if (!fac) 55 | { 56 | return false; 57 | } 58 | auto plugin_count = fac->get_plugin_count(fac); 59 | if (plugin_count <= 0) 60 | { 61 | return false; 62 | } 63 | for (uint32_t pl = 0; pl < plugin_count; ++pl) 64 | { 65 | auto desc = fac->get_plugin_descriptor(fac, pl); 66 | if (!desc) 67 | return false; 68 | cb(desc); 69 | } 70 | return true; 71 | } 72 | } // namespace clap_scanner -------------------------------------------------------------------------------- /.github/workflows/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build_feature: 10 | name: Build ${{ matrix.name }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | include: 15 | - os: ubuntu-latest 16 | name: linux 17 | dir_build: ./build 18 | file_name: clap-info 19 | run_test: true 20 | 21 | - os: macos-latest 22 | name: mac-universal 23 | dir_build: ./build 24 | file_name: clap-info 25 | cmake_args: -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" 26 | run_test: true 27 | 28 | - os: windows-latest 29 | name: win-x64 30 | dir_build: ./build/Release 31 | file_name: clap-info 32 | file_ext: .exe 33 | cmake_args: -G"Visual Studio 17 2022" -A x64 34 | run_test: true 35 | 36 | - os: windows-latest 37 | name: win-arm64ec 38 | dir_build: ./build/Release 39 | file_name: clap-info 40 | file_ext: .exe 41 | cmake_args: -G"Visual Studio 17 2022" -A arm64ec -DCMAKE_SYSTEM_VERSION=10 42 | run_test: false 43 | 44 | 45 | steps: 46 | - name: Install Windows dependencies 47 | if: matrix.os == 'windows-latest' 48 | run: choco install zip 49 | 50 | - name: Checkout code 51 | uses: actions/checkout@v2 52 | with: 53 | submodules: recursive 54 | 55 | - name: Build binary 56 | run: | 57 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} 58 | cmake --build ./build --config Release 59 | 60 | - name: List files 61 | run: ls "${{ matrix.dir_build }}" 62 | 63 | - name: Test binary 64 | if: ${{ matrix.run_test }} 65 | run: ${{ matrix.dir_build }}/${{ matrix.file_name }}${{ matrix.file_ext }} -h 66 | 67 | - name: Compress binary 68 | run: | 69 | cd "${{ matrix.dir_build }}" 70 | zip ${{ matrix.file_name }}-${{ matrix.name }}.zip ${{ matrix.file_name }}${{ matrix.file_ext }} 71 | 72 | - name: Upload binary 73 | uses: actions/upload-artifact@v3 74 | with: 75 | name: ${{ matrix.file_name }}-${{ matrix.name }}.zip 76 | path: ${{ matrix.dir_build }}/${{ matrix.file_name }}-${{ matrix.name }}.zip 77 | -------------------------------------------------------------------------------- /src/clap-info/info-params.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "clap/ext/params.h" 17 | 18 | #include "info.h" 19 | 20 | namespace clap_info_host 21 | { 22 | Json::Value createParamsJson(const clap_plugin *inst) 23 | { 24 | auto inst_param = (clap_plugin_params_t *)inst->get_extension(inst, CLAP_EXT_PARAMS); 25 | 26 | Json::Value pluginParams; 27 | if (inst_param) 28 | { 29 | auto pc = inst_param->count(inst); 30 | pluginParams["implemented"] = true; 31 | pluginParams["param-count"] = pc; 32 | 33 | Json::Value instParams; 34 | instParams.resize(0); 35 | for (auto i = 0U; i < pc; ++i) 36 | { 37 | clap_param_info_t inf; 38 | inst_param->get_info(inst, i, &inf); 39 | 40 | Json::Value instParam; 41 | 42 | std::stringstream ss; 43 | ss << "0x" << std::hex << inf.id; 44 | instParam["id"] = ss.str(); 45 | 46 | instParam["module"] = inf.module; 47 | instParam["name"] = inf.name; 48 | 49 | Json::Value values; 50 | values["min"] = inf.min_value; 51 | values["max"] = inf.max_value; 52 | values["default"] = inf.default_value; 53 | double d; 54 | inst_param->get_value(inst, inf.id, &d); 55 | values["current"] = d; 56 | instParam["values"] = values; 57 | 58 | auto cp = [&inf, &instParam](auto x, auto y) { 59 | if (inf.flags & x) 60 | { 61 | instParam["flags"].append(y); 62 | } 63 | }; 64 | cp(CLAP_PARAM_IS_STEPPED, "stepped"); 65 | cp(CLAP_PARAM_IS_PERIODIC, "periodic"); 66 | cp(CLAP_PARAM_IS_HIDDEN, "hidden"); 67 | cp(CLAP_PARAM_IS_READONLY, "readonly"); 68 | cp(CLAP_PARAM_IS_BYPASS, "bypass"); 69 | 70 | cp(CLAP_PARAM_IS_AUTOMATABLE, "auto"); 71 | cp(CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID, "auto-noteid"); 72 | cp(CLAP_PARAM_IS_AUTOMATABLE_PER_KEY, "auto-key"); 73 | cp(CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL, "auto-channel"); 74 | cp(CLAP_PARAM_IS_AUTOMATABLE_PER_PORT, "auto-port"); 75 | 76 | cp(CLAP_PARAM_IS_MODULATABLE, "mod"); 77 | cp(CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID, "mod-noteid"); 78 | cp(CLAP_PARAM_IS_MODULATABLE_PER_KEY, "mod-key"); 79 | cp(CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL, "mod-channel"); 80 | cp(CLAP_PARAM_IS_MODULATABLE_PER_PORT, "mod-port"); 81 | 82 | cp(CLAP_PARAM_REQUIRES_PROCESS, "requires-process"); 83 | instParams.append(instParam); 84 | } 85 | pluginParams["param-info"] = instParams; 86 | } 87 | else 88 | { 89 | pluginParams["implemented"] = false; 90 | } 91 | return pluginParams; 92 | } 93 | } // namespace clap_info_host -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | cmake_policy(SET CMP0091 NEW) 3 | 4 | # CLAP supports standard C; but this validator uses std::filesystem so requires C++ 17 5 | # and macOS 10.15 6 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "Minimum macOS version") 7 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 8 | 9 | project(clap-info VERSION 1.0.0 LANGUAGES C CXX) 10 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 11 | set(CMAKE_CXX_STANDARD 17) 12 | 13 | # Build-time options 14 | # Use a different version of clap 15 | if (NOT DEFINED ${CLAP_INFO_CLAP_DIR}) 16 | set(CLAP_INFO_CLAP_DIR "libs/clap" CACHE STRING "Location of the CLAP cmake project") 17 | endif() 18 | # Use the Address Sanitizer (currently macOS only) 19 | option(USE_USE_SANITIZER "Build and link with ASAN" FALSE) 20 | 21 | 22 | add_subdirectory(libs) 23 | message(STATUS "Using CLAP installed from ${CLAP_INFO_CLAP_DIR}") 24 | add_subdirectory(${CLAP_INFO_CLAP_DIR}) 25 | 26 | add_library(clap-scanner 27 | src/clap-scanner/resolve_entrypoint.cpp 28 | src/clap-scanner/scanner.cpp 29 | ) 30 | 31 | target_include_directories(clap-scanner PUBLIC include) 32 | target_link_libraries(clap-scanner PUBLIC clap-core) 33 | 34 | add_executable(${PROJECT_NAME} 35 | src/clap-info/main.cpp 36 | src/clap-info/clap-info-host.cpp 37 | 38 | src/clap-info/info-params.cpp 39 | src/clap-info/info-ports.cpp 40 | src/clap-info/info-other-extensions.cpp 41 | ) 42 | 43 | 44 | add_custom_target(clap-info-zip) 45 | add_dependencies(clap-info-zip clap-info) 46 | set(INSTALLER_DIR ${CMAKE_BINARY_DIR}/installer) 47 | set(INFO_DIR ${INSTALLER_DIR}/clap-info) 48 | 49 | add_custom_command(TARGET clap-info-zip 50 | POST_BUILD 51 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 52 | COMMAND ${CMAKE_COMMAND} -E make_directory ${INFO_DIR} 53 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/README.md ${INFO_DIR} 54 | COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/LICENSE.md ${INFO_DIR} 55 | COMMAND ${CMAKE_COMMAND} -E copy $ ${INFO_DIR} 56 | COMMAND git log -1 > ${INFO_DIR}/version.txt 57 | COMMAND git log --pretty=oneline -10 >> ${INFO_DIR}/version.txt 58 | ) 59 | 60 | if (UNIX) 61 | add_custom_command(TARGET clap-info-zip 62 | POST_BUILD 63 | WORKING_DIRECTORY ${INSTALLER_DIR} 64 | COMMAND tar cvzf clap-info.tar.gz clap-info ) 65 | else() 66 | add_custom_command(TARGET clap-info-zip 67 | POST_BUILD 68 | WORKING_DIRECTORY ${INSTALLER_DIR} 69 | COMMAND 7z a -r clap-info.zip clap-info) 70 | endif() 71 | 72 | if (APPLE) 73 | target_compile_definitions(clap-scanner PUBLIC MAC=1) 74 | target_sources(clap-scanner PRIVATE src/clap-scanner/resolve_macosLocations.mm) 75 | target_link_libraries(clap-scanner PUBLIC dl "-framework CoreFoundation" "-framework Foundation") 76 | 77 | target_compile_definitions(${PROJECT_NAME} PRIVATE MAC=1) 78 | target_compile_options(${PROJECT_NAME} PRIVATE 79 | $<$:-fUSE_SANITIZER=address> 80 | $<$:-fUSE_SANITIZER=undefined> 81 | ) 82 | target_link_options(${PROJECT_NAME} PRIVATE 83 | $<$:-fUSE_SANITIZER=address> 84 | $<$:-fUSE_SANITIZER=undefined> 85 | ) 86 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Werror) 87 | 88 | elseif(UNIX) 89 | target_compile_definitions(clap-scanner PUBLIC LIN=1) 90 | target_link_libraries(clap-scanner PUBLIC -ldl) 91 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Werror) 92 | else() 93 | target_compile_definitions(clap-scanner PUBLIC WIN=1) 94 | endif() 95 | 96 | target_include_directories(${PROJECT_NAME} PRIVATE src) 97 | target_link_libraries(${PROJECT_NAME} PRIVATE CLI11 jsoncpp clap-scanner) 98 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | create_release: 10 | name: Create release 11 | runs-on: ubuntu-latest 12 | outputs: 13 | upload_id: ${{ steps.draft_release.outputs.id }} 14 | upload_url: ${{ steps.draft_release.outputs.upload_url }} 15 | steps: 16 | - name: Draft release 17 | id: draft_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | draft: true 25 | 26 | build_release: 27 | name: Build ${{ matrix.name }} 28 | needs: create_release 29 | runs-on: ${{ matrix.os }} 30 | strategy: 31 | matrix: 32 | include: 33 | - os: ubuntu-latest 34 | name: linux-x64 35 | dir_build: ./build 36 | file_name: clap-info 37 | run_test: true 38 | 39 | - os: macos-latest 40 | name: mac-universal 41 | dir_build: ./build 42 | file_name: clap-info 43 | cmake_args: -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" 44 | run_test: true 45 | 46 | - os: windows-latest 47 | name: win-x64 48 | dir_build: ./build/Release 49 | file_name: clap-info 50 | file_ext: .exe 51 | cmake_args: -G"Visual Studio 17 2022" -A x64 52 | run_test: true 53 | 54 | - os: windows-latest 55 | name: win-arm64ec 56 | dir_build: ./build/Release 57 | file_name: clap-info 58 | file_ext: .exe 59 | cmake_args: -G"Visual Studio 17 2022" -A arm64ec -DCMAKE_SYSTEM_VERSION=10 60 | run_test: false 61 | 62 | 63 | steps: 64 | - name: Install Windows dependencies 65 | if: matrix.os == 'windows-latest' 66 | run: choco install zip 67 | 68 | - name: Checkout code 69 | uses: actions/checkout@v2 70 | with: 71 | submodules: recursive 72 | 73 | - name: Build binary 74 | run: | 75 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} 76 | cmake --build ./build --config Release 77 | 78 | - name: List files 79 | run: ls "${{ matrix.dir_build }}" 80 | 81 | - name: Test binary 82 | if: ${{ matrix.run_test }} 83 | run: ${{ matrix.dir_build }}/${{ matrix.file_name }}${{ matrix.file_ext }} -h 84 | 85 | - name: Compress binary 86 | run: | 87 | cd "${{ matrix.dir_build }}" 88 | zip ${{ matrix.file_name }}-${{ matrix.name }}.zip ${{ matrix.file_name }}${{ matrix.file_ext }} 89 | 90 | - name: Upload binary 91 | uses: actions/upload-artifact@v3 92 | with: 93 | name: ${{ matrix.file_name }}-${{ matrix.name }}.zip 94 | path: ${{ matrix.dir_build }}/${{ matrix.file_name }}-${{ matrix.name }}.zip 95 | 96 | - name: Upload binary 97 | uses: actions/upload-release-asset@v1 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | upload_url: ${{ needs.create_release.outputs.upload_url }} 102 | asset_path: ${{ matrix.dir_build }}/${{ matrix.file_name }}-${{ matrix.name }}.zip 103 | asset_name: ${{ matrix.file_name }}-${{ matrix.name }}.zip 104 | asset_content_type: application/zip 105 | 106 | publish_release: 107 | name: Publish release 108 | needs: [create_release, build_release] 109 | runs-on: ubuntu-latest 110 | steps: 111 | - uses: eregon/publish-release@v1 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | with: 115 | release_id: ${{ needs.create_release.outputs.upload_id }} 116 | -------------------------------------------------------------------------------- /src/clap-scanner/resolve_entrypoint.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include "clap-scanner/scanner.h" 14 | #include 15 | 16 | #if MAC 17 | #include 18 | #endif 19 | 20 | #if WIN 21 | #include 22 | #include 23 | #endif 24 | 25 | #if LIN 26 | #include 27 | #endif 28 | 29 | namespace clap_scanner 30 | { 31 | #if MAC 32 | const clap_plugin_entry_t *entryFromCLAPPath(const std::filesystem::path &p) 33 | { 34 | auto ps = p.u8string(); 35 | auto cs = CFStringCreateWithBytes(kCFAllocatorDefault, (uint8_t *)ps.c_str(), ps.size(), 36 | kCFStringEncodingUTF8, false); 37 | auto bundleURL = 38 | CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cs, kCFURLPOSIXPathStyle, true); 39 | 40 | auto bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL); 41 | 42 | auto db = CFBundleGetDataPointerForName(bundle, CFSTR("clap_entry")); 43 | 44 | CFRelease(bundle); 45 | CFRelease(bundleURL); 46 | CFRelease(cs); 47 | 48 | return (clap_plugin_entry_t *)db; 49 | } 50 | 51 | void getSystemPaths(std::vector &); 52 | #endif 53 | 54 | #if WIN 55 | const clap_plugin_entry_t *entryFromCLAPPath(const std::filesystem::path &p) 56 | { 57 | auto han = LoadLibrary((LPCSTR)(p.generic_string().c_str())); 58 | if (!han) 59 | return nullptr; 60 | auto phan = GetProcAddress(han, "clap_entry"); 61 | // std::cout << "phan is " << phan << std::endl; 62 | return (clap_plugin_entry_t *)phan; 63 | } 64 | #endif 65 | 66 | #if LIN 67 | const clap_plugin_entry_t *entryFromCLAPPath(const std::filesystem::path &p) 68 | { 69 | void *handle; 70 | int *iptr; 71 | 72 | handle = dlopen(p.u8string().c_str(), RTLD_LOCAL | RTLD_LAZY); 73 | if (!handle) 74 | { 75 | std::cerr << "dlopen failed on Linux: " << dlerror() << std::endl; 76 | return nullptr; 77 | } 78 | 79 | iptr = (int *)dlsym(handle, "clap_entry"); 80 | 81 | return (clap_plugin_entry_t *)iptr; 82 | } 83 | 84 | #endif 85 | 86 | std::vector validCLAPSearchPaths() 87 | { 88 | std::vector res; 89 | 90 | #if MAC 91 | getSystemPaths(res); 92 | #endif 93 | 94 | #if LIN 95 | res.emplace_back("/usr/lib/clap"); 96 | res.emplace_back(std::filesystem::path(getenv("HOME")) / std::filesystem::path(".clap")); 97 | #endif 98 | 99 | #if WIN 100 | { 101 | // I think this should use SHGetKnownFilderLocation but I don't know windows well enough 102 | auto p = getenv("COMMONPROGRAMFILES"); 103 | if (p) 104 | { 105 | res.emplace_back(std::filesystem::path{p} / "CLAP"); 106 | } 107 | auto q = getenv("LOCALAPPDATA"); 108 | if (q) 109 | { 110 | res.emplace_back(std::filesystem::path{q} / "Programs" / "Common" / "CLAP"); 111 | } 112 | } 113 | #endif 114 | 115 | auto p = getenv("CLAP_PATH"); 116 | 117 | if (p) 118 | { 119 | #if WIN 120 | auto sep = ';'; 121 | #else 122 | auto sep = ':'; 123 | #endif 124 | auto cp = std::string(p); 125 | 126 | size_t pos; 127 | while ((pos = cp.find(sep)) != std::string::npos) 128 | { 129 | auto item = cp.substr(0, pos); 130 | cp = cp.substr(pos + 1); 131 | res.emplace_back(std::filesystem::path{item}); 132 | } 133 | if (cp.size()) 134 | res.emplace_back(std::filesystem::path{cp}); 135 | } 136 | 137 | return res; 138 | } 139 | 140 | } // namespace clap_scanner 141 | -------------------------------------------------------------------------------- /src/clap-info/info-other-extensions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include "info.h" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace clap_info_host 20 | { 21 | 22 | Json::Value unimpl(const Json::Value &v) 23 | { 24 | if (v.type() == Json::nullValue) 25 | { 26 | Json::Value r; 27 | r["implemented"] = false; 28 | return r; 29 | } 30 | return v; 31 | } 32 | 33 | Json::Value createLatencyJson(const clap_plugin *inst) 34 | { 35 | auto inst_latency = (clap_plugin_latency_t *)inst->get_extension(inst, CLAP_EXT_LATENCY); 36 | 37 | Json::Value res; 38 | if (inst_latency) 39 | { 40 | res["implemented"] = true; 41 | res["latency"] = inst_latency->get(inst); 42 | } 43 | return unimpl(res); 44 | } 45 | 46 | Json::Value createTailJson(const clap_plugin *inst) 47 | { 48 | auto inst_tail = (clap_plugin_tail_t *)inst->get_extension(inst, CLAP_EXT_TAIL); 49 | 50 | Json::Value res; 51 | if (inst_tail) 52 | { 53 | res["implemented"] = true; 54 | res["tail"] = inst_tail->get(inst); 55 | } 56 | return unimpl(res); 57 | } 58 | 59 | Json::Value createGuiJson(const clap_plugin *inst) 60 | { 61 | auto inst_gui = (clap_plugin_gui_t *)inst->get_extension(inst, CLAP_EXT_GUI); 62 | 63 | Json::Value res; 64 | if (inst_gui) 65 | { 66 | res["implemented"] = true; 67 | res["api_supported"] = Json::Value(); 68 | for (const auto &api : {CLAP_WINDOW_API_COCOA, CLAP_WINDOW_API_WIN32, CLAP_WINDOW_API_X11, 69 | CLAP_WINDOW_API_WAYLAND}) 70 | { 71 | if (inst_gui->is_api_supported(inst, api, false)) 72 | res["api_supported"].append(api); 73 | if (inst_gui->is_api_supported(inst, api, true)) 74 | res["api_supported"].append(std::string(api) + ".floating"); 75 | } 76 | 77 | const char *prefA{nullptr}; 78 | bool fl{false}; 79 | if (inst_gui->get_preferred_api(inst, &prefA, &fl) && prefA) 80 | { 81 | res["preferred_api"] = Json::Value(); 82 | res["preferred_api"]["api"] = prefA; 83 | res["preferred_api"]["floating"] = fl; 84 | } 85 | } 86 | return unimpl(res); 87 | } 88 | 89 | Json::Value createStateJson(const clap_plugin *inst) 90 | { 91 | auto inst_state = (clap_plugin_state_t *)inst->get_extension(inst, CLAP_EXT_STATE); 92 | Json::Value res; 93 | if (inst_state) 94 | { 95 | res["implemented"] = true; 96 | 97 | clap_ostream_t os; 98 | uint64_t written{0}; 99 | 100 | os.ctx = &written; 101 | os.write = [](const struct clap_ostream *stream, const void *buffer, 102 | uint64_t size) -> int64_t { 103 | uint64_t *c = static_cast(stream->ctx); 104 | *(c) += size; 105 | return size; 106 | }; 107 | inst_state->save(inst, &os); 108 | res["bytes-written"] = written; 109 | } 110 | return unimpl(res); 111 | } 112 | 113 | Json::Value createNoteNameJson(const clap_plugin *inst) 114 | { 115 | auto inst_notename = 116 | (clap_plugin_note_name /*_t*/ *)inst->get_extension(inst, CLAP_EXT_NOTE_NAME); 117 | 118 | Json::Value res; 119 | if (inst_notename) 120 | { 121 | res["implemented"] = true; 122 | auto ct = inst_notename->count(inst); 123 | res["count"] = ct; 124 | if (ct > 0) 125 | { 126 | Json::Value names; 127 | for (uint32_t i = 0; i < inst_notename->count(inst); ++i) 128 | { 129 | clap_note_name_t nn; 130 | inst_notename->get(inst, i, &nn); 131 | Json::Value jn; 132 | jn["name"] = nn.name; 133 | jn["port"] = nn.port; 134 | jn["key"] = nn.key; 135 | jn["channel"] = nn.channel; 136 | names.append(jn); 137 | } 138 | res["note_names"] = names; 139 | } 140 | } 141 | return unimpl(res); 142 | } 143 | 144 | Json::Value createAudioPortsConfigJson(const clap_plugin *inst) 145 | { 146 | auto inst_apc = 147 | (clap_plugin_audio_ports_config_t *)inst->get_extension(inst, CLAP_EXT_AUDIO_PORTS_CONFIG); 148 | 149 | Json::Value res; 150 | if (inst_apc) 151 | { 152 | res["implemented"] = true; 153 | auto ct = inst_apc->count(inst); 154 | res["count"] = ct; 155 | if (ct > 0) 156 | { 157 | Json::Value cfgs; 158 | for (uint32_t i = 0; i < inst_apc->count(inst); ++i) 159 | { 160 | clap_audio_ports_config apc; 161 | inst_apc->get(inst, i, &apc); 162 | Json::Value jn; 163 | jn["id"] = apc.id; 164 | jn["name"] = apc.name; 165 | jn["input-port-count"] = apc.input_port_count; 166 | jn["output-port-count"] = apc.output_port_count; 167 | 168 | jn["has-main-input"] = apc.has_main_input; 169 | jn["main-input-channel-count"] = apc.main_input_channel_count; 170 | jn["main-input-port_type"] = apc.main_input_port_type; 171 | 172 | jn["has-main-output"] = apc.has_main_output; 173 | jn["main-output-channel-count"] = apc.main_output_channel_count; 174 | jn["main-output-port_type"] = apc.main_output_port_type; 175 | cfgs.append(jn); 176 | } 177 | res["configs"] = cfgs; 178 | } 179 | } 180 | return unimpl(res); 181 | } 182 | } // namespace clap_info_host 183 | -------------------------------------------------------------------------------- /src/clap-info/info-ports.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include "clap/ext/audio-ports.h" 14 | #include "clap/ext/note-ports.h" 15 | 16 | #include "info.h" 17 | 18 | #include "json/json.h" 19 | 20 | namespace clap_info_host 21 | { 22 | 23 | Json::Value createAudioPortsJson(const clap_plugin *inst) 24 | { 25 | auto inst_ports = (clap_plugin_audio_ports_t *)inst->get_extension(inst, CLAP_EXT_AUDIO_PORTS); 26 | int inPorts{0}, outPorts{0}; 27 | 28 | Json::Value audioPorts; 29 | if (inst_ports) 30 | { 31 | audioPorts["implemented"] = true; 32 | inPorts = inst_ports->count(inst, true); 33 | audioPorts["input-port-count"] = inPorts; 34 | outPorts = inst_ports->count(inst, false); 35 | audioPorts["output-port-count"] = outPorts; 36 | 37 | auto makeFlags = [](const auto &p) { 38 | Json::Value flag; 39 | flag["value"] = p; 40 | Json::Value desc; 41 | 42 | #define ADDF(x) \ 43 | if (p & x) \ 44 | { \ 45 | desc.append(#x); \ 46 | } 47 | ADDF(CLAP_AUDIO_PORT_IS_MAIN); 48 | ADDF(CLAP_AUDIO_PORT_SUPPORTS_64BITS); 49 | ADDF(CLAP_AUDIO_PORT_PREFERS_64BITS); 50 | ADDF(CLAP_AUDIO_PORT_REQUIRES_COMMON_SAMPLE_SIZE); 51 | #undef ADDF 52 | 53 | if (desc.size() != 0) 54 | { 55 | flag["fields"] = desc; 56 | } 57 | return flag; 58 | }; 59 | 60 | Json::Value inputPorts; 61 | inputPorts.resize(0); 62 | for (int i = 0; i < inPorts; ++i) 63 | { 64 | clap_audio_port_info_t inf; 65 | inst_ports->get(inst, i, true, &inf); 66 | Json::Value inputPort; 67 | inputPort["name"] = inf.name; 68 | inputPort["id"] = inf.id; 69 | inputPort["channel-count"] = inf.channel_count; 70 | if (inf.port_type) 71 | { 72 | inputPort["port-type"] = inf.port_type; 73 | } 74 | inputPort["flags"] = makeFlags(inf.flags); 75 | 76 | if (inf.in_place_pair != CLAP_INVALID_ID) 77 | { 78 | inputPort["in-place-pair"] = inf.in_place_pair; 79 | } 80 | inputPorts.append(inputPort); 81 | } 82 | audioPorts["input-ports"] = inputPorts; 83 | 84 | Json::Value outputPorts; 85 | outputPorts.resize(0); 86 | for (int i = 0; i < outPorts; ++i) 87 | { 88 | clap_audio_port_info_t inf; 89 | inst_ports->get(inst, i, false, &inf); 90 | Json::Value outputPort; 91 | outputPort["name"] = inf.name; 92 | outputPort["id"] = inf.id; 93 | outputPort["channel-count"] = inf.channel_count; 94 | if (inf.port_type) 95 | { 96 | outputPort["port-type"] = inf.port_type; 97 | } 98 | 99 | outputPort["flags"] = makeFlags(inf.flags); 100 | 101 | if (inf.in_place_pair != CLAP_INVALID_ID) 102 | { 103 | outputPort["in-place-pair"] = inf.in_place_pair; 104 | } 105 | outputPorts.append(outputPort); 106 | } 107 | audioPorts["output-ports"] = outputPorts; 108 | } 109 | else 110 | { 111 | audioPorts["implemented"] = false; 112 | } 113 | return audioPorts; 114 | } 115 | 116 | Json::Value createNotePortsJson(const clap_plugin *inst) 117 | { 118 | auto inst_ports = (clap_plugin_note_ports_t *)inst->get_extension(inst, CLAP_EXT_NOTE_PORTS); 119 | int inPorts{0}, outPorts{0}; 120 | 121 | Json::Value notePorts; 122 | if (inst_ports) 123 | { 124 | notePorts["implemented"] = true; 125 | inPorts = inst_ports->count(inst, true); 126 | notePorts["input-port-count"] = inPorts; 127 | outPorts = inst_ports->count(inst, false); 128 | notePorts["output-port-count"] = outPorts; 129 | 130 | auto dial = [](Json::Value &inputPort, auto supported, auto pref) { 131 | 132 | #define CHECKD(x) \ 133 | if (supported & x) \ 134 | { \ 135 | inputPort["dialects"].append(#x); \ 136 | if (pref == x) \ 137 | inputPort["preferred"] = #x; \ 138 | } 139 | CHECKD(CLAP_NOTE_DIALECT_CLAP); 140 | CHECKD(CLAP_NOTE_DIALECT_MIDI); 141 | CHECKD(CLAP_NOTE_DIALECT_MIDI_MPE); 142 | CHECKD(CLAP_NOTE_DIALECT_MIDI2); 143 | 144 | #undef CHECKD 145 | }; 146 | Json::Value inputPorts; 147 | inputPorts.resize(0); 148 | for (int i = 0; i < inPorts; ++i) 149 | { 150 | clap_note_port_info_t inf; 151 | inst_ports->get(inst, i, true, &inf); 152 | Json::Value inputPort; 153 | inputPort["id"] = inf.id; 154 | inputPort["name"] = inf.name; 155 | dial(inputPort, inf.supported_dialects, inf.preferred_dialect); 156 | inputPorts.append(inputPort); 157 | } 158 | notePorts["input-ports"] = inputPorts; 159 | 160 | Json::Value outputPorts; 161 | outputPorts.resize(0); 162 | for (int i = 0; i < outPorts; ++i) 163 | { 164 | clap_note_port_info_t inf; 165 | inst_ports->get(inst, i, false, &inf); 166 | Json::Value outputPort; 167 | outputPort["id"] = inf.id; 168 | outputPort["name"] = inf.name; 169 | dial(outputPort, inf.supported_dialects, inf.preferred_dialect); 170 | outputPorts.append(outputPort); 171 | } 172 | notePorts["output-ports"] = outputPorts; 173 | } 174 | else 175 | { 176 | notePorts["implmeented"] = false; 177 | } 178 | return notePorts; 179 | } 180 | } // namespace clap_info_host 181 | -------------------------------------------------------------------------------- /src/clap-info/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CLAP-INFO 3 | * 4 | * https://github.com/free-audio/clap-info 5 | * 6 | * CLAP-INFO is Free and Open Source software, released under the MIT 7 | * License, a copy of which is included with this source in the file 8 | * "LICENSE.md" 9 | * 10 | * Copyright (c) 2022 Various Authors, per the Git Transaction Log 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "clap/all.h" 17 | 18 | #include "clap-info-host.h" 19 | #include "clap-scanner/scanner.h" 20 | #include "clap/factory/plugin-factory.h" 21 | 22 | #include "info.h" 23 | 24 | #include "CLI11/CLI11.hpp" 25 | 26 | struct CLAPInfoJsonRoot 27 | { 28 | Json::Value root; 29 | bool active{true}; 30 | std::string outFile; 31 | ~CLAPInfoJsonRoot() 32 | { 33 | if (active) 34 | { 35 | Json::StyledWriter writer; 36 | std::string out_string = writer.write(root); 37 | if (outFile.empty()) 38 | { 39 | std::cout << out_string << std::endl; 40 | } 41 | else 42 | { 43 | auto ofs = std::ofstream(outFile); 44 | if (ofs.is_open()) 45 | { 46 | ofs << out_string; 47 | ofs.close(); 48 | } 49 | else 50 | { 51 | std::cout << "Unable to open output file '" << outFile << "' for writing"; 52 | } 53 | } 54 | } 55 | } 56 | }; 57 | 58 | int main(int argc, char **argv) 59 | { 60 | CLI::App app("clap-info: CLAP command line validation tool"); 61 | 62 | app.set_version_flag("--version", "0.9.0"); 63 | std::string clap; 64 | app.add_option("-f,--file,file", clap, "CLAP plugin file location"); 65 | 66 | bool showClaps{false}; 67 | app.add_flag("-l,--list-clap-files", showClaps, 68 | "Show all CLAP files in the search path then exit"); 69 | 70 | bool showClapsWithDesc{false}; 71 | app.add_flag("-s,--scan-clap-files", showClapsWithDesc, 72 | "Show all descriptions in all CLAP files in the search path, then exit"); 73 | 74 | std::string outFile{}; 75 | app.add_option("-o,--output", outFile, "Redirect JSON to an output file rather than stdout"); 76 | 77 | bool create{true}; 78 | app.add_option("--create", create, 79 | "Choose whether to create an instance of the plugin or just scan the entry.") 80 | ->default_str("TRUE"); 81 | 82 | int32_t which_plugin{-1}; 83 | app.add_option( 84 | "--which", which_plugin, 85 | "Choose which plugin to create (if the CLAP has more than one). If you set to -1 " 86 | "we will traverse all plugins.") 87 | ->default_str("-1"); 88 | 89 | bool annExt{false}; 90 | app.add_option("--announce-extensions", annExt, "Announce extensions queried by plugin.") 91 | ->default_str("FALSE"); 92 | 93 | bool descShow{true}; 94 | app.add_option("--descriptions", descShow, "Show the descriptions of the plugins in this CLAP") 95 | ->default_str("TRUE"); 96 | 97 | bool paramShow{true}; 98 | app.add_option("--params", paramShow, "Print plugin parameters.")->default_str("TRUE"); 99 | 100 | /*int paramVerbosity{2}; 101 | app.add_option("--param-verbosity", paramVerbosity, 102 | "How verbosely to display/query params (1-4)") 103 | ->default_str("2"); 104 | */ 105 | 106 | bool audioPorts{true}; 107 | app.add_option("--audio-ports", audioPorts, "Display the Audio Ports configuration") 108 | ->default_str("TRUE"); 109 | 110 | bool notePorts{true}; 111 | app.add_option("--note-ports", notePorts, "Display the Note Ports configuration") 112 | ->default_str("TRUE"); 113 | 114 | bool otherExt{true}; 115 | app.add_option("--other-ext", otherExt, "Display brief information about all other extensions") 116 | ->default_str("TRUE"); 117 | 118 | bool searchPath{false}; 119 | app.add_flag("--search-path", searchPath, "Show the CLAP plugin search paths then exit"); 120 | 121 | bool brief{false}; 122 | app.add_flag("--brief", brief, "Output brief infomation only"); 123 | 124 | CLI11_PARSE(app, argc, argv); 125 | 126 | CLAPInfoJsonRoot doc; 127 | doc.outFile = outFile; 128 | 129 | // If brief param is set, override other params to output concise information only 130 | if (brief) 131 | { 132 | annExt = false; 133 | audioPorts = false; 134 | notePorts = false; 135 | paramShow = false; 136 | otherExt = false; 137 | } 138 | 139 | if (searchPath) 140 | { 141 | doc.root["action"] = "display clap search paths"; 142 | Json::Value res; 143 | auto sp = clap_scanner::validCLAPSearchPaths(); 144 | for (const auto &q : sp) 145 | res.append(q.u8string()); 146 | doc.root["result"] = res; 147 | 148 | return 0; 149 | } 150 | 151 | if (showClaps) 152 | { 153 | doc.root["action"] = "display paths for installed claps"; 154 | Json::Value res; 155 | auto sp = clap_scanner::installedCLAPs(); 156 | for (const auto &q : sp) 157 | res.append(q.u8string()); 158 | doc.root["result"] = res; 159 | 160 | return 0; 161 | } 162 | 163 | if (showClapsWithDesc) 164 | { 165 | doc.root["action"] = "display descriptions for installed claps"; 166 | Json::Value res; 167 | auto sp = clap_scanner::installedCLAPs(); 168 | for (const auto &q : sp) 169 | { 170 | Json::Value entryJson; 171 | if (auto entry = clap_scanner::entryFromCLAPPath(q)) 172 | { 173 | entry->init(q.u8string().c_str()); 174 | entryJson["path"] = q.u8string(); 175 | entryJson["clap-version"] = std::to_string(entry->clap_version.major) + "." + 176 | std::to_string(entry->clap_version.minor) + "." + 177 | std::to_string(entry->clap_version.revision); 178 | entryJson["plugins"] = Json::Value(); 179 | clap_scanner::foreachCLAPDescription( 180 | entry, [&entryJson](const clap_plugin_descriptor_t *desc) { 181 | Json::Value thisPlugin; 182 | thisPlugin["name"] = desc->name; 183 | if (desc->version) 184 | thisPlugin["version"] = desc->version; 185 | thisPlugin["id"] = desc->id; 186 | if (desc->vendor) 187 | thisPlugin["vendor"] = desc->vendor; 188 | if (desc->description) 189 | thisPlugin["description"] = desc->description; 190 | 191 | Json::Value features; 192 | 193 | auto f = desc->features; 194 | int idx = 0; 195 | while (f[0]) 196 | { 197 | bool nullWithinSize{false}; 198 | for (int i = 0; i < CLAP_NAME_SIZE; ++i) 199 | { 200 | if (f[0][i] == 0) 201 | { 202 | nullWithinSize = true; 203 | } 204 | } 205 | 206 | if (!nullWithinSize) 207 | { 208 | std::cerr 209 | << "Feature element at index " << idx 210 | << " lacked null within CLAP_NAME_SIZE." 211 | << "This means either a feature at this index overflowed or " 212 | "you didn't null terminate your " 213 | << "features array" << std::endl; 214 | break; 215 | } 216 | features.append(f[0]); 217 | f++; 218 | idx++; 219 | } 220 | thisPlugin["features"] = features; 221 | entryJson["plugins"].append(thisPlugin); 222 | }); 223 | res.append(entryJson); 224 | entry->deinit(); 225 | } 226 | } 227 | doc.root["result"] = res; 228 | return 0; 229 | } 230 | 231 | if (clap == "") 232 | { 233 | std::cout << app.help() << std::endl; 234 | doc.active = false; 235 | return 1; 236 | } 237 | 238 | auto clapPath = std::filesystem::path(clap); 239 | #if MAC 240 | if (!(std::filesystem::is_directory(clapPath) || std::filesystem::is_regular_file(clapPath))) 241 | { 242 | std::cerr << "Your file '" << clap << "' is neither a bundle nor a file" << std::endl; 243 | doc.active = false; 244 | return 2; 245 | } 246 | #else 247 | if (!std::filesystem::is_regular_file(clapPath)) 248 | { 249 | std::cerr << "Your file '" << clap << "' is not a regular file" << std::endl; 250 | doc.active = false; 251 | return 2; 252 | } 253 | #endif 254 | 255 | // std::cout << "Loading clap : " << clap << std::endl; 256 | auto entry = clap_scanner::entryFromCLAPPath(clapPath); 257 | 258 | if (!entry) 259 | { 260 | std::cerr << " clap_entry returned a nullptr\n" 261 | << " either this plugin is not a CLAP or it has exported the incorrect symbol." 262 | << std::endl; 263 | doc.active = false; 264 | return 3; 265 | } 266 | 267 | Json::Value &root = doc.root; 268 | root["file"] = clap; 269 | 270 | auto version = entry->clap_version; 271 | std::stringstream ss; 272 | ss << version.major << "." << version.minor << "." << version.revision; 273 | root["clap-version"] = ss.str(); 274 | 275 | entry->init(clap.c_str()); 276 | 277 | auto fac = (clap_plugin_factory_t *)entry->get_factory(CLAP_PLUGIN_FACTORY_ID); 278 | auto plugin_count = fac->get_plugin_count(fac); 279 | if (plugin_count <= 0) 280 | { 281 | std::cerr << "Plugin factory has no plugins" << std::endl; 282 | doc.active = 0; 283 | return 4; 284 | } 285 | 286 | if (descShow && !create) 287 | { 288 | // Only loop if we don't create 289 | root["plugin-count"] = plugin_count; 290 | Json::Value factory; 291 | factory.resize(0); 292 | for (uint32_t pl = 0; pl < plugin_count; ++pl) 293 | { 294 | Json::Value pluginDescriptor; 295 | auto desc = fac->get_plugin_descriptor(fac, pl); 296 | 297 | pluginDescriptor["name"] = desc->name; 298 | if (desc->version) 299 | pluginDescriptor["version"] = desc->version; 300 | pluginDescriptor["id"] = desc->id; 301 | if (desc->vendor) 302 | pluginDescriptor["vendor"] = desc->vendor; 303 | if (desc->description) 304 | pluginDescriptor["description"] = desc->description; 305 | 306 | auto f = desc->features; 307 | while (f[0]) 308 | { 309 | pluginDescriptor["features"].append(f[0]); 310 | f++; 311 | } 312 | factory.append(pluginDescriptor); 313 | } 314 | root[CLAP_PLUGIN_FACTORY_ID] = factory; 315 | } 316 | 317 | if (!create) 318 | { 319 | return 0; 320 | } 321 | 322 | if (which_plugin != -1 && (which_plugin < 0 || which_plugin >= (int)plugin_count)) 323 | { 324 | std::cerr << "Unable to create plugin " << which_plugin << " which must be between 0 and " 325 | << plugin_count - 1 << " or be a -1 sentinel" << std::endl; 326 | doc.active = false; 327 | return 4; 328 | } 329 | 330 | int startPlugin = (which_plugin < 0 ? 0 : which_plugin); 331 | int endPlugin = (which_plugin < 0 ? plugin_count : which_plugin + 1); 332 | 333 | root["plugin_count"] = plugin_count; 334 | root["plugins"] = Json::Value(); 335 | 336 | for (int plug = startPlugin; plug < endPlugin; ++plug) 337 | { 338 | auto thisPluginJson = Json::Value(); 339 | auto desc = fac->get_plugin_descriptor(fac, plug); 340 | 341 | if (descShow) 342 | { 343 | // Only loop if we don't create 344 | Json::Value pluginDescriptor; 345 | 346 | pluginDescriptor["name"] = desc->name; 347 | if (desc->version) 348 | pluginDescriptor["version"] = desc->version; 349 | pluginDescriptor["id"] = desc->id; 350 | if (desc->vendor) 351 | pluginDescriptor["vendor"] = desc->vendor; 352 | if (desc->description) 353 | pluginDescriptor["description"] = desc->description; 354 | 355 | auto f = desc->features; 356 | while (f[0]) 357 | { 358 | pluginDescriptor["features"].append(f[0]); 359 | f++; 360 | } 361 | thisPluginJson["descriptor"] = pluginDescriptor; 362 | thisPluginJson["plugin-index"] = plug; 363 | } 364 | 365 | // Now lets make an instance 366 | auto host = clap_info_host::createCLAPInfoHost(); 367 | clap_info_host::getHostConfig()->announceQueriedExtensions = annExt; 368 | auto inst = fac->create_plugin(fac, host, desc->id); 369 | if (!inst) 370 | { 371 | std::cerr << "Unable to create plugin; inst is null" << std::endl; 372 | doc.active = false; 373 | return 5; 374 | } 375 | 376 | bool result = inst->init(inst); 377 | if (!result) 378 | { 379 | std::cerr << "Unable to init plugin" << std::endl; 380 | doc.active = false; 381 | return 6; 382 | } 383 | inst->activate(inst, 48000, 32, 4096); 384 | 385 | Json::Value extensions; 386 | 387 | if (paramShow) 388 | { 389 | extensions[CLAP_EXT_PARAMS] = clap_info_host::createParamsJson(inst); 390 | } 391 | 392 | if (audioPorts) 393 | { 394 | extensions[CLAP_EXT_AUDIO_PORTS] = clap_info_host::createAudioPortsJson(inst); 395 | } 396 | 397 | if (notePorts) 398 | { 399 | extensions[CLAP_EXT_NOTE_PORTS] = clap_info_host::createNotePortsJson(inst); 400 | } 401 | 402 | if (otherExt) 403 | { 404 | extensions[CLAP_EXT_LATENCY] = clap_info_host::createLatencyJson(inst); 405 | extensions[CLAP_EXT_TAIL] = clap_info_host::createTailJson(inst); 406 | extensions[CLAP_EXT_GUI] = clap_info_host::createGuiJson(inst); 407 | extensions[CLAP_EXT_STATE] = clap_info_host::createStateJson(inst); 408 | extensions[CLAP_EXT_NOTE_NAME] = clap_info_host::createNoteNameJson(inst); 409 | extensions[CLAP_EXT_AUDIO_PORTS_CONFIG] = 410 | clap_info_host::createAudioPortsConfigJson(inst); 411 | 412 | // Some 'is implemented' only ones. This is the 413 | // entire 1.2.0 list generated with 414 | // grep -r CLAP_EXT libs/clap/include | grep static | awk '{print $5}' | sed -e 415 | // 's/\[\]/,/' and then remove the ones handled above by hand 416 | for (auto ext : { 417 | CLAP_EXT_AMBISONIC, 418 | CLAP_EXT_AMBISONIC_COMPAT, 419 | CLAP_EXT_AUDIO_PORTS_ACTIVATION, 420 | CLAP_EXT_AUDIO_PORTS_ACTIVATION_COMPAT, 421 | CLAP_EXT_AUDIO_PORTS_CONFIG_INFO, 422 | CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT, 423 | CLAP_EXT_CONFIGURABLE_AUDIO_PORTS, 424 | CLAP_EXT_CONFIGURABLE_AUDIO_PORTS_COMPAT, 425 | CLAP_EXT_CONTEXT_MENU, 426 | CLAP_EXT_CONTEXT_MENU_COMPAT, 427 | CLAP_EXT_EVENT_REGISTRY, 428 | CLAP_EXT_EXTENSIBLE_AUDIO_PORTS, 429 | CLAP_EXT_LOG, 430 | CLAP_EXT_PARAM_INDICATION, 431 | CLAP_EXT_PARAM_INDICATION_COMPAT, 432 | CLAP_EXT_POSIX_FD_SUPPORT, 433 | CLAP_EXT_PRESET_LOAD, 434 | CLAP_EXT_PRESET_LOAD_COMPAT, 435 | CLAP_EXT_REMOTE_CONTROLS, 436 | CLAP_EXT_REMOTE_CONTROLS_COMPAT, 437 | CLAP_EXT_RENDER, 438 | CLAP_EXT_RESOURCE_DIRECTORY, 439 | CLAP_EXT_STATE_CONTEXT, 440 | CLAP_EXT_SURROUND, 441 | CLAP_EXT_SURROUND_COMPAT, 442 | CLAP_EXT_THREAD_CHECK, 443 | CLAP_EXT_THREAD_POOL, 444 | CLAP_EXT_TIMER_SUPPORT, 445 | CLAP_EXT_TRACK_INFO, 446 | CLAP_EXT_TRACK_INFO_COMPAT, 447 | CLAP_EXT_TRANSPORT_CONTROL, 448 | CLAP_EXT_TRIGGERS, 449 | CLAP_EXT_TUNING, 450 | CLAP_EXT_VOICE_INFO, 451 | 452 | }) 453 | { 454 | auto exf = inst->get_extension(inst, ext); 455 | Json::Value r; 456 | r["implemented"] = exf ? true : false; 457 | extensions[ext] = r; 458 | } 459 | } 460 | 461 | thisPluginJson["extensions"] = extensions; 462 | root["plugins"].append(thisPluginJson); 463 | 464 | inst->deactivate(inst); 465 | inst->destroy(inst); 466 | } 467 | 468 | entry->deinit(); 469 | 470 | return 0; 471 | } 472 | --------------------------------------------------------------------------------