13#include <pulp/format/detail/editor_environment.hpp>
14#include <pulp/format/detail/locale_independent_float.hpp>
22#if !(defined(PULP_WCLAP) && PULP_WCLAP)
23#define PULP_CLAP_CONTRACT_GUI 1
26#if defined(PULP_CLAP_GUI) && PULP_CLAP_GUI
34#include <clap/ext/audio-ports-config.h>
87 rec.factory = factory;
88 rec.publish_keyed = publish_keyed;
96 rec.
desc = proc->descriptor();
100 rec.
features[0] = CLAP_PLUGIN_FEATURE_AUDIO_EFFECT;
103 rec.
features[0] = CLAP_PLUGIN_FEATURE_INSTRUMENT;
106 rec.
features[0] = CLAP_PLUGIN_FEATURE_NOTE_EFFECT;
114 .clap_version = CLAP_VERSION,
134 if (!rec.initialized)
continue;
141 if (std::strcmp(
g_clap_index[j]->clap_desc.id, rec.clap_desc.id) == 0) {
156 if (rec.publish_keyed &&
find_plugin(rec.clap_desc.id) ==
nullptr) {
158 .id = rec.clap_desc.id,
159 .factory = rec.factory,
179 if (!
id)
return nullptr;
192 auto desc = self->
processor ? self->processor->descriptor()
193 : self->descriptor_snapshot;
194 return static_cast<uint32_t
>(is_input ? desc.input_buses.size() : desc.output_buses.size());
198 clap_audio_port_info_t* info) {
200 auto desc = self->
processor ? self->processor->descriptor()
201 : self->descriptor_snapshot;
202 auto& buses = is_input ? desc.input_buses : desc.output_buses;
204 if (index >= buses.size())
return false;
205 auto& bus = buses[index];
207 int channel_count = bus.default_channels;
208 if (!desc.supported_bus_layouts.empty()) {
209 const auto selected = std::min<std::size_t>(
210 self->selected_bus_layout, desc.supported_bus_layouts.size() - 1);
211 const auto& layout = desc.supported_bus_layouts[selected];
212 const auto& widths = is_input ? layout.inputs : layout.outputs;
213 if (index < widths.size()) channel_count = widths[index];
216 info->id =
static_cast<clap_id
>((is_input ? 0 : 100) + index);
218 info->channel_count =
static_cast<uint32_t
>(std::max(0, channel_count));
230 info->flags = (index == 0) ? CLAP_AUDIO_PORT_IS_MAIN : 0;
231 info->flags |= CLAP_AUDIO_PORT_SUPPORTS_64BITS;
232 if (desc.effective_capabilities().supports_f64_audio) {
233 info->flags |= CLAP_AUDIO_PORT_PREFERS_64BITS;
235 info->port_type = channel_count == 1 ? CLAP_PORT_MONO
236 : channel_count == 2 ? CLAP_PORT_STEREO :
nullptr;
237 info->in_place_pair = CLAP_INVALID_ID;
247 if (!self || !self->processor)
return 0;
248 return static_cast<uint32_t
>(
249 self->processor->descriptor().supported_bus_layouts.size());
253 clap_audio_ports_config_t* config) {
254 if (!config)
return false;
256 if (!self || !self->processor)
return false;
257 const auto desc = self->
processor->descriptor();
258 if (index >= desc.supported_bus_layouts.size())
return false;
259 const auto& layout = desc.supported_bus_layouts[index];
260 std::memset(config, 0,
sizeof(*config));
263 layout.name.empty() ? std::string(
"Layout ") + std::to_string(index + 1)
265 config->input_port_count =
static_cast<uint32_t
>(layout.inputs.size());
266 config->output_port_count =
static_cast<uint32_t
>(layout.outputs.size());
267 config->has_main_input = !layout.inputs.empty() && layout.inputs.front() > 0;
268 config->main_input_channel_count = config->has_main_input
269 ?
static_cast<uint32_t
>(layout.inputs.front()) : 0;
270 config->main_input_port_type = layout.inputs.empty() ? nullptr
271 : layout.inputs.front() == 1 ? CLAP_PORT_MONO
272 : layout.inputs.front() == 2 ? CLAP_PORT_STEREO :
nullptr;
273 config->has_main_output = !layout.outputs.empty() && layout.outputs.front() > 0;
274 config->main_output_channel_count = config->has_main_output
275 ?
static_cast<uint32_t
>(layout.outputs.front()) : 0;
276 config->main_output_port_type = layout.outputs.empty() ? nullptr
277 : layout.outputs.front() == 1 ? CLAP_PORT_MONO
278 : layout.outputs.front() == 2 ? CLAP_PORT_STEREO :
nullptr;
284 if (!self || !self->processor)
return false;
285 const auto desc = self->
processor->descriptor();
286 if (config_id >= desc.supported_bus_layouts.size())
return false;
287 const auto& layout = desc.supported_bus_layouts[config_id];
288 if (!self->processor->is_bus_layout_supported(
289 {layout.inputs, layout.outputs}))
return false;
290 self->selected_bus_layout = config_id;
303 if (!self || !self->processor)
return false;
310 std::size_t written = 0;
311 while (written < data.size()) {
312 const auto n = stream->write(stream,
313 data.data() + written,
314 data.size() - written);
315 if (n <= 0)
return false;
316 written +=
static_cast<std::size_t
>(n);
323 if (!self || !self->processor)
return false;
324 std::vector<uint8_t> data;
327 auto read = stream->read(stream, buf,
sizeof(buf));
328 if (read <= 0)
break;
329 data.insert(data.end(), buf, buf + read);
339 auto restore_lock = self->state_restore_gate.lock_for_restore();
350 if (
ok && self->host && self->host_params && self->host_params->rescan) {
351 self->host_params->rescan(self->host, CLAP_PARAM_RESCAN_VALUES);
361 return static_cast<uint32_t
>(self->store.param_count());
370 flags |= CLAP_PARAM_IS_AUTOMATABLE;
372 flags |= CLAP_PARAM_IS_HIDDEN;
374 flags |= CLAP_PARAM_IS_READONLY;
377 flags |= CLAP_PARAM_IS_STEPPED;
378 if (is_bypass) flags |= CLAP_PARAM_IS_BYPASS;
383 flags |= CLAP_PARAM_IS_MODULATABLE;
390 if (index >= params.size())
return false;
391 auto& p = params[index];
392 memset(info, 0,
sizeof(*info));
396 if (
const auto* group = groups.
find(p.group_id))
398 info->min_value = p.range.min;
399 info->max_value = p.range.max;
400 info->default_value = p.range.default_value;
407 *value = self->store.get_value(
static_cast<state::ParamID>(param_id));
412 double value,
char* display, uint32_t size) {
414 auto* info = self->store.info(
static_cast<state::ParamID>(param_id));
415 if (!info)
return false;
417 if (out.empty())
return false;
418 snprintf(display, size,
"%s", out.c_str());
423 const char* text,
double* value) {
424 if (!text)
return false;
426 if (!
plugin)
return false;
428 const auto* info = self->store.info(
static_cast<state::ParamID>(param_id));
429 if (!info)
return false;
431 if (!parsed)
return false;
437 const clap_output_events_t* out) {
450 const auto& desc = self->
processor ? self->processor->descriptor()
451 : self->descriptor_snapshot;
452 if (is_input && desc.accepts_midi)
return 1;
453 if (!is_input && desc.produces_midi)
return 1;
458 clap_note_port_info_t* info) {
460 const auto& desc = self->
processor ? self->processor->descriptor()
461 : self->descriptor_snapshot;
462 if (index != 0)
return false;
463 if (is_input && !desc.accepts_midi)
return false;
464 if (!is_input && !desc.produces_midi)
return false;
466 info->id = is_input ? 0 : 1;
468 info->supported_dialects = CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI;
473 const auto caps = desc.effective_capabilities();
474 if (caps.supports_mpe)
475 info->supported_dialects |= CLAP_NOTE_DIALECT_MIDI_MPE;
476 if (caps.supports_ump)
477 info->supported_dialects |= CLAP_NOTE_DIALECT_MIDI2;
479 info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
490 const auto& desc = self->
processor ? self->processor->descriptor() : self->descriptor_snapshot;
491 return desc.voice_count;
498 info->voice_count = count;
499 info->voice_capacity = count;
512 if (!self->processor)
return 0;
516 return static_cast<uint32_t
>(
526 if (!self->processor)
return 0;
527 auto tail = self->
processor->descriptor().tail_samples;
528 if (tail < 0)
return UINT32_MAX;
529 return static_cast<uint32_t
>(tail);
536#if defined(PULP_CLAP_GUI) && PULP_CLAP_GUI
538inline bool gui_is_api_supported(
const clap_plugin_t*,
const char* api,
bool is_floating) {
539 if (pulp::format::detail::editor_launch_blocked_by_environment())
return false;
540 if (is_floating)
return false;
542 return strcmp(api, CLAP_WINDOW_API_COCOA) == 0;
544 return strcmp(api, CLAP_WINDOW_API_WIN32) == 0;
545#elif defined(__linux__)
546 return strcmp(api, CLAP_WINDOW_API_X11) == 0;
553inline bool gui_get_preferred_api(
const clap_plugin_t*,
const char** api,
bool* is_floating) {
554 if (pulp::format::detail::editor_launch_blocked_by_environment())
return false;
555 *is_floating =
false;
557 *api = CLAP_WINDOW_API_COCOA;
559 *api = CLAP_WINDOW_API_WIN32;
560#elif defined(__linux__)
561 *api = CLAP_WINDOW_API_X11;
568inline bool request_host_resize(
569 clap_adapter::PulpClapPlugin&
plugin,
572 const clap_host_gui_t* gui =
nullptr;
574 gui =
static_cast<const clap_host_gui_t*
>(
577 return gui && gui->request_resize &&
578 gui->request_resize(
plugin.host, width, height);
581inline void install_editor_resize_handler(
582 clap_adapter::PulpClapPlugin&
plugin) {
583 if (!
plugin.processor)
return;
584 plugin.processor->set_editor_resize_handler(
586 [&
plugin](uint32_t width, uint32_t height) ->
bool {
587 if (!
plugin.bridge)
return false;
589 *
plugin.bridge, width, height,
590 [&
plugin](uint32_t requested_width,
591 uint32_t requested_height) {
592 return request_host_resize(
593 plugin, requested_width, requested_height);
595 if (!accepted)
return false;
611inline void stop_native_gui_timer(clap_adapter::PulpClapPlugin&
plugin) {
612 if (
plugin.editor_timer_id != CLAP_INVALID_ID &&
plugin.editor_host_timer &&
plugin.host &&
613 plugin.editor_host_timer->unregister_timer) {
616 plugin.editor_timer_id = CLAP_INVALID_ID;
617 plugin.editor_host_timer =
nullptr;
620inline bool start_native_gui_timer(clap_adapter::PulpClapPlugin&
plugin) {
621 if (
plugin.editor_timer_id != CLAP_INVALID_ID)
625 auto*
timer =
static_cast<const clap_host_timer_support_t*
>(
626 plugin.host->get_extension(
plugin.host, CLAP_EXT_TIMER_SUPPORT));
627 if (!timer || !
timer->register_timer || !
timer->unregister_timer) {
629 "continuous animation disabled");
632 clap_id
id = CLAP_INVALID_ID;
633 if (!
timer->register_timer(
plugin.host, 16, &
id) ||
id == CLAP_INVALID_ID) {
635 "continuous animation disabled");
639 plugin.editor_timer_id = id;
643inline void native_gui_on_timer(
const clap_plugin_t*
plugin, clap_id timer_id) {
644 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
645 if (!p || !
p->editor_host || !
p->editor_parent_attached || !
p->editor_visible ||
646 p->editor_timer_id != timer_id)
649 std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
650 p->editor_host->advance_frame(now);
653inline const clap_plugin_timer_support_t native_timer_ext = {
654 .on_timer = native_gui_on_timer,
657inline bool gui_create(
const clap_plugin_t*
plugin,
const char*,
bool) {
658 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
659 if (pulp::format::detail::editor_launch_blocked_by_environment()) {
663 if (!
p->processor || !
p->processor->has_editor())
return false;
665 std::string editor_error;
666 p->bridge = std::make_unique<ViewBridge>(
667 *
p->processor,
p->store,
p->owner_alive.capture(),
669 if (!
p->bridge->open(&editor_error)) {
675 const auto& hints =
p->bridge->size_hints();
678 *
p->bridge, gpu, {hints.preferred_width, hints.preferred_height});
681 if (
p->editor_host) {
691 *
p->editor_host,
p->bridge->scripted_ui(), gpu,
"CLAP");
709 install_editor_resize_handler(*p);
711 hints.preferred_width, hints.preferred_height,
712 gpu.mode,
p->editor_host->is_gpu_backed());
718 return p->editor_host !=
nullptr;
721inline void gui_destroy(
const clap_plugin_t*
plugin) {
722 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
725 if (
p->processor)
p->processor->set_editor_resize_handler(p,
nullptr);
728 stop_native_gui_timer(*p);
729 p->editor_parent_attached =
false;
730 p->gpu_surface_binding.reset();
731 p->editor_host.reset();
736 p->editor_visible =
false;
739inline bool gui_set_scale(
const clap_plugin_t*,
double) {
743inline bool gui_get_size(
const clap_plugin_t*
plugin, uint32_t* width, uint32_t* height) {
744 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
745 if (!
p->processor)
return false;
747 const auto&
h =
p->bridge->size_hints();
748 *
width =
h.preferred_width;
751 auto [
w, ht] =
p->processor->editor_size();
767inline bool gui_can_resize(
const clap_plugin_t*
plugin) {
768 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
769 if (!
p->processor)
return false;
770 const auto vs =
p->processor->view_size();
771 return vs.min_width > 0 && vs.min_height > 0;
774inline bool gui_get_resize_hints(
const clap_plugin_t*
plugin,
775 clap_gui_resize_hints_t* hints) {
776 if (!hints)
return false;
777 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
778 if (!
p->bridge)
return false;
779 const auto& size_hints =
p->bridge->size_hints();
780 if (size_hints.preferred_width == 0 || size_hints.preferred_height == 0) {
783 hints->can_resize_horizontally =
true;
784 hints->can_resize_vertically =
true;
787 const bool resizable =
788 size_hints.min_width > 0 && size_hints.min_height > 0;
789 const bool free_resize = resizable && size_hints.aspect_ratio <= 0.0;
790 hints->preserve_aspect_ratio = !free_resize;
791 hints->aspect_ratio_width = size_hints.preferred_width;
792 hints->aspect_ratio_height = size_hints.preferred_height;
796inline bool gui_adjust_size(
const clap_plugin_t*
plugin,
797 uint32_t* width, uint32_t* height) {
798 if (!width || !height || *width == 0 || *height == 0)
return false;
799 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
800 if (!
p->bridge)
return false;
801 const auto& size_hints =
p->bridge->size_hints();
802 if (size_hints.preferred_width == 0 || size_hints.preferred_height == 0) {
808 const bool resizable =
809 size_hints.min_width > 0 && size_hints.min_height > 0;
810 const bool free_resize = resizable && size_hints.aspect_ratio <= 0.0;
812 if (size_hints.min_width > 0 && *width < size_hints.min_width)
813 *
width = size_hints.min_width;
814 if (size_hints.min_height > 0 && *height < size_hints.min_height)
815 *
height = size_hints.min_height;
816 if (size_hints.max_width > 0 && *width > size_hints.max_width)
817 *
width = size_hints.max_width;
818 if (size_hints.max_height > 0 && *height > size_hints.max_height)
819 *
height = size_hints.max_height;
825 const double design_aspect =
826 static_cast<double>(size_hints.preferred_width) /
827 static_cast<double>(size_hints.preferred_height);
828 const double req_aspect =
829 static_cast<double>(*width) /
830 static_cast<double>(*height);
831 if (req_aspect > design_aspect) {
833 *
width =
static_cast<uint32_t
>(
834 static_cast<double>(*height) * design_aspect + 0.5);
835 }
else if (req_aspect < design_aspect) {
837 *
height =
static_cast<uint32_t
>(
838 static_cast<double>(*width) / design_aspect + 0.5);
847 auto resnap = [&]() {
850 const double aspect_now =
851 static_cast<double>(*width) /
static_cast<double>(*height);
852 if (aspect_now > design_aspect) {
853 *
height =
static_cast<uint32_t
>(
854 static_cast<double>(*width) / design_aspect + 0.5);
855 }
else if (aspect_now < design_aspect) {
856 *
width =
static_cast<uint32_t
>(
857 static_cast<double>(*height) * design_aspect + 0.5);
860 if (size_hints.min_width > 0 && *width < size_hints.min_width) {
861 *
width = size_hints.min_width;
864 if (size_hints.min_height > 0 && *height < size_hints.min_height) {
865 *
height = size_hints.min_height;
868 if (size_hints.max_width > 0 && *width > size_hints.max_width) {
869 *
width = size_hints.max_width;
872 if (size_hints.max_height > 0 && *height > size_hints.max_height) {
873 *
height = size_hints.max_height;
879inline bool gui_set_size(
const clap_plugin_t*
plugin, uint32_t width, uint32_t height) {
880 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
881 if (
p->bridge)
p->bridge->resize(width, height);
882 if (
p->editor_host) {
883 p->editor_host->set_size(width, height);
889inline bool gui_set_parent(
const clap_plugin_t*
plugin,
const clap_window_t* window) {
890 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
891 if (!
p->editor_host)
return false;
897 stop_native_gui_timer(*p);
898 p->editor_parent_attached =
false;
900 bool attached =
false;
902 if (strcmp(window->api, CLAP_WINDOW_API_COCOA) == 0) {
903 attached =
p->editor_host->try_attach_to_parent(window->cocoa);
906 if (strcmp(window->api, CLAP_WINDOW_API_WIN32) == 0) {
907 attached =
p->editor_host->try_attach_to_parent(window->win32);
909#elif defined(__linux__)
910 if (strcmp(window->api, CLAP_WINDOW_API_X11) == 0) {
911 attached =
p->editor_host->try_attach_to_parent(
reinterpret_cast<void*
>(window->x11));
914 if (attached &&
p->bridge) {
915 p->editor_parent_attached =
true;
916 p->bridge->notify_attached();
919 p->bridge->prepare_first_frame(*
p->editor_host);
920#if defined(__linux__)
924 if (
p->editor_visible)
925 start_native_gui_timer(*p);
931inline bool gui_set_transient(
const clap_plugin_t*,
const clap_window_t*) {
935inline void gui_suggest_title(
const clap_plugin_t*,
const char*) {
939inline bool gui_show(
const clap_plugin_t*
plugin) {
940 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
941 if (
p->editor_host) {
942 p->editor_visible =
true;
943#if defined(__linux__)
944 start_native_gui_timer(*p);
946 p->editor_host->repaint();
952inline bool gui_hide(
const clap_plugin_t*
plugin) {
953 auto*
p =
static_cast<clap_adapter::PulpClapPlugin*
>(
plugin->plugin_data);
954 p->editor_visible =
false;
955#if defined(__linux__)
956 stop_native_gui_timer(*p);
961 p->editor_host->release_gpu_caches();
965inline const clap_plugin_gui_t gui_ext = {
966 .is_api_supported = gui_is_api_supported,
967 .get_preferred_api = gui_get_preferred_api,
968 .create = gui_create,
969 .destroy = gui_destroy,
970 .set_scale = gui_set_scale,
971 .get_size = gui_get_size,
972 .can_resize = gui_can_resize,
973 .get_resize_hints = gui_get_resize_hints,
974 .adjust_size = gui_adjust_size,
975 .set_size = gui_set_size,
976 .set_parent = gui_set_parent,
977 .set_transient = gui_set_transient,
978 .suggest_title = gui_suggest_title,
987 if (!
id)
return nullptr;
988 if (strcmp(
id, CLAP_EXT_GUI) == 0) {
991#if defined(PULP_CLAP_CONTRACT_GUI)
995#if defined(PULP_CLAP_GUI) && PULP_CLAP_GUI
996 if (pulp::format::detail::editor_launch_blocked_by_environment())
return nullptr;
1002#if defined(PULP_CLAP_CONTRACT_GUI)
1003 if (strcmp(
id, CLAP_EXT_TIMER_SUPPORT) == 0)
1007#if defined(PULP_CLAP_GUI) && PULP_CLAP_GUI && defined(__linux__)
1008 if (strcmp(
id, CLAP_EXT_TIMER_SUPPORT) == 0)
1009 return &native_timer_ext;
1012 if (strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
1014 if (strcmp(
id, CLAP_EXT_NOTE_PORTS) == 0)
return &
note_ports_ext;
1015 if (strcmp(
id, CLAP_EXT_VOICE_INFO) == 0)
1017 if (strcmp(
id, CLAP_EXT_PARAMS) == 0)
return &
params_ext;
1018 if (strcmp(
id, CLAP_EXT_STATE) == 0)
return &
state_ext;
1019 if (strcmp(
id, CLAP_EXT_LATENCY) == 0)
return &
latency_ext;
1020 if (strcmp(
id, CLAP_EXT_TAIL) == 0)
return &
tail_ext;
1033 const clap_host_t*
host,
1034 const char* plugin_id) {
1038 if (!rec)
return nullptr;
1041 instance->factory = rec->
factory;
1044 instance->descriptor_snapshot = rec->
desc;
1047 instance->host =
host;
1048 instance->plugin = {
1050 .plugin_data = instance,
1062 return &instance->plugin;
1098 if (strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) == 0)
return &
plugin_factory;
1108#define PULP_CLAP_BUNDLE_ENTRY() \
1109 extern "C" CLAP_EXPORT const clap_plugin_entry_t clap_entry = { \
1110 .clap_version = CLAP_VERSION, \
1111 .init = pulp::format::clap_generic::entry_init, \
1112 .deinit = pulp::format::clap_generic::entry_deinit, \
1113 .get_factory = pulp::format::clap_generic::entry_get_factory, \
1122#define PULP_CLAP_BUNDLE_PLUGIN(Ident, factory_fn) \
1124 struct Ident##_ClapRegistrar { \
1125 Ident##_ClapRegistrar() { \
1126 pulp::format::clap_generic::register_clap_record(factory_fn, \
1129 } Ident##_clap_registrar_; \
1137#define PULP_CLAP_PLUGIN(factory_fn) \
1139 struct PulpClapInit { \
1141 pulp::format::clap_generic::register_clap_record(factory_fn, \
1143 pulp::format::register_plugin(factory_fn); \
1145 } _pulp_clap_init; \
1147 PULP_CLAP_BUNDLE_ENTRY()
std::span< const ParamInfo > all_params() const
View of all registered parameters (in registration order).
Definition store.hpp:439
static std::unique_ptr< PluginViewHost > create(View &root, Size size)
@ accepted
Definition realtime_time_stretch.hpp:31
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
@ timer
Conscious ~60 Hz timer fallback (Linux/other).
Definition render_loop.hpp:56
void log_warn(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:190
void log_error(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:195
void copy_c_string(char *dest, std::size_t dest_size, std::string_view src)
Definition system.hpp:55
void log_info(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:185
@ width
Definition config.hpp:225
bool is_bypass_param(const ParamInfo &info)
Decide whether info is a bypass control.
Definition parameter.hpp:514
bool is_modulatable_param(const ParamInfo &info)
Decide whether a host may apply a modulation offset to info.
Definition parameter.hpp:537
bool is_hidden_param(const ParamInfo &info)
Whether the host should omit this parameter from its generic parameter list and automation lanes.
Definition parameter.hpp:560
bool is_discrete_param(const ParamInfo &info)
Definition parameter.hpp:417
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
bool is_read_only_param(const ParamInfo &info)
Whether the host may read this parameter but must not write it.
Definition parameter.hpp:574
bool is_automatable_param(const ParamInfo &info)
Whether the host may record and play back automation for this parameter.
Definition parameter.hpp:590
@ gpu
Offscreen GPU (Dawn + Skia via pulp::render::HeadlessSurface).
Definition screenshot.hpp:16
@ h
Definition input_events.hpp:39
@ p
Definition input_events.hpp:40
@ w
Definition input_events.hpp:41
@ height
Definition css_animation.hpp:128
Immutable parameter metadata, registered once at plugin initialization.
Definition parameter.hpp:316