diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01072ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f4011a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf +*.bat eol=crlf + +**/*.v linguist-language=V +**/*.vv linguist-language=V +**/*.vsh linguist-language=V +**/v.mod linguist-language=V diff --git a/.gitignore b/.gitignore index 259148f..4ffd649 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,10 @@ *.exe *.out *.app + +# V-specific +c2v_output/ + +# Build artifacts +build/ +*.clap diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..140d9ca --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +SOURCEDIR = src +BUILDDIR = build +TARGET = $(BUILDDIR)/hello_world.clap + +# List all V source files +V_SRC = $(wildcard $(SOURCEDIR)/*.v) + + +# Debug and Release build options +DEBUG ?= 0 +RELEASE ?= 0 +ifeq ($(DEBUG),1) + V_FLAGS = -cg -show-c-output +else ifeq ($(RELEASE),1) + V_FLAGS = -prod -skip-unused -cflags -fvisibility=hidden +endif + + +all: $(TARGET) + +# Use dependency on V source files to avoid recompilation +$(TARGET): $(V_SRC) | dir + v -cc gcc -shared -enable-globals $(V_FLAGS) $(SOURCEDIR) -o $@.so +ifeq ($(RELEASE),1) + strip $@.so +endif + mv $@.so $@ + +dir: + mkdir -p $(BUILDDIR) + +clean: + rm -rf $(BUILDDIR) + +info: $(TARGET) + clap-info $< + +install: $(TARGET) + mkdir -p ~/.clap + cp $(TARGET) ~/.clap/ + +.PHONY: all clean dir info install diff --git a/README.md b/README.md index 6ff0a89..c38e4f2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,38 @@ # vclap -Demonstration of a CLAP audio plugin in V +Demonstration of a [CLAP](https://github.com/free-audio/clap) audio plugin in V. + +Currently it does nothing besides being correctly built against official +CLAP headers and loading into a DAW without any errors. + +**Note**: Only tested on Linux. + +## Quickstart + +Ensure you have a working [V language](https://vlang.io/) environment. + +On top of that you'd need: + +- GNU Make +- GCC + +Start with: +```sh +git clone https://github.com/mo-foss/vclap.git +cd vclap +make +``` + +To confirm the plugin was built correctly you can use +[this tool](https://github.com/free-audio/clap-info/): +```sh +clap-info build/hello_world.clap +``` + +To test with your DAW, you have to make it discoverable: +``` +make install +``` + +Here is the plugin being correctly loaded in Bitwig Studio 5.1: +![](./assets/running.png) + diff --git a/assets/running.png b/assets/running.png new file mode 100644 index 0000000..58bd1ec Binary files /dev/null and b/assets/running.png differ diff --git a/include/clap/audio-buffer.h b/include/clap/audio-buffer.h new file mode 100644 index 0000000..cd18f3a --- /dev/null +++ b/include/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/include/clap/clap.h b/include/clap/clap.h new file mode 100644 index 0000000..e57dbf4 --- /dev/null +++ b/include/clap/clap.h @@ -0,0 +1,71 @@ +/* + * CLAP - CLever Audio Plugin + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Copyright (c) 2014...2022 Alexandre BIQUE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "entry.h" + +#include "factory/plugin-factory.h" +#include "factory/draft/plugin-invalidation.h" +#include "factory/draft/preset-discovery.h" + +#include "plugin.h" +#include "plugin-features.h" +#include "host.h" + +#include "ext/audio-ports-config.h" +#include "ext/audio-ports.h" +#include "ext/event-registry.h" +#include "ext/gui.h" +#include "ext/latency.h" +#include "ext/log.h" +#include "ext/note-name.h" +#include "ext/note-ports.h" +#include "ext/params.h" +#include "ext/posix-fd-support.h" +#include "ext/render.h" +#include "ext/state.h" +#include "ext/tail.h" +#include "ext/thread-check.h" +#include "ext/thread-pool.h" +#include "ext/timer-support.h" +#include "ext/voice-info.h" + +#include "ext/draft/ambisonic.h" +#include "ext/draft/audio-ports-activation.h" +#include "ext/draft/context-menu.h" +#include "ext/draft/cv.h" +#include "ext/draft/midi-mappings.h" +#include "ext/draft/param-indication.h" +#include "ext/draft/preset-load.h" +#include "ext/draft/remote-controls.h" +#include "ext/draft/resource-directory.h" +#include "ext/draft/state-context.h" +#include "ext/draft/surround.h" +#include "ext/draft/track-info.h" +#include "ext/draft/triggers.h" +#include "ext/draft/tuning.h" +#include "ext/draft/configurable-audio-ports.h" +#include "ext/draft/extensible-audio-ports.h" diff --git a/include/clap/color.h b/include/clap/color.h new file mode 100644 index 0000000..3f64359 --- /dev/null +++ b/include/clap/color.h @@ -0,0 +1,18 @@ +#pragma once + +#include "private/std.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_color { + uint8_t alpha; + uint8_t red; + uint8_t green; + uint8_t blue; +} clap_color_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/entry.h b/include/clap/entry.h new file mode 100644 index 0000000..36f91f8 --- /dev/null +++ b/include/clap/entry.h @@ -0,0 +1,71 @@ +#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 +// - %COMMONPROGRAMFILES%\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. + // + // plugin_path is the path to the DSO (Linux, Windows), or the bundle (macOS). + bool(CLAP_ABI *init)(const char *plugin_path); + + // No more calls into the DSO must be made after calling deinit(). + void(CLAP_ABI *deinit)(void); + + // Get the pointer to a factory. See factory/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 *(CLAP_ABI *get_factory)(const char *factory_id); +} clap_plugin_entry_t; + +/* Entry point */ +CLAP_EXPORT extern clap_plugin_entry_t clap_entry; +// CLAP_EXPORT extern const clap_plugin_entry_t clap_entry; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/events.h b/include/clap/events.h new file mode 100644 index 0000000..3a587be --- /dev/null +++ b/include/clap/events.h @@ -0,0 +1,286 @@ +#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 track + // + // 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 = 0, + CLAP_EVENT_NOTE_OFF = 1, + CLAP_EVENT_NOTE_CHOKE = 2, + CLAP_EVENT_NOTE_END = 3, + + // Represents a note expression. + // Uses clap_event_note_expression. + CLAP_EVENT_NOTE_EXPRESSION = 4, + + // 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 = 5, + CLAP_EVENT_PARAM_MOD = 6, + + // 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 = 7, + CLAP_EVENT_PARAM_GESTURE_END = 8, + + CLAP_EVENT_TRANSPORT = 9, // update the transport info; clap_event_transport + CLAP_EVENT_MIDI = 10, // raw midi event; clap_event_midi + CLAP_EVENT_MIDI_SYSEX = 11, // raw midi sysex event; clap_event_midi_sysex + CLAP_EVENT_MIDI2 = 12, // 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 = 0, + + // pan, 0 left, 0.5 center, 1 right + CLAP_NOTE_EXPRESSION_PAN = 1, + + // relative tuning in semitone, from -120 to +120 + CLAP_NOTE_EXPRESSION_TUNING = 2, + + // 0..1 + CLAP_NOTE_EXPRESSION_VIBRATO = 3, + CLAP_NOTE_EXPRESSION_EXPRESSION = 4, + CLAP_NOTE_EXPRESSION_BRIGHTNESS = 5, + CLAP_NOTE_EXPRESSION_PRESSURE = 6, +}; +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 sample 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. The host will deliver these sorted in sample order. +typedef struct clap_input_events { + void *ctx; // reserved pointer for the list + + // returns the number of events in the list + uint32_t(CLAP_ABI *size)(const struct clap_input_events *list); + + // Don't free the returned event, it belongs to the list + const clap_event_header_t *(CLAP_ABI *get)(const struct clap_input_events *list, uint32_t index); +} clap_input_events_t; + +// Output event list. The plugin must insert events in sample sorted order when inserting events +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(CLAP_ABI *try_push)(const struct clap_output_events *list, + const clap_event_header_t *event); +} clap_output_events_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/audio-ports-config.h b/include/clap/ext/audio-ports-config.h new file mode 100644 index 0000000..4627154 --- /dev/null +++ b/include/clap/ext/audio-ports-config.h @@ -0,0 +1,103 @@ +#pragma once + +#include "../string-sizes.h" +#include "../plugin.h" +#include "audio-ports.h" + +/// @page Audio Ports Config +/// +/// This extension let the plugin provide port configurations presets. +/// For example mono, stereo, surround, ambisonic, ... +/// +/// After the plugin initialization, the host may scan the list of configurations and eventually +/// select one that fits the plugin context. The host can only select a configuration if the plugin +/// is deactivated. +/// +/// A configuration is a very simple description of the audio ports: +/// - it describes the main input and output ports +/// - it has a name that can be displayed to the user +/// +/// The idea behind the configurations, is to let the user choose one via a menu. +/// +/// Plugins with very complex configuration possibilities should let the user configure the ports +/// from the plugin GUI, and call @ref clap_host_audio_ports.rescan(CLAP_AUDIO_PORTS_RESCAN_ALL). +/// +/// To inquire the exact bus layout, the plugin implements the clap_plugin_audio_ports_config_info_t +/// extension where all busses can be retrieved in the same way as in the audio-port extension. + +static CLAP_CONSTEXPR const char CLAP_EXT_AUDIO_PORTS_CONFIG[] = "clap.audio-ports-config"; +static CLAP_CONSTEXPR const char CLAP_EXT_AUDIO_PORTS_CONFIG_INFO[] = + "clap.audio-ports-config-info/draft-0"; + +#ifdef __cplusplus +extern "C" { +#endif + +// Minimalistic description of ports configuration +typedef struct clap_audio_ports_config { + clap_id id; + char name[CLAP_NAME_SIZE]; + + uint32_t input_port_count; + uint32_t output_port_count; + + // main input info + bool has_main_input; + uint32_t main_input_channel_count; + const char *main_input_port_type; + + // main output info + bool has_main_output; + uint32_t main_output_channel_count; + const char *main_output_port_type; +} clap_audio_ports_config_t; + +// The audio ports config scan has to be done while the plugin is deactivated. +typedef struct clap_plugin_audio_ports_config { + // Gets the number of available configurations + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Gets information about a configuration + // Returns true on success and stores the result into config. + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, + uint32_t index, + clap_audio_ports_config_t *config); + + // Selects the configuration designated by id + // Returns true if the configuration could be applied. + // Once applied the host should scan again the audio ports. + // [main-thread & plugin-deactivated] + bool(CLAP_ABI *select)(const clap_plugin_t *plugin, clap_id config_id); +} clap_plugin_audio_ports_config_t; + +// Extended config info +typedef struct clap_plugin_audio_ports_config_info { + + // Gets the id of the currently selected config, or CLAP_INVALID_ID if the current port + // layout isn't part of the config list. + // + // [main-thread] + clap_id(CLAP_ABI *current_config)(const clap_plugin_t *plugin); + + // Get info about an audio port, for a given config_id. + // This is analogous to clap_plugin_audio_ports.get(). + // Returns true on success and stores the result into info. + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, + clap_id config_id, + uint32_t port_index, + bool is_input, + clap_audio_port_info_t *info); +} clap_plugin_audio_ports_config_info_t; + +typedef struct clap_host_audio_ports_config { + // Rescan the full list of configs. + // [main-thread] + void(CLAP_ABI *rescan)(const clap_host_t *host); +} clap_host_audio_ports_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/audio-ports.h b/include/clap/ext/audio-ports.h new file mode 100644 index 0000000..74b94c3 --- /dev/null +++ b/include/clap/ext/audio-ports.h @@ -0,0 +1,117 @@ +#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 field 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(CLAP_ABI *count)(const clap_plugin_t *plugin, bool is_input); + + // Get info about an audio port. + // Returns true on success and stores the result into info. + // [main-thread] + bool(CLAP_ABI *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(CLAP_ABI *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(CLAP_ABI *rescan)(const clap_host_t *host, uint32_t flags); +} clap_host_audio_ports_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/ambisonic.h b/include/clap/ext/draft/ambisonic.h new file mode 100644 index 0000000..556af9a --- /dev/null +++ b/include/clap/ext/draft/ambisonic.h @@ -0,0 +1,63 @@ +#pragma once + +#include "../../plugin.h" + +// This extension can be used to specify the channel mapping used by the plugin. + +static CLAP_CONSTEXPR const char CLAP_EXT_AMBISONIC[] = "clap.ambisonic.draft/3"; + +static CLAP_CONSTEXPR const char CLAP_PORT_AMBISONIC[] = "ambisonic"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum clap_ambisonic_ordering { + // FuMa channel ordering + CLAP_AMBISONIC_ORDERING_FUMA = 0, + + // ACN channel ordering + CLAP_AMBISONIC_ORDERING_ACN = 1, +}; + +enum clap_ambisonic_normalization { + CLAP_AMBISONIC_NORMALIZATION_MAXN = 0, + CLAP_AMBISONIC_NORMALIZATION_SN3D = 1, + CLAP_AMBISONIC_NORMALIZATION_N3D = 2, + CLAP_AMBISONIC_NORMALIZATION_SN2D = 3, + CLAP_AMBISONIC_NORMALIZATION_N2D = 4, +}; + +typedef struct clap_ambisonic_config { + uint32_t ordering; // see clap_ambisonic_ordering + uint32_t normalization; // see clap_ambisonic_normalization +} clap_ambisonic_config_t; + +typedef struct clap_plugin_ambisonic { + // Returns true if the given configuration is supported. + // [main-thread] + bool(CLAP_ABI *is_config_supported)(const clap_plugin_t *plugin, + const clap_ambisonic_config_t *config); + + // Returns true on success + // + // config_id: the configuration id, see clap_plugin_audio_ports_config. + // If config_id is CLAP_INVALID_ID, then this function queries the current port info. + // [main-thread] + bool(CLAP_ABI *get_config)(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + clap_ambisonic_config_t *config); + +} clap_plugin_ambisonic_t; + +typedef struct clap_host_ambisonic { + // Informs the host that the info has changed. + // The info can only change when the plugin is de-activated. + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_ambisonic_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/audio-ports-activation.h b/include/clap/ext/draft/audio-ports-activation.h new file mode 100644 index 0000000..f451c00 --- /dev/null +++ b/include/clap/ext/draft/audio-ports-activation.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../../plugin.h" + +/// @page Audio Ports Activation +/// +/// This extension provides a way for the host to activate and de-activate audio ports. +/// Deactivating a port provides the following benefits: +/// - the plugin knows ahead of time that a given input is not present and can choose +/// an optimized computation path, +/// - the plugin knows that an output is not consumed by the host, and doesn't need to +/// compute it. +/// +/// Audio ports can only be activated or deactivated when the plugin is deactivated, unless +/// can_activate_while_processing() returns true. +/// +/// Audio buffers must still be provided if the audio port is deactivated. +/// In such case, they shall be filled with 0 (or whatever is the neutral value in your context) +/// and the constant_mask shall be set. +/// +/// Audio ports are initially in the active state after creating the plugin instance. +/// Audio ports state are not saved in the plugin state, so the host must restore the +/// audio ports state after creating the plugin instance. +/// +/// Audio ports state is invalidated by clap_plugin_audio_ports_config.select() and +/// clap_host_audio_ports.rescan(CLAP_AUDIO_PORTS_RESCAN_LIST). + +static CLAP_CONSTEXPR const char CLAP_EXT_AUDIO_PORTS_ACTIVATION[] = + "clap.audio-ports-activation/draft-2"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_audio_ports_activation { + // Returns true if the plugin supports activation/deactivation while processing. + // [main-thread] + bool(CLAP_ABI *can_activate_while_processing)(const clap_plugin_t *plugin); + + // Activate the given port. + // + // It is only possible to activate and de-activate on the audio-thread if + // can_activate_while_processing() returns true. + // + // sample_size indicate if the host will provide 32 bit audio buffers or 64 bits one. + // Possible values are: 32, 64 or 0 if unspecified. + // + // returns false if failed, or invalid parameters + // [active ? audio-thread : main-thread] + bool(CLAP_ABI *set_active)(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + bool is_active, + uint32_t sample_size); +} clap_plugin_audio_ports_activation_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/check-for-update.h b/include/clap/ext/draft/check-for-update.h new file mode 100644 index 0000000..71ebe81 --- /dev/null +++ b/include/clap/ext/draft/check-for-update.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_CHECK_FOR_UPDATE[] = "clap.check_for_update.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_check_for_update_info { + const char *version; // latest version + const char *release_date; // YYYY-MM-DD + const char *url; // url to a download page which the user can visit + + bool is_preview; // true if this version is a preview release +} clap_check_for_update_info_t; + +typedef struct clap_plugin_check_for_update { + // [main-thread] + void(CLAP_ABI *check)(const clap_plugin_t *plugin, bool include_preview); +} clap_plugin_check_for_update_t; + +typedef struct clap_host_check_for_update { + // [main-thread] + void(CLAP_ABI *on_new_version)(const clap_host_t *host, + const clap_check_for_update_info_t *update_info); +} clap_host_check_for_update_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/configurable-audio-ports.h b/include/clap/ext/draft/configurable-audio-ports.h new file mode 100644 index 0000000..4fe60cd --- /dev/null +++ b/include/clap/ext/draft/configurable-audio-ports.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../audio-ports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// This extension lets the host configure the plugin's input and output audio ports. +// This is a "push" approach to audio ports configuration. +static CLAP_CONSTEXPR const char CLAP_EXT_CONFIGURABLE_AUDIO_PORTS[] = + "clap.configurable-audio-ports.draft1"; + +typedef struct clap_audio_port_configuration_request { + // Identifies the port by is_input and port_index + bool is_input; + uint32_t port_index; + + // The requested number of channels. + uint32_t channel_count; + + // The port type, see audio-ports.h, clap_audio_port_info.port_type for interpretation. + const char *port_type; + + // cast port_details according to port_type: + // - CLAP_PORT_MONO: (discard) + // - CLAP_PORT_STEREO: (discard) + // - CLAP_PORT_SURROUND: const uint8_t *channel_map + // - CLAP_PORT_AMBISONIC: const clap_ambisonic_config_t *info + const void *port_details; +} clap_audio_port_configuration_request_t; + +typedef struct clap_plugin_configurable_audio_ports { + // Returns true if the given configurations can be applied using apply_configuration(). + // [main-thread && !active] + bool(CLAP_ABI *can_apply_configuration)( + const clap_plugin_t *plugin, + const struct clap_audio_port_configuration_request *requests, + uint32_t request_count); + + // Submit a bunch of configuration requests which will atomically be applied together, + // or discarded together. + // + // Once the configuration is successfully applied, it isn't necessary for the plugin to call + // clap_host_audio_ports->changed(); and it isn't necessary for the host to scan the + // audio ports. + // + // Returns true if applied. + // [main-thread && !active] + bool(CLAP_ABI *apply_configuration)(const clap_plugin_t *plugin, + const struct clap_audio_port_configuration_request *requests, + uint32_t request_count); +} clap_plugin_configurable_audio_ports_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/context-menu.h b/include/clap/ext/draft/context-menu.h new file mode 100644 index 0000000..85c3abb --- /dev/null +++ b/include/clap/ext/draft/context-menu.h @@ -0,0 +1,164 @@ +#pragma once + +#include "../../plugin.h" + +// This extension lets the host and plugin exchange menu items and let the plugin ask the host to +// show its context menu. + +static CLAP_CONSTEXPR const char CLAP_EXT_CONTEXT_MENU[] = "clap.context-menu.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +// There can be different target kind for a context menu +enum { + CLAP_CONTEXT_MENU_TARGET_KIND_GLOBAL = 0, + CLAP_CONTEXT_MENU_TARGET_KIND_PARAM = 1, + // TODO: kind trigger once the trigger ext is marked as stable +}; + +// Describes the context menu target +typedef struct clap_context_menu_target { + uint32_t kind; + clap_id id; +} clap_context_menu_target_t; + +enum { + // Adds a clickable menu entry. + // data: const clap_context_menu_item_entry_t* + CLAP_CONTEXT_MENU_ITEM_ENTRY, + + // Adds a clickable menu entry which will feature both a checkmark and a label. + // data: const clap_context_menu_item_check_entry_t* + CLAP_CONTEXT_MENU_ITEM_CHECK_ENTRY, + + // Adds a separator line. + // data: NULL + CLAP_CONTEXT_MENU_ITEM_SEPARATOR, + + // Starts a sub menu with the given label. + // data: const clap_context_menu_item_begin_submenu_t* + CLAP_CONTEXT_MENU_ITEM_BEGIN_SUBMENU, + + // Ends the current sub menu. + // data: NULL + CLAP_CONTEXT_MENU_ITEM_END_SUBMENU, + + // Adds a title entry + // data: const clap_context_menu_item_title_t * + CLAP_CONTEXT_MENU_ITEM_TITLE, +}; +typedef uint32_t clap_context_menu_item_kind_t; + +typedef struct clap_context_menu_entry { + // text to be displayed + const char *label; + + // if false, then the menu entry is greyed out and not clickable + bool is_enabled; + clap_id action_id; +} clap_context_menu_entry_t; + +typedef struct clap_context_menu_check_entry { + // text to be displayed + const char *label; + + // if false, then the menu entry is greyed out and not clickable + bool is_enabled; + + // if true, then the menu entry will be displayed as checked + bool is_checked; + clap_id action_id; +} clap_context_menu_check_entry_t; + +typedef struct clap_context_menu_item_title { + // text to be displayed + const char *title; + + // if false, then the menu entry is greyed out + bool is_enabled; +} clap_context_menu_item_title_t; + +typedef struct clap_context_menu_submenu { + // text to be displayed + const char *label; + + // if false, then the menu entry is greyed out and won't show submenu + bool is_enabled; +} clap_context_menu_submenu_t; + +// Context menu builder. +// This object isn't thread-safe and must be used on the same thread as it was provided. +typedef struct clap_context_menu_builder { + void *ctx; + + // Adds an entry to the menu. + // item_data type is determined by item_kind. + // Returns true on success. + bool(CLAP_ABI *add_item)(const struct clap_context_menu_builder *builder, + clap_context_menu_item_kind_t item_kind, + const void *item_data); + + // Returns true if the menu builder supports the given item kind + bool(CLAP_ABI *supports)(const struct clap_context_menu_builder *builder, + clap_context_menu_item_kind_t item_kind); +} clap_context_menu_builder_t; + +typedef struct clap_plugin_context_menu { + // Insert plugin's menu items into the menu builder. + // If target is null, assume global context. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *populate)(const clap_plugin_t *plugin, + const clap_context_menu_target_t *target, + const clap_context_menu_builder_t *builder); + + // Performs the given action, which was previously provided to the host via populate(). + // If target is null, assume global context. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *perform)(const clap_plugin_t *plugin, + const clap_context_menu_target_t *target, + clap_id action_id); +} clap_plugin_context_menu_t; + +typedef struct clap_host_context_menu { + // Insert host's menu items into the menu builder. + // If target is null, assume global context. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *populate)(const clap_host_t *host, + const clap_context_menu_target_t *target, + const clap_context_menu_builder_t *builder); + + // Performs the given action, which was previously provided to the plugin via populate(). + // If target is null, assume global context. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *perform)(const clap_host_t *host, + const clap_context_menu_target_t *target, + clap_id action_id); + + // Returns true if the host can display a popup menu for the plugin. + // This may depend upon the current windowing system used to display the plugin, so the + // return value is invalidated after creating the plugin window. + // [main-thread] + bool(CLAP_ABI *can_popup)(const clap_host_t *host); + + // Shows the host popup menu for a given parameter. + // If the plugin is using embedded GUI, then x and y are relative to the plugin's window, + // otherwise they're absolute coordinate, and screen index might be set accordingly. + // If target is null, assume global context. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *popup)(const clap_host_t *host, + const clap_context_menu_target_t *target, + int32_t screen_index, + int32_t x, + int32_t y); +} clap_host_context_menu_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/cv.h b/include/clap/ext/draft/cv.h new file mode 100644 index 0000000..343c836 --- /dev/null +++ b/include/clap/ext/draft/cv.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../../plugin.h" + +// This extension can be used to specify the cv channel type used by the plugin. +// Work in progress, suggestions are welcome + +static CLAP_CONSTEXPR const char CLAP_EXT_CV[] = "clap.cv.draft/0"; +static CLAP_CONSTEXPR const char CLAP_PORT_CV[] = "cv"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // TODO: standardize values? + CLAP_CV_VALUE = 0, + CLAP_CV_GATE = 1, + CLAP_CV_PITCH = 2, +}; + +// TODO: maybe we want a channel_info instead, where we could have more details about the supported +// ranges? + +typedef struct clap_plugin_cv { + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *get_channel_type)(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + uint32_t channel_index, + uint32_t *channel_type); +} clap_plugin_cv_t; + +typedef struct clap_host_cv { + // Informs the host that the channels type have changed. + // The channels type can only change when the plugin is de-activated. + // [main-thread,!active] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_cv_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/extensible-audio-ports.h b/include/clap/ext/draft/extensible-audio-ports.h new file mode 100644 index 0000000..278594b --- /dev/null +++ b/include/clap/ext/draft/extensible-audio-ports.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../audio-ports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// This extension lets the host add and remove audio ports to the plugin. +static CLAP_CONSTEXPR const char CLAP_EXT_EXTENSIBLE_AUDIO_PORTS[] = + "clap.extensible-audio-ports.draft0"; + +typedef struct clap_plugin_extensible_audio_ports { + // Asks the plugin to add a new port (at the end of the list), with the following settings. + // port_type: see clap_audio_port_info.port_type for interpretation. + // port_details: see clap_audio_port_configuration_request.port_details for interpretation. + // Returns true on success. + // [main-thread && !is_active] + bool(CLAP_ABI *add_port)(const clap_plugin_t *plugin, + bool is_input, + uint32_t channel_count, + const char *port_type, + const void *port_details); + + // Asks the plugin to remove a port. + // Returns true on success. + // [main-thread && !is_active] + bool(CLAP_ABI *remove_port)(const clap_plugin_t *plugin, bool is_input, uint32_t index); +} clap_plugin_extensible_audio_ports_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/midi-mappings.h b/include/clap/ext/draft/midi-mappings.h new file mode 100644 index 0000000..c584e9d --- /dev/null +++ b/include/clap/ext/draft/midi-mappings.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_MIDI_MAPPINGS[] = "clap.midi-mappings.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CLAP_MIDI_MAPPING_CC7, + CLAP_MIDI_MAPPING_CC14, + CLAP_MIDI_MAPPING_RPN, + CLAP_MIDI_MAPPING_NRPN, +}; +typedef int32_t clap_midi_mapping_type; + +typedef struct clap_midi_mapping { + int32_t channel; + int32_t number; + clap_id param_id; +} clap_midi_mapping_t; + +typedef struct clap_plugin_midi_mappings { + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Returns true on success and stores the result into mapping. + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, uint32_t index, clap_midi_mapping_t *mapping); +} clap_plugin_midi_mappings_t; + +typedef struct clap_host_midi_mappings { + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_midi_mappings_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/param-indication.h b/include/clap/ext/draft/param-indication.h new file mode 100644 index 0000000..5ec45f7 --- /dev/null +++ b/include/clap/ext/draft/param-indication.h @@ -0,0 +1,73 @@ +#pragma once + +#include "../params.h" +#include "../../color.h" + +// This extension lets the host tell the plugin to display a little color based indication on the +// parameter. This can be used to indicate: +// - a physical controller is mapped to a parameter +// - the parameter is current playing an automation +// - the parameter is overriding the automation +// - etc... +// +// The color semantic depends upon the host here and the goal is to have a consistent experience +// across all plugins. + +static CLAP_CONSTEXPR const char CLAP_EXT_PARAM_INDICATION[] = "clap.param-indication.draft/4"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // The host doesn't have an automation for this parameter + CLAP_PARAM_INDICATION_AUTOMATION_NONE = 0, + + // The host has an automation for this parameter, but it isn't playing it + CLAP_PARAM_INDICATION_AUTOMATION_PRESENT = 1, + + // The host is playing an automation for this parameter + CLAP_PARAM_INDICATION_AUTOMATION_PLAYING = 2, + + // The host is recording an automation on this parameter + CLAP_PARAM_INDICATION_AUTOMATION_RECORDING = 3, + + // The host should play an automation for this parameter, but the user has started to adjust this + // parameter and is overriding the automation playback + CLAP_PARAM_INDICATION_AUTOMATION_OVERRIDING = 4, +}; + +typedef struct clap_plugin_param_indication { + // Sets or clears a mapping indication. + // + // has_mapping: does the parameter currently has a mapping? + // color: if set, the color to use to highlight the control in the plugin GUI + // label: if set, a small string to display on top of the knob which identifies the hardware + // controller description: if set, a string which can be used in a tooltip, which describes the + // current mapping + // + // Parameter indications should not be saved in the plugin context, and are off by default. + // [main-thread] + void(CLAP_ABI *set_mapping)(const clap_plugin_t *plugin, + clap_id param_id, + bool has_mapping, + const clap_color_t *color, + const char *label, + const char *description); + + // Sets or clears an automation indication. + // + // automation_state: current automation state for the given parameter + // color: if set, the color to use to display the automation indication in the plugin GUI + // + // Parameter indications should not be saved in the plugin context, and are off by default. + // [main-thread] + void(CLAP_ABI *set_automation)(const clap_plugin_t *plugin, + clap_id param_id, + uint32_t automation_state, + const clap_color_t *color); +} clap_plugin_param_indication_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/preset-load.h b/include/clap/ext/draft/preset-load.h new file mode 100644 index 0000000..92263f1 --- /dev/null +++ b/include/clap/ext/draft/preset-load.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../../plugin.h" + +static const char CLAP_EXT_PRESET_LOAD[] = "clap.preset-load.draft/2"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_preset_load { + // Loads a preset in the plugin native preset file format from a location. + // The preset discovery provider defines the location and load_key to be passed to this function. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *from_location)(const clap_plugin_t *plugin, + uint32_t location_kind, + const char *location, + const char *load_key); +} clap_plugin_preset_load_t; + +typedef struct clap_host_preset_load { + // Called if clap_plugin_preset_load.load() failed. + // os_error: the operating system error, if applicable. If not applicable set it to a non-error + // value, eg: 0 on unix and Windows. + // + // [main-thread] + void(CLAP_ABI *on_error)(const clap_host_t *host, + uint32_t location_kind, + const char *location, + const char *load_key, + int32_t os_error, + const char *msg); + + // Informs the host that the following preset has been loaded. + // This contributes to keep in sync the host preset browser and plugin preset browser. + // If the preset was loaded from a container file, then the load_key must be set, otherwise it + // must be null. + // + // [main-thread] + void(CLAP_ABI *loaded)(const clap_host_t *host, + uint32_t location_kind, + const char *location, + const char *load_key); +} clap_host_preset_load_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/remote-controls.h b/include/clap/ext/draft/remote-controls.h new file mode 100644 index 0000000..07566d0 --- /dev/null +++ b/include/clap/ext/draft/remote-controls.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../../plugin.h" +#include "../../string-sizes.h" + +// This extension let the plugin provide a structured way of mapping parameters to an hardware +// controller. +// +// This is done by providing a set of remote control pages organized by section. +// A page contains up to 8 controls, which references parameters using param_id. +// +// |`- [section:main] +// | `- [name:main] performance controls +// |`- [section:osc] +// | |`- [name:osc1] osc1 page +// | |`- [name:osc2] osc2 page +// | |`- [name:osc-sync] osc sync page +// | `- [name:osc-noise] osc noise page +// |`- [section:filter] +// | |`- [name:flt1] filter 1 page +// | `- [name:flt2] filter 2 page +// |`- [section:env] +// | |`- [name:env1] env1 page +// | `- [name:env2] env2 page +// |`- [section:lfo] +// | |`- [name:lfo1] env1 page +// | `- [name:lfo2] env2 page +// `- etc... +// +// One possible workflow is to have a set of buttons, which correspond to a section. +// Pressing that button once gets you to the first page of the section. +// Press it again to cycle through the section's pages. + +static CLAP_CONSTEXPR const char CLAP_EXT_REMOTE_CONTROLS[] = "clap.remote-controls.draft/2"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { CLAP_REMOTE_CONTROLS_COUNT = 8 }; + +typedef struct clap_remote_controls_page { + char section_name[CLAP_NAME_SIZE]; + clap_id page_id; + char page_name[CLAP_NAME_SIZE]; + clap_id param_ids[CLAP_REMOTE_CONTROLS_COUNT]; + + // This is used to separate device pages versus preset pages. + // If true, then this page is specific to this preset. + bool is_for_preset; +} clap_remote_controls_page_t; + +typedef struct clap_plugin_remote_controls { + // Returns the number of pages. + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Get a page by index. + // Returns true on success and stores the result into page. + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, + uint32_t page_index, + clap_remote_controls_page_t *page); +} clap_plugin_remote_controls_t; + +typedef struct clap_host_remote_controls { + // Informs the host that the remote controls have changed. + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); + + // Suggest a page to the host because it corresponds to what the user is currently editing in the + // plugin's GUI. + // [main-thread] + void(CLAP_ABI *suggest_page)(const clap_host_t *host, clap_id page_id); +} clap_host_remote_controls_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/resource-directory.h b/include/clap/ext/draft/resource-directory.h new file mode 100644 index 0000000..fc65eaa --- /dev/null +++ b/include/clap/ext/draft/resource-directory.h @@ -0,0 +1,88 @@ +#pragma once + +#include "../../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_RESOURCE_DIRECTORY[] = "clap.resource-directory.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +/// @page Resource Directory +/// +/// This extension provides a way for the plugin to store its resources as file in a directory +/// provided by the host and recover them later on. +/// +/// The plugin **must** store relative path in its state toward resource directories. +/// +/// Resource sharing: +/// - shared directory is shared among all plugin instances, hence mostly appropriate for read-only +/// content +/// -> suitable for read-only content +/// - exclusive directory is exclusive to the plugin instance +/// -> if the plugin, then its exclusive directory must be duplicated too +/// -> suitable for read-write content +/// +/// Keeping the shared directory clean: +/// - to avoid clashes in the shared directory, plugins are encouraged to organize their files in +/// sub-folders, for example create one subdirectory using the vendor name +/// - don't use symbolic links or hard links which points outside of the directory +/// +/// Resource life-time: +/// - exclusive folder content is managed by the plugin instance +/// - exclusive folder content is deleted when the plugin instance is removed from the project +/// - shared folder content isn't managed by the host, until all plugins using the shared directory +/// are removed from the project +/// +/// Note for the host +/// - try to use the filesystem's copy-on-write feature when possible for reducing exclusive folder +/// space usage on duplication +/// - host can "garbage collect" the files in the shared folder using: +/// clap_plugin_resource_directory.get_files_count() +/// clap_plugin_resource_directory.get_file_path() +/// but be **very** careful before deleting any resources + +typedef struct clap_plugin_resource_directory { + // Sets the directory in which the plugin can save its resources. + // The directory remains valid until it is overridden or the plugin is destroyed. + // If path is null or blank, it clears the directory location. + // path must be absolute. + // [main-thread] + void(CLAP_ABI *set_directory)(const clap_plugin_t *plugin, const char *path, bool is_shared); + + // Asks the plugin to put its resources into the resource directory. + // It is not necessary to collect files which belongs to the plugin's + // factory content unless the param all is true. + // [main-thread] + void(CLAP_ABI *collect)(const clap_plugin_t *plugin, bool all); + + // Returns the number of files used by the plugin in the shared resource folder. + // [main-thread] + uint32_t(CLAP_ABI *get_files_count)(const clap_plugin_t *plugin); + + // Retrieves relative file path to the resource directory. + // @param path writable memory to store the path + // @param path_size number of available bytes in path + // Returns the number of bytes in the path, or -1 on error + // [main-thread] + int32_t(CLAP_ABI *get_file_path)(const clap_plugin_t *plugin, + uint32_t index, + char *path, + uint32_t path_size); +} clap_plugin_resource_directory_t; + +typedef struct clap_host_resource_directory { + // Request the host to setup a resource directory with the specified sharing. + // Returns true if the host will perform the request. + // [main-thread] + bool(CLAP_ABI *request_directory)(const clap_host_t *host, bool is_shared); + + // Tell the host that the resource directory of the specified sharing is no longer required. + // If is_shared = false, then the host may delete the directory content. + // [main-thread] + void(CLAP_ABI *release_directory)(const clap_host_t *host, bool is_shared); +} clap_host_resource_directory_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/state-context.h b/include/clap/ext/draft/state-context.h new file mode 100644 index 0000000..4912a6e --- /dev/null +++ b/include/clap/ext/draft/state-context.h @@ -0,0 +1,64 @@ +#pragma once + +#include "../../plugin.h" +#include "../../stream.h" + +/// @page state-context extension +/// @brief extended state handling +/// +/// This extension lets the host save and load the plugin state with different semantics depending +/// on the context. +/// +/// Briefly, when loading a preset or duplicating a device, the plugin may want to partially load +/// the state and initialize certain things differently. +/// +/// Save and Load operations may have a different context. +/// All three operations should be equivalent: +/// 1. clap_plugin_state_context.load(clap_plugin_state.save(), CLAP_STATE_CONTEXT_FOR_PRESET) +/// 2. clap_plugin_state.load(clap_plugin_state_context.save(CLAP_STATE_CONTEXT_FOR_PRESET)) +/// 3. clap_plugin_state_context.load( +/// clap_plugin_state_context.save(CLAP_STATE_CONTEXT_FOR_PRESET), +/// CLAP_STATE_CONTEXT_FOR_PRESET) +/// +/// If the plugin implements CLAP_EXT_STATE_CONTEXT then it is mandatory to also implement +/// CLAP_EXT_STATE. + +#ifdef __cplusplus +extern "C" { +#endif + +static CLAP_CONSTEXPR const char CLAP_EXT_STATE_CONTEXT[] = "clap.state-context.draft/1"; + +enum clap_plugin_state_context_type { + // suitable for duplicating a plugin instance + CLAP_STATE_CONTEXT_FOR_DUPLICATE = 1, + + // suitable for loading a state as a preset + CLAP_STATE_CONTEXT_FOR_PRESET = 2, +}; + +typedef struct clap_plugin_state_context { + // Saves the plugin state into stream, according to context_type. + // Returns true if the state was correctly saved. + // + // Note that the result may be loaded by both clap_plugin_state.load() and + // clap_plugin_state_context.load(). + // [main-thread] + bool(CLAP_ABI *save)(const clap_plugin_t *plugin, + const clap_ostream_t *stream, + uint32_t context_type); + + // Loads the plugin state from stream, according to context_type. + // Returns true if the state was correctly restored. + // + // Note that the state may have been saved by clap_plugin_state.save() or + // clap_plugin_state_context.save() with a different context_type. + // [main-thread] + bool(CLAP_ABI *load)(const clap_plugin_t *plugin, + const clap_istream_t *stream, + uint32_t context_type); +} clap_plugin_state_context_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/surround.h b/include/clap/ext/draft/surround.h new file mode 100644 index 0000000..82f4e88 --- /dev/null +++ b/include/clap/ext/draft/surround.h @@ -0,0 +1,83 @@ +#pragma once + +#include "../../plugin.h" + +// This extension can be used to specify the channel mapping used by the plugin. +// +// To have consistent surround features across all the plugin instances, +// here is the proposed workflow: +// 1. the plugin queries the host preferred channel mapping and +// adjusts its configuration to match it. +// 2. the host checks how the plugin is effectively configured and honors it. +// +// If the host decides to change the project's surround setup: +// 1. deactivate the plugin +// 2. host calls clap_plugin_surround->changed() +// 3. plugin calls clap_host_surround->get_preferred_channel_map() +// 4. plugin eventually calls clap_host_surround->changed() +// 5. host calls clap_plugin_surround->get_channel_map() if changed +// 6. host activates the plugin and can start processing audio +// +// If the plugin wants to change its surround setup: +// 1. call host->request_restart() if the plugin is active +// 2. once deactivated plugin calls clap_host_surround->changed() +// 3. host calls clap_plugin_surround->get_channel_map() +// 4. host activates the plugin and can start processing audio + +static CLAP_CONSTEXPR const char CLAP_EXT_SURROUND[] = "clap.surround.draft/4"; + +static CLAP_CONSTEXPR const char CLAP_PORT_SURROUND[] = "surround"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CLAP_SURROUND_FL = 0, // Front Left + CLAP_SURROUND_FR = 1, // Front Right + CLAP_SURROUND_FC = 2, // Front Center + CLAP_SURROUND_LFE = 3, // Low Frequency + CLAP_SURROUND_BL = 4, // Back Left + CLAP_SURROUND_BR = 5, // Back Right + CLAP_SURROUND_FLC = 6, // Front Left of Center + CLAP_SURROUND_FRC = 7, // Front Right of Center + CLAP_SURROUND_BC = 8, // Back Center + CLAP_SURROUND_SL = 9, // Side Left + CLAP_SURROUND_SR = 10, // Side Right + CLAP_SURROUND_TC = 11, // Top Center + CLAP_SURROUND_TFL = 12, // Front Left Height + CLAP_SURROUND_TFC = 13, // Front Center Height + CLAP_SURROUND_TFR = 14, // Front Right Height + CLAP_SURROUND_TBL = 15, // Rear Left Height + CLAP_SURROUND_TBC = 16, // Rear Center Height + CLAP_SURROUND_TBR = 17, // Rear Right Height +}; + +typedef struct clap_plugin_surround { + // Checks if a given channel mask is supported. + // The channel mask is a bitmask, for example: + // (1 << CLAP_SURROUND_FL) | (1 << CLAP_SURROUND_FR) | ... + // [main-thread] + bool(CLAP_ABI *is_channel_mask_supported)(const clap_plugin_t *plugin, uint64_t channel_mask); + + // Stores the surround identifier of each channel into the channel_map array. + // Returns the number of elements stored in channel_map. + // channel_map_capacity must be greater or equal to the channel count of the given port. + // [main-thread] + uint32_t(CLAP_ABI *get_channel_map)(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + uint8_t *channel_map, + uint32_t channel_map_capacity); +} clap_plugin_surround_t; + +typedef struct clap_host_surround { + // Informs the host that the channel map has changed. + // The channel map can only change when the plugin is de-activated. + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_surround_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/track-info.h b/include/clap/ext/draft/track-info.h new file mode 100644 index 0000000..a79e67c --- /dev/null +++ b/include/clap/ext/draft/track-info.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../../plugin.h" +#include "../../color.h" +#include "../../string-sizes.h" + +// This extension let the plugin query info about the track it's in. +// It is useful when the plugin is created, to initialize some parameters (mix, dry, wet) +// and pick a suitable configuration regarding audio port type and channel count. + +static CLAP_CONSTEXPR const char CLAP_EXT_TRACK_INFO[] = "clap.track-info.draft/1"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CLAP_TRACK_INFO_HAS_TRACK_NAME = (1 << 0), + CLAP_TRACK_INFO_HAS_TRACK_COLOR = (1 << 1), + CLAP_TRACK_INFO_HAS_AUDIO_CHANNEL = (1 << 2), + + // This plugin is on a return track, initialize with wet 100% + CLAP_TRACK_INFO_IS_FOR_RETURN_TRACK = (1 << 3), + + // This plugin is on a bus track, initialize with appropriate settings for bus processing + CLAP_TRACK_INFO_IS_FOR_BUS = (1 << 4), + + // This plugin is on the master, initialize with appropriate settings for channel processing + CLAP_TRACK_INFO_IS_FOR_MASTER = (1 << 5), +}; + +typedef struct clap_track_info { + uint64_t flags; // see the flags above + + // track name, available if flags contain CLAP_TRACK_INFO_HAS_TRACK_NAME + char name[CLAP_NAME_SIZE]; + + // track color, available if flags contain CLAP_TRACK_INFO_HAS_TRACK_COLOR + clap_color_t color; + + // available if flags contain CLAP_TRACK_INFO_HAS_AUDIO_CHANNEL + // see audio-ports.h, struct clap_audio_port_info to learn how to use channel count and port type + int32_t audio_channel_count; + const char *audio_port_type; +} clap_track_info_t; + +typedef struct clap_plugin_track_info { + // Called when the info changes. + // [main-thread] + void(CLAP_ABI *changed)(const clap_plugin_t *plugin); +} clap_plugin_track_info_t; + +typedef struct clap_host_track_info { + // Get info about the track the plugin belongs to. + // Returns true on success and stores the result into info. + // [main-thread] + bool(CLAP_ABI *get)(const clap_host_t *host, clap_track_info_t *info); +} clap_host_track_info_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/transport-control.h b/include/clap/ext/draft/transport-control.h new file mode 100644 index 0000000..8a7e7b5 --- /dev/null +++ b/include/clap/ext/draft/transport-control.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../../plugin.h" + +// This extension lets the plugin submit transport requests to the host. +// The host has no obligation to execute these requests, so the interface may be +// partially working. + +static CLAP_CONSTEXPR const char CLAP_EXT_TRANSPORT_CONTROL[] = "clap.transport-control.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_host_transport_control { + // Jumps back to the start point and starts the transport + // [main-thread] + void(CLAP_ABI *request_start)(const clap_host_t *host); + + // Stops the transport, and jumps to the start point + // [main-thread] + void(CLAP_ABI *request_stop)(const clap_host_t *host); + + // If not playing, starts the transport from its current position + // [main-thread] + void(CLAP_ABI *request_continue)(const clap_host_t *host); + + // If playing, stops the transport at the current position + // [main-thread] + void(CLAP_ABI *request_pause)(const clap_host_t *host); + + // Equivalent to what "space bar" does with most DAWs + // [main-thread] + void(CLAP_ABI *request_toggle_play)(const clap_host_t *host); + + // Jumps the transport to the given position. + // Does not start the transport. + // [main-thread] + void(CLAP_ABI *request_jump)(const clap_host_t *host, clap_beattime position); + + // Sets the loop region + // [main-thread] + void(CLAP_ABI *request_loop_region)(const clap_host_t *host, + clap_beattime start, + clap_beattime duration); + + // Toggles looping + // [main-thread] + void(CLAP_ABI *request_toggle_loop)(const clap_host_t *host); + + // Enables/Disables looping + // [main-thread] + void(CLAP_ABI *request_enable_loop)(const clap_host_t *host, bool is_enabled); + + // Enables/Disables recording + // [main-thread] + void(CLAP_ABI *request_record)(const clap_host_t *host, bool is_recording); + + // Toggles recording + // [main-thread] + void(CLAP_ABI *request_toggle_record)(const clap_host_t *host); +} clap_host_transport_control_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/triggers.h b/include/clap/ext/draft/triggers.h new file mode 100644 index 0000000..fa1e83a --- /dev/null +++ b/include/clap/ext/draft/triggers.h @@ -0,0 +1,144 @@ +#pragma once + +#include "../../plugin.h" +#include "../../events.h" +#include "../../string-sizes.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_TRIGGERS[] = "clap.triggers.draft/0"; + +#ifdef __cplusplus +extern "C" { +#endif + +/// @page Trigger events +/// +/// This extension enables the plugin to expose a set of triggers to the host. +/// +/// Some examples for triggers: +/// - trigger an envelope which is independent of the notes +/// - trigger a sample-and-hold unit (maybe even per-voice) + +enum { + // Does this trigger support per note automations? + CLAP_TRIGGER_IS_AUTOMATABLE_PER_NOTE_ID = 1 << 0, + + // Does this trigger support per key automations? + CLAP_TRIGGER_IS_AUTOMATABLE_PER_KEY = 1 << 1, + + // Does this trigger support per channel automations? + CLAP_TRIGGER_IS_AUTOMATABLE_PER_CHANNEL = 1 << 2, + + // Does this trigger support per port automations? + CLAP_TRIGGER_IS_AUTOMATABLE_PER_PORT = 1 << 3, +}; +typedef uint32_t clap_trigger_info_flags; + +// Given that this extension is still draft, it'll use the event-registry and its own event +// namespace until we stabilize it. +// +// #include +// +// uint16_t CLAP_EXT_TRIGGER_EVENT_SPACE_ID = UINT16_MAX; +// if (host_event_registry->query(host, CLAP_EXT_TRIGGERS, &CLAP_EXT_TRIGGER_EVENT_SPACE_ID)) { +// /* we can use trigger events */ +// } +// +// /* later on */ +// clap_event_trigger ev; +// ev.header.space_id = CLAP_EXT_TRIGGER_EVENT_SPACE_ID; +// ev.header.type = CLAP_EVENT_TRIGGER; + +enum { CLAP_EVENT_TRIGGER = 0 }; + +typedef struct clap_event_trigger { + clap_event_header_t header; + + // target trigger + clap_id trigger_id; // @ref clap_trigger_info.id + void *cookie; // @ref clap_trigger_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; +} clap_event_trigger_t; + +/* This describes a trigger */ +typedef struct clap_trigger_info { + // stable trigger identifier, it must never change. + clap_id id; + + clap_trigger_info_flags flags; + + // in analogy to clap_param_info.cookie + void *cookie; + + // displayable name + char name[CLAP_NAME_SIZE]; + + // the module path containing the trigger, eg:"sequencers/seq1" + // '/' will be used as a separator to show a tree like structure. + char module[CLAP_PATH_SIZE]; +} clap_trigger_info_t; + +typedef struct clap_plugin_triggers { + // Returns the number of triggers. + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Copies the trigger's info to trigger_info and returns true on success. + // [main-thread] + bool(CLAP_ABI *get_info)(const clap_plugin_t *plugin, + uint32_t index, + clap_trigger_info_t *trigger_info); +} clap_plugin_triggers_t; + +enum { + // The trigger info did change, use this flag for: + // - name change + // - module change + // New info takes effect immediately. + CLAP_TRIGGER_RESCAN_INFO = 1 << 0, + + // Invalidates everything the host knows about triggers. + // It can only be used while the plugin is deactivated. + // If the plugin is activated use clap_host->restart() and delay any change until the host calls + // clap_plugin->deactivate(). + // + // You must use this flag if: + // - some triggers were added or removed. + // - some triggers had critical changes: + // - is_per_note (flag) + // - is_per_key (flag) + // - is_per_channel (flag) + // - is_per_port (flag) + // - cookie + CLAP_TRIGGER_RESCAN_ALL = 1 << 1, +}; +typedef uint32_t clap_trigger_rescan_flags; + +enum { + // Clears all possible references to a trigger + CLAP_TRIGGER_CLEAR_ALL = 1 << 0, + + // Clears all automations to a trigger + CLAP_TRIGGER_CLEAR_AUTOMATIONS = 1 << 1, +}; +typedef uint32_t clap_trigger_clear_flags; + +typedef struct clap_host_triggers { + // Rescan the full list of triggers according to the flags. + // [main-thread] + void(CLAP_ABI *rescan)(const clap_host_t *host, clap_trigger_rescan_flags flags); + + // Clears references to a trigger. + // [main-thread] + void(CLAP_ABI *clear)(const clap_host_t *host, + clap_id trigger_id, + clap_trigger_clear_flags flags); +} clap_host_triggers_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/draft/tuning.h b/include/clap/ext/draft/tuning.h new file mode 100644 index 0000000..db8d976 --- /dev/null +++ b/include/clap/ext/draft/tuning.h @@ -0,0 +1,76 @@ +#pragma once + +#include "../../plugin.h" +#include "../../events.h" +#include "../../string-sizes.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_TUNING[] = "clap.tuning.draft/2"; + +#ifdef __cplusplus +extern "C" { +#endif + +// Use clap_host_event_registry->query(host, CLAP_EXT_TUNING, &space_id) to know the event space. +// +// This event defines the tuning to be used on the given port/channel. +typedef struct clap_event_tuning { + clap_event_header_t header; + + int16_t port_index; // -1 global + int16_t channel; // 0..15, -1 global + clap_id tunning_id; +} clap_event_tuning_t; + +typedef struct clap_tuning_info { + clap_id tuning_id; + char name[CLAP_NAME_SIZE]; + bool is_dynamic; // true if the values may vary with time +} clap_tuning_info_t; + +typedef struct clap_plugin_tuning { + // Called when a tuning is added or removed from the pool. + // [main-thread] + void(CLAP_ABI *changed)(const clap_plugin_t *plugin); +} clap_plugin_tuning_t; + +// This extension provides a dynamic tuning table to the plugin. +typedef struct clap_host_tuning { + // Gets the relative tuning in semitones against equal temperament with A4=440Hz. + // The plugin may query the tuning at a rate that makes sense for *low* frequency modulations. + // + // If the tuning_id is not found or equals to CLAP_INVALID_ID, + // then the function shall gracefully return a sensible value. + // + // sample_offset is the sample offset from the beginning of the current process block. + // + // should_play(...) should be checked before calling this function. + // + // [audio-thread & in-process] + double(CLAP_ABI *get_relative)(const clap_host_t *host, + clap_id tuning_id, + int32_t channel, + int32_t key, + uint32_t sample_offset); + + // Returns true if the note should be played. + // [audio-thread & in-process] + bool(CLAP_ABI *should_play)(const clap_host_t *host, + clap_id tuning_id, + int32_t channel, + int32_t key); + + // Returns the number of tunings in the pool. + // [main-thread] + uint32_t(CLAP_ABI *get_tuning_count)(const clap_host_t *host); + + // Gets info about a tuning + // Returns true on success and stores the result into info. + // [main-thread] + bool(CLAP_ABI *get_info)(const clap_host_t *host, + uint32_t tuning_index, + clap_tuning_info_t *info); +} clap_host_tuning_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/event-registry.h b/include/clap/ext/event-registry.h new file mode 100644 index 0000000..c89cea4 --- /dev/null +++ b/include/clap/ext/event-registry.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_EVENT_REGISTRY[] = "clap.event-registry"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_host_event_registry { + // Queries an event space id. + // The space id 0 is reserved for CLAP's core events. See CLAP_CORE_EVENT_SPACE. + // + // Return false and sets *space_id to UINT16_MAX if the space name is unknown to the host. + // [main-thread] + bool(CLAP_ABI *query)(const clap_host_t *host, const char *space_name, uint16_t *space_id); +} clap_host_event_registry_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/gui.h b/include/clap/ext/gui.h new file mode 100644 index 0000000..7704da9 --- /dev/null +++ b/include/clap/ext/gui.h @@ -0,0 +1,237 @@ +#pragma once + +#include "../plugin.h" + +/// @page GUI +/// +/// This extension defines how the plugin will present its GUI. +/// +/// There are two approaches: +/// 1. the plugin creates a window and embeds it into the host's window +/// 2. the plugin creates a floating window +/// +/// Embedding the window gives more control to the host, and feels more integrated. +/// Floating window are sometimes the only option due to technical limitations. +/// +/// Showing the GUI works as follow: +/// 1. clap_plugin_gui->is_api_supported(), check what can work +/// 2. clap_plugin_gui->create(), allocates gui resources +/// 3. if the plugin window is floating +/// 4. -> clap_plugin_gui->set_transient() +/// 5. -> clap_plugin_gui->suggest_title() +/// 6. else +/// 7. -> clap_plugin_gui->set_scale() +/// 8. -> clap_plugin_gui->can_resize() +/// 9. -> if resizable and has known size from previous session, clap_plugin_gui->set_size() +/// 10. -> else clap_plugin_gui->get_size(), gets initial size +/// 11. -> clap_plugin_gui->set_parent() +/// 12. clap_plugin_gui->show() +/// 13. clap_plugin_gui->hide()/show() ... +/// 14. clap_plugin_gui->destroy() when done with the gui +/// +/// Resizing the window (initiated by the plugin, if embedded): +/// 1. Plugins calls clap_host_gui->request_resize() +/// 2. If the host returns true the new size is accepted, +/// the host doesn't have to call clap_plugin_gui->set_size(). +/// If the host returns false, the new size is rejected. +/// +/// Resizing the window (drag, if embedded)): +/// 1. Only possible if clap_plugin_gui->can_resize() returns true +/// 2. Mouse drag -> new_size +/// 3. clap_plugin_gui->adjust_size(new_size) -> working_size +/// 4. clap_plugin_gui->set_size(working_size) + +static CLAP_CONSTEXPR const char CLAP_EXT_GUI[] = "clap.gui"; + +// If your windowing API is not listed here, please open an issue and we'll figure it out. +// https://github.com/free-audio/clap/issues/new + +// uses physical size +// embed using https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent +static const CLAP_CONSTEXPR char CLAP_WINDOW_API_WIN32[] = "win32"; + +// uses logical size, don't call clap_plugin_gui->set_scale() +static const CLAP_CONSTEXPR char CLAP_WINDOW_API_COCOA[] = "cocoa"; + +// uses physical size +// embed using https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html +static const CLAP_CONSTEXPR char CLAP_WINDOW_API_X11[] = "x11"; + +// uses physical size +// embed is currently not supported, use floating windows +static const CLAP_CONSTEXPR char CLAP_WINDOW_API_WAYLAND[] = "wayland"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *clap_hwnd; +typedef void *clap_nsview; +typedef unsigned long clap_xwnd; + +// Represent a window reference. +typedef struct clap_window { + const char *api; // one of CLAP_WINDOW_API_XXX + union { + clap_nsview cocoa; + clap_xwnd x11; + clap_hwnd win32; + void *ptr; // for anything defined outside of clap + }; +} clap_window_t; + +// Information to improve window resizing when initiated by the host or window manager. +typedef struct clap_gui_resize_hints { + bool can_resize_horizontally; + bool can_resize_vertically; + + // only if can resize horizontally and vertically + bool preserve_aspect_ratio; + uint32_t aspect_ratio_width; + uint32_t aspect_ratio_height; +} clap_gui_resize_hints_t; + +// Size (width, height) is in pixels; the corresponding windowing system extension is +// responsible for defining if it is physical pixels or logical pixels. +typedef struct clap_plugin_gui { + // Returns true if the requested gui api is supported + // [main-thread] + bool(CLAP_ABI *is_api_supported)(const clap_plugin_t *plugin, const char *api, bool is_floating); + + // Returns true if the plugin has a preferred api. + // The host has no obligation to honor the plugin preference, this is just a hint. + // The const char **api variable should be explicitly assigned as a pointer to + // one of the CLAP_WINDOW_API_ constants defined above, not strcopied. + // [main-thread] + bool(CLAP_ABI *get_preferred_api)(const clap_plugin_t *plugin, + const char **api, + bool *is_floating); + + // Create and allocate all resources necessary for the gui. + // + // If is_floating is true, then the window will not be managed by the host. The plugin + // can set its window to stays above the parent window, see set_transient(). + // api may be null or blank for floating window. + // + // If is_floating is false, then the plugin has to embed its window into the parent window, see + // set_parent(). + // + // After this call, the GUI may not be visible yet; don't forget to call show(). + // + // Returns true if the GUI is successfully created. + // [main-thread] + bool(CLAP_ABI *create)(const clap_plugin_t *plugin, const char *api, bool is_floating); + + // Free all resources associated with the gui. + // [main-thread] + void(CLAP_ABI *destroy)(const clap_plugin_t *plugin); + + // Set the absolute GUI scaling factor, and override any OS info. + // Should not be used if the windowing api relies upon logical pixels. + // + // If the plugin prefers to work out the scaling factor itself by querying the OS directly, + // then ignore the call. + // + // scale = 2 means 200% scaling. + // + // Returns true if the scaling could be applied + // Returns false if the call was ignored, or the scaling could not be applied. + // [main-thread] + bool(CLAP_ABI *set_scale)(const clap_plugin_t *plugin, double scale); + + // Get the current size of the plugin UI. + // clap_plugin_gui->create() must have been called prior to asking the size. + // + // Returns true if the plugin could get the size. + // [main-thread] + bool(CLAP_ABI *get_size)(const clap_plugin_t *plugin, uint32_t *width, uint32_t *height); + + // Returns true if the window is resizeable (mouse drag). + // [main-thread & !floating] + bool(CLAP_ABI *can_resize)(const clap_plugin_t *plugin); + + // Returns true if the plugin can provide hints on how to resize the window. + // [main-thread & !floating] + bool(CLAP_ABI *get_resize_hints)(const clap_plugin_t *plugin, clap_gui_resize_hints_t *hints); + + // If the plugin gui is resizable, then the plugin will calculate the closest + // usable size which fits in the given size. + // This method does not change the size. + // + // Returns true if the plugin could adjust the given size. + // [main-thread & !floating] + bool(CLAP_ABI *adjust_size)(const clap_plugin_t *plugin, uint32_t *width, uint32_t *height); + + // Sets the window size. + // + // Returns true if the plugin could resize its window to the given size. + // [main-thread & !floating] + bool(CLAP_ABI *set_size)(const clap_plugin_t *plugin, uint32_t width, uint32_t height); + + // Embeds the plugin window into the given window. + // + // Returns true on success. + // [main-thread & !floating] + bool(CLAP_ABI *set_parent)(const clap_plugin_t *plugin, const clap_window_t *window); + + // Set the plugin floating window to stay above the given window. + // + // Returns true on success. + // [main-thread & floating] + bool(CLAP_ABI *set_transient)(const clap_plugin_t *plugin, const clap_window_t *window); + + // Suggests a window title. Only for floating windows. + // + // [main-thread & floating] + void(CLAP_ABI *suggest_title)(const clap_plugin_t *plugin, const char *title); + + // Show the window. + // + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *show)(const clap_plugin_t *plugin); + + // Hide the window, this method does not free the resources, it just hides + // the window content. Yet it may be a good idea to stop painting timers. + // + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *hide)(const clap_plugin_t *plugin); +} clap_plugin_gui_t; + +typedef struct clap_host_gui { + // The host should call get_resize_hints() again. + // [thread-safe & !floating] + void(CLAP_ABI *resize_hints_changed)(const clap_host_t *host); + + // Request the host to resize the client area to width, height. + // Return true if the new size is accepted, false otherwise. + // The host doesn't have to call set_size(). + // + // Note: if not called from the main thread, then a return value simply means that the host + // acknowledged the request and will process it asynchronously. If the request then can't be + // satisfied then the host will call set_size() to revert the operation. + // [thread-safe & !floating] + bool(CLAP_ABI *request_resize)(const clap_host_t *host, uint32_t width, uint32_t height); + + // Request the host to show the plugin gui. + // Return true on success, false otherwise. + // [thread-safe] + bool(CLAP_ABI *request_show)(const clap_host_t *host); + + // Request the host to hide the plugin gui. + // Return true on success, false otherwise. + // [thread-safe] + bool(CLAP_ABI *request_hide)(const clap_host_t *host); + + // The floating window has been closed, or the connection to the gui has been lost. + // + // If was_destroyed is true, then the host must call clap_plugin_gui->destroy() to acknowledge + // the gui destruction. + // [thread-safe] + void(CLAP_ABI *closed)(const clap_host_t *host, bool was_destroyed); +} clap_host_gui_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/latency.h b/include/clap/ext/latency.h new file mode 100644 index 0000000..862b334 --- /dev/null +++ b/include/clap/ext/latency.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_LATENCY[] = "clap.latency"; + +#ifdef __cplusplus +extern "C" { +#endif + +// The audio ports scan has to be done while the plugin is deactivated. +typedef struct clap_plugin_latency { + // Returns the plugin latency in samples. + // [main-thread] + uint32_t(CLAP_ABI *get)(const clap_plugin_t *plugin); +} clap_plugin_latency_t; + +typedef struct clap_host_latency { + // Tell the host that the latency changed. + // The latency is only allowed to change if the plugin is deactivated. + // If the plugin is activated, call host->request_restart() + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_latency_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/log.h b/include/clap/ext/log.h new file mode 100644 index 0000000..9609c6c --- /dev/null +++ b/include/clap/ext/log.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_LOG[] = "clap.log"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CLAP_LOG_DEBUG = 0, + CLAP_LOG_INFO = 1, + CLAP_LOG_WARNING = 2, + CLAP_LOG_ERROR = 3, + CLAP_LOG_FATAL = 4, + + // These severities should be used to report misbehaviour. + // The plugin one can be used by a layer between the plugin and the host. + CLAP_LOG_HOST_MISBEHAVING = 5, + CLAP_LOG_PLUGIN_MISBEHAVING = 6, +}; +typedef int32_t clap_log_severity; + +typedef struct clap_host_log { + // Log a message through the host. + // [thread-safe] + void(CLAP_ABI *log)(const clap_host_t *host, clap_log_severity severity, const char *msg); +} clap_host_log_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/note-name.h b/include/clap/ext/note-name.h new file mode 100644 index 0000000..41e6a83 --- /dev/null +++ b/include/clap/ext/note-name.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../plugin.h" +#include "../string-sizes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static CLAP_CONSTEXPR const char CLAP_EXT_NOTE_NAME[] = "clap.note-name"; + +typedef struct clap_note_name { + char name[CLAP_NAME_SIZE]; + int16_t port; // -1 for every port + int16_t key; // -1 for every key + int16_t channel; // -1 for every channel +} clap_note_name_t; + +typedef struct clap_plugin_note_name { + // Return the number of note names + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Returns true on success and stores the result into note_name + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, uint32_t index, clap_note_name_t *note_name); +} clap_plugin_note_name_t; + +typedef struct clap_host_note_name { + // Informs the host that the note names have changed. + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_note_name_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/note-ports.h b/include/clap/ext/note-ports.h new file mode 100644 index 0000000..f91b527 --- /dev/null +++ b/include/clap/ext/note-ports.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../plugin.h" +#include "../string-sizes.h" + +/// @page Note Ports +/// +/// This extension provides a way for the plugin to describe its current note ports. +/// If the plugin does not implement this extension, it won't have note input or output. +/// The plugin is only allowed to change its note ports configuration while it is deactivated. + +static CLAP_CONSTEXPR const char CLAP_EXT_NOTE_PORTS[] = "clap.note-ports"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum clap_note_dialect { + // Uses clap_event_note and clap_event_note_expression. + CLAP_NOTE_DIALECT_CLAP = 1 << 0, + + // Uses clap_event_midi, no polyphonic expression + CLAP_NOTE_DIALECT_MIDI = 1 << 1, + + // Uses clap_event_midi, with polyphonic expression (MPE) + CLAP_NOTE_DIALECT_MIDI_MPE = 1 << 2, + + // Uses clap_event_midi2 + CLAP_NOTE_DIALECT_MIDI2 = 1 << 3, +}; + +typedef struct clap_note_port_info { + // id identifies a port and must be stable. + // id may overlap between input and output ports. + clap_id id; + uint32_t supported_dialects; // bitfield, see clap_note_dialect + uint32_t preferred_dialect; // one value of clap_note_dialect + char name[CLAP_NAME_SIZE]; // displayable name, i18n? +} clap_note_port_info_t; + +// The note ports scan has to be done while the plugin is deactivated. +typedef struct clap_plugin_note_ports { + // Number of ports, for either input or output. + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin, bool is_input); + + // Get info about a note port. + // Returns true on success and stores the result into info. + // [main-thread] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, + uint32_t index, + bool is_input, + clap_note_port_info_t *info); +} clap_plugin_note_ports_t; + +enum { + // The ports have changed, the host shall perform a full scan of the ports. + // This flag can only be used if the plugin is not active. + // If the plugin active, call host->request_restart() and then call rescan() + // when the host calls deactivate() + CLAP_NOTE_PORTS_RESCAN_ALL = 1 << 0, + + // The ports name did change, the host can scan them right away. + CLAP_NOTE_PORTS_RESCAN_NAMES = 1 << 1, +}; + +typedef struct clap_host_note_ports { + // Query which dialects the host supports + // [main-thread] + uint32_t(CLAP_ABI *supported_dialects)(const clap_host_t *host); + + // Rescan the full list of note ports according to the flags. + // [main-thread] + void(CLAP_ABI *rescan)(const clap_host_t *host, uint32_t flags); +} clap_host_note_ports_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/params.h b/include/clap/ext/params.h new file mode 100644 index 0000000..2ec7e02 --- /dev/null +++ b/include/clap/ext/params.h @@ -0,0 +1,382 @@ +#pragma once + +#include "../plugin.h" +#include "../string-sizes.h" + +/// @page Parameters +/// @brief parameters management +/// +/// Main idea: +/// +/// The host sees the plugin as an atomic entity; and acts as a controller on top of its parameters. +/// The plugin is responsible for keeping its audio processor and its GUI in sync. +/// +/// The host can at any time read parameters' value on the [main-thread] using +/// @ref clap_plugin_params.value(). +/// +/// There are two options to communicate parameter value changes, and they are not concurrent. +/// - send automation points during clap_plugin.process() +/// - send automation points during clap_plugin_params.flush(), for parameter changes +/// without processing audio +/// +/// When the plugin changes a parameter value, it must inform the host. +/// It will send @ref CLAP_EVENT_PARAM_VALUE event during process() or flush(). +/// If the user is adjusting the value, don't forget to mark the beginning and end +/// of the gesture by sending CLAP_EVENT_PARAM_GESTURE_BEGIN and CLAP_EVENT_PARAM_GESTURE_END +/// events. +/// +/// @note MIDI CCs are tricky because you may not know when the parameter adjustment ends. +/// Also if the host records incoming MIDI CC and parameter change automation at the same time, +/// there will be a conflict at playback: MIDI CC vs Automation. +/// The parameter automation will always target the same parameter because the param_id is stable. +/// The MIDI CC may have a different mapping in the future and may result in a different playback. +/// +/// When a MIDI CC changes a parameter's value, set the flag CLAP_EVENT_DONT_RECORD in +/// clap_event_param.header.flags. That way the host may record the MIDI CC automation, but not the +/// parameter change and there won't be conflict at playback. +/// +/// Scenarios: +/// +/// I. Loading a preset +/// - load the preset in a temporary state +/// - call @ref clap_host_params.rescan() if anything changed +/// - call @ref clap_host_latency.changed() if latency changed +/// - invalidate any other info that may be cached by the host +/// - if the plugin is activated and the preset will introduce breaking changes +/// (latency, audio ports, new parameters, ...) be sure to wait for the host +/// to deactivate the plugin to apply those changes. +/// If there are no breaking changes, the plugin can apply them them right away. +/// The plugin is responsible for updating both its audio processor and its gui. +/// +/// II. Turning a knob on the DAW interface +/// - the host will send an automation event to the plugin via a process() or flush() +/// +/// III. Turning a knob on the Plugin interface +/// - the plugin is responsible for sending the parameter value to its audio processor +/// - call clap_host_params->request_flush() or clap_host->request_process(). +/// - when the host calls either clap_plugin->process() or clap_plugin_params->flush(), +/// send an automation event and don't forget to wrap the parameter change(s) +/// with CLAP_EVENT_PARAM_GESTURE_BEGIN and CLAP_EVENT_PARAM_GESTURE_END to define the +/// beginning and end of the gesture. +/// +/// IV. Turning a knob via automation +/// - host sends an automation point during clap_plugin->process() or clap_plugin_params->flush(). +/// - the plugin is responsible for updating its GUI +/// +/// V. Turning a knob via plugin's internal MIDI mapping +/// - the plugin sends a CLAP_EVENT_PARAM_VALUE output event, set should_record to false +/// - the plugin is responsible for updating its GUI +/// +/// VI. Adding or removing parameters +/// - if the plugin is activated call clap_host->restart() +/// - once the plugin isn't active: +/// - apply the new state +/// - if a parameter is gone or is created with an id that may have been used before, +/// call clap_host_params.clear(host, param_id, CLAP_PARAM_CLEAR_ALL) +/// - call clap_host_params->rescan(CLAP_PARAM_RESCAN_ALL) +/// +/// CLAP allows the plugin to change the parameter range, yet the plugin developer +/// should be aware that doing so isn't without risk, especially if you made the +/// promise to never change the sound. If you want to be 100% certain that the +/// sound will not change with all host, then simply never change the range. +/// +/// There are two approaches to automations, either you automate the plain value, +/// or you automate the knob position. The first option will be robust to a range +/// increase, while the second won't be. +/// +/// If the host goes with the second approach (automating the knob position), it means +/// that the plugin is hosted in a relaxed environment regarding sound changes (they are +/// accepted, and not a concern as long as they are reasonable). Though, stepped parameters +/// should be stored as plain value in the document. +/// +/// If the host goes with the first approach, there will still be situation where the +/// sound may inevitably change. For example, if the plugin increase the range, there +/// is an automation playing at the max value and on top of that an LFO is applied. +/// See the following curve: +/// . +/// . . +/// ..... . . +/// before: . . and after: . . +/// +/// Persisting parameter values: +/// +/// Plugins are responsible for persisting their parameter's values between +/// sessions by implementing the state extension. Otherwise parameter value will +/// not be recalled when reloading a project. Hosts should _not_ try to save and +/// restore parameter values for plugins that don't implement the state +/// extension. +/// +/// Advice for the host: +/// +/// - store plain values in the document (automation) +/// - store modulation amount in plain value delta, not in percentage +/// - when you apply a CC mapping, remember the min/max plain values so you can adjust +/// - do not implement a parameter saving fall back for plugins that don't +/// implement the state extension +/// +/// Advice for the plugin: +/// +/// - think carefully about your parameter range when designing your DSP +/// - avoid shrinking parameter ranges, they are very likely to change the sound +/// - consider changing the parameter range as a tradeoff: what you improve vs what you break +/// - make sure to implement saving and loading the parameter values using the +/// state extension +/// - if you plan to use adapters for other plugin formats, then you need to pay extra +/// attention to the adapter requirements + +static CLAP_CONSTEXPR const char CLAP_EXT_PARAMS[] = "clap.params"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // Is this param stepped? (integer values only) + // if so the double value is converted to integer using a cast (equivalent to trunc). + CLAP_PARAM_IS_STEPPED = 1 << 0, + + // Useful for periodic parameters like a phase + CLAP_PARAM_IS_PERIODIC = 1 << 1, + + // The parameter should not be shown to the user, because it is currently not used. + // It is not necessary to process automation for this parameter. + CLAP_PARAM_IS_HIDDEN = 1 << 2, + + // The parameter can't be changed by the host. + CLAP_PARAM_IS_READONLY = 1 << 3, + + // This parameter is used to merge the plugin and host bypass button. + // It implies that the parameter is stepped. + // min: 0 -> bypass off + // max: 1 -> bypass on + CLAP_PARAM_IS_BYPASS = 1 << 4, + + // When set: + // - automation can be recorded + // - automation can be played back + // + // The host can send live user changes for this parameter regardless of this flag. + // + // If this parameter affects the internal processing structure of the plugin, ie: max delay, fft + // size, ... and the plugins needs to re-allocate its working buffers, then it should call + // host->request_restart(), and perform the change once the plugin is re-activated. + CLAP_PARAM_IS_AUTOMATABLE = 1 << 5, + + // Does this parameter support per note automations? + CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID = 1 << 6, + + // Does this parameter support per key automations? + CLAP_PARAM_IS_AUTOMATABLE_PER_KEY = 1 << 7, + + // Does this parameter support per channel automations? + CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL = 1 << 8, + + // Does this parameter support per port automations? + CLAP_PARAM_IS_AUTOMATABLE_PER_PORT = 1 << 9, + + // Does this parameter support the modulation signal? + CLAP_PARAM_IS_MODULATABLE = 1 << 10, + + // Does this parameter support per note modulations? + CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID = 1 << 11, + + // Does this parameter support per key modulations? + CLAP_PARAM_IS_MODULATABLE_PER_KEY = 1 << 12, + + // Does this parameter support per channel modulations? + CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL = 1 << 13, + + // Does this parameter support per port modulations? + CLAP_PARAM_IS_MODULATABLE_PER_PORT = 1 << 14, + + // Any change to this parameter will affect the plugin output and requires to be done via + // process() if the plugin is active. + // + // A simple example would be a DC Offset, changing it will change the output signal and must be + // processed. + CLAP_PARAM_REQUIRES_PROCESS = 1 << 15, + + // This parameter represents an enumerated value. + // If you set this flag, then you must set CLAP_PARAM_IS_STEPPED too. + // All values from min to max must not have a blank value_to_text(). + CLAP_PARAM_IS_ENUM = 1 << 16, +}; +typedef uint32_t clap_param_info_flags; + +/* This describes a parameter */ +typedef struct clap_param_info { + // Stable parameter identifier, it must never change. + clap_id id; + + clap_param_info_flags flags; + + // This value is optional and set by the plugin. + // Its purpose is to provide fast access to the plugin parameter object by caching its pointer. + // For instance: + // + // in clap_plugin_params.get_info(): + // Parameter *p = findParameter(param_id); + // param_info->cookie = p; + // + // later, in clap_plugin.process(): + // + // Parameter *p = (Parameter *)event->cookie; + // if (!p) [[unlikely]] + // p = findParameter(event->param_id); + // + // where findParameter() is a function the plugin implements to map parameter ids to internal + // objects. + // + // Important: + // - The cookie is invalidated by a call to clap_host_params->rescan(CLAP_PARAM_RESCAN_ALL) or + // when the plugin is destroyed. + // - The host will either provide the cookie as issued or nullptr in events addressing + // parameters. + // - The plugin must gracefully handle the case of a cookie which is nullptr. + // - Many plugins will process the parameter events more quickly if the host can provide the + // cookie in a faster time than a hashmap lookup per param per event. + void *cookie; + + // The display name. eg: "Volume". This does not need to be unique. Do not include the module + // text in this. The host should concatenate/format the module + name in the case where showing + // the name alone would be too vague. + char name[CLAP_NAME_SIZE]; + + // The module path containing the param, eg: "Oscillators/Wavetable 1". + // '/' will be used as a separator to show a tree-like structure. + char module[CLAP_PATH_SIZE]; + + double min_value; // Minimum plain value + double max_value; // Maximum plain value + double default_value; // Default plain value +} clap_param_info_t; + +typedef struct clap_plugin_params { + // Returns the number of parameters. + // [main-thread] + uint32_t(CLAP_ABI *count)(const clap_plugin_t *plugin); + + // Copies the parameter's info to param_info. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *get_info)(const clap_plugin_t *plugin, + uint32_t param_index, + clap_param_info_t *param_info); + + // Writes the parameter's current value to out_value. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *get_value)(const clap_plugin_t *plugin, clap_id param_id, double *out_value); + + // Fills out_buffer with a null-terminated UTF-8 string that represents the parameter at the + // given 'value' argument. eg: "2.3 kHz". The host should always use this to format parameter + // values before displaying it to the user. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *value_to_text)(const clap_plugin_t *plugin, + clap_id param_id, + double value, + char *out_buffer, + uint32_t out_buffer_capacity); + + // Converts the null-terminated UTF-8 param_value_text into a double and writes it to out_value. + // The host can use this to convert user input into a parameter value. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *text_to_value)(const clap_plugin_t *plugin, + clap_id param_id, + const char *param_value_text, + double *out_value); + + // Flushes a set of parameter changes. + // This method must not be called concurrently to clap_plugin->process(). + // + // Note: if the plugin is processing, then the process() call will already achieve the + // parameter update (bi-directional), so a call to flush isn't required, also be aware + // that the plugin may use the sample offset in process(), while this information would be + // lost within flush(). + // + // [active ? audio-thread : main-thread] + void(CLAP_ABI *flush)(const clap_plugin_t *plugin, + const clap_input_events_t *in, + const clap_output_events_t *out); +} clap_plugin_params_t; + +enum { + // The parameter values did change, eg. after loading a preset. + // The host will scan all the parameters value. + // The host will not record those changes as automation points. + // New values takes effect immediately. + CLAP_PARAM_RESCAN_VALUES = 1 << 0, + + // The value to text conversion changed, and the text needs to be rendered again. + CLAP_PARAM_RESCAN_TEXT = 1 << 1, + + // The parameter info did change, use this flag for: + // - name change + // - module change + // - is_periodic (flag) + // - is_hidden (flag) + // New info takes effect immediately. + CLAP_PARAM_RESCAN_INFO = 1 << 2, + + // Invalidates everything the host knows about parameters. + // It can only be used while the plugin is deactivated. + // If the plugin is activated use clap_host->restart() and delay any change until the host calls + // clap_plugin->deactivate(). + // + // You must use this flag if: + // - some parameters were added or removed. + // - some parameters had critical changes: + // - is_per_note (flag) + // - is_per_key (flag) + // - is_per_channel (flag) + // - is_per_port (flag) + // - is_readonly (flag) + // - is_bypass (flag) + // - is_stepped (flag) + // - is_modulatable (flag) + // - min_value + // - max_value + // - cookie + CLAP_PARAM_RESCAN_ALL = 1 << 3, +}; +typedef uint32_t clap_param_rescan_flags; + +enum { + // Clears all possible references to a parameter + CLAP_PARAM_CLEAR_ALL = 1 << 0, + + // Clears all automations to a parameter + CLAP_PARAM_CLEAR_AUTOMATIONS = 1 << 1, + + // Clears all modulations to a parameter + CLAP_PARAM_CLEAR_MODULATIONS = 1 << 2, +}; +typedef uint32_t clap_param_clear_flags; + +typedef struct clap_host_params { + // Rescan the full list of parameters according to the flags. + // [main-thread] + void(CLAP_ABI *rescan)(const clap_host_t *host, clap_param_rescan_flags flags); + + // Clears references to a parameter. + // [main-thread] + void(CLAP_ABI *clear)(const clap_host_t *host, clap_id param_id, clap_param_clear_flags flags); + + // Request a parameter flush. + // + // The host will then schedule a call to either: + // - clap_plugin.process() + // - clap_plugin_params.flush() + // + // This function is always safe to use and should not be called from an [audio-thread] as the + // plugin would already be within process() or flush(). + // + // [thread-safe,!audio-thread] + void(CLAP_ABI *request_flush)(const clap_host_t *host); +} clap_host_params_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/posix-fd-support.h b/include/clap/ext/posix-fd-support.h new file mode 100644 index 0000000..c713ec8 --- /dev/null +++ b/include/clap/ext/posix-fd-support.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../plugin.h" + +// This extension let your plugin hook itself into the host select/poll/epoll/kqueue reactor. +// This is useful to handle asynchronous I/O on the main thread. +static CLAP_CONSTEXPR const char CLAP_EXT_POSIX_FD_SUPPORT[] = "clap.posix-fd-support"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // IO events flags, they can be used to form a mask which describes: + // - which events you are interested in (register_fd/modify_fd) + // - which events happened (on_fd) + CLAP_POSIX_FD_READ = 1 << 0, + CLAP_POSIX_FD_WRITE = 1 << 1, + CLAP_POSIX_FD_ERROR = 1 << 2, +}; +typedef uint32_t clap_posix_fd_flags_t; + +typedef struct clap_plugin_posix_fd_support { + // This callback is "level-triggered". + // It means that a writable fd will continuously produce "on_fd()" events; + // don't forget using modify_fd() to remove the write notification once you're + // done writing. + // + // [main-thread] + void(CLAP_ABI *on_fd)(const clap_plugin_t *plugin, int fd, clap_posix_fd_flags_t flags); +} clap_plugin_posix_fd_support_t; + +typedef struct clap_host_posix_fd_support { + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *register_fd)(const clap_host_t *host, int fd, clap_posix_fd_flags_t flags); + + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *modify_fd)(const clap_host_t *host, int fd, clap_posix_fd_flags_t flags); + + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *unregister_fd)(const clap_host_t *host, int fd); +} clap_host_posix_fd_support_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/render.h b/include/clap/ext/render.h new file mode 100644 index 0000000..0e1c1cd --- /dev/null +++ b/include/clap/ext/render.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_RENDER[] = "clap.render"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // Default setting, for "realtime" processing + CLAP_RENDER_REALTIME = 0, + + // For processing without realtime pressure + // The plugin may use more expensive algorithms for higher sound quality. + CLAP_RENDER_OFFLINE = 1, +}; +typedef int32_t clap_plugin_render_mode; + +// The render extension is used to let the plugin know if it has "realtime" +// pressure to process. +// +// If this information does not influence your rendering code, then don't +// implement this extension. +typedef struct clap_plugin_render { + // Returns true if the plugin has a hard requirement to process in real-time. + // This is especially useful for plugin acting as a proxy to an hardware device. + // [main-thread] + bool(CLAP_ABI *has_hard_realtime_requirement)(const clap_plugin_t *plugin); + + // Returns true if the rendering mode could be applied. + // [main-thread] + bool(CLAP_ABI *set)(const clap_plugin_t *plugin, clap_plugin_render_mode mode); +} clap_plugin_render_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/state.h b/include/clap/ext/state.h new file mode 100644 index 0000000..8d028e1 --- /dev/null +++ b/include/clap/ext/state.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../plugin.h" +#include "../stream.h" + +/// @page State +/// @brief state management +/// +/// Plugins can implement this extension to save and restore both parameter +/// values and non-parameter state. This is used to persist a plugin's state +/// between project reloads, when duplicating and copying plugin instances, and +/// for host-side preset management. + +static CLAP_CONSTEXPR const char CLAP_EXT_STATE[] = "clap.state"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_state { + // Saves the plugin state into stream. + // Returns true if the state was correctly saved. + // [main-thread] + bool(CLAP_ABI *save)(const clap_plugin_t *plugin, const clap_ostream_t *stream); + + // Loads the plugin state from stream. + // Returns true if the state was correctly restored. + // [main-thread] + bool(CLAP_ABI *load)(const clap_plugin_t *plugin, const clap_istream_t *stream); +} clap_plugin_state_t; + +typedef struct clap_host_state { + // Tell the host that the plugin state has changed and should be saved again. + // If a parameter value changes, then it is implicit that the state is dirty. + // [main-thread] + void(CLAP_ABI *mark_dirty)(const clap_host_t *host); +} clap_host_state_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/tail.h b/include/clap/ext/tail.h new file mode 100644 index 0000000..5d79e3a --- /dev/null +++ b/include/clap/ext/tail.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_TAIL[] = "clap.tail"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_tail { + // Returns tail length in samples. + // Any value greater or equal to INT32_MAX implies infinite tail. + // [main-thread,audio-thread] + uint32_t(CLAP_ABI *get)(const clap_plugin_t *plugin); +} clap_plugin_tail_t; + +typedef struct clap_host_tail { + // Tell the host that the tail has changed. + // [audio-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_tail_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/thread-check.h b/include/clap/ext/thread-check.h new file mode 100644 index 0000000..7b68e1b --- /dev/null +++ b/include/clap/ext/thread-check.h @@ -0,0 +1,53 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_THREAD_CHECK[] = "clap.thread-check"; + +#ifdef __cplusplus +extern "C" { +#endif + +/// @page thread-check +/// +/// CLAP defines two symbolic threads: +/// +/// main-thread: +/// This is the thread in which most of the interaction between the plugin and host happens. +/// This will be the same OS thread throughout the lifetime of the plug-in. +/// On macOS and Windows, this must be the thread on which gui and timer events are received +/// (i.e., the main thread of the program). +/// It isn't a realtime thread, yet this thread needs to respond fast enough to user interaction, +/// so it is recommended to run long and expensive tasks such as preset indexing or asset loading +/// in dedicated background threads. +/// +/// audio-thread: +/// This thread is used for realtime audio processing. Its execution should be as deterministic +/// as possible to meet the audio interface's deadline (can be <1ms). In other words, there is a +/// known set of operations that should be avoided: malloc() and free(), mutexes (spin mutexes +/// are worse), I/O, waiting, ... +/// The audio-thread is something symbolic, there isn't one OS thread that remains the +/// audio-thread for the plugin lifetime. As you may guess, the host is likely to have a +/// thread pool and the plugin.process() call may be scheduled on different OS threads over time. +/// The most important thing is that there can't be two audio-threads at the same time. All the +/// functions marked with [audio-thread] **ARE NOT CONCURRENT**. The host may mark any OS thread, +/// including the main-thread as the audio-thread, as long as it can guarantee that only one OS +/// thread is the audio-thread at a time. The audio-thread can be seen as a concurrency guard for +/// all functions marked with [audio-thread]. + +// This interface is useful to do runtime checks and make +// sure that the functions are called on the correct threads. +// It is highly recommended that hosts implement this extension. +typedef struct clap_host_thread_check { + // Returns true if "this" thread is the main thread. + // [thread-safe] + bool(CLAP_ABI *is_main_thread)(const clap_host_t *host); + + // Returns true if "this" thread is one of the audio threads. + // [thread-safe] + bool(CLAP_ABI *is_audio_thread)(const clap_host_t *host); +} clap_host_thread_check_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/thread-pool.h b/include/clap/ext/thread-pool.h new file mode 100644 index 0000000..790bef9 --- /dev/null +++ b/include/clap/ext/thread-pool.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../plugin.h" + +/// @page +/// +/// This extension lets the plugin use the host's thread pool. +/// +/// The plugin must provide @ref clap_plugin_thread_pool, and the host may provide @ref +/// clap_host_thread_pool. If it doesn't, the plugin should process its data by its own means. In +/// the worst case, a single threaded for-loop. +/// +/// Simple example with N voices to process +/// +/// @code +/// void myplug_thread_pool_exec(const clap_plugin *plugin, uint32_t voice_index) +/// { +/// compute_voice(plugin, voice_index); +/// } +/// +/// void myplug_process(const clap_plugin *plugin, const clap_process *process) +/// { +/// ... +/// bool didComputeVoices = false; +/// if (host_thread_pool && host_thread_pool.exec) +/// didComputeVoices = host_thread_pool.request_exec(host, plugin, N); +/// +/// if (!didComputeVoices) +/// for (uint32_t i = 0; i < N; ++i) +/// myplug_thread_pool_exec(plugin, i); +/// ... +/// } +/// @endcode +/// +/// Be aware that using a thread pool may break hard real-time rules due to the thread +/// synchronization involved. +/// +/// If the host knows that it is running under hard real-time pressure it may decide to not +/// provide this interface. + +static CLAP_CONSTEXPR const char CLAP_EXT_THREAD_POOL[] = "clap.thread-pool"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_thread_pool { + // Called by the thread pool + void(CLAP_ABI *exec)(const clap_plugin_t *plugin, uint32_t task_index); +} clap_plugin_thread_pool_t; + +typedef struct clap_host_thread_pool { + // Schedule num_tasks jobs in the host thread pool. + // It can't be called concurrently or from the thread pool. + // Will block until all the tasks are processed. + // This must be used exclusively for realtime processing within the process call. + // Returns true if the host did execute all the tasks, false if it rejected the request. + // The host should check that the plugin is within the process call, and if not, reject the exec + // request. + // [audio-thread] + bool(CLAP_ABI *request_exec)(const clap_host_t *host, uint32_t num_tasks); +} clap_host_thread_pool_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/timer-support.h b/include/clap/ext/timer-support.h new file mode 100644 index 0000000..221de96 --- /dev/null +++ b/include/clap/ext/timer-support.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../plugin.h" + +static CLAP_CONSTEXPR const char CLAP_EXT_TIMER_SUPPORT[] = "clap.timer-support"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_timer_support { + // [main-thread] + void(CLAP_ABI *on_timer)(const clap_plugin_t *plugin, clap_id timer_id); +} clap_plugin_timer_support_t; + +typedef struct clap_host_timer_support { + // Registers a periodic timer. + // The host may adjust the period if it is under a certain threshold. + // 30 Hz should be allowed. + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *register_timer)(const clap_host_t *host, uint32_t period_ms, clap_id *timer_id); + + // Returns true on success. + // [main-thread] + bool(CLAP_ABI *unregister_timer)(const clap_host_t *host, clap_id timer_id); +} clap_host_timer_support_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/ext/voice-info.h b/include/clap/ext/voice-info.h new file mode 100644 index 0000000..344ed8a --- /dev/null +++ b/include/clap/ext/voice-info.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../plugin.h" + +// This extension indicates the number of voices the synthesizer has. +// It is useful for the host when performing polyphonic modulations, +// because the host needs its own voice management and should try to follow +// what the plugin is doing: +// - make the host's voice pool coherent with what the plugin has +// - turn the host's voice management to mono when the plugin is mono + +static const char CLAP_EXT_VOICE_INFO[] = "clap.voice-info"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + // Allows the host to send overlapping NOTE_ON events. + // The plugin will then rely upon the note_id to distinguish between them. + CLAP_VOICE_INFO_SUPPORTS_OVERLAPPING_NOTES = 1 << 0, +}; + +typedef struct clap_voice_info { + // voice_count is the current number of voices that the patch can use + // voice_capacity is the number of voices allocated voices + // voice_count should not be confused with the number of active voices. + // + // 1 <= voice_count <= voice_capacity + // + // For example, a synth can have a capacity of 8 voices, but be configured + // to only use 4 voices: {count: 4, capacity: 8}. + // + // If the voice_count is 1, then the synth is working in mono and the host + // can decide to only use global modulation mapping. + uint32_t voice_count; + uint32_t voice_capacity; + + uint64_t flags; +} clap_voice_info_t; + +typedef struct clap_plugin_voice_info { + // gets the voice info, returns true on success + // [main-thread && active] + bool(CLAP_ABI *get)(const clap_plugin_t *plugin, clap_voice_info_t *info); +} clap_plugin_voice_info_t; + +typedef struct clap_host_voice_info { + // informs the host that the voice info has changed + // [main-thread] + void(CLAP_ABI *changed)(const clap_host_t *host); +} clap_host_voice_info_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/factory/draft/plugin-invalidation.h b/include/clap/factory/draft/plugin-invalidation.h new file mode 100644 index 0000000..ea698e5 --- /dev/null +++ b/include/clap/factory/draft/plugin-invalidation.h @@ -0,0 +1,47 @@ +#pragma once + +#include "../../private/std.h" +#include "../../private/macros.h" + +// Use it to retrieve const clap_plugin_invalidation_factory_t* from +// clap_plugin_entry.get_factory() +static const CLAP_CONSTEXPR char CLAP_PLUGIN_INVALIDATION_FACTORY_ID[] = + "clap.plugin-invalidation-factory/draft0"; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_plugin_invalidation_source { + // Directory containing the file(s) to scan, must be absolute + const char *directory; + + // globing pattern, in the form *.dll + const char *filename_glob; + + // should the directory be scanned recursively? + bool recursive_scan; +} clap_plugin_invalidation_source_t; + +// Used to figure out when a plugin needs to be scanned again. +// Imagine a situation with a single entry point: my-plugin.clap which then scans itself +// a set of "sub-plugins". New plugin may be available even if my-plugin.clap file doesn't change. +// This interfaces solves this issue and gives a way to the host to monitor additional files. +typedef struct clap_plugin_invalidation_factory { + // Get the number of invalidation source. + uint32_t(CLAP_ABI *count)(const struct clap_plugin_invalidation_factory *factory); + + // Get the invalidation source by its index. + // [thread-safe] + const clap_plugin_invalidation_source_t *(CLAP_ABI *get)( + const struct clap_plugin_invalidation_factory *factory, uint32_t index); + + // In case the host detected a invalidation event, it can call refresh() to let the + // plugin_entry update the set of plugins available. + // If the function returned false, then the plugin needs to be reloaded. + bool(CLAP_ABI *refresh)(const struct clap_plugin_invalidation_factory *factory); +} clap_plugin_invalidation_factory_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/factory/draft/preset-discovery.h b/include/clap/factory/draft/preset-discovery.h new file mode 100644 index 0000000..05223d9 --- /dev/null +++ b/include/clap/factory/draft/preset-discovery.h @@ -0,0 +1,327 @@ +/* + Preset Discovery API. + + Preset Discovery enables a plug-in host to identify where presets are found, what + extensions they have, which plug-ins they apply to, and other metadata associated with the + presets so that they can be indexed and searched for quickly within the plug-in host's browser. + + This has a number of advantages for the user: + - it allows them to browse for presets from one central location in a consistent way + - the user can browse for presets without having to commit to a particular plug-in first + + The API works as follow to index presets and presets metadata: + 1. clap_plugin_entry.get_factory(CLAP_PRESET_DISCOVERY_FACTORY_ID) + 2. clap_preset_discovery_factory_t.create(...) + 3. clap_preset_discovery_provider.init() (only necessary the first time, declarations + can be cached) + `-> clap_preset_discovery_indexer.declare_filetype() + `-> clap_preset_discovery_indexer.declare_location() + `-> clap_preset_discovery_indexer.declare_soundpack() (optional) + `-> clap_preset_discovery_indexer.set_invalidation_watch_file() (optional) + 4. crawl the given locations and monitor file system changes + `-> clap_preset_discovery_indexer.get_metadata() for each presets files + + Then to load a preset, use ext/draft/preset-load.h. + TODO: create a dedicated repo for other plugin abi preset-load extension. + + The design of this API deliberately does not define a fixed set tags or categories. It is the + plug-in host's job to try to intelligently map the raw list of features that are found for a + preset and to process this list to generate something that makes sense for the host's tagging and + categorization system. The reason for this is to reduce the work for a plug-in developer to add + Preset Discovery support for their existing preset file format and not have to be concerned with + all the different hosts and how they want to receive the metadata. + + VERY IMPORTANT: + - the whole indexing process has to be **fast** + - clap_preset_provider->get_metadata() has to be fast and avoid unnecessary operations + - the whole indexing process must not be interactive + - don't show dialogs, windows, ... + - don't ask for user input +*/ + +#pragma once + +#include "../../private/std.h" +#include "../../private/macros.h" +#include "../../version.h" + +// Use it to retrieve const clap_preset_discovery_factory_t* from +// clap_plugin_entry.get_factory() +static const CLAP_CONSTEXPR char CLAP_PRESET_DISCOVERY_FACTORY_ID[] = + "clap.preset-discovery-factory/draft-2"; + +#ifdef __cplusplus +extern "C" { +#endif + +enum clap_preset_discovery_location_kind { + // The preset are located in a file on the OS filesystem. + // The location is then a path which works with the OS file system functions (open, stat, ...) + // So both '/' and '\' shall work on Windows as a separator. + CLAP_PRESET_DISCOVERY_LOCATION_FILE = 0, + + // The preset is bundled within the plugin DSO itself. + // The location must then be null, as the preset are within the plugin itself and then the plugin + // will act as a preset container. + CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN = 1, +}; + +enum clap_preset_discovery_flags { + // This is for factory or sound-pack presets. + CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT = 1 << 0, + + // This is for user presets. + CLAP_PRESET_DISCOVERY_IS_USER_CONTENT = 1 << 1, + + // This location is meant for demo presets, those are preset which may trigger + // some limitation in the plugin because they require additional features which the user + // needs to purchase or the content itself needs to be bought and is only available in + // demo mode. + CLAP_PRESET_DISCOVERY_IS_DEMO_CONTENT = 1 << 2, + + // This preset is a user's favorite + CLAP_PRESET_DISCOVERY_IS_FAVORITE = 1 << 3, +}; + +// TODO: move clap_timestamp_t, CLAP_TIMESTAMP_UNKNOWN and clap_plugin_id_t to parent files once we +// settle with preset discovery + +// This type defines a timestamp: the number of seconds since UNIX EPOCH. +// See C's time_t time(time_t *). +typedef uint64_t clap_timestamp_t; + +// Value for unknown timestamp. +static const clap_timestamp_t CLAP_TIMESTAMP_UNKNOWN = 0; + +// Pair of plugin ABI and plugin identifier +typedef struct clap_plugin_id { + // The plugin ABI name, in lowercase. + // eg: "clap" + const char *abi; + + // The plugin ID, for example "com.u-he.Diva". + // If the ABI rely upon binary plugin ids, then they shall be hex encoded (lower case). + const char *id; +} clap_plugin_id_t; + +// Receiver that receives the metadata for a single preset file. +// The host would define the various callbacks in this interface and the preset parser function +// would then call them. +// +// This interface isn't thread-safe. +typedef struct clap_preset_discovery_metadata_receiver { + void *receiver_data; // reserved pointer for the metadata receiver + + // If there is an error reading metadata from a file this should be called with an error + // message. + // os_error: the operating system error, if applicable. If not applicable set it to a non-error + // value, eg: 0 on unix and Windows. + void(CLAP_ABI *on_error)(const struct clap_preset_discovery_metadata_receiver *receiver, + int32_t os_error, + const char *error_message); + + // This must be called for every preset in the file and before any preset metadata is + // sent with the calls below. + // + // If the preset file is a preset container then name and load_key are mandatory, otherwise + // they are optional. + // + // The load_key is a machine friendly string used to load the preset inside the container via a + // the preset-load plug-in extension. The load_key can also just be the subpath if that's what + // the plugin wants but it could also be some other unique id like a database primary key or a + // binary offset. It's use is entirely up to the plug-in. + // + // If the function returns false, then the provider must stop calling back into the receiver. + bool(CLAP_ABI *begin_preset)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *name, + const char *load_key); + + // Adds a plug-in id that this preset can be used with. + void(CLAP_ABI *add_plugin_id)(const struct clap_preset_discovery_metadata_receiver *receiver, + const clap_plugin_id_t *plugin_id); + + // Sets the sound pack to which the preset belongs to. + void(CLAP_ABI *set_soundpack_id)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *soundpack_id); + + // Sets the flags, see clap_preset_discovery_flags. + // If unset, they are then inherited from the location. + void(CLAP_ABI *set_flags)(const struct clap_preset_discovery_metadata_receiver *receiver, + uint32_t flags); + + // Adds a creator name for the preset. + void(CLAP_ABI *add_creator)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *creator); + + // Sets a description of the preset. + void(CLAP_ABI *set_description)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *description); + + // Sets the creation time and last modification time of the preset. + // If one of the times isn't known, set it to CLAP_TIMESTAMP_UNKNOWN. + // If this function is not called, then the indexer may look at the file's creation and + // modification time. + void(CLAP_ABI *set_timestamps)(const struct clap_preset_discovery_metadata_receiver *receiver, + clap_timestamp_t creation_time, + clap_timestamp_t modification_time); + + // Adds a feature to the preset. + // + // The feature string is arbitrary, it is the indexer's job to understand it and remap it to its + // internal categorization and tagging system. + // + // However, the strings from plugin-features.h should be understood by the indexer and one of the + // plugin category could be provided to determine if the preset will result into an audio-effect, + // instrument, ... + // + // Examples: + // kick, drum, tom, snare, clap, cymbal, bass, lead, metalic, hardsync, crossmod, acid, + // distorted, drone, pad, dirty, etc... + void(CLAP_ABI *add_feature)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *feature); + + // Adds extra information to the metadata. + void(CLAP_ABI *add_extra_info)(const struct clap_preset_discovery_metadata_receiver *receiver, + const char *key, + const char *value); +} clap_preset_discovery_metadata_receiver_t; + +typedef struct clap_preset_discovery_filetype { + const char *name; + const char *description; // optional + + // `.' isn't included in the string. + // If empty or NULL then every file should be matched. + const char *file_extension; +} clap_preset_discovery_filetype_t; + +// Defines a place in which to search for presets +typedef struct clap_preset_discovery_location { + uint32_t flags; // see enum clap_preset_discovery_flags + const char *name; // name of this location + uint32_t kind; // See clap_preset_discovery_location_kind + + // Actual location in which to crawl presets. + // For FILE kind, the location can be either a path to a directory or a file. + // For PLUGIN kind, the location must be null. + const char *location; +} clap_preset_discovery_location_t; + +// Describes an installed sound pack. +typedef struct clap_preset_discovery_soundpack { + uint32_t flags; // see enum clap_preset_discovery_flags + const char *id; // sound pack identifier + const char *name; // name of this sound pack + const char *description; // optional, reasonably short description of the sound pack + const char *homepage_url; // optional, url to the pack's homepage + const char *vendor; // optional, sound pack's vendor + const char *image_path; // optional, an image on disk + clap_timestamp_t release_timestamp; // release date, CLAP_TIMESTAMP_UNKNOWN if unavailable +} clap_preset_discovery_soundpack_t; + +// Describes a preset provider +typedef struct clap_preset_discovery_provider_descriptor { + clap_version_t clap_version; // initialized to CLAP_VERSION + const char *id; // see plugin.h for advice on how to choose a good identifier + const char *name; // eg: "Diva's preset provider" + const char *vendor; // optional, eg: u-he +} clap_preset_discovery_provider_descriptor_t; + +// This interface isn't thread-safe. +typedef struct clap_preset_discovery_provider { + const clap_preset_discovery_provider_descriptor_t *desc; + + void *provider_data; // reserved pointer for the provider + + // Initialize the preset provider. + // It should declare all its locations, filetypes and sound packs. + // Returns false if initialization failed. + bool(CLAP_ABI *init)(const struct clap_preset_discovery_provider *provider); + + // Destroys the preset provider + void(CLAP_ABI *destroy)(const struct clap_preset_discovery_provider *provider); + + // reads metadata from the given file and passes them to the metadata receiver + // Returns true on success. + bool(CLAP_ABI *get_metadata)(const struct clap_preset_discovery_provider *provider, + uint32_t location_kind, + const char *location, + const clap_preset_discovery_metadata_receiver_t *metadata_receiver); + + // Query an extension. + // The returned pointer is owned by the provider. + // It is forbidden to call it before provider->init(). + // You can call it within provider->init() call, and after. + const void *(CLAP_ABI *get_extension)(const struct clap_preset_discovery_provider *provider, + const char *extension_id); +} clap_preset_discovery_provider_t; + +// This interface isn't thread-safe +typedef struct clap_preset_discovery_indexer { + clap_version_t clap_version; // initialized to CLAP_VERSION + const char *name; // eg: "Bitwig Studio" + const char *vendor; // optional, eg: "Bitwig GmbH" + const char *url; // optional, eg: "https://bitwig.com" + const char *version; // optional, eg: "4.3", see plugin.h for advice on how to format the version + + void *indexer_data; // reserved pointer for the indexer + + // Declares a preset filetype. + // Don't callback into the provider during this call. + // Returns false if the filetype is invalid. + bool(CLAP_ABI *declare_filetype)(const struct clap_preset_discovery_indexer *indexer, + const clap_preset_discovery_filetype_t *filetype); + + // Declares a preset location. + // Don't callback into the provider during this call. + // Returns false if the location is invalid. + bool(CLAP_ABI *declare_location)(const struct clap_preset_discovery_indexer *indexer, + const clap_preset_discovery_location_t *location); + + // Declares a sound pack. + // Don't callback into the provider during this call. + // Returns false if the sound pack is invalid. + bool(CLAP_ABI *declare_soundpack)(const struct clap_preset_discovery_indexer *indexer, + const clap_preset_discovery_soundpack_t *soundpack); + + // Query an extension. + // The returned pointer is owned by the indexer. + // It is forbidden to call it before provider->init(). + // You can call it within provider->init() call, and after. + const void *(CLAP_ABI *get_extension)(const struct clap_preset_discovery_indexer *indexer, + const char *extension_id); +} clap_preset_discovery_indexer_t; + +// Every methods in this factory must be thread-safe. +// It is encouraged to perform preset indexing in background threads, maybe even in background +// process. +// +// The host may use clap_plugin_invalidation_factory to detect filesystem changes +// which may change the factory's content. +typedef struct clap_preset_discovery_factory { + // Get the number of preset providers available. + // [thread-safe] + uint32_t(CLAP_ABI *count)(const struct clap_preset_discovery_factory *factory); + + // Retrieves a preset provider descriptor by its index. + // Returns null in case of error. + // The descriptor must not be freed. + // [thread-safe] + const clap_preset_discovery_provider_descriptor_t *(CLAP_ABI *get_descriptor)( + const struct clap_preset_discovery_factory *factory, uint32_t index); + + // Create a preset provider by its id. + // The returned pointer must be freed by calling preset_provider->destroy(preset_provider); + // The preset provider is not allowed to use the indexer callbacks in the create method. + // It is forbidden to call back into the indexer before the indexer calls provider->init(). + // Returns null in case of error. + // [thread-safe] + const clap_preset_discovery_provider_t *(CLAP_ABI *create)( + const struct clap_preset_discovery_factory *factory, + const clap_preset_discovery_indexer_t *indexer, + const char *provider_id); +} clap_preset_discovery_factory_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/factory/plugin-factory.h b/include/clap/factory/plugin-factory.h new file mode 100644 index 0000000..0899665 --- /dev/null +++ b/include/clap/factory/plugin-factory.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../plugin.h" + +// Use it to retrieve const clap_plugin_factory_t* from +// clap_plugin_entry.get_factory() +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. +// +// The host may use clap_plugin_invalidation_factory to detect filesystem changes +// which may change the factory's content. +typedef struct clap_plugin_factory { + // Get the number of plugins available. + // [thread-safe] + uint32_t(CLAP_ABI *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 *(CLAP_ABI *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 *(CLAP_ABI *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/include/clap/fixedpoint.h b/include/clap/fixedpoint.h new file mode 100644 index 0000000..fb042d3 --- /dev/null +++ b/include/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/include/clap/host.h b/include/clap/host.h new file mode 100644 index 0000000..ddfeb8e --- /dev/null +++ b/include/clap/host.h @@ -0,0 +1,44 @@ +#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", see plugin.h for advice on how to format the version + + // Query an extension. + // The returned pointer is owned by the host. + // It is forbidden to call it before plugin->init(). + // You can call it within plugin->init() call, and after. + // [thread-safe] + const void *(CLAP_ABI *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(CLAP_ABI *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(CLAP_ABI *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(CLAP_ABI *request_callback)(const struct clap_host *host); +} clap_host_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/id.h b/include/clap/id.h new file mode 100644 index 0000000..3b2b6e6 --- /dev/null +++ b/include/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/include/clap/plugin-features.h b/include/clap/plugin-features.h new file mode 100644 index 0000000..e5dc18c --- /dev/null +++ b/include/clap/plugin-features.h @@ -0,0 +1,79 @@ +#pragma once + +// 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 formatted 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 converts audio to notes +#define CLAP_PLUGIN_FEATURE_NOTE_DETECTOR "note-detector" + +// 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_EXPANDER "expander" +#define CLAP_PLUGIN_FEATURE_GATE "gate" +#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/include/clap/plugin.h b/include/clap/plugin.h new file mode 100644 index 0000000..97ead81 --- /dev/null +++ b/include/clap/plugin.h @@ -0,0 +1,110 @@ +#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. + // + // Some indications regarding id and version + // - id is an arbitrary string which should be unique to your plugin, + // we encourage you to use a reverse URI eg: "com.u-he.diva" + // - version is an arbitrary string which describes a plugin, + // it is useful for the host to understand and be able to compare two different + // version strings, so here is a regex like expression which is likely to be + // understood by most hosts: MAJOR(.MINOR(.REVISION)?)?( (Alpha|Beta) XREV)? + 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 *const *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. + // If init returns true, then the plugin is initialized and in the deactivated state. + // [main-thread] + bool(CLAP_ABI *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(CLAP_ABI *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. + // Returns true on success. + // [main-thread & !active_state] + bool(CLAP_ABI *activate)(const struct clap_plugin *plugin, + double sample_rate, + uint32_t min_frames_count, + uint32_t max_frames_count); + // [main-thread & active_state] + void(CLAP_ABI *deactivate)(const struct clap_plugin *plugin); + + // Call start processing before processing. + // Returns true on success. + // [audio-thread & active_state & !processing_state] + bool(CLAP_ABI *start_processing)(const struct clap_plugin *plugin); + + // Call stop processing before sending the plugin to sleep. + // [audio-thread & active_state & processing_state] + void(CLAP_ABI *stop_processing)(const struct clap_plugin *plugin); + + // - Clears all buffers, performs a full reset of the processing state (filters, oscillators, + // envelopes, lfo, ...) and kills all voices. + // - The parameter's value remain unchanged. + // - clap_process.steady_time may jump backward. + // + // [audio-thread & active_state] + void(CLAP_ABI *reset)(const struct clap_plugin *plugin); + + // process audio, events, ... + // All the pointers coming from clap_process_t and its nested attributes, + // are valid until process() returns. + // [audio-thread & active_state & processing_state] + clap_process_status(CLAP_ABI *process)(const struct clap_plugin *plugin, + const clap_process_t *process); + + // Query an extension. + // The returned pointer is owned by the plugin. + // It is forbidden to call it before plugin->init(). + // You can call it within plugin->init() call, and after. + // [thread-safe] + const void *(CLAP_ABI *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(CLAP_ABI *on_main_thread)(const struct clap_plugin *plugin); +} clap_plugin_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/private/macros.h b/include/clap/private/macros.h new file mode 100644 index 0000000..ba1d665 --- /dev/null +++ b/include/clap/private/macros.h @@ -0,0 +1,50 @@ +#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(CLAP_ABI) +# if defined _WIN32 || defined __CYGWIN__ +# define CLAP_ABI __cdecl +# else +# define CLAP_ABI +# endif +#endif + +#if defined(_MSVC_LANG) +# define CLAP_CPLUSPLUS _MSVC_LANG +#elif defined(__cplusplus) +# define CLAP_CPLUSPLUS __cplusplus +#endif + +#if defined(CLAP_CPLUSPLUS) && CLAP_CPLUSPLUS >= 201103L +# define CLAP_HAS_CXX11 +# define CLAP_CONSTEXPR constexpr +#else +# define CLAP_CONSTEXPR +#endif + +#if defined(CLAP_CPLUSPLUS) && CLAP_CPLUSPLUS >= 201703L +# define CLAP_HAS_CXX17 +# define CLAP_NODISCARD [[nodiscard]] +#else +# define CLAP_NODISCARD +#endif + +#if defined(CLAP_CPLUSPLUS) && CLAP_CPLUSPLUS >= 202002L +# define CLAP_HAS_CXX20 +#endif diff --git a/include/clap/private/std.h b/include/clap/private/std.h new file mode 100644 index 0000000..1c2ffdd --- /dev/null +++ b/include/clap/private/std.h @@ -0,0 +1,16 @@ +#pragma once + +#include "macros.h" + +#ifdef CLAP_HAS_CXX11 +# include +#else +# include +#endif + +#ifdef __cplusplus +# include +#else +# include +# include +#endif diff --git a/include/clap/process.h b/include/clap/process.h new file mode 100644 index 0000000..c9dbf92 --- /dev/null +++ b/include/clap/process.h @@ -0,0 +1,66 @@ +#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->count(). + // The index maps to clap_plugin_audio_ports->get(). + // Input buffer and its contents are read-only. + const clap_audio_buffer_t *audio_inputs; + clap_audio_buffer_t *audio_outputs; + uint32_t audio_inputs_count; + uint32_t audio_outputs_count; + + // The input event list can't be modified. + // Input read-only event list. The host will deliver these sorted in sample order. + const clap_input_events_t *in_events; + + // Output event list. The plugin must insert events in sample sorted order when inserting events + const clap_output_events_t *out_events; +} clap_process_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/stream.h b/include/clap/stream.h new file mode 100644 index 0000000..b9b0b6c --- /dev/null +++ b/include/clap/stream.h @@ -0,0 +1,38 @@ +#pragma once + +#include "private/std.h" +#include "private/macros.h" + +/// @page Streams +/// +/// ## Notes on using streams +/// +/// When working with `clap_istream` and `clap_ostream` objects to load and save +/// state, it is important to keep in mind that the host may limit the number of +/// bytes that can be read or written at a time. The return values for the +/// stream read and write functions indicate how many bytes were actually read +/// or written. You need to use a loop to ensure that you read or write the +/// entirety of your state. Don't forget to also consider the negative return +/// values for the end of file and IO error codes. + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct clap_istream { + void *ctx; // reserved pointer for the stream + + // returns the number of bytes read; 0 indicates end of file and -1 a read error + int64_t(CLAP_ABI *read)(const struct clap_istream *stream, void *buffer, uint64_t size); +} clap_istream_t; + +typedef struct clap_ostream { + void *ctx; // reserved pointer for the stream + + // returns the number of bytes written; -1 on write error + int64_t(CLAP_ABI *write)(const struct clap_ostream *stream, const void *buffer, uint64_t size); +} clap_ostream_t; + +#ifdef __cplusplus +} +#endif diff --git a/include/clap/string-sizes.h b/include/clap/string-sizes.h new file mode 100644 index 0000000..4334c6d --- /dev/null +++ b/include/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/include/clap/version.h b/include/clap/version.h new file mode 100644 index 0000000..33072a2 --- /dev/null +++ b/include/clap/version.h @@ -0,0 +1,42 @@ +#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 correspond 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 1 +#define CLAP_VERSION_MINOR 1 +#define CLAP_VERSION_REVISION 10 + +#define CLAP_VERSION_INIT \ + { (uint32_t)CLAP_VERSION_MAJOR, (uint32_t)CLAP_VERSION_MINOR, (uint32_t)CLAP_VERSION_REVISION } + +#define CLAP_VERSION_LT(maj,min,rev) ((CLAP_VERSION_MAJOR < (maj)) || \ + ((maj) == CLAP_VERSION_MAJOR && CLAP_VERSION_MINOR < (min)) || \ + ((maj) == CLAP_VERSION_MAJOR && (min) == CLAP_VERSION_MINOR && CLAP_VERSION_REVISION < (rev))) +#define CLAP_VERSION_EQ(maj,min,rev) (((maj) == CLAP_VERSION_MAJOR) && ((min) == CLAP_VERSION_MINOR) && ((rev) == CLAP_VERSION_REVISION)) +#define CLAP_VERSION_GE(maj,min,rev) (!CLAP_VERSION_LT(maj,min,rev)) + +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/src/c_abi.v b/src/c_abi.v new file mode 100644 index 0000000..1548e94 --- /dev/null +++ b/src/c_abi.v @@ -0,0 +1,77 @@ +module plugin + +#flag -I./include +#include "clap/clap.h" + +enum ClapProcessStatus { + clap_process_error = 0 + clap_process_continue = 1 + clap_process_continue_if_not_quiet = 2 + clap_process_tail = 3 + clap_process_sleep = 4 +} + +@[typedef] +struct C.clap_version_t { + major u32 = u32(C.CLAP_VERSION_MAJOR) + minor u32 = u32(C.CLAP_VERSION_MINOR) + revision u32 = u32(C.CLAP_VERSION_REVISION) +} + +@[typedef] +struct C.clap_plugin_descriptor_t { + clap_version C.clap_version_t = C.clap_version_t{} + id &char + name &char + vendor &char + url &char = ''.str + manual_url &char = ''.str + support_url &char = ''.str + version &char + description &char + features &&char +} + +@[typedef] +struct C.clap_plugin_t { + desc &C.clap_plugin_descriptor_t @[required] + plugin_data voidptr @[required] + + init fn (&C.clap_plugin_t) bool @[required] + destroy fn (&C.clap_plugin_t) @[required] + activate fn (&C.clap_plugin_t, f64, u32, u32) bool @[required] + deactivate fn (&C.clap_plugin_t) @[required] + start_processing fn (&C.clap_plugin_t) bool + stop_processing fn (&C.clap_plugin_t) + reset fn (&C.clap_plugin_t) + process fn (&C.clap_plugin_t, &C.clap_process_t) ClapProcessStatus + get_extension fn (&C.clap_plugin_t, &char) voidptr + on_main_thread fn (&C.clap_plugin_t) +} + +@[typedef] +struct C.clap_host_t { + clap_version C.clap_version_t +} + +@[typedef] +struct C.clap_plugin_factory_t { + get_plugin_count fn (&C.clap_plugin_factory_t) u32 + get_plugin_descriptor fn (&C.clap_plugin_factory_t, u32) &C.clap_plugin_descriptor_t + create_plugin fn (&C.clap_plugin_factory_t, &C.clap_host_t, &char) &C.clap_plugin_t +} + +@[typedef] +struct C.clap_plugin_entry_t { + clap_version C.clap_version_t + init fn (&char) bool + deinit fn () + get_factory fn (&char) voidptr +} + +@[typedef] +struct C.clap_process_t + +const clap_plugin_factory_id = unsafe { (&char(C.CLAP_PLUGIN_FACTORY_ID)).vstring() } + +fn C.clap_version_is_compatible(C.clap_version_t) bool diff --git a/src/main.v b/src/main.v new file mode 100644 index 0000000..146d850 --- /dev/null +++ b/src/main.v @@ -0,0 +1,9 @@ +module plugin + +// Exposes the plugin to the host (DAW). + +// This requires modification to `clap/entry.h`. +// Remove "const" so you get: +// CLAP_EXPORT extern clap_plugin_entry_t clap_entry; +@[markused] +__global clap_entry = _plugin_entry diff --git a/src/plugin.v b/src/plugin.v new file mode 100644 index 0000000..d483677 --- /dev/null +++ b/src/plugin.v @@ -0,0 +1,46 @@ +module plugin + +// This should be the actual implementation of the plugin with +// all the interesting logic like DSP, UI, etc. +struct MinimalPlugin { +} + +// TODO: Consider mapping to struct's methods? +fn MinimalPlugin.init(clap_plugin &C.clap_plugin_t) bool { + // TODO: Actually init. + return true +} + +fn MinimalPlugin.destroy(clap_plugin &C.clap_plugin_t) { + // TODO: Ensure memory clean. +} + +fn MinimalPlugin.activate(clap_plugin &C.clap_plugin_t, sample_rate f64, min_frames_count u32, max_frames_count u32) bool { + // TODO: Store on plugin: sample_rate. + return false +} + +fn MinimalPlugin.deactivate(clap_plugin &C.clap_plugin_t) { +} + +fn MinimalPlugin.start_processing(clap_plugin &C.clap_plugin_t) bool { + return true +} + +fn MinimalPlugin.stop_processing(clap_plugin &C.clap_plugin_t) { +} + +fn MinimalPlugin.reset(clap_plugin &C.clap_plugin_t) { +} + +fn MinimalPlugin.process(clap_plugin &C.clap_plugin_t, process &C.clap_process_t) ClapProcessStatus { + return ClapProcessStatus.clap_process_continue +} + +fn MinimalPlugin.get_extension(clap_plugin &C.clap_plugin_t, id &char) voidptr { + // TODO + return voidptr(0) +} + +fn MinimalPlugin.on_main_thread(clap_plugin &C.clap_plugin_t) { +} diff --git a/src/setup.v b/src/setup.v new file mode 100644 index 0000000..f6e34f5 --- /dev/null +++ b/src/setup.v @@ -0,0 +1,96 @@ +module plugin + +// Features of the CLAP plugin. +// Have to be defined separately here, otherwise wrong C is generated. +const _plugin_features = [ + c'instrument', + unsafe { nil }, +]! + +// Plugin information, extracted at load time. +const _plugin_id = 'example.hello.world' +const _plugin_descriptor = C.clap_plugin_descriptor_t{ + id: _plugin_id.str + name: c'CLAP V Hello World' + vendor: c'MOFOSS' + version: c'0.1.0' + description: c'MVP of a CLAP plugin in V.' + // voidptr is to fix warning about const char**. + features: voidptr(unsafe { + &&char(&_plugin_features[0]) + }) +} + +const _plugin_entry = C.clap_plugin_entry_t{ + clap_version: C.clap_version_t{} + init: entry_init + deinit: entry_deinit + get_factory: entry_get_factory +} + +fn get_plugin_count(factory &C.clap_plugin_factory_t) u32 { + return 1 +} + +fn create_plugin(factory &C.clap_plugin_factory_t, host &C.clap_host_t, plugin_id &char) &C.clap_plugin_t { + + // Sanity checks for lib version and correct plugin expected. + if !C.clap_version_is_compatible(host.clap_version) { + return unsafe { nil } + } + v_plugin_id := unsafe { cstring_to_vstring(plugin_id) } + if v_plugin_id != _plugin_id { + return unsafe { nil } + } + + // Build actual plugin -- our custom structure. + main_plugin := &MinimalPlugin{} + // This is the "official" plugin. + clap_plugin := &C.clap_plugin_t{ + desc: &_plugin_descriptor + // It always carries a pointer to our custom structure. + plugin_data: main_plugin + init: MinimalPlugin.init + destroy: MinimalPlugin.destroy + activate: MinimalPlugin.activate + deactivate: MinimalPlugin.deactivate + start_processing: MinimalPlugin.start_processing + stop_processing: MinimalPlugin.stop_processing + reset: MinimalPlugin.reset + process: MinimalPlugin.process + get_extension: MinimalPlugin.get_extension + on_main_thread: MinimalPlugin.on_main_thread + } + + return clap_plugin +} + +fn get_plugin_descriptor(factory &C.clap_plugin_factory_t, index u32) &C.clap_plugin_descriptor_t { + if index == 0 { + return &_plugin_descriptor + } else { + return unsafe { nil } + } +} + +fn entry_init(plugin_path &char) bool { + return true +} + +fn entry_deinit() { +} + +fn entry_get_factory(factory_id &char) voidptr { + factory_id_v := unsafe { factory_id.vstring() } + + if factory_id_v == clap_plugin_factory_id { + factory := C.clap_plugin_factory_t{ + get_plugin_count: get_plugin_count + get_plugin_descriptor: get_plugin_descriptor + create_plugin: create_plugin + } + return voidptr(&factory) + } + + return unsafe { nil } +}