Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document the special lifetime of clap_event_midi_sysex::buffer #426

Merged
merged 10 commits into from
Nov 1, 2024
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ cmake-build*/

# A place to store stuff and get it git ignored
ignore/*

.DS_Store
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Changes in 1.2.2

* [thread-check.h](include/clap/ext/thread-check.h): expand the thread-doc to clarify and expand realtime
* [latency.h](include/clap/ext/latency.h): adjust latency extension requirements
* [undo.h](include/clap/ext/draft/undo.h): re-design the interface
* the plugin interfaces have been separated into 2 independent ones
* the plugin interfaces are optional
* simplification of the design

# Changes in 1.2.1

## New draft extensions
Expand Down
18 changes: 17 additions & 1 deletion include/clap/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,27 @@ typedef struct clap_event_midi {
uint8_t data[3];
} clap_event_midi_t;

// clap_event_midi_sysex contains a pointer to a sysex contents buffer.
// The lifetime of this buffer is (from host->plugin) only the process
// call in which the event is delivered or (from plugin->host) only the
// duration of a try_push call.
//
// Since `clap_output_events.try_push` requires hosts to make a copy of
// an event, host implementers receiving sysex messages from plugins need
// to take care to both copy the event (so header, size, etc...) but
// also memcpy the contents of the sysex pointer to host-owned memory, and
// not just copy the data pointer.
//
// Similarly plugins retaining the sysex outside the lifetime of a single
// process call must copy the sysex buffer to plugin-owned memory.
//
// As a consequence, the data structure pointed to by the sysex buffer
// must be contiguous and copyable with `memcpy` of `size` bytes.
typedef struct clap_event_midi_sysex {
clap_event_header_t header;

uint16_t port_index;
const uint8_t *buffer; // midi buffer
const uint8_t *buffer; // midi buffer. See lifetime comment above.
uint32_t size;
} clap_event_midi_sysex_t;

Expand Down
142 changes: 90 additions & 52 deletions include/clap/ext/draft/undo.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#pragma once

#include "../../plugin.h"
#include "../../stream.h"

static CLAP_CONSTEXPR const char CLAP_EXT_UNDO[] = "clap.undo/2";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO[] = "clap.undo/4";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO_CONTEXT[] = "clap.undo_context/4";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO_DELTA[] = "clap.undo_delta/4";

#ifdef __cplusplus
extern "C" {
Expand All @@ -15,7 +18,7 @@ extern "C" {
///
/// Calling host->undo() or host->redo() is equivalent to clicking undo/redo within the host's GUI.
///
/// If the plugin implements this interface then its undo and redo should be entirely delegated to
/// If the plugin uses this interface then its undo and redo should be entirely delegated to
/// the host; clicking in the plugin's UI undo or redo is equivalent to clicking undo or redo in the
/// host's UI.
///
Expand All @@ -40,31 +43,32 @@ extern "C" {
/// history. This simplifies the host implementation, leading to less bugs, a more robust design
/// and maybe an easier experience for the user because there's a single undo context versus one
/// for the host and one for each plugin instance.

enum clap_undo_context_flags {
// While the host is within a change, it is impossible to perform undo or redo.
CLAP_UNDO_IS_WITHIN_CHANGE = 1 << 0,
};

enum clap_undo_delta_properties_flags {
// If not set, then all clap_undo_delta_properties's attributes become irrelevant.
// If set, then the plugin will provide deltas in host->change_made().
CLAP_UNDO_DELTA_PROPERTIES_HAS_DELTA = 1 << 0,

// If set, then the delta will be reusable in the future as long as the plugin is
// compatible with the given format_version.
CLAP_UNDO_DELTA_PROPERTIES_IS_PERSISTENT = 1 << 1,
};
///
/// This extension tries to make it as easy as possible for the plugin to hook into the host undo
/// and make it efficient when possible by using deltas. The plugin interfaces are all optional, and
/// the plugin can for a minimal implementation, just use the host interface and call
/// host->change_made() without providing a delta. This is enough for the host to know that it can
/// capture a plugin state for the undo step.

typedef struct clap_undo_delta_properties {
// Bitmask of clap_undo_delta_properties_flags
uint64_t flags;
// If true, then the plugin will provide deltas in host->change_made().
// If false, then all clap_undo_delta_properties's attributes become irrelevant.
bool has_delta;

// If true, then the deltas can be stored on disk and re-used in the future as long as the plugin
// is compatible with the given format_version.
//
// If false, then format_version must be set to CLAP_INVALID_ID.
bool are_deltas_persistent;

// This represents the delta format version that the plugin is using.
uint32_t format_version;
// This represents the delta format version that the plugin is currently using.
// Use CLAP_INVALID_ID for invalid value.
clap_id format_version;
} clap_undo_delta_properties_t;

typedef struct clap_plugin_undo {
// Use CLAP_EXT_UNDO_DELTA.
// This is an optional interface, using deltas is an optimization versus making a state snapshot.
typedef struct clap_plugin_undo_delta {
// Asks the plugin the delta properties.
// [main-thread]
void(CLAP_ABI *get_delta_properties)(const clap_plugin_t *plugin,
Expand All @@ -76,25 +80,42 @@ typedef struct clap_plugin_undo {
bool(CLAP_ABI *can_use_delta_format_version)(const clap_plugin_t *plugin,
clap_id format_version);

// Applies synchronously a delta.
// Undo using the delta.
// Returns true on success.
//
// [main-thread]
bool(CLAP_ABI *apply_delta)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);

// Sets the undo context.
// flags: bitmask of clap_undo_context_flags values
// names: null terminated string if an redo/undo step exists, null otherwise.
// [main-thread]
void(CLAP_ABI *set_context_info)(const clap_plugin_t *plugin,
uint64_t flags,
const char *undo_name,
const char *redo_name);
} clap_plugin_undo_t;
bool(CLAP_ABI *undo)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);

// Redo using the delta.
// Returns true on success.
//
// [main-thread]
bool(CLAP_ABI *redo)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);
} clap_plugin_undo_delta_t;

// Use CLAP_EXT_UNDO_CONTEXT.
// This is an optional interface, that the plugin can implement in order to know about
// the current undo context.
typedef struct clap_plugin_undo_context {
// Indicate if it is currently possible to perform an undo or redo operation.
// [main-thread & plugin-subscribed-to-undo-context]
void(CLAP_ABI *set_can_undo)(const clap_plugin_t *plugin, bool can_undo);
void(CLAP_ABI *set_can_redo)(const clap_plugin_t *plugin, bool can_redo);

// Sets the name of the next undo or redo step.
// name: null terminated string.
// [main-thread & plugin-subscribed-to-undo-context]
void(CLAP_ABI *set_undo_name)(const clap_plugin_t *plugin, const char *name);
void(CLAP_ABI *set_redo_name)(const clap_plugin_t *plugin, const char *name);
} clap_plugin_undo_context_t;

// Use CLAP_EXT_UNDO.
typedef struct clap_host_undo {
// Begins a long running change.
// The plugin must not call this twice: there must be either a call to cancel_change() or
Expand All @@ -112,36 +133,51 @@ typedef struct clap_host_undo {
//
// name: mandatory null terminated string describing the change, this is displayed to the user
//
// deltas: optional, they are binary blobs used to perform the undo and redo. When not available
// delta: optional, it is a binary blobs used to perform the undo and redo. When not available
// the host will save the plugin state and use state->load() to perform undo and redo.
// The plugin must be able to perform a redo operation using the delta, though the undo operation
// is only possible if delta_can_undo is true.
//
// Note: the provided delta may be used for incremental state saving and crash recovery. The
// plugin can indicate a format version id and the validity lifetime for the binary blobs.
// The host can use these to verify the compatibility before applying the delta.
// If the plugin is unable to use a delta, a notification should be provided to the user and
// the crash recovery should perform a best effort job, at least restoring the latest saved state.
// the crash recovery should perform a best effort job, at least restoring the latest saved
// state.
//
// Special case: for objects with shared and synchronized state, changes shouldn't be reported
// as the host already knows about it.
// For example, plugin parameter changes shouldn't produce a call to change_made().
//
// Note: if the plugin asked for this interface, then host_state->mark_dirty() will not create an
// implicit undo step.
//
// Note: if the plugin did load a preset or did something that leads to a large delta,
// it may consider not producing a delta (pass null) and let the host make a state snapshot
// instead.
//
// Note: if a plugin is producing a lot of changes within a small amount of time, the host
// may merge them into a single undo step.
//
// [main-thread]
void(CLAP_ABI *change_made)(const clap_host_t *host,
const char *name,
const void *redo_delta,
size_t redo_delta_size,
const void *undo_delta,
size_t undo_delta_size);

// Asks the host to perform the next undo step.
// This operation may be asynchronous.
// [main-thread]
void(CLAP_ABI *undo)(const clap_host_t *host);
const void *delta,
size_t delta_size,
bool delta_can_undo);

// Asks the host to perform the next redo step.
// This operation may be asynchronous.
// Asks the host to perform the next undo or redo step.
//
// Note: this maybe a complex and asynchronous operation, which may complete after
// this function returns.
//
// Note: the host may ignore this request if there is no undo/redo step to perform,
// or if the host is unable to perform undo/redo at the time (eg: a long running
// change is going on).
//
// [main-thread]
void(CLAP_ABI *redo)(const clap_host_t *host);
void(CLAP_ABI *request_undo)(const clap_host_t *host);
void(CLAP_ABI *request_redo)(const clap_host_t *host);

// Subscribes to or unsubscribes from undo context info.
//
Expand All @@ -154,8 +190,10 @@ typedef struct clap_host_undo {
//
// is_subscribed: set to true to receive context info
//
// It is mandatory for the plugin to implement CLAP_EXT_UNDO_CONTEXT when using this method.
//
// [main-thread]
void(CLAP_ABI *set_context_info_subscription)(const clap_host_t *host, bool is_subscribed);
void(CLAP_ABI *set_wants_context_updates)(const clap_host_t *host, bool is_subscribed);
} clap_host_undo_t;

#ifdef __cplusplus
Expand Down
6 changes: 3 additions & 3 deletions include/clap/ext/latency.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ extern "C" {

typedef struct clap_plugin_latency {
// Returns the plugin latency in samples.
// [main-thread & active]
// [main-thread & (being-activated | active)]
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.
// The latency is only allowed to change during plugin->activate.
// If the plugin is activated, call host->request_restart()
// [main-thread]
// [main-thread & being-activated]
void(CLAP_ABI *changed)(const clap_host_t *host);
} clap_host_latency_t;

Expand Down
47 changes: 34 additions & 13 deletions include/clap/ext/thread-check.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,44 @@ extern "C" {
/// 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.
/// It isn't a realtime thread, yet this thread needs to respond fast enough to allow responsive
/// user interaction, so it is strongly recommended plugins run long,and expensive or blocking
/// tasks such as preset indexing or asset loading in dedicated background threads started by the
/// plugin.
///
/// 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
/// This thread can be used for realtime audio processing. Its execution should be as
/// deterministic as possible to meet the audio interface's deadline (can be <1ms). There are a
/// known set of operations that should be avoided: malloc() and free(), contended locks and
/// mutexes, I/O, waiting, and so forth.
///
/// The audio-thread is symbolic, there isn't one OS thread that remains the
/// audio-thread for the plugin lifetime. A host is may opt 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,
/// However, the host must guarantee that single plugin instance will not be two audio-threads
/// at the same time.
///
/// 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].
/// thread is the audio-thread at a time in a plugin instance. The audio-thread can be seen as a
/// concurrency guard for all functions marked with [audio-thread].
///
/// The real-time constraint on the [audio-thread] interacts closely with the render extension.
/// If a plugin doesn't implement render, then that plugin must have all [audio-thread] functions
/// meet the real time standard. If the plugin does implement render, and returns true when
/// render mode is set to real-time or if the plugin advertises a hard realtime requirement, it
/// must implement realtime constraints. Hosts also provide functions marked [audio-thread].
/// These can be safely called by a plugin in the audio thread. Therefore hosts must either (1)
/// implement those functions meeting the real-time constraints or (2) not process plugins which
/// advertise a hard realtime constraint or don't implement the render extension. Hosts which
/// provide [audio-thread] functions outside these conditions may experience inconsistent or
/// inaccurate rendering.
///
/// Clap also tags some functions as [thread-safe]. Functions tagged as [thread-safe] can be called
/// from any thread unless explicitly counter-indicated (for instance [thread-safe, !audio-thread])
/// and may be called concurrently. Since a [thread-safe] function may be called from the
/// [audio-thread] unless explicitly counter-indicated, it must also meet the realtime constraints
/// as describes above.

// This interface is useful to do runtime checks and make
// sure that the functions are called on the correct threads.
Expand Down
6 changes: 6 additions & 0 deletions include/clap/host.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ typedef struct clap_host {
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.
// This callback should be called as soon as practicable, usually in the host application's next
// available main thread time slice. Typically callbacks occur withink 33ms / 30hz.
// Despite this guidance, plugins should not make assumptions about the exactness of timing for
// a main thread callback, but hosts should endeavour to be prompt. For example, in high load situations
// the environment may starve the gui/main thread in favor of audio processing, leading to substantially
// longer latencies for the callback than the indicative times given here.
// [thread-safe]
void(CLAP_ABI *request_callback)(const struct clap_host *host);
} clap_host_t;
Expand Down
2 changes: 1 addition & 1 deletion include/clap/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ typedef struct clap_version {

#define CLAP_VERSION_MAJOR 1
#define CLAP_VERSION_MINOR 2
#define CLAP_VERSION_REVISION 1
#define CLAP_VERSION_REVISION 2

#define CLAP_VERSION_INIT \
{ (uint32_t)CLAP_VERSION_MAJOR, (uint32_t)CLAP_VERSION_MINOR, (uint32_t)CLAP_VERSION_REVISION }
Expand Down
Loading