DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

commit 04d86c71962b8f21fd96b0d1f119d62e58715165
parent 23692d024e57c617bd0692beb1835d48f98eb914
Author: falkTX <falktx@falktx.com>
Date:   Mon,  5 Sep 2022 18:12:13 +0100

Start implementing CLAP

Diffstat:
MMakefile.plugins.mk | 4+++-
Mdistrho/DistrhoPluginMain.cpp | 2+-
Adistrho/src/DistrhoPluginCLAP.cpp | 515+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/audio-buffer.h | 37+++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/entry.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/events.h | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/ext/audio-ports.h | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/fixedpoint.h | 16++++++++++++++++
Adistrho/src/clap/host.h | 41+++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/id.h | 8++++++++
Adistrho/src/clap/plugin-factory.h | 39+++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/plugin-features.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/plugin.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/private/macros.h | 36++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/private/std.h | 16++++++++++++++++
Adistrho/src/clap/process.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/string-sizes.h | 21+++++++++++++++++++++
Adistrho/src/clap/version.h | 34++++++++++++++++++++++++++++++++++
Mutils/symbols/clap.def | 1+
Mutils/symbols/clap.exp | 1+
Mutils/symbols/clap.version | 2+-
21 files changed, 1474 insertions(+), 3 deletions(-)

diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk @@ -309,7 +309,7 @@ SYMBOLS_LV2UI = -sEXPORTED_FUNCTIONS="['lv2ui_descriptor']" SYMBOLS_LV2 = -sEXPORTED_FUNCTIONS="['lv2_descriptor','lv2_generate_ttl','lv2ui_descriptor']" SYMBOLS_VST2 = -sEXPORTED_FUNCTIONS="['VSTPluginMain']" SYMBOLS_VST3 = -sEXPORTED_FUNCTIONS="['GetPluginFactory','ModuleEntry','ModuleExit']" -SYMBOLS_CLAP = -sEXPORTED_FUNCTIONS="['']" +SYMBOLS_CLAP = -sEXPORTED_FUNCTIONS="['clap_entry']" SYMBOLS_SHARED = -sEXPORTED_FUNCTIONS="['createSharedPlugin']" else ifeq ($(WINDOWS),true) SYMBOLS_LADSPA = $(DPF_PATH)/utils/symbols/ladspa.def @@ -605,6 +605,7 @@ endif -include $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.d +-include $(BUILD_DIR)/DistrhoPluginMain_CLAP.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.d @@ -613,6 +614,7 @@ endif -include $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_VST2.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.d +-include $(BUILD_DIR)/DistrhoUIMain_CLAP.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_SHARED.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_STATIC.cpp.d diff --git a/distrho/DistrhoPluginMain.cpp b/distrho/DistrhoPluginMain.cpp @@ -19,7 +19,7 @@ #if defined(DISTRHO_PLUGIN_TARGET_CARLA) # include "src/DistrhoPluginCarla.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_CLAP) -# include "src/DistrhoPluginStub.cpp" +# include "src/DistrhoPluginCLAP.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_JACK) # include "src/DistrhoPluginJACK.cpp" #elif (defined(DISTRHO_PLUGIN_TARGET_LADSPA) || defined(DISTRHO_PLUGIN_TARGET_DSSI)) diff --git a/distrho/src/DistrhoPluginCLAP.cpp b/distrho/src/DistrhoPluginCLAP.cpp @@ -0,0 +1,515 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * 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 + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "DistrhoPluginInfo.h" +#include "DistrhoPluginInternal.hpp" +#include "extra/ScopedPointer.hpp" + +#include "clap/entry.h" +#include "clap/plugin-factory.h" +#include "clap/ext/audio-ports.h" +#include "src/DistrhoDefines.h" +#include "src/clap/version.h" + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT +static constexpr const writeMidiFunc writeMidiCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_STATE +static const updateStateValueFunc updateStateValueCallback = nullptr; +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * CLAP plugin class. + */ +class PluginCLAP +{ +public: + PluginCLAP(const clap_host_t* const host) + : fPlugin(this, + writeMidiCallback, + requestParameterValueChangeCallback, + updateStateValueCallback), + fHost(host), + fOutputEvents(nullptr) + { + } + + bool init() + { + if (!clap_version_is_compatible(fHost->clap_version)) + return false; + + // TODO check host features + return true; + } + + void activate(const double sampleRate, const uint32_t maxFramesCount) + { + fPlugin.setSampleRate(sampleRate, true); + fPlugin.setBufferSize(maxFramesCount, true); + fPlugin.activate(); + } + + void deactivate() + { + fPlugin.deactivate(); + } + + bool process(const clap_process_t* const process) + { + #if DISTRHO_PLUGIN_WANT_TIMEPOS + if (const clap_event_transport_t* const transport = process->transport) + { + fTimePosition.playing = transport->flags & CLAP_TRANSPORT_IS_PLAYING; + + fTimePosition.frame = process->steady_time >= 0 ? process->steady_time : 0; + + if (transport->flags & CLAP_TRANSPORT_HAS_TEMPO) + fTimePosition.bbt.beatsPerMinute = transport->tempo; + else + fTimePosition.bbt.beatsPerMinute = 120.0; + + // ticksPerBeat is not possible with CLAP + fTimePosition.bbt.ticksPerBeat = 1920.0; + + // TODO verify if this works or makes any sense + if ((transport->flags & (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) == (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) + { + const double ppqPos = std::abs(transport->song_pos_beats); + const int ppqPerBar = transport->tsig_num * 4 / transport->tsig_denom; + const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * transport->tsig_num; + const double rest = std::fmod(barBeats, 1.0); + + fTimePosition.bbt.valid = true; + fTimePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1; + fTimePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1; + fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat; + fTimePosition.bbt.beatsPerBar = transport->tsig_num; + fTimePosition.bbt.beatType = transport->tsig_denom; + + if (transport->song_pos_beats < 0.0) + { + --fTimePosition.bbt.bar; + fTimePosition.bbt.beat = transport->tsig_num - fTimePosition.bbt.beat + 1; + fTimePosition.bbt.tick = fTimePosition.bbt.ticksPerBeat - fTimePosition.bbt.tick - 1; + } + } + else + { + fTimePosition.bbt.valid = false; + fTimePosition.bbt.bar = 1; + fTimePosition.bbt.beat = 1; + fTimePosition.bbt.tick = 0.0; + fTimePosition.bbt.beatsPerBar = 4.0f; + fTimePosition.bbt.beatType = 4.0f; + } + + fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* + fTimePosition.bbt.beatsPerBar* + (fTimePosition.bbt.bar-1); + } + else + { + fTimePosition.playing = false; + fTimePosition.frame = 0; + fTimePosition.bbt.valid = false; + fTimePosition.bbt.beatsPerMinute = 120.0; + fTimePosition.bbt.bar = 1; + fTimePosition.bbt.beat = 1; + fTimePosition.bbt.tick = 0.0; + fTimePosition.bbt.beatsPerBar = 4.f; + fTimePosition.bbt.beatType = 4.f; + fTimePosition.bbt.barStartTick = 0; + } + + fPlugin.setTimePosition(fTimePosition); + #endif + + if (const clap_input_events_t* const inputEvents = process->in_events) + { + if (const uint32_t len = inputEvents->size(inputEvents)) + { + for (uint32_t i=0; i<len; ++i) + { + const clap_event_header_t* const event = inputEvents->get(inputEvents, i); + + // event->time + switch (event->type) + { + case CLAP_EVENT_NOTE_ON: + case CLAP_EVENT_NOTE_OFF: + case CLAP_EVENT_NOTE_CHOKE: + case CLAP_EVENT_NOTE_END: + case CLAP_EVENT_NOTE_EXPRESSION: + case CLAP_EVENT_PARAM_VALUE: + case CLAP_EVENT_PARAM_MOD: + case CLAP_EVENT_PARAM_GESTURE_BEGIN: + case CLAP_EVENT_PARAM_GESTURE_END: + case CLAP_EVENT_TRANSPORT: + case CLAP_EVENT_MIDI: + case CLAP_EVENT_MIDI_SYSEX: + case CLAP_EVENT_MIDI2: + break; + } + } + } + } + + if (const uint32_t frames = process->frames_count) + { + DISTRHO_SAFE_ASSERT_UINT2_RETURN(process->audio_inputs_count == DISTRHO_PLUGIN_NUM_INPUTS, + process->audio_inputs_count, DISTRHO_PLUGIN_NUM_INPUTS, false); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(process->audio_outputs_count == DISTRHO_PLUGIN_NUM_OUTPUTS, + process->audio_outputs_count, DISTRHO_PLUGIN_NUM_OUTPUTS, false); + + // TODO multi-port bus stuff + const float** inputs = process->audio_inputs != nullptr + ? const_cast<const float**>(process->audio_inputs->data32) + : nullptr; + /**/ float** outputs = process->audio_outputs != nullptr + ? process->audio_inputs->data32 + : nullptr; + + fOutputEvents = process->out_events; + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + fPlugin.run(inputs, outputs, frames, fMidiEvents, midiEventCount); + #else + fPlugin.run(inputs, outputs, frames); + #endif + + fOutputEvents = nullptr; + } + + return true; + } + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // Plugin + PluginExporter fPlugin; + + // CLAP stuff + const clap_host_t* const fHost; + const clap_output_events_t* fOutputEvents; + #if DISTRHO_PLUGIN_WANT_TIMEPOS + TimePosition fTimePosition; + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + bool writeMidi(const MidiEvent&) + { + return true; + } + + static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent) + { + return ((PluginStub*)ptr)->writeMidi(midiEvent); + } + #endif + + #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + bool requestParameterValueChange(uint32_t, float) + { + return true; + } + + static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + { + return ((PluginStub*)ptr)->requestParameterValueChange(index, value); + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + bool updateState(const char*, const char*) + { + return true; + } + + static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value) + { + return ((PluginStub*)ptr)->updateState(key, value); + } + #endif +}; + +// -------------------------------------------------------------------------------------------------------------------- + +static ScopedPointer<PluginExporter> sPlugin; + +// -------------------------------------------------------------------------------------------------------------------- +// plugin audio ports + +static uint32_t clap_plugin_audio_ports_count(const clap_plugin_t*, const bool is_input) +{ + return is_input ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; +} + +static bool clap_plugin_audio_ports_get(const clap_plugin_t*, + const uint32_t index, + const bool is_input, + clap_audio_port_info_t* const info) +{ + const uint32_t maxPortCount = is_input ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < maxPortCount, index, maxPortCount, false); + + AudioPortWithBusId& audioPort(sPlugin->getAudioPort(is_input, index)); + + info->id = index; + std::strncpy(info->name, audioPort.name, CLAP_NAME_SIZE-1); + + // TODO bus stuff + info->flags = CLAP_AUDIO_PORT_IS_MAIN; + info->channel_count = maxPortCount; + + // TODO CV + // info->port_type = audioPort.hints & kAudioPortIsCV ? CLAP_PORT_CV : nullptr; + info->port_type = nullptr; + + info->in_place_pair = index < (is_input ? DISTRHO_PLUGIN_NUM_OUTPUTS : DISTRHO_PLUGIN_NUM_INPUTS) + ? index + : CLAP_INVALID_ID; + + return true; +} + +static const clap_plugin_audio_ports_t clap_plugin_audio_ports = { + clap_plugin_audio_ports_count, + clap_plugin_audio_ports_get +}; + +// -------------------------------------------------------------------------------------------------------------------- +// plugin + +static bool clap_plugin_init(const clap_plugin_t* const plugin) +{ + PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); + return instance->init(); +} + +static void clap_plugin_destroy(const clap_plugin_t* const plugin) +{ + delete static_cast<PluginCLAP*>(plugin->plugin_data); + std::free(const_cast<clap_plugin_t*>(plugin)); +} + +static 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; + + PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); + instance->activate(sample_rate, max_frames_count); + return true; +} + +static void clap_plugin_deactivate(const clap_plugin_t* const plugin) +{ + PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); + instance->deactivate(); +} + +static bool clap_plugin_start_processing(const clap_plugin_t*) +{ + // nothing to do + return true; +} + +static void clap_plugin_stop_processing(const clap_plugin_t*) +{ + // nothing to do +} + +static 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) +{ + PluginCLAP* const instance = static_cast<PluginCLAP*>(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) +{ + if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0) + return &clap_plugin_audio_ports; + return nullptr; +} + +static void clap_plugin_on_main_thread(const clap_plugin_t*) +{ + // nothing to do +} + +// -------------------------------------------------------------------------------------------------------------------- +// plugin factory + +static uint32_t clap_get_plugin_count(const clap_plugin_factory_t*) +{ + return 1; +} + +static const clap_plugin_descriptor_t* clap_get_plugin_descriptor(const clap_plugin_factory_t*, const uint32_t index) +{ + DISTRHO_SAFE_ASSERT_UINT_RETURN(index == 0, index, nullptr); + + static const char* features[] = { + #ifdef DISTRHO_PLUGIN_CLAP_FEATURES + DISTRHO_PLUGIN_CLAP_FEATURES, + #elif DISTRHO_PLUGIN_IS_SYNTH + "instrument", + #endif + nullptr + }; + + static const clap_plugin_descriptor_t descriptor = { + CLAP_VERSION, + sPlugin->getLabel(), + sPlugin->getName(), + sPlugin->getMaker(), + // TODO url + "", + // TODO manual url + "", + // TODO support url + "", + // TODO version string + "", + sPlugin->getDescription(), + features + }; + + return &descriptor; +} + +static const clap_plugin_t* clap_create_plugin(const clap_plugin_factory_t* const factory, + const clap_host_t* const host, + const char*) +{ + clap_plugin_t* const pluginptr = static_cast<clap_plugin_t*>(std::malloc(sizeof(clap_plugin_t))); + DISTRHO_SAFE_ASSERT_RETURN(pluginptr != nullptr, nullptr); + + // default early values + if (d_nextBufferSize == 0) + d_nextBufferSize = 1024; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; + + d_nextCanRequestParameterValueChanges = true; + + const clap_plugin_t plugin = { + clap_get_plugin_descriptor(factory, 0), + new PluginCLAP(host), + clap_plugin_init, + clap_plugin_destroy, + clap_plugin_activate, + clap_plugin_deactivate, + clap_plugin_start_processing, + clap_plugin_stop_processing, + clap_plugin_reset, + clap_plugin_process, + clap_plugin_get_extension, + clap_plugin_on_main_thread + }; + + std::memcpy(pluginptr, &plugin, sizeof(clap_plugin_t)); + return pluginptr; +} + +static const clap_plugin_factory_t clap_plugin_factory = { + clap_get_plugin_count, + clap_get_plugin_descriptor, + clap_create_plugin +}; + +// -------------------------------------------------------------------------------------------------------------------- +// plugin entry + +static bool clap_plugin_entry_init(const char* const plugin_path) +{ + static String bundlePath; + bundlePath = plugin_path; + d_nextBundlePath = bundlePath.buffer(); + + // init dummy plugin + if (sPlugin == nullptr) + { + // set valid but dummy values + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + d_nextCanRequestParameterValueChanges = true; + + // Create dummy plugin to get data from + sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr); + + // unset + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; + d_nextCanRequestParameterValueChanges = false; + } + + return true; +} + +static void clap_plugin_entry_deinit(void) +{ + sPlugin = nullptr; +} + +static const void* clap_plugin_entry_get_factory(const char* const factory_id) +{ + if (std::strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) == 0) + return &clap_plugin_factory; + return nullptr; +} + +static const clap_plugin_entry_t clap_plugin_entry = { + CLAP_VERSION, + clap_plugin_entry_init, + clap_plugin_entry_deinit, + clap_plugin_entry_get_factory +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +DISTRHO_PLUGIN_EXPORT +const clap_plugin_entry_t clap_entry = DISTRHO_NAMESPACE::clap_plugin_entry; + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/clap/audio-buffer.h b/distrho/src/clap/audio-buffer.h @@ -0,0 +1,37 @@ +#pragma once + +#include "private/std.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Sample code for reading a stereo buffer: +// +// bool isLeftConstant = (buffer->constant_mask & (1 << 0)) != 0; +// bool isRightConstant = (buffer->constant_mask & (1 << 1)) != 0; +// +// for (int i = 0; i < N; ++i) { +// float l = data32[0][isLeftConstant ? 0 : i]; +// float r = data32[1][isRightConstant ? 0 : i]; +// } +// +// Note: checking the constant mask is optional, and this implies that +// the buffer must be filled with the constant value. +// Rationale: if a buffer reader doesn't check the constant mask, then it may +// process garbage samples and in result, garbage samples may be transmitted +// to the audio interface with all the bad consequences it can have. +// +// The constant mask is a hint. +typedef struct clap_audio_buffer { + // Either data32 or data64 pointer will be set. + float **data32; + double **data64; + uint32_t channel_count; + uint32_t latency; // latency from/to the audio interface + uint64_t constant_mask; +} clap_audio_buffer_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/entry.h b/distrho/src/clap/entry.h @@ -0,0 +1,68 @@ +#pragma once + +#include "version.h" +#include "private/macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// This interface is the entry point of the dynamic library. +// +// CLAP plugins standard search path: +// +// Linux +// - ~/.clap +// - /usr/lib/clap +// +// Windows +// - %CommonFilesFolder%/CLAP/ +// - %LOCALAPPDATA%/Programs/Common/CLAP/ +// +// MacOS +// - /Library/Audio/Plug-Ins/CLAP +// - ~/Library/Audio/Plug-Ins/CLAP +// +// In addition to the OS-specific default locations above, a CLAP host must query the environment +// for a CLAP_PATH variable, which is a list of directories formatted in the same manner as the host +// OS binary search path (PATH on Unix, separated by `:` and Path on Windows, separated by ';', as +// of this writing). +// +// Each directory should be recursively searched for files and/or bundles as appropriate in your OS +// ending with the extension `.clap`. +// +// Every method must be thread-safe. +typedef struct clap_plugin_entry { + clap_version_t clap_version; // initialized to CLAP_VERSION + + // This function must be called first, and can only be called once. + // + // It should be as fast as possible, in order to perform a very quick scan of the plugin + // descriptors. + // + // It is forbidden to display graphical user interface in this call. + // It is forbidden to perform user interaction in this call. + // + // If the initialization depends upon expensive computation, maybe try to do them ahead of time + // and cache the result. + // + // If init() returns false, then the host must not call deinit() nor any other clap + // related symbols from the DSO. + bool (*init)(const char *plugin_path); + + // No more calls into the DSO must be made after calling deinit(). + void (*deinit)(void); + + // Get the pointer to a factory. See plugin-factory.h for an example. + // + // Returns null if the factory is not provided. + // The returned pointer must *not* be freed by the caller. + const void *(*get_factory)(const char *factory_id); +} clap_plugin_entry_t; + +/* Entry point */ +CLAP_EXPORT extern const clap_plugin_entry_t clap_entry; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/events.h b/distrho/src/clap/events.h @@ -0,0 +1,283 @@ +#pragma once + +#include "private/std.h" +#include "fixedpoint.h" +#include "id.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// event header +// must be the first attribute of the event +typedef struct clap_event_header { + uint32_t size; // event size including this header, eg: sizeof (clap_event_note) + uint32_t time; // sample offset within the buffer for this event + uint16_t space_id; // event space, see clap_host_event_registry + uint16_t type; // event type + uint32_t flags; // see clap_event_flags +} clap_event_header_t; + +// The clap core event space +static const CLAP_CONSTEXPR uint16_t CLAP_CORE_EVENT_SPACE_ID = 0; + +enum clap_event_flags { + // Indicate a live user event, for example a user turning a physical knob + // or playing a physical key. + CLAP_EVENT_IS_LIVE = 1 << 0, + + // Indicate that the event should not be recorded. + // For example this is useful when a parameter changes because of a MIDI CC, + // because if the host records both the MIDI CC automation and the parameter + // automation there will be a conflict. + CLAP_EVENT_DONT_RECORD = 1 << 1, +}; + +// Some of the following events overlap, a note on can be expressed with: +// - CLAP_EVENT_NOTE_ON +// - CLAP_EVENT_MIDI +// - CLAP_EVENT_MIDI2 +// +// The preferred way of sending a note event is to use CLAP_EVENT_NOTE_*. +// +// The same event must not be sent twice: it is forbidden to send a the same note on +// encoded with both CLAP_EVENT_NOTE_ON and CLAP_EVENT_MIDI. +// +// The plugins are encouraged to be able to handle note events encoded as raw midi or midi2, +// or implement clap_plugin_event_filter and reject raw midi and midi2 events. +enum { + // NOTE_ON and NOTE_OFF represent a key pressed and key released event, respectively. + // A NOTE_ON with a velocity of 0 is valid and should not be interpreted as a NOTE_OFF. + // + // NOTE_CHOKE is meant to choke the voice(s), like in a drum machine when a closed hihat + // chokes an open hihat. This event can be sent by the host to the plugin. Here are two use cases: + // - a plugin is inside a drum pad in Bitwig Studio's drum machine, and this pad is choked by + // another one + // - the user double clicks the DAW's stop button in the transport which then stops the sound on + // every tracks + // + // NOTE_END is sent by the plugin to the host. The port, channel, key and note_id are those given + // by the host in the NOTE_ON event. In other words, this event is matched against the + // plugin's note input port. + // NOTE_END is useful to help the host to match the plugin's voice life time. + // + // When using polyphonic modulations, the host has to allocate and release voices for its + // polyphonic modulator. Yet only the plugin effectively knows when the host should terminate + // a voice. NOTE_END solves that issue in a non-intrusive and cooperative way. + // + // CLAP assumes that the host will allocate a unique voice on NOTE_ON event for a given port, + // channel and key. This voice will run until the plugin will instruct the host to terminate + // it by sending a NOTE_END event. + // + // Consider the following sequence: + // - process() + // Host->Plugin NoteOn(port:0, channel:0, key:16, time:t0) + // Host->Plugin NoteOn(port:0, channel:0, key:64, time:t0) + // Host->Plugin NoteOff(port:0, channel:0, key:16, t1) + // Host->Plugin NoteOff(port:0, channel:0, key:64, t1) + // # on t2, both notes did terminate + // Host->Plugin NoteOn(port:0, channel:0, key:64, t3) + // # Here the plugin finished processing all the frames and will tell the host + // # to terminate the voice on key 16 but not 64, because a note has been started at t3 + // Plugin->Host NoteEnd(port:0, channel:0, key:16, time:ignored) + // + // These four events use clap_event_note. + CLAP_EVENT_NOTE_ON, + CLAP_EVENT_NOTE_OFF, + CLAP_EVENT_NOTE_CHOKE, + CLAP_EVENT_NOTE_END, + + // Represents a note expression. + // Uses clap_event_note_expression. + CLAP_EVENT_NOTE_EXPRESSION, + + // PARAM_VALUE sets the parameter's value; uses clap_event_param_value. + // PARAM_MOD sets the parameter's modulation amount; uses clap_event_param_mod. + // + // The value heard is: param_value + param_mod. + // + // In case of a concurrent global value/modulation versus a polyphonic one, + // the voice should only use the polyphonic one and the polyphonic modulation + // amount will already include the monophonic signal. + CLAP_EVENT_PARAM_VALUE, + CLAP_EVENT_PARAM_MOD, + + // Indicates that the user started or finished adjusting a knob. + // This is not mandatory to wrap parameter changes with gesture events, but this improves + // the user experience a lot when recording automation or overriding automation playback. + // Uses clap_event_param_gesture. + CLAP_EVENT_PARAM_GESTURE_BEGIN, + CLAP_EVENT_PARAM_GESTURE_END, + + CLAP_EVENT_TRANSPORT, // update the transport info; clap_event_transport + CLAP_EVENT_MIDI, // raw midi event; clap_event_midi + CLAP_EVENT_MIDI_SYSEX, // raw midi sysex event; clap_event_midi_sysex + CLAP_EVENT_MIDI2, // raw midi 2 event; clap_event_midi2 +}; + +// Note on, off, end and choke events. +// In the case of note choke or end events: +// - the velocity is ignored. +// - key and channel are used to match active notes, a value of -1 matches all. +typedef struct clap_event_note { + clap_event_header_t header; + + int32_t note_id; // -1 if unspecified, otherwise >=0 + int16_t port_index; + int16_t channel; // 0..15 + int16_t key; // 0..127 + double velocity; // 0..1 +} clap_event_note_t; + +enum { + // with 0 < x <= 4, plain = 20 * log(x) + CLAP_NOTE_EXPRESSION_VOLUME, + + // pan, 0 left, 0.5 center, 1 right + CLAP_NOTE_EXPRESSION_PAN, + + // relative tuning in semitone, from -120 to +120 + CLAP_NOTE_EXPRESSION_TUNING, + + // 0..1 + CLAP_NOTE_EXPRESSION_VIBRATO, + CLAP_NOTE_EXPRESSION_EXPRESSION, + CLAP_NOTE_EXPRESSION_BRIGHTNESS, + CLAP_NOTE_EXPRESSION_PRESSURE, +}; +typedef int32_t clap_note_expression; + +typedef struct clap_event_note_expression { + clap_event_header_t header; + + clap_note_expression expression_id; + + // target a specific note_id, port, key and channel, -1 for global + int32_t note_id; + int16_t port_index; + int16_t channel; + int16_t key; + + double value; // see expression for the range +} clap_event_note_expression_t; + +typedef struct clap_event_param_value { + clap_event_header_t header; + + // target parameter + clap_id param_id; // @ref clap_param_info.id + void *cookie; // @ref clap_param_info.cookie + + // target a specific note_id, port, key and channel, -1 for global + int32_t note_id; + int16_t port_index; + int16_t channel; + int16_t key; + + double value; +} clap_event_param_value_t; + +typedef struct clap_event_param_mod { + clap_event_header_t header; + + // target parameter + clap_id param_id; // @ref clap_param_info.id + void *cookie; // @ref clap_param_info.cookie + + // target a specific note_id, port, key and channel, -1 for global + int32_t note_id; + int16_t port_index; + int16_t channel; + int16_t key; + + double amount; // modulation amount +} clap_event_param_mod_t; + +typedef struct clap_event_param_gesture { + clap_event_header_t header; + + // target parameter + clap_id param_id; // @ref clap_param_info.id +} clap_event_param_gesture_t; + +enum clap_transport_flags { + CLAP_TRANSPORT_HAS_TEMPO = 1 << 0, + CLAP_TRANSPORT_HAS_BEATS_TIMELINE = 1 << 1, + CLAP_TRANSPORT_HAS_SECONDS_TIMELINE = 1 << 2, + CLAP_TRANSPORT_HAS_TIME_SIGNATURE = 1 << 3, + CLAP_TRANSPORT_IS_PLAYING = 1 << 4, + CLAP_TRANSPORT_IS_RECORDING = 1 << 5, + CLAP_TRANSPORT_IS_LOOP_ACTIVE = 1 << 6, + CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL = 1 << 7, +}; + +typedef struct clap_event_transport { + clap_event_header_t header; + + uint32_t flags; // see clap_transport_flags + + clap_beattime song_pos_beats; // position in beats + clap_sectime song_pos_seconds; // position in seconds + + double tempo; // in bpm + double tempo_inc; // tempo increment for each samples and until the next + // time info event + + clap_beattime loop_start_beats; + clap_beattime loop_end_beats; + clap_sectime loop_start_seconds; + clap_sectime loop_end_seconds; + + clap_beattime bar_start; // start pos of the current bar + int32_t bar_number; // bar at song pos 0 has the number 0 + + uint16_t tsig_num; // time signature numerator + uint16_t tsig_denom; // time signature denominator +} clap_event_transport_t; + +typedef struct clap_event_midi { + clap_event_header_t header; + + uint16_t port_index; + uint8_t data[3]; +} clap_event_midi_t; + +typedef struct clap_event_midi_sysex { + clap_event_header_t header; + + uint16_t port_index; + const uint8_t *buffer; // midi buffer + uint32_t size; +} clap_event_midi_sysex_t; + +// While it is possible to use a series of midi2 event to send a sysex, +// prefer clap_event_midi_sysex if possible for efficiency. +typedef struct clap_event_midi2 { + clap_event_header_t header; + + uint16_t port_index; + uint32_t data[4]; +} clap_event_midi2_t; + +// Input event list, events must be sorted by time. +typedef struct clap_input_events { + void *ctx; // reserved pointer for the list + + uint32_t (*size)(const struct clap_input_events *list); + + // Don't free the returned event, it belongs to the list + const clap_event_header_t *(*get)(const struct clap_input_events *list, uint32_t index); +} clap_input_events_t; + +// Output event list, events must be sorted by time. +typedef struct clap_output_events { + void *ctx; // reserved pointer for the list + + // Pushes a copy of the event + // returns false if the event could not be pushed to the queue (out of memory?) + bool (*try_push)(const struct clap_output_events *list, const clap_event_header_t *event); +} clap_output_events_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/ext/audio-ports.h b/distrho/src/clap/ext/audio-ports.h @@ -0,0 +1,116 @@ +#pragma once + +#include "../plugin.h" +#include "../string-sizes.h" + +/// @page Audio Ports +/// +/// This extension provides a way for the plugin to describe its current audio ports. +/// +/// If the plugin does not implement this extension, it won't have audio ports. +/// +/// 32 bits support is required for both host and plugins. 64 bits audio is optional. +/// +/// The plugin is only allowed to change its ports configuration while it is deactivated. + +static CLAP_CONSTEXPR const char CLAP_EXT_AUDIO_PORTS[] = "clap.audio-ports"; +static CLAP_CONSTEXPR const char CLAP_PORT_MONO[] = "mono"; +static CLAP_CONSTEXPR const char CLAP_PORT_STEREO[] = "stereo"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // This port is the main audio input or output. + // There can be only one main input and main output. + // Main port must be at index 0. + CLAP_AUDIO_PORT_IS_MAIN = 1 << 0, + + // This port can be used with 64 bits audio + CLAP_AUDIO_PORT_SUPPORTS_64BITS = 1 << 1, + + // 64 bits audio is preferred with this port + CLAP_AUDIO_PORT_PREFERS_64BITS = 1 << 2, + + // This port must be used with the same sample size as all the other ports which have this flag. + // In other words if all ports have this flag then the plugin may either be used entirely with + // 64 bits audio or 32 bits audio, but it can't be mixed. + CLAP_AUDIO_PORT_REQUIRES_COMMON_SAMPLE_SIZE = 1 << 3, +}; + +typedef struct clap_audio_port_info { + // id identifies a port and must be stable. + // id may overlap between input and output ports. + clap_id id; + char name[CLAP_NAME_SIZE]; // displayable name + + uint32_t flags; + uint32_t channel_count; + + // If null or empty then it is unspecified (arbitrary audio). + // This filed can be compared against: + // - CLAP_PORT_MONO + // - CLAP_PORT_STEREO + // - CLAP_PORT_SURROUND (defined in the surround extension) + // - CLAP_PORT_AMBISONIC (defined in the ambisonic extension) + // - CLAP_PORT_CV (defined in the cv extension) + // + // An extension can provide its own port type and way to inspect the channels. + const char *port_type; + + // in-place processing: allow the host to use the same buffer for input and output + // if supported set the pair port id. + // if not supported set to CLAP_INVALID_ID + clap_id in_place_pair; +} clap_audio_port_info_t; + +// 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] + uint32_t (*count)(const clap_plugin_t *plugin, bool is_input); + + // get info about about an audio port. + // [main-thread] + bool (*get)(const clap_plugin_t *plugin, + uint32_t index, + bool is_input, + clap_audio_port_info_t *info); +} clap_plugin_audio_ports_t; + +enum { + // The ports name did change, the host can scan them right away. + CLAP_AUDIO_PORTS_RESCAN_NAMES = 1 << 0, + + // [!active] The flags did change + CLAP_AUDIO_PORTS_RESCAN_FLAGS = 1 << 1, + + // [!active] The channel_count did change + CLAP_AUDIO_PORTS_RESCAN_CHANNEL_COUNT = 1 << 2, + + // [!active] The port type did change + CLAP_AUDIO_PORTS_RESCAN_PORT_TYPE = 1 << 3, + + // [!active] The in-place pair did change, this requires. + CLAP_AUDIO_PORTS_RESCAN_IN_PLACE_PAIR = 1 << 4, + + // [!active] The list of ports have changed: entries have been removed/added. + CLAP_AUDIO_PORTS_RESCAN_LIST = 1 << 5, +}; + +typedef struct clap_host_audio_ports { + // Checks if the host allows a plugin to change a given aspect of the audio ports definition. + // [main-thread] + bool (*is_rescan_flag_supported)(const clap_host_t *host, uint32_t flag); + + // Rescan the full list of audio ports according to the flags. + // It is illegal to ask the host to rescan with a flag that is not supported. + // Certain flags require the plugin to be de-activated. + // [main-thread] + void (*rescan)(const clap_host_t *host, uint32_t flags); +} clap_host_audio_ports_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/fixedpoint.h b/distrho/src/clap/fixedpoint.h @@ -0,0 +1,16 @@ +#pragma once + +#include "private/std.h" +#include "private/macros.h" + +/// We use fixed point representation of beat time and seconds time +/// Usage: +/// double x = ...; // in beats +/// clap_beattime y = round(CLAP_BEATTIME_FACTOR * x); + +// This will never change +static const CLAP_CONSTEXPR int64_t CLAP_BEATTIME_FACTOR = 1LL << 31; +static const CLAP_CONSTEXPR int64_t CLAP_SECTIME_FACTOR = 1LL << 31; + +typedef int64_t clap_beattime; +typedef int64_t clap_sectime; diff --git a/distrho/src/clap/host.h b/distrho/src/clap/host.h @@ -0,0 +1,41 @@ +#pragma once + +#include "version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_host { + clap_version_t clap_version; // initialized to CLAP_VERSION + + void *host_data; // reserved pointer for the host + + // name and version are mandatory. + const char *name; // eg: "Bitwig Studio" + const char *vendor; // eg: "Bitwig GmbH" + const char *url; // eg: "https://bitwig.com" + const char *version; // eg: "4.3" + + // Query an extension. + // [thread-safe] + const void *(*get_extension)(const struct clap_host *host, const char *extension_id); + + // Request the host to deactivate and then reactivate the plugin. + // The operation may be delayed by the host. + // [thread-safe] + void (*request_restart)(const struct clap_host *host); + + // Request the host to activate and start processing the plugin. + // This is useful if you have external IO and need to wake up the plugin from "sleep". + // [thread-safe] + void (*request_process)(const struct clap_host *host); + + // Request the host to schedule a call to plugin->on_main_thread(plugin) on the main thread. + // [thread-safe] + void (*request_callback)(const struct clap_host *host); +} clap_host_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/id.h b/distrho/src/clap/id.h @@ -0,0 +1,8 @@ +#pragma once + +#include "private/std.h" +#include "private/macros.h" + +typedef uint32_t clap_id; + +static const CLAP_CONSTEXPR clap_id CLAP_INVALID_ID = UINT32_MAX; diff --git a/distrho/src/clap/plugin-factory.h b/distrho/src/clap/plugin-factory.h @@ -0,0 +1,39 @@ +#pragma once + +#include "plugin.h" + +static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_ID[] = "clap.plugin-factory"; + +#ifdef __cplusplus +extern "C" { +#endif + +// Every method must be thread-safe. +// It is very important to be able to scan the plugin as quickly as possible. +// +// If the content of the factory may change due to external events, like the user installed +typedef struct clap_plugin_factory { + // Get the number of plugins available. + // [thread-safe] + uint32_t (*get_plugin_count)(const struct clap_plugin_factory *factory); + + // Retrieves a plugin descriptor by its index. + // Returns null in case of error. + // The descriptor must not be freed. + // [thread-safe] + const clap_plugin_descriptor_t *(*get_plugin_descriptor)( + const struct clap_plugin_factory *factory, uint32_t index); + + // Create a clap_plugin by its plugin_id. + // The returned pointer must be freed by calling plugin->destroy(plugin); + // The plugin is not allowed to use the host callbacks in the create method. + // Returns null in case of error. + // [thread-safe] + const clap_plugin_t *(*create_plugin)(const struct clap_plugin_factory *factory, + const clap_host_t *host, + const char *plugin_id); +} clap_plugin_factory_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/plugin-features.h b/distrho/src/clap/plugin-features.h @@ -0,0 +1,76 @@ +#pragma once + +#include "private/macros.h" + +// This file provides a set of standard plugin features meant to be used +// within clap_plugin_descriptor.features. +// +// For practical reasons we'll avoid spaces and use `-` instead to facilitate +// scripts that generate the feature array. +// +// Non-standard features should be formated as follow: "$namespace:$feature" + +///////////////////// +// Plugin category // +///////////////////// + +// Add this feature if your plugin can process note events and then produce audio +#define CLAP_PLUGIN_FEATURE_INSTRUMENT "instrument" + +// Add this feature if your plugin is an audio effect +#define CLAP_PLUGIN_FEATURE_AUDIO_EFFECT "audio-effect" + +// Add this feature if your plugin is a note effect or a note generator/sequencer +#define CLAP_PLUGIN_FEATURE_NOTE_EFFECT "note-effect" + +// Add this feature if your plugin is an analyzer +#define CLAP_PLUGIN_FEATURE_ANALYZER "analyzer" + +///////////////////////// +// Plugin sub-category // +///////////////////////// + +#define CLAP_PLUGIN_FEATURE_SYNTHESIZER "synthesizer" +#define CLAP_PLUGIN_FEATURE_SAMPLER "sampler" +#define CLAP_PLUGIN_FEATURE_DRUM "drum" // For single drum +#define CLAP_PLUGIN_FEATURE_DRUM_MACHINE "drum-machine" + +#define CLAP_PLUGIN_FEATURE_FILTER "filter" +#define CLAP_PLUGIN_FEATURE_PHASER "phaser" +#define CLAP_PLUGIN_FEATURE_EQUALIZER "equalizer" +#define CLAP_PLUGIN_FEATURE_DEESSER "de-esser" +#define CLAP_PLUGIN_FEATURE_PHASE_VOCODER "phase-vocoder" +#define CLAP_PLUGIN_FEATURE_GRANULAR "granular" +#define CLAP_PLUGIN_FEATURE_FREQUENCY_SHIFTER "frequency-shifter" +#define CLAP_PLUGIN_FEATURE_PITCH_SHIFTER "pitch-shifter" + +#define CLAP_PLUGIN_FEATURE_DISTORTION "distortion" +#define CLAP_PLUGIN_FEATURE_TRANSIENT_SHAPER "transient-shaper" +#define CLAP_PLUGIN_FEATURE_COMPRESSOR "compressor" +#define CLAP_PLUGIN_FEATURE_LIMITER "limiter" + +#define CLAP_PLUGIN_FEATURE_FLANGER "flanger" +#define CLAP_PLUGIN_FEATURE_CHORUS "chorus" +#define CLAP_PLUGIN_FEATURE_DELAY "delay" +#define CLAP_PLUGIN_FEATURE_REVERB "reverb" + +#define CLAP_PLUGIN_FEATURE_TREMOLO "tremolo" +#define CLAP_PLUGIN_FEATURE_GLITCH "glitch" + +#define CLAP_PLUGIN_FEATURE_UTILITY "utility" +#define CLAP_PLUGIN_FEATURE_PITCH_CORRECTION "pitch-correction" +#define CLAP_PLUGIN_FEATURE_RESTORATION "restoration" // repair the sound + +#define CLAP_PLUGIN_FEATURE_MULTI_EFFECTS "multi-effects" + +#define CLAP_PLUGIN_FEATURE_MIXING "mixing" +#define CLAP_PLUGIN_FEATURE_MASTERING "mastering" + +//////////////////////// +// Audio Capabilities // +//////////////////////// + +#define CLAP_PLUGIN_FEATURE_MONO "mono" +#define CLAP_PLUGIN_FEATURE_STEREO "stereo" +#define CLAP_PLUGIN_FEATURE_SURROUND "surround" +#define CLAP_PLUGIN_FEATURE_AMBISONIC "ambisonic" diff --git a/distrho/src/clap/plugin.h b/distrho/src/clap/plugin.h @@ -0,0 +1,96 @@ +#pragma once + +#include "private/macros.h" +#include "host.h" +#include "process.h" +#include "plugin-features.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_descriptor { + clap_version_t clap_version; // initialized to CLAP_VERSION + + // Mandatory fields must be set and must not be blank. + // Otherwise the fields can be null or blank, though it is safer to make them blank. + const char *id; // eg: "com.u-he.diva", mandatory + const char *name; // eg: "Diva", mandatory + const char *vendor; // eg: "u-he" + const char *url; // eg: "https://u-he.com/products/diva/" + const char *manual_url; // eg: "https://dl.u-he.com/manuals/plugins/diva/Diva-user-guide.pdf" + const char *support_url; // eg: "https://u-he.com/support/" + const char *version; // eg: "1.4.4" + const char *description; // eg: "The spirit of analogue" + + // Arbitrary list of keywords. + // They can be matched by the host indexer and used to classify the plugin. + // The array of pointers must be null terminated. + // For some standard features see plugin-features.h + const char **features; +} clap_plugin_descriptor_t; + +typedef struct clap_plugin { + const clap_plugin_descriptor_t *desc; + + void *plugin_data; // reserved pointer for the plugin + + // Must be called after creating the plugin. + // If init returns false, the host must destroy the plugin instance. + // [main-thread] + bool (*init)(const struct clap_plugin *plugin); + + // Free the plugin and its resources. + // It is required to deactivate the plugin prior to this call. + // [main-thread & !active] + void (*destroy)(const struct clap_plugin *plugin); + + // Activate and deactivate the plugin. + // In this call the plugin may allocate memory and prepare everything needed for the process + // call. The process's sample rate will be constant and process's frame count will included in + // the [min, max] range, which is bounded by [1, INT32_MAX]. + // Once activated the latency and port configuration must remain constant, until deactivation. + // + // [main-thread & !active_state] + bool (*activate)(const struct clap_plugin *plugin, + double sample_rate, + uint32_t min_frames_count, + uint32_t max_frames_count); + + // [main-thread & active_state] + void (*deactivate)(const struct clap_plugin *plugin); + + // Call start processing before processing. + // [audio-thread & active_state & !processing_state] + bool (*start_processing)(const struct clap_plugin *plugin); + + // Call stop processing before sending the plugin to sleep. + // [audio-thread & active_state & processing_state] + void (*stop_processing)(const struct clap_plugin *plugin); + + // - Clears all buffers, performs a full reset of the processing state (filters, oscillators, + // enveloppes, lfo, ...) and kills all voices. + // - The parameter's value remain unchanged. + // - clap_process.steady_time may jump backward. + // + // [audio-thread & active_state] + void (*reset)(const struct clap_plugin *plugin); + + // process audio, events, ... + // [audio-thread & active_state & processing_state] + clap_process_status (*process)(const struct clap_plugin *plugin, const clap_process_t *process); + + // Query an extension. + // The returned pointer is owned by the plugin. + // [thread-safe] + const void *(*get_extension)(const struct clap_plugin *plugin, const char *id); + + // Called by the host on the main thread in response to a previous call to: + // host->request_callback(host); + // [main-thread] + void (*on_main_thread)(const struct clap_plugin *plugin); +} clap_plugin_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/private/macros.h b/distrho/src/clap/private/macros.h @@ -0,0 +1,36 @@ +#pragma once + +// Define CLAP_EXPORT +#if !defined(CLAP_EXPORT) +# if defined _WIN32 || defined __CYGWIN__ +# ifdef __GNUC__ +# define CLAP_EXPORT __attribute__((dllexport)) +# else +# define CLAP_EXPORT __declspec(dllexport) +# endif +# else +# if __GNUC__ >= 4 || defined(__clang__) +# define CLAP_EXPORT __attribute__((visibility("default"))) +# else +# define CLAP_EXPORT +# endif +# endif +#endif + +#if defined(__cplusplus) && __cplusplus >= 201103L +# define CLAP_HAS_CXX11 +# define CLAP_CONSTEXPR constexpr +#else +# define CLAP_CONSTEXPR +#endif + +#if defined(__cplusplus) && __cplusplus >= 201703L +# define CLAP_HAS_CXX17 +# define CLAP_NODISCARD [[nodiscard]] +#else +# define CLAP_NODISCARD +#endif + +#if defined(__cplusplus) && __cplusplus >= 202002L +# define CLAP_HAS_CXX20 +#endif diff --git a/distrho/src/clap/private/std.h b/distrho/src/clap/private/std.h @@ -0,0 +1,16 @@ +#pragma once + +#include "macros.h" + +#ifdef CLAP_HAS_CXX11 +# include <cstdint> +#else +# include <stdint.h> +#endif + +#ifdef __cplusplus +# include <cstddef> +#else +# include <stddef.h> +# include <stdbool.h> +#endif diff --git a/distrho/src/clap/process.h b/distrho/src/clap/process.h @@ -0,0 +1,65 @@ +#pragma once + +#include "events.h" +#include "audio-buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // Processing failed. The output buffer must be discarded. + CLAP_PROCESS_ERROR = 0, + + // Processing succeeded, keep processing. + CLAP_PROCESS_CONTINUE = 1, + + // Processing succeeded, keep processing if the output is not quiet. + CLAP_PROCESS_CONTINUE_IF_NOT_QUIET = 2, + + // Rely upon the plugin's tail to determine if the plugin should continue to process. + // see clap_plugin_tail + CLAP_PROCESS_TAIL = 3, + + // Processing succeeded, but no more processing is required, + // until the next event or variation in audio input. + CLAP_PROCESS_SLEEP = 4, +}; +typedef int32_t clap_process_status; + +typedef struct clap_process { + // A steady sample time counter. + // This field can be used to calculate the sleep duration between two process calls. + // This value may be specific to this plugin instance and have no relation to what + // other plugin instances may receive. + // + // Set to -1 if not available, otherwise the value must be greater or equal to 0, + // and must be increased by at least `frames_count` for the next call to process. + int64_t steady_time; + + // Number of frames to process + uint32_t frames_count; + + // time info at sample 0 + // If null, then this is a free running host, no transport events will be provided + const clap_event_transport_t *transport; + + // Audio buffers, they must have the same count as specified + // by clap_plugin_audio_ports->get_count(). + // The index maps to clap_plugin_audio_ports->get_info(). + const clap_audio_buffer_t *audio_inputs; + clap_audio_buffer_t *audio_outputs; + uint32_t audio_inputs_count; + uint32_t audio_outputs_count; + + // Input and output events. + // + // Events must be sorted by time. + // The input event list can't be modified. + const clap_input_events_t *in_events; + const clap_output_events_t *out_events; +} clap_process_t; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/string-sizes.h b/distrho/src/clap/string-sizes.h @@ -0,0 +1,21 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // String capacity for names that can be displayed to the user. + CLAP_NAME_SIZE = 256, + + // String capacity for describing a path, like a parameter in a module hierarchy or path within a + // set of nested track groups. + // + // This is not suited for describing a file path on the disk, as NTFS allows up to 32K long + // paths. + CLAP_PATH_SIZE = 1024, +}; + +#ifdef __cplusplus +} +#endif diff --git a/distrho/src/clap/version.h b/distrho/src/clap/version.h @@ -0,0 +1,34 @@ +#pragma once + +#include "private/macros.h" +#include "private/std.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_version { + // This is the major ABI and API design + // Version 0.X.Y correspond to the development stage, API and ABI are not stable + // Version 1.X.Y correspont to the release stage, API and ABI are stable + uint32_t major; + uint32_t minor; + uint32_t revision; +} clap_version_t; + +#ifdef __cplusplus +} +#endif + +#define CLAP_VERSION_MAJOR ((uint32_t)1) +#define CLAP_VERSION_MINOR ((uint32_t)1) +#define CLAP_VERSION_REVISION ((uint32_t)1) +#define CLAP_VERSION_INIT {CLAP_VERSION_MAJOR, CLAP_VERSION_MINOR, CLAP_VERSION_REVISION} + +static const CLAP_CONSTEXPR clap_version_t CLAP_VERSION = CLAP_VERSION_INIT; + +CLAP_NODISCARD static inline CLAP_CONSTEXPR bool +clap_version_is_compatible(const clap_version_t v) { + // versions 0.x.y were used during development stage and aren't compatible + return v.major >= 1; +} diff --git a/utils/symbols/clap.def b/utils/symbols/clap.def @@ -1 +1,2 @@ EXPORTS +clap_entry diff --git a/utils/symbols/clap.exp b/utils/symbols/clap.exp @@ -0,0 +1 @@ +_clap_entry diff --git a/utils/symbols/clap.version b/utils/symbols/clap.version @@ -1,4 +1,4 @@ { - global: __name; + global: clap_entry; local: *; };