diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 71453009e..714d57489 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -11,7 +11,7 @@ CPMAddPackage( CPMAddPackage( NAME clap-juce-extensions GITHUB_REPOSITORY free-audio/clap-juce-extensions - GIT_TAG 10bc7d4ddb82eab4796b1ce7d1d2dadd46552f27 + GIT_TAG e72d59a870ab6dea156d4912cbd004b715fca5f7 ) include(AddJUCEModules) diff --git a/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.cpp b/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.cpp new file mode 100644 index 000000000..95afe1e72 --- /dev/null +++ b/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.cpp @@ -0,0 +1,117 @@ +#include "chowdsp_CLAPPresetDiscoveryProviders.h" + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4127) +#include +#include +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +namespace chowdsp::presets::discovery +{ +EmbeddedPresetsProvider::EmbeddedPresetsProvider (const clap_universal_plugin_id& this_plug_id, + const clap_preset_discovery_provider_descriptor& desc, + const clap_preset_discovery_location& location, + const clap_preset_discovery_indexer* indexer) + : CLAPPresetsProviderBase (&desc, indexer), + this_plugin_id (this_plug_id), + discoveryLocation (location) +{ + // CLAP requires that a location containing embedded presets must be nullptr + jassert (discoveryLocation.location == nullptr); +} + +std::vector EmbeddedPresetsProvider::getPresets() { return {}; } + +bool EmbeddedPresetsProvider::init() noexcept +{ + indexer()->declare_location (indexer(), &discoveryLocation); + return true; +} + +bool EmbeddedPresetsProvider::getMetadata (uint32_t location_kind, + [[maybe_unused]] const char* location, + const clap_preset_discovery_metadata_receiver_t* metadata_receiver) noexcept +{ + if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN) + return false; + + for (const auto& factoryPreset : getPresets()) + { + DBG ("Indexing factory preset: " + factoryPreset.getName()); + if (metadata_receiver->begin_preset (metadata_receiver, factoryPreset.getName().toRawUTF8(), factoryPreset.getName().toRawUTF8())) + { + metadata_receiver->add_plugin_id (metadata_receiver, &this_plugin_id); + metadata_receiver->add_creator (metadata_receiver, factoryPreset.getVendor().toRawUTF8()); + + if (factoryPreset.getCategory().isNotEmpty()) + metadata_receiver->add_feature (metadata_receiver, factoryPreset.getCategory().toRawUTF8()); + } + else + { + break; + } + } + + return true; +} + +//============================================================================== +FilePresetsProvider::FilePresetsProvider (const clap_universal_plugin_id& this_plug_id, + const clap_preset_discovery_provider_descriptor& desc, + const clap_preset_discovery_filetype& filetype, + const clap_preset_discovery_indexer* indexer) + : CLAPPresetsProviderBase (&desc, indexer), + this_plugin_id (this_plug_id), + presets_filetype (filetype) +{ +} + +bool FilePresetsProvider::init() noexcept +{ + indexer()->declare_filetype (indexer(), &presets_filetype); + + discoveryLocation.flags = CLAP_PRESET_DISCOVERY_IS_USER_CONTENT; + discoveryLocation.kind = CLAP_PRESET_DISCOVERY_LOCATION_FILE; + if (! fillInLocation (discoveryLocation)) + return false; + + indexer()->declare_location (indexer(), &discoveryLocation); + + return true; +} + +bool FilePresetsProvider::getMetadata (uint32_t location_kind, + const char* location, + const clap_preset_discovery_metadata_receiver_t* metadata_receiver) noexcept +{ + if (location_kind != CLAP_PRESET_DISCOVERY_LOCATION_FILE || location == nullptr) + return false; + + const auto userPresetFile = juce::File { location }; + if (! userPresetFile.existsAsFile()) + return false; + + Preset preset { userPresetFile }; + if (! preset.isValid()) + return false; + + DBG ("Indexing user preset: " + preset.getName() + ", from path: " + userPresetFile.getFullPathName()); + if (metadata_receiver->begin_preset (metadata_receiver, userPresetFile.getFullPathName().toRawUTF8(), "")) + { + metadata_receiver->add_plugin_id (metadata_receiver, &this_plugin_id); + metadata_receiver->add_creator (metadata_receiver, preset.getVendor().toRawUTF8()); + + if (preset.getCategory().isNotEmpty()) + metadata_receiver->add_feature (metadata_receiver, preset.getCategory().toRawUTF8()); + + metadata_receiver->set_timestamps (metadata_receiver, + (clap_timestamp) userPresetFile.getCreationTime().toMilliseconds() / 1000, + (clap_timestamp) userPresetFile.getLastModificationTime().toMilliseconds() / 1000); + } + + return true; +} +} // namespace chowdsp::presets::discovery diff --git a/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.h b/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.h new file mode 100644 index 000000000..c0cff783d --- /dev/null +++ b/modules/plugin/chowdsp_clap_extensions/PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.h @@ -0,0 +1,63 @@ +#pragma once + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) +#include +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +namespace chowdsp::presets +{ +class Preset; +} + +namespace chowdsp::presets::discovery +{ +using CLAPPresetsProviderBase = +#if JUCE_DEBUG + clap::helpers::PresetDiscoveryProvider; +#else + clap::helpers::PresetDiscoveryProvider; +#endif + +/** A CLAP preset provider for presets that are embedded in the plugin's binary data. */ +struct EmbeddedPresetsProvider : CLAPPresetsProviderBase +{ + const clap_universal_plugin_id& this_plugin_id; + const clap_preset_discovery_location& discoveryLocation {}; + + EmbeddedPresetsProvider (const clap_universal_plugin_id& this_plug_id, + const clap_preset_discovery_provider_descriptor& desc, + const clap_preset_discovery_location& location, + const clap_preset_discovery_indexer* indexer); + + /** Users are expected to override this method to provide the relevant presets. */ + virtual std::vector getPresets(); + + bool init() noexcept override; + bool getMetadata (uint32_t location_kind, + const char* location, + const clap_preset_discovery_metadata_receiver_t* metadata_receiver) noexcept override; +}; + +/** A CLAP preset provider for presets that are stored in the user's filesystem. */ +struct FilePresetsProvider : CLAPPresetsProviderBase +{ + const clap_universal_plugin_id& this_plugin_id; + const clap_preset_discovery_filetype& presets_filetype; + clap_preset_discovery_location discoveryLocation {}; + + FilePresetsProvider (const clap_universal_plugin_id& this_plug_id, + const clap_preset_discovery_provider_descriptor& desc, + const clap_preset_discovery_filetype& filetype, + const clap_preset_discovery_indexer* indexer); + + /** Users are expected to override this method to fill in the location name and path. */ + virtual bool fillInLocation (clap_preset_discovery_location&) = 0; + + bool init() noexcept override; + bool getMetadata (uint32_t location_kind, + const char* location, + const clap_preset_discovery_metadata_receiver_t* metadata_receiver) noexcept override; +}; +} // namespace chowdsp::presets::discovery diff --git a/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.cpp b/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.cpp new file mode 100644 index 000000000..fc642a16b --- /dev/null +++ b/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.cpp @@ -0,0 +1,7 @@ +#include "chowdsp_clap_extensions.h" + +// LCOV_EXCL_START +#if JUCE_MODULE_AVAILABLE_chowdsp_presets_v2 +#include "PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.cpp" +#endif +// LCOV_EXCL_END diff --git a/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.h b/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.h index 8d30728bf..19c85b356 100644 --- a/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.h +++ b/modules/plugin/chowdsp_clap_extensions/chowdsp_clap_extensions.h @@ -44,4 +44,8 @@ namespace CLAPExtensions // LCOV_EXCL_START #include "ParameterExtensions/chowdsp_ModParamMixin.h" #include "PluginExtensions/chowdsp_CLAPInfoExtensions.h" + +#if JUCE_MODULE_AVAILABLE_chowdsp_presets_v2 +#include "PresetExtensions/chowdsp_CLAPPresetDiscoveryProviders.h" +#endif // LCOV_EXCL_END diff --git a/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_PluginBase.h b/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_PluginBase.h index bd01a51e3..62032e49a 100644 --- a/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_PluginBase.h +++ b/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_PluginBase.h @@ -117,6 +117,14 @@ class PluginBase : public juce::AudioProcessor virtual juce::String getWrapperTypeString() const; bool supportsParameterModulation() const; +#if HAS_CLAP_JUCE_EXTENSIONS && JUCE_MODULE_AVAILABLE_chowdsp_presets_v2 + bool supportsPresetLoad() const noexcept override + { + return presetManager != nullptr; + } + bool presetLoadFromLocation (uint32_t location_kind, const char* location, const char* load_key) noexcept override; +#endif + protected: #if JUCE_MODULE_AVAILABLE_chowdsp_plugin_state PluginStateType state; @@ -311,4 +319,14 @@ bool PluginBase

::supportsParameterModulation() const return false; #endif } + +#if HAS_CLAP_JUCE_EXTENSIONS && JUCE_MODULE_AVAILABLE_chowdsp_presets_v2 +template +bool PluginBase

::presetLoadFromLocation (uint32_t location_kind, const char* location, const char* load_key) noexcept +{ + if (presetManager == nullptr) + return false; + return presetManager->loadCLAPPreset (location_kind, location, load_key); +} +#endif } // namespace chowdsp diff --git a/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_SynthBase.h b/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_SynthBase.h index 11c1bb71e..161ddb7e4 100644 --- a/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_SynthBase.h +++ b/modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_SynthBase.h @@ -44,8 +44,8 @@ class SynthBase : public PluginBase { return true; } - int noteNameCount() noexcept override { return 0; } - bool noteNameGet (int /*index*/, clap_note_name* /*noteName*/) noexcept override { return false; } + uint32_t noteNameCount() noexcept override { return 0; } + bool noteNameGet (uint32_t /*index*/, clap_note_name* /*noteName*/) noexcept override { return false; } #endif private: diff --git a/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.cpp b/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.cpp index c1461e4e2..36228be1e 100644 --- a/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.cpp +++ b/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.cpp @@ -5,12 +5,14 @@ namespace chowdsp FileListener::FileListener (const juce::File& file, int timerSeconds) : fileToListenTo (file) { fileModificationTime = fileToListenTo.getLastModificationTime().toMilliseconds(); - startTimer (timerSeconds * 1000); + if (timerSeconds > 0) + startTimer (timerSeconds * 1000); } FileListener::~FileListener() { - stopTimer(); + if (isTimerRunning()) + stopTimer(); } void FileListener::timerCallback() diff --git a/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.h b/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.h index 1e0574131..1dd3768f7 100644 --- a/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.h +++ b/modules/plugin/chowdsp_plugin_utils/Files/chowdsp_FileListener.h @@ -3,10 +3,15 @@ namespace chowdsp { /** Abstract class to allow the derived class to listen for changes to a file. */ -class FileListener : private juce::Timer +class FileListener : public juce::Timer { public: - /** Initialize this FileListener for a given file and update time. */ + /** + * Initialize this FileListener for a given file and update time. + * + * If the given update time is less than or equal to zero, then + * the timer will not be started. + */ FileListener (const juce::File& file, int timerSeconds); ~FileListener() override; diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.cpp b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.cpp index 0f4b209ed..05152588b 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.cpp +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.cpp @@ -1,5 +1,13 @@ #include "chowdsp_PresetManager.h" +#if HAS_CLAP_JUCE_EXTENSIONS +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) +#include +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + namespace chowdsp::presets { PresetManager::PresetManager (PluginState& state, @@ -124,4 +132,52 @@ void PresetManager::loadUserPresetsFromFolder (const juce::File& file) addPresets (std::move (presets), false); } + +void PresetManager::loadPreset (const Preset& preset) +{ + saverLoader.loadPreset (preset); + +#if HAS_CLAP_JUCE_EXTENSIONS + if (preset.getPresetFile() == juce::File {}) + clapPresetLoadedBroadcaster (CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN, + nullptr, + preset.getName().toRawUTF8()); + clapPresetLoadedBroadcaster (CLAP_PRESET_DISCOVERY_LOCATION_FILE, + preset.getPresetFile().getFullPathName().toRawUTF8(), + nullptr); +#endif +} + +#if HAS_CLAP_JUCE_EXTENSIONS +bool PresetManager::loadCLAPPreset (uint32_t location_kind, const char* location, const char* load_key) noexcept +{ + if (location_kind == CLAP_PRESET_DISCOVERY_LOCATION_FILE) + { + const auto presetFile = juce::File { location }; + if (! presetFile.existsAsFile()) + return false; + + const auto preset = Preset { presetFile }; + if (! preset.isValid()) + return false; + + loadPreset (preset); + return true; + } + + if (location_kind == CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN) + { + for (const auto& preset : getFactoryPresets()) + { + if (preset.getName() == load_key) + { + loadPreset (preset); + return true; + } + } + } + + return false; +} +#endif } // namespace chowdsp::presets diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.h b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.h index 336500c30..24eb55184 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.h +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetManager.h @@ -15,7 +15,14 @@ class PresetManager virtual ~PresetManager() = default; /** Loads a preset by reference. */ - void loadPreset (const Preset& preset) { saverLoader.loadPreset (preset); } + void loadPreset (const Preset& preset); + +#if HAS_CLAP_JUCE_EXTENSIONS + /** Loads a preset based on information provided by the CLAP preset-load extension. */ + virtual bool loadCLAPPreset (uint32_t location_kind, const char* location, const char* load_key) noexcept; + + Broadcaster clapPresetLoadedBroadcaster {}; +#endif /** Returns the currently loaded preset, or nullptr if no preset is loaded. */ [[nodiscard]] const Preset* getCurrentPreset() const { return saverLoader.getCurrentPreset(); } diff --git a/tests/plugin_tests/CMakeLists.txt b/tests/plugin_tests/CMakeLists.txt index dd4025dff..104ee7962 100644 --- a/tests/plugin_tests/CMakeLists.txt +++ b/tests/plugin_tests/CMakeLists.txt @@ -13,7 +13,11 @@ setup_juce_lib(plugin_tests_lib ) if(TARGET clap_juce_extensions) - target_link_libraries(plugin_tests_lib PUBLIC clap_juce_extensions) + target_link_libraries(plugin_tests_lib + PUBLIC + clap_juce_extensions + chowdsp::chowdsp_clap_extensions + ) endif() target_compile_definitions(plugin_tests_lib