diff --git a/distrho/src/DistrhoPluginCLAP.cpp b/distrho/src/DistrhoPluginCLAP.cpp index 22dcc8a1..ee336dbc 100644 --- a/distrho/src/DistrhoPluginCLAP.cpp +++ b/distrho/src/DistrhoPluginCLAP.cpp @@ -40,10 +40,12 @@ #include "clap/entry.h" #include "clap/plugin-factory.h" #include "clap/ext/audio-ports.h" -#include "clap/ext/note-ports.h" +#include "clap/ext/latency.h" #include "clap/ext/gui.h" +#include "clap/ext/note-ports.h" #include "clap/ext/params.h" #include "clap/ext/state.h" +#include "clap/ext/thread-check.h" #include "clap/ext/timer-support.h" #if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI @@ -621,7 +623,7 @@ private: void editParameter(const uint32_t rindex, const bool started) const { const ClapEventQueue::Event ev = { - started ? ClapEventQueue::kEventGestureBegin : ClapEventQueue::kEventGestureBegin, + started ? ClapEventQueue::kEventGestureBegin : ClapEventQueue::kEventGestureEnd, rindex, 0.f }; fEventQueue.addEventFromUI(ev); @@ -736,10 +738,15 @@ public: requestParameterValueChangeCallback, updateStateValueCallback), fHost(host), - fOutputEvents(nullptr) + fOutputEvents(nullptr), + #if DISTRHO_PLUGIN_WANT_LATENCY + fLatencyChanged(false), + fLastKnownLatency(0), + #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - , fMidiEventCount(0) + fMidiEventCount(0), #endif + fHostExtensions(host) { fCachedParameters.setup(fPlugin.getParameterCount()); #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT @@ -768,8 +775,7 @@ public: if (!clap_version_is_compatible(fHost->clap_version)) return false; - // TODO check host features - return true; + return fHostExtensions.init(); } void activate(const double sampleRate, const uint32_t maxFramesCount) @@ -782,6 +788,10 @@ public: void deactivate() { fPlugin.deactivate(); + #if DISTRHO_PLUGIN_WANT_LATENCY + checkForLatencyChanges(false, true); + reportLatencyChangeIfNeeded(); + #endif } bool process(const clap_process_t* const process) @@ -1009,9 +1019,20 @@ public: fOutputEvents = nullptr; } + #if DISTRHO_PLUGIN_WANT_LATENCY + checkForLatencyChanges(true, false); + #endif + return true; } + void onMainThread() + { + #if DISTRHO_PLUGIN_WANT_LATENCY + reportLatencyChangeIfNeeded(); + #endif + } + // ---------------------------------------------------------------------------------------------------------------- // parameters @@ -1176,6 +1197,10 @@ public: } } } + + #if DISTRHO_PLUGIN_WANT_LATENCY + checkForLatencyChanges(fPlugin.isActive(), false); + #endif } // ---------------------------------------------------------------------------------------------------------------- @@ -1217,6 +1242,57 @@ public: fPlugin.setParameterValue(event->param_id, event->value); } + // ---------------------------------------------------------------------------------------------------------------- + // latency + + #if DISTRHO_PLUGIN_WANT_LATENCY + uint32_t getLatency() const noexcept + { + return fPlugin.getLatency(); + } + + void checkForLatencyChanges(const bool isActive, const bool fromMainThread) + { + const uint32_t latency = fPlugin.getLatency(); + + if (fLastKnownLatency == latency) + return; + + fLastKnownLatency = latency; + + if (isActive) + { + fLatencyChanged = true; + fHost->request_restart(fHost); + } + else + { + // if this is main-thread we can report latency change directly + if (fromMainThread || (fHostExtensions.threadCheck != nullptr && fHostExtensions.threadCheck->is_main_thread(fHost))) + { + fLatencyChanged = false; + fHostExtensions.latency->changed(fHost); + } + // otherwise we need to request a main-thread callback + else + { + fLatencyChanged = true; + fHost->request_callback(fHost); + } + } + } + + // called from main thread + void reportLatencyChangeIfNeeded() + { + if (fLatencyChanged) + { + fLatencyChanged = false; + fHostExtensions.latency->changed(fHost); + } + } + #endif + // ---------------------------------------------------------------------------------------------------------------- // state @@ -1501,8 +1577,13 @@ public: } } - if (const clap_host_params_t* const hostParams = getHostExtension(CLAP_EXT_PARAMS)) - hostParams->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT); + if (fHostExtensions.params != nullptr) + fHostExtensions.params->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT); + + #if DISTRHO_PLUGIN_WANT_LATENCY + checkForLatencyChanges(fPlugin.isActive(), true); + reportLatencyChangeIfNeeded(); + #endif #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) @@ -1591,6 +1672,10 @@ private: const clap_host_t* const fHost; const clap_output_events_t* fOutputEvents; + #if DISTRHO_PLUGIN_WANT_LATENCY + bool fLatencyChanged; + uint32_t fLastKnownLatency; + #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT uint32_t fMidiEventCount; MidiEvent fMidiEvents[kMaxMidiEvents]; @@ -1602,6 +1687,36 @@ private: TimePosition fTimePosition; #endif + struct HostExtensions { + const clap_host_t* const host; + const clap_host_params_t* params; + #if DISTRHO_PLUGIN_WANT_LATENCY + const clap_host_latency_t* latency; + const clap_host_thread_check_t* threadCheck; + #endif + + HostExtensions(const clap_host_t* const host) + : host(host), + params(nullptr) + #if DISTRHO_PLUGIN_WANT_LATENCY + , latency(nullptr) + , threadCheck(nullptr) + #endif + {} + + bool init() + { + params = static_cast(host->get_extension(host, CLAP_EXT_PARAMS)); + #if DISTRHO_PLUGIN_WANT_LATENCY + DISTRHO_SAFE_ASSERT_RETURN(host->request_restart != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(host->request_callback != nullptr, false); + latency = static_cast(host->get_extension(host, CLAP_EXT_LATENCY)); + threadCheck = static_cast(host->get_extension(host, CLAP_EXT_THREAD_CHECK)); + #endif + return true; + } + } fHostExtensions; + // ---------------------------------------------------------------------------------------------------------------- // DPF callbacks @@ -1940,37 +2055,37 @@ static const clap_plugin_note_ports_t clap_plugin_note_ports = { // -------------------------------------------------------------------------------------------------------------------- // plugin parameters -static uint32_t clap_plugin_params_count(const clap_plugin_t* const plugin) +static CLAP_ABI uint32_t clap_plugin_params_count(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->getParameterCount(); } -static bool clap_plugin_params_get_info(const clap_plugin_t* const plugin, const uint32_t index, clap_param_info_t* const info) +static CLAP_ABI bool clap_plugin_params_get_info(const clap_plugin_t* const plugin, const uint32_t index, clap_param_info_t* const info) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->getParameterInfo(index, info); } -static bool clap_plugin_params_get_value(const clap_plugin_t* const plugin, const clap_id param_id, double* const value) +static CLAP_ABI bool clap_plugin_params_get_value(const clap_plugin_t* const plugin, const clap_id param_id, double* const value) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->getParameterValue(param_id, value); } -static bool clap_plugin_params_value_to_text(const clap_plugin_t* plugin, const clap_id param_id, const double value, char* const display, const uint32_t size) +static CLAP_ABI bool clap_plugin_params_value_to_text(const clap_plugin_t* plugin, const clap_id param_id, const double value, char* const display, const uint32_t size) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->getParameterStringForValue(param_id, value, display, size); } -static bool clap_plugin_params_text_to_value(const clap_plugin_t* plugin, const clap_id param_id, const char* const display, double* const value) +static CLAP_ABI bool clap_plugin_params_text_to_value(const clap_plugin_t* plugin, const clap_id param_id, const char* const display, double* const value) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->getParameterValueForString(param_id, display, value); } -static void clap_plugin_params_flush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out) +static CLAP_ABI void clap_plugin_params_flush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->flushParameters(in, out); @@ -1985,16 +2100,32 @@ static const clap_plugin_params_t clap_plugin_params = { clap_plugin_params_flush }; +#if DISTRHO_PLUGIN_WANT_LATENCY +// -------------------------------------------------------------------------------------------------------------------- +// plugin latency + +static CLAP_ABI uint32_t clap_plugin_latency_get(const clap_plugin_t* const plugin) +{ + PluginCLAP* const instance = static_cast(plugin->plugin_data); + return instance->getLatency(); +} + +static const clap_plugin_latency_t clap_plugin_latency = { + clap_plugin_latency_get +}; +#endif + +#if DISTRHO_PLUGIN_WANT_STATE // -------------------------------------------------------------------------------------------------------------------- // plugin state -static bool clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream) +static CLAP_ABI bool clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->stateSave(stream); } -static bool clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream) +static CLAP_ABI bool clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->stateLoad(stream); @@ -2004,26 +2135,27 @@ static const clap_plugin_state_t clap_plugin_state = { clap_plugin_state_save, clap_plugin_state_load }; +#endif // -------------------------------------------------------------------------------------------------------------------- // plugin -static bool clap_plugin_init(const clap_plugin_t* const plugin) +static CLAP_ABI bool clap_plugin_init(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->init(); } -static void clap_plugin_destroy(const clap_plugin_t* const plugin) +static CLAP_ABI void clap_plugin_destroy(const clap_plugin_t* const plugin) { delete static_cast(plugin->plugin_data); std::free(const_cast(plugin)); } -static bool clap_plugin_activate(const clap_plugin_t* const plugin, - const double sample_rate, - uint32_t, - const uint32_t max_frames_count) +static CLAP_ABI bool clap_plugin_activate(const clap_plugin_t* const plugin, + const double sample_rate, + uint32_t, + const uint32_t max_frames_count) { d_nextBufferSize = max_frames_count; d_nextSampleRate = sample_rate; @@ -2033,35 +2165,35 @@ static bool clap_plugin_activate(const clap_plugin_t* const plugin, return true; } -static void clap_plugin_deactivate(const clap_plugin_t* const plugin) +static CLAP_ABI void clap_plugin_deactivate(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast(plugin->plugin_data); instance->deactivate(); } -static bool clap_plugin_start_processing(const clap_plugin_t*) +static CLAP_ABI bool clap_plugin_start_processing(const clap_plugin_t*) { // nothing to do return true; } -static void clap_plugin_stop_processing(const clap_plugin_t*) +static CLAP_ABI void clap_plugin_stop_processing(const clap_plugin_t*) { // nothing to do } -static void clap_plugin_reset(const clap_plugin_t*) +static CLAP_ABI void clap_plugin_reset(const clap_plugin_t*) { // nothing to do } -static clap_process_status clap_plugin_process(const clap_plugin_t* const plugin, const clap_process_t* const process) +static CLAP_ABI clap_process_status clap_plugin_process(const clap_plugin_t* const plugin, const clap_process_t* const process) { PluginCLAP* const instance = static_cast(plugin->plugin_data); return instance->process(process) ? CLAP_PROCESS_CONTINUE : CLAP_PROCESS_ERROR; } -static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* const id) +static CLAP_ABI const void* clap_plugin_get_extension(const clap_plugin_t*, const char* const id) { if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0) return &clap_plugin_audio_ports; @@ -2069,6 +2201,10 @@ static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* c return &clap_plugin_note_ports; if (std::strcmp(id, CLAP_EXT_PARAMS) == 0) return &clap_plugin_params; + #if DISTRHO_PLUGIN_WANT_LATENCY + if (std::strcmp(id, CLAP_EXT_LATENCY) == 0) + return &clap_plugin_latency; + #endif #if DISTRHO_PLUGIN_WANT_STATE if (std::strcmp(id, CLAP_EXT_STATE) == 0) return &clap_plugin_state; @@ -2084,9 +2220,10 @@ static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* c return nullptr; } -static void clap_plugin_on_main_thread(const clap_plugin_t*) +static void clap_plugin_on_main_thread(const clap_plugin_t* const plugin) { - // nothing to do + PluginCLAP* const instance = static_cast(plugin->plugin_data); + instance->onMainThread(); } // -------------------------------------------------------------------------------------------------------------------- diff --git a/examples/Latency/DistrhoPluginInfo.h b/examples/Latency/DistrhoPluginInfo.h index f763bc5c..c489bb88 100644 --- a/examples/Latency/DistrhoPluginInfo.h +++ b/examples/Latency/DistrhoPluginInfo.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2015 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,9 +17,10 @@ #ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED #define DISTRHO_PLUGIN_INFO_H_INCLUDED -#define DISTRHO_PLUGIN_BRAND "DISTRHO" -#define DISTRHO_PLUGIN_NAME "Latency" -#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/Latency" +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "Latency" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/Latency" +#define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.examples.latency" #define DISTRHO_PLUGIN_HAS_UI 0 #define DISTRHO_PLUGIN_IS_RT_SAFE 1 diff --git a/examples/Latency/LatencyExamplePlugin.cpp b/examples/Latency/LatencyExamplePlugin.cpp index cc40bd50..87a1384a 100644 --- a/examples/Latency/LatencyExamplePlugin.cpp +++ b/examples/Latency/LatencyExamplePlugin.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2015 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -31,7 +31,8 @@ public: fLatency(1.0f), fLatencyInFrames(0), fBuffer(nullptr), - fBufferPos(0) + fBufferPos(0), + fBufferSize(0) { // allocates buffer sampleRateChanged(getSampleRate()); @@ -114,7 +115,7 @@ protected: */ void initAudioPort(bool input, uint32_t index, AudioPort& port) override { - // treat meter audio ports as stereo + // mark the (single) latency audio port as mono port.groupId = kPortGroupMono; // everything else is as default @@ -167,13 +168,21 @@ protected: fLatency = value; fLatencyInFrames = value*getSampleRate(); - setLatency(fLatencyInFrames); } /* -------------------------------------------------------------------------------------------------------- * Audio/MIDI Processing */ + /** + Activate this plugin. + */ + void activate() + { + fBufferPos = 0; + std::memset(fBuffer, 0, sizeof(float)*fBufferSize); + } + /** Run/process function for plugins without MIDI input. @note Some parameters might be null if there are no audio inputs or outputs. @@ -222,16 +231,14 @@ protected: */ void sampleRateChanged(double newSampleRate) override { - if (fBuffer != nullptr) - delete[] fBuffer; - - const uint32_t maxFrames = newSampleRate*6; // 6 seconds + fBufferSize = newSampleRate*6; // 6 seconds - fBuffer = new float[maxFrames]; - std::memset(fBuffer, 0, sizeof(float)*maxFrames); + delete[] fBuffer; + fBuffer = new float[fBufferSize]; + // buffer reset is done during activate() fLatencyInFrames = fLatency*newSampleRate; - fBufferPos = 0; + setLatency(fLatencyInFrames); } // ------------------------------------------------------------------------------------------------------- @@ -243,7 +250,7 @@ private: // Buffer for previous audio, size depends on sample rate float* fBuffer; - uint32_t fBufferPos; + uint32_t fBufferPos, fBufferSize; /** Set our plugin class as non-copyable and add a leak detector just in case. diff --git a/examples/Latency/Makefile b/examples/Latency/Makefile index 51e51564..9ed3adc4 100644 --- a/examples/Latency/Makefile +++ b/examples/Latency/Makefile @@ -27,6 +27,7 @@ TARGETS += ladspa TARGETS += lv2_sep TARGETS += vst2 TARGETS += vst3 +TARGETS += clap all: $(TARGETS)