1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
|
#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 begining 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 resonsible 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 set begin_adjust,
/// end_adjust and should_record flags
///
/// 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_SET output event, set should_record to false
/// - the plugin is responsible to update 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 developper
/// 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 innevitably 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: . .
///
/// 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
///
/// 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
/// - 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 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,
};
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 a 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". Returns true on success. The host should always use
// this to format parameter values before displaying it to the user. [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.
// Returns true on success. The host can use this to convert user input into a parameter value.
// [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
|