clap

CLAP Audio Plugin API
Log | Files | Refs | README | LICENSE

commit 1f428a898fca85585e4d5ee183d6c143f8a86099
parent 54bcf55d56219cc1714a07f276ac4e3ccd8ae2e8
Author: Alexandre BIQUE <bique.alexandre@gmail.com>
Date:   Mon, 24 May 2021 14:02:44 +0200

Nicer way to deal with channel count

Diffstat:
Mexamples/plugins/gain/gain.cc | 64++++++++++++++++++++++++++--------------------------------------
Mexamples/plugins/gain/gain.hh | 11+++++------
Mexamples/plugins/plugin.cc | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mexamples/plugins/plugin.hh | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Minclude/clap/clap.h | 8+++-----
Minclude/clap/ext/audio-ports.h | 27+++++++++++++--------------
6 files changed, 261 insertions(+), 103 deletions(-)

diff --git a/examples/plugins/gain/gain.cc b/examples/plugins/gain/gain.cc @@ -1,3 +1,5 @@ +#include <cstring> + #include "gain.hh" const clap_plugin_descriptor *Gain::descriptor() { @@ -21,52 +23,38 @@ const clap_plugin_descriptor *Gain::descriptor() { Gain::Gain(clap_host *host) : Plugin(descriptor(), host) {} -bool Gain::init() { - updateChannelCount(false); +bool Gain::activate(int sample_rate) { + channelCount_ = trackChannelCount(); return true; } -void Gain::updateChannelCount(bool shouldNotifyHost) { - if (!canUseTrackInfo()) - return; - - clap_track_info info; - if (!hostTrackInfo_->get(host_, &info)) - return; - - if (channelCount_ == info.channel_count) - return; - - if (isActive_) - // we can't change our ports now, delay it - schedulePortUpdate_ = true; - else - channelCount_ = info.channel_count; - - if (canChangeAudioPorts()) - hostAudioPorts_->changed(host_); -} - -bool Gain::activate(int sample_rate) { return true; } - -void Gain::deactivate() { - if (schedulePortUpdate_) { - schedulePortUpdate_ = false; - updateChannelCount(true); - } -} - -void Gain::trackInfoChanged() { updateChannelCount(true); } +void Gain::deactivate() { channelCount_ = 0; } clap_process_status Gain::process(const clap_process *process) { - float *in = process->audio_inputs[0].data32i; - float *out = process->audio_outputs[0].data32i; + float **in = process->audio_inputs[0].data32; + float **out = process->audio_outputs[0].data32; float k = 1; - const auto N = process->frames_count * channelCount_; - for (int i = 0; i < N; ++i) { - out[i] = k * in[i]; + for (int i = 0; i < process->frames_count; ++i) { + for (int c = 0; c < channelCount_; ++c) + out[c][i] = k * in[c][i]; } return CLAP_PROCESS_CONTINUE_IF_NOT_QUIET; } + +void Gain::defineAudioPorts(std::vector<clap_audio_port_info> &inputPorts, + std::vector<clap_audio_port_info> &outputPorts) { + clap_audio_port_info info; + info.id = 0; + strncpy(info.name, "main", sizeof(info.name)); + info.is_main = true; + info.is_cv = false; + info.supports_64_bits = false; + info.supports_in_place = true; + info.channel_count = channelCount_; + info.channel_map = CLAP_CHMAP_UNSPECIFIED; + + inputPorts.push_back(info); + outputPorts.push_back(info); +} diff --git a/examples/plugins/gain/gain.hh b/examples/plugins/gain/gain.hh @@ -4,22 +4,21 @@ #include "../plugin.hh" -class Gain : public Plugin { +class Gain final : public Plugin { public: Gain(clap_host *host); static const clap_plugin_descriptor *descriptor(); protected: - bool init() override; bool activate(int sample_rate) override; void deactivate() override; clap_process_status process(const clap_process *process) override; - void trackInfoChanged() override; - void updateChannelCount(bool shouldNotifyHost); + void defineAudioPorts(std::vector<clap_audio_port_info> &inputPorts, + std::vector<clap_audio_port_info> &outputPorts) override; + bool shouldInvalidateAudioPortsDefinitionOnTrackChannelChange() const override { return true; } private: - int channelCount_ = 2; - bool schedulePortUpdate_ = false; + int channelCount_ = 0; }; \ No newline at end of file diff --git a/examples/plugins/plugin.cc b/examples/plugins/plugin.cc @@ -1,22 +1,22 @@ #include <cassert> +#include <cstring> #include <iostream> #include <sstream> #include <stdexcept> -#include <cstring> #include "plugin.hh" Plugin::Plugin(const clap_plugin_descriptor *desc, clap_host *host) : host_(host) { plugin_.plugin_data = this; plugin_.desc = desc; - plugin_.init = Plugin::clapPluginInit; - plugin_.destroy = Plugin::clapPluginDestroy; - plugin_.extension = Plugin::clapPluginExtension; - plugin_.process = Plugin::clapPluginProcess; - plugin_.activate = Plugin::clapPluginActivate; - plugin_.deactivate = Plugin::clapPluginDeactivate; - plugin_.start_processing = Plugin::clapPluginStartProcessing; - plugin_.stop_processing = Plugin::clapPluginStopProcessing; + plugin_.init = Plugin::clapInit; + plugin_.destroy = Plugin::clapDestroy; + plugin_.extension = nullptr; + plugin_.process = nullptr; + plugin_.activate = nullptr; + plugin_.deactivate = nullptr; + plugin_.start_processing = nullptr; + plugin_.stop_processing = nullptr; } ///////////////////// @@ -24,20 +24,30 @@ Plugin::Plugin(const clap_plugin_descriptor *desc, clap_host *host) : host_(host ///////////////////// // clap_plugin interface -bool Plugin::clapPluginInit(clap_plugin *plugin) { +bool Plugin::clapInit(clap_plugin *plugin) { auto &self = from(plugin); + + self.plugin_.extension = Plugin::clapExtension; + self.plugin_.process = Plugin::clapProcess; + self.plugin_.activate = Plugin::clapActivate; + self.plugin_.deactivate = Plugin::clapDeactivate; + self.plugin_.start_processing = Plugin::clapStartProcessing; + self.plugin_.stop_processing = Plugin::clapStopProcessing; + self.initInterfaces(); self.ensureMainThread("clap_plugin.init"); + self.initTrackInfo(); + self.defineAudioPorts(self.inputAudioPorts_, self.outputAudioPorts_); return self.init(); } -void Plugin::clapPluginDestroy(clap_plugin *plugin) { +void Plugin::clapDestroy(clap_plugin *plugin) { auto &self = from(plugin); self.ensureMainThread("clap_plugin.destroy"); delete &from(plugin); } -bool Plugin::clapPluginActivate(clap_plugin *plugin, int sample_rate) { +bool Plugin::clapActivate(clap_plugin *plugin, int sample_rate) { auto &self = from(plugin); self.ensureMainThread("clap_plugin.activate"); @@ -51,7 +61,7 @@ bool Plugin::clapPluginActivate(clap_plugin *plugin, int sample_rate) { << ". The host must deactivate the plugin first." << std::endl << "Simulating deactivation."; self.hostMisbehaving(msg.str()); - clapPluginDeactivate(plugin); + clapDeactivate(plugin); } } @@ -76,7 +86,7 @@ bool Plugin::clapPluginActivate(clap_plugin *plugin, int sample_rate) { return true; } -void Plugin::clapPluginDeactivate(clap_plugin *plugin) { +void Plugin::clapDeactivate(clap_plugin *plugin) { auto &self = from(plugin); self.ensureMainThread("clap_plugin.deactivate"); @@ -85,10 +95,13 @@ void Plugin::clapPluginDeactivate(clap_plugin *plugin) { return; } + if (self.scheduleAudioPortsUpdate_) + self.updateAudioPorts(); + self.deactivate(); } -bool Plugin::clapPluginStartProcessing(clap_plugin *plugin) { +bool Plugin::clapStartProcessing(clap_plugin *plugin) { auto &self = from(plugin); self.ensureAudioThread("clap_plugin.start_processing"); @@ -106,7 +119,7 @@ bool Plugin::clapPluginStartProcessing(clap_plugin *plugin) { return self.isProcessing_; } -void Plugin::clapPluginStopProcessing(clap_plugin *plugin) { +void Plugin::clapStopProcessing(clap_plugin *plugin) { auto &self = from(plugin); self.ensureAudioThread("clap_plugin.stop_processing"); @@ -124,8 +137,7 @@ void Plugin::clapPluginStopProcessing(clap_plugin *plugin) { self.isProcessing_ = false; } -clap_process_status Plugin::clapPluginProcess(struct clap_plugin *plugin, - const clap_process *process) { +clap_process_status Plugin::clapProcess(struct clap_plugin *plugin, const clap_process *process) { auto &self = from(plugin); self.ensureAudioThread("clap_plugin.process"); @@ -135,23 +147,114 @@ clap_process_status Plugin::clapPluginProcess(struct clap_plugin *plugin, } if (!self.isProcessing_) { - self.hostMisbehaving("Host called clap_plugin.process() without calling clap_plugin.start_processing()"); + self.hostMisbehaving( + "Host called clap_plugin.process() without calling clap_plugin.start_processing()"); return CLAP_PROCESS_ERROR; } return self.process(process); } -const void *Plugin::clapPluginExtension(struct clap_plugin *plugin, const char *id) { +const void *Plugin::clapExtension(struct clap_plugin *plugin, const char *id) { auto &self = from(plugin); self.ensureMainThread("clap_plugin.extension"); if (!strcmp(id, CLAP_EXT_RENDER)) return &self.pluginRender_; + if (!strcmp(id, CLAP_EXT_TRACK_INFO)) + return &pluginTrackInfo_; + if (!strcmp(id, CLAP_EXT_AUDIO_PORTS)) + return &pluginAudioPorts_; return from(plugin).extension(id); } +void Plugin::clapTrackInfoChanged(clap_plugin *plugin) { + auto &self = from(plugin); + self.ensureMainThread("clap_plugin_track_info.changed"); + + if (!self.canUseTrackInfo()) { + self.hostMisbehaving("Host called clap_plugin_track_info.changed() but does not provide a " + "complete clap_host_track_info interface"); + return; + } + + clap_track_info info; + if (!self.hostTrackInfo_->get(self.host_, &info)) { + self.hasTrackInfo_ = false; + self.hostMisbehaving( + "clap_host_track_info.get() failed after calling clap_plugin_track_info.changed()"); + return; + } + + const bool didChannelChange = info.channel_count != self.trackInfo_.channel_count || + info.channel_map != self.trackInfo_.channel_map; + self.trackInfo_ = info; + self.hasTrackInfo_ = true; + + if (didChannelChange && self.canChangeAudioPorts() && + self.shouldInvalidateAudioPortsDefinitionOnTrackChannelChange()) + self.invalidateAudioPortsDefinition(); + + self.trackInfoChanged(); +} + +void Plugin::invalidateAudioPortsDefinition() { + checkMainThread(); + + if (isActive()) { + scheduleAudioPortsUpdate_ = true; + hostAudioPorts_->rescan(host_, CLAP_AUDIO_PORTS_RESCAN_ALL); + return; + } + + updateAudioPorts(); + hostAudioPorts_->rescan(host_, CLAP_AUDIO_PORTS_RESCAN_ALL); +} + +void Plugin::initTrackInfo() { + checkMainThread(); + + assert(!hasTrackInfo_); + if (!canUseTrackInfo()) + return; + + hasTrackInfo_ = hostTrackInfo_->get(host_, &trackInfo_); +} + +uint32_t Plugin::clapAudioPortsCount(clap_plugin *plugin, bool is_input) { + auto &self = from(plugin); + self.ensureMainThread("clap_plugin_audio_ports.count"); + + return is_input ? self.inputAudioPorts_.size() : self.outputAudioPorts_.size(); +} + +bool Plugin::clapAudioPortsInfo(clap_plugin * plugin, + uint32_t index, + bool is_input, + clap_audio_port_info *info) { + auto &self = from(plugin); + self.ensureMainThread("clap_plugin_audio_ports.info"); + auto count = clapAudioPortsCount(plugin, is_input); + if (index >= count) { + std::ostringstream msg; + msg << "Host called clap_plugin_audio_ports.info() with an index out of bounds: " << index + << " >= " << count; + self.hostMisbehaving(msg.str()); + return false; + } + + *info = is_input ? self.inputAudioPorts_[index] : self.outputAudioPorts_[index]; + return true; +} + +void Plugin::updateAudioPorts() { + scheduleAudioPortsUpdate_ = false; + inputAudioPorts_.clear(); + outputAudioPorts_.clear(); + defineAudioPorts(inputAudioPorts_, outputAudioPorts_); +} + ///////////// // Logging // ///////////// @@ -178,6 +281,14 @@ bool Plugin::canUseThreadCheck() const noexcept { // Thread Checking // ///////////////////// +void Plugin::checkMainThread() { + if (!hostThreadCheck_ || !hostThreadCheck_->is_main_thread || + hostThreadCheck_->is_main_thread(host_)) + return; + + std::terminate(); +} + void Plugin::ensureMainThread(const char *method) { if (!hostThreadCheck_ || !hostThreadCheck_->is_main_thread || hostThreadCheck_->is_main_thread(host_)) @@ -244,4 +355,11 @@ void Plugin::initInterfaces() { initInterface(hostTrackInfo_, CLAP_EXT_TRACK_INFO); initInterface(hostState_, CLAP_EXT_STATE); initInterface(hostNoteName_, CLAP_EXT_NOTE_NAME); +} + +int Plugin::sampleRate() const noexcept { + assert(isActive_ && + "there is no point in querying the sample rate if the plugin isn't activated"); + assert(isActive_ ? sampleRate_ > 0 : sampleRate_ == 0); + return sampleRate_; } \ No newline at end of file diff --git a/examples/plugins/plugin.hh b/examples/plugins/plugin.hh @@ -1,7 +1,9 @@ #pragma once -#include <string_view> +#include <cassert> #include <string> +#include <string_view> +#include <vector> #include <clap/all.h> @@ -13,11 +15,16 @@ protected: Plugin(const clap_plugin_descriptor *desc, clap_host *host); virtual ~Plugin() = default; + // not copyable, not moveable Plugin(const Plugin &) = delete; Plugin(Plugin &&) = delete; Plugin &operator=(const Plugin &) = delete; Plugin &operator=(Plugin &&) = delete; + ///////////////////////// + // Methods to override // + ///////////////////////// + virtual bool init() { return true; } virtual bool activate(int sample_rate) { return true; } virtual void deactivate() {} @@ -26,29 +33,19 @@ protected: virtual clap_process_status process(const clap_process *process) { return CLAP_PROCESS_SLEEP; } virtual const void * extension(const char *id) { return nullptr; } - virtual void trackInfoChanged() {} + virtual void defineAudioPorts(std::vector<clap_audio_port_info> &inputPorts, + std::vector<clap_audio_port_info> &outputPorts) {} + void invalidateAudioPortsDefinition(); + virtual bool shouldInvalidateAudioPortsDefinitionOnTrackChannelChange() const { return false; } - ///////////////////// - // CLAP Interfaces // - ///////////////////// - - // clap_plugin interface - static bool clapPluginInit(clap_plugin *plugin); - static void clapPluginDestroy(clap_plugin *plugin); - static bool clapPluginActivate(clap_plugin *plugin, int sample_rate); - static void clapPluginDeactivate(clap_plugin *plugin); - static bool clapPluginStartProcessing(clap_plugin *plugin); - static void clapPluginStopProcessing(clap_plugin *plugin); - static clap_process_status clapPluginProcess(struct clap_plugin *plugin, - const clap_process *process); - static const void * clapPluginExtension(struct clap_plugin *plugin, const char *id); + virtual void trackInfoChanged() {} ///////////// // Logging // ///////////// void log(clap_log_severity severity, const char *msg) const; void hostMisbehaving(const char *msg); - void hostMisbehaving(const std::string& msg) { hostMisbehaving(msg.c_str()); } + void hostMisbehaving(const std::string &msg) { hostMisbehaving(msg.c_str()); } ///////////////////////////////// // Interface consistency check // @@ -61,6 +58,7 @@ protected: ///////////////////// // Thread Checking // ///////////////////// + void checkMainThread(); void ensureMainThread(const char *method); void ensureAudioThread(const char *method); @@ -73,14 +71,33 @@ protected: void initInterface(const T *&ptr, const char *id); void initInterfaces(); + ////////////////////// + // Processing State // + ////////////////////// + bool isActive() const noexcept { return isActive_; } + bool isProcessing() const noexcept { return isProcessing_; } + int sampleRate() const noexcept; + + ////////////////////// + // Cached Host Info // + ////////////////////// + bool hasTrackInfo() const noexcept { return hasTrackInfo_; } + const clap_track_info &trackInfo() const noexcept { + assert(hasTrackInfo_); + return trackInfo_; + } + uint32_t trackChannelCount() const noexcept { + return hasTrackInfo_ ? trackInfo_.channel_count : 2; + } + clap_chmap trackChannelMap() const noexcept { + return hasTrackInfo_ ? trackInfo_.channel_map : CLAP_CHMAP_STEREO; + } + protected: - clap_plugin plugin_; - clap_plugin_audio_ports pluginAudioPorts_; clap_plugin_event_filter pluginEventFilter_; clap_plugin_latency pluginLatency_; clap_plugin_params pluginParams_; clap_plugin_render pluginRender_; - clap_plugin_track_info pluginTrackInfo_; clap_plugin_note_name pluginNoteName_; clap_plugin_thread_pool pluginThreadPool_; @@ -111,8 +128,47 @@ protected: const clap_host_state * hostState_ = nullptr; const clap_host_note_name * hostNoteName_ = nullptr; +private: + ///////////////////// + // CLAP Interfaces // + ///////////////////// + + clap_plugin plugin_; + // clap_plugin + static bool clapInit(clap_plugin *plugin); + static void clapDestroy(clap_plugin *plugin); + static bool clapActivate(clap_plugin *plugin, int sample_rate); + static void clapDeactivate(clap_plugin *plugin); + static bool clapStartProcessing(clap_plugin *plugin); + static void clapStopProcessing(clap_plugin *plugin); + static clap_process_status clapProcess(struct clap_plugin *plugin, const clap_process *process); + static const void * clapExtension(struct clap_plugin *plugin, const char *id); + + // clap_plugin_track_info + static void clapTrackInfoChanged(clap_plugin *plugin); + void initTrackInfo(); + + // clap_plugin_audio_ports + static uint32_t clapAudioPortsCount(clap_plugin *plugin, bool is_input); + static bool clapAudioPortsInfo(clap_plugin * plugin, + uint32_t index, + bool is_input, + clap_audio_port_info *info); + void updateAudioPorts(); + + static const constexpr clap_plugin_track_info pluginTrackInfo_ = {clapTrackInfoChanged}; + static const constexpr clap_plugin_audio_ports pluginAudioPorts_ = {clapAudioPortsCount, + clapAudioPortsInfo}; + // state bool isActive_ = false; bool isProcessing_ = false; int sampleRate_ = 0; + + bool hasTrackInfo_ = false; + clap_track_info trackInfo_; + + bool scheduleAudioPortsUpdate_ = false; + std::vector<clap_audio_port_info> inputAudioPorts_; + std::vector<clap_audio_port_info> outputAudioPorts_; }; \ No newline at end of file diff --git a/include/clap/clap.h b/include/clap/clap.h @@ -60,11 +60,9 @@ enum { typedef int32_t clap_process_status; typedef struct clap_audio_buffer { - // Only one of dataXXX pointer will be set. - float ** data32; // non-interleaved - float * data32i; // interleaved - double **data64; // non-interleaved - double * data64i; // interleaved + // Either data32 or data64 pointer will be set. + float ** data32; + double **data64; int32_t channel_count; uint32_t latency; // latency from/to the audio interface uint64_t constant_mask; // mask & (1 << N) to test if channel N is constant diff --git a/include/clap/ext/audio-ports.h b/include/clap/ext/audio-ports.h @@ -20,33 +20,32 @@ typedef struct clap_audio_port_info { // and output, only for main input to main output int32_t channel_count; clap_chmap channel_map; - bool interleave; // selects interleaved buffer } clap_audio_port_info; // The audio ports scan has to be done while the plugin is deactivated. typedef struct clap_plugin_audio_ports { // number of ports, for either input or output // [main-thread] - int32_t (*get_count)(clap_plugin *plugin, bool is_input); + uint32_t (*count)(clap_plugin *plugin, bool is_input); // get info about about an audio port. // [main-thread] - void (*get_info)(clap_plugin *plugin, int32_t index, bool is_input, clap_audio_port_info *info); - - void (*set_active)( - clap_plugin *plugin, int32_t index, bool is_input, bool use_64, bool is_active); + bool (*get_info)(clap_plugin *plugin, uint32_t index, bool is_input, clap_audio_port_info *info); } clap_plugin_audio_ports; -typedef struct clap_host_audio_ports { - // Tell the host that the plugin ports has changed. - // The host shall deactivate the plugin and then scan the ports again. - // [main-thread] - void (*changed)(clap_host *host); +enum { + // The ports name did change, the host can scan them right away. + CLAP_AUDIO_PORTS_RESCAN_NAMES = 1 << 0, - // Tell the host that the plugin ports name have changed. - // It is not necessary to deactivates the plugin. + // The ports have changed, the host shall deactivate the plugin + // and perform a full scan of the ports. + CLAP_AUDIO_PORTS_RESCAN_ALL = 1 << 1, +}; + +typedef struct clap_host_audio_ports { + // Rescan the full list of audio ports according to the flags. // [main-thread] - void (*name_changed)(clap_host *host); + void (*rescan)(clap_host *host, uint32_t flags); } clap_host_audio_ports; #ifdef __cplusplus