Skip to content

Commit

Permalink
Helpers for implementing CLAP preset discovery (#487)
Browse files Browse the repository at this point in the history
* Setting up extensions for CLAP preset discovery and loading

* Update CLAP note name API

* Add missing HAS_CLAP check

* More presets/CLAP interop updates

* Apply clang-format

* Update CHE version for building examples

* Trying to fix test compiler errors

* Undo temp thing

* Trying to fix warnings on Windows

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Jan 29, 2024
1 parent 19fd177 commit dfd267c
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 9 deletions.
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include "chowdsp_CLAPPresetDiscoveryProviders.h"

#include <chowdsp_presets_v2/chowdsp_presets_v2.h>

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100 4127)
#include <clap/helpers/preset-discovery-provider.hh>
#include <clap/helpers/preset-discovery-provider.hxx>
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<Preset> 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100)
#include <clap/helpers/preset-discovery-provider.hh>
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<clap::helpers::MisbehaviourHandler::Terminate, clap::helpers::CheckingLevel::Maximal>;
#else
clap::helpers::PresetDiscoveryProvider<clap::helpers::MisbehaviourHandler::Ignore, clap::helpers::CheckingLevel::Minimal>;
#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<Preset> 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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 18 additions & 0 deletions modules/plugin/chowdsp_plugin_base/PluginBase/chowdsp_PluginBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -311,4 +319,14 @@ bool PluginBase<P>::supportsParameterModulation() const
return false;
#endif
}

#if HAS_CLAP_JUCE_EXTENSIONS && JUCE_MODULE_AVAILABLE_chowdsp_presets_v2
template <class P>
bool PluginBase<P>::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
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ class SynthBase : public PluginBase<Processor>
{
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <clap/helpers/preset-discovery-provider.hh>
JUCE_END_IGNORE_WARNINGS_MSVC
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif

namespace chowdsp::presets
{
PresetManager::PresetManager (PluginState& state,
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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<void (uint32_t location_kind, const char* location, const char* load_key)> clapPresetLoadedBroadcaster {};
#endif

/** Returns the currently loaded preset, or nullptr if no preset is loaded. */
[[nodiscard]] const Preset* getCurrentPreset() const { return saverLoader.getCurrentPreset(); }
Expand Down
6 changes: 5 additions & 1 deletion tests/plugin_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit dfd267c

Please sign in to comment.