Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
clapplugin.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "clapplugin.hh"
3#include "clapdevice.hh"
4#include "storage.hh"
5#include "project.hh"
6#include "processor.hh"
7#include "path.hh"
8#include "main.hh"
9#include "compress.hh"
10#include "serialize.hh"
11#include "internal.hh"
12#include "gtk2wrap.hh"
13#include <clap/ext/draft/file-reference.h>
14#include <dlfcn.h>
15#include <glob.h>
16#include <math.h>
17
18#define CDEBUG(...) Ase::debug ("clap", __VA_ARGS__)
19#define CDEBUG_ENABLED() Ase::debug_key_enabled ("clap")
20#define PDEBUG(...) Ase::debug ("clapparam", __VA_ARGS__)
21#define PDEBUG_ENABLED() Ase::debug_key_enabled ("clapparam")
22#define CLAPEVENT_ENABLED() Ase::debug_key_enabled ("clapevent")
23
24namespace Ase {
25
26ASE_CLASS_DECLS (ClapAudioProcessor);
27ASE_CLASS_DECLS (ClapPluginHandleImpl);
28using ClapEventParamS = std::vector<clap_event_param_value>;
29union ClapEventUnion;
30using ClapEventUnionS = std::vector<ClapEventUnion>;
31using ClapResourceHash = std::tuple<clap_id,String>; // resource_id, hex_hash
32using ClapResourceHashS = std::vector<ClapResourceHash>;
33using ClapParamIdValue = std::tuple<clap_id,double>; // param_id, value
34using ClapParamIdValueS = std::vector<ClapParamIdValue>;
35
36
37// == fwd decls ==
38static const char* anklang_host_name ();
39static String clapid (const clap_host *host);
40static ClapPluginHandleImpl* handle_ptr (const clap_host *host);
41static ClapPluginHandleImplP handle_sptr (const clap_host *host);
42static const clap_plugin* access_clap_plugin (ClapPluginHandle *handle);
43static const void* host_get_extension_mt (const clap_host *host, const char *extension_id);
44static void host_request_restart_mt (const clap_host *host);
45static void host_request_process_mt (const clap_host *host);
46static void host_request_callback_mt (const clap_host *host);
47static bool host_unregister_fd (const clap_host_t *host, int fd);
48static bool host_unregister_timer (const clap_host *host, clap_id timer_id);
49static bool event_unions_try_push (ClapEventUnionS &events, const clap_event_header_t *event);
50static void try_load_x11wrapper ();
51static Gtk2DlWrapEntry *x11wrapper = nullptr;
52static float scratch_float_buffer[AUDIO_BLOCK_FLOAT_ZEROS_SIZE];
53
54// == ClapFileHandle ==
56 void *dlhandle_ = nullptr;
57 uint open_count_ = 0;
58public:
59 const std::string dlfile;
60 const clap_plugin_entry *pluginentry = nullptr;
61 explicit
62 ClapFileHandle (const String &pathname) :
63 dlfile (pathname)
64 {}
65 virtual
67 {
68 assert_return (!opened());
69 }
70 bool
71 opened () const
72 {
73 return dlhandle_ && pluginentry;
74 }
75 void
76 open ()
77 {
78 if (open_count_++ == 0 && !dlhandle_) {
79 const String dlfile_mem = Path::stringread (dlfile);
80 dlhandle_ = dlopen (dlfile.c_str(), RTLD_LOCAL | RTLD_NOW);
81 CDEBUG ("%s: dlopen: %s", dlfile, dlhandle_ ? "OK" : get_dlerror());
82 if (dlhandle_) {
83 pluginentry = symbol<const clap_plugin_entry*> ("clap_entry");
84 bool initialized = false;
85 if (pluginentry && clap_version_is_compatible (pluginentry->clap_version))
86 initialized = pluginentry->init (dlfile.c_str());
87 if (!initialized) {
88 CDEBUG ("unusable clap_entry: %s", !pluginentry ? "NULL" :
89 string_format ("clap-%u.%u.%u", pluginentry->clap_version.major, pluginentry->clap_version.minor,
90 pluginentry->clap_version.revision));
91 pluginentry = nullptr;
92 dlclose (dlhandle_);
93 dlhandle_ = nullptr;
94 }
95 }
96 }
97 }
98 void
99 close ()
100 {
101 assert_return (open_count_ > 0);
102 open_count_--;
103 return_unless (open_count_ == 0);
104 if (pluginentry) {
105 pluginentry->deinit();
106 pluginentry = nullptr;
107 }
108 if (dlhandle_) {
109 const bool closingok = dlclose (dlhandle_) == 0;
110 CDEBUG ("%s: dlclose: %s", dlfile, closingok ? "OK" : get_dlerror());
111 dlhandle_ = nullptr;
112 }
113 }
114 template<typename Ptr> Ptr
115 symbol (const char *symname) const
116 {
117 void *p = dlhandle_ ? dlsym (dlhandle_, symname) : nullptr;
118 return (Ptr) p;
119 }
120 static String
121 get_dlerror ()
122 {
123 const char *err = dlerror();
124 return err ? err : "unknown dlerror";
125 }
126};
127
128// == ClapParamInfoImpl ==
130 void *cookie_ = nullptr;
131 double next_value_ = NAN;
133 void
134 operator= (const clap_param_info &cinfo)
135 {
136 unset();
137 param_id = cinfo.id;
138 ident = string_format ("%08x", param_id);
139 name = cinfo.name;
140 module = cinfo.module;
141 min_value = cinfo.min_value;
142 max_value = cinfo.max_value;
143 default_value = cinfo.default_value;
144 cookie_ = cinfo.cookie;
145 next_value_ = NAN;
146 }
147};
149
150// == ClapParamInfo ==
151ClapParamInfo::ClapParamInfo()
152{
153 unset();
154}
155
156void
157ClapParamInfo::unset()
158{
159 param_id = CLAP_INVALID_ID;
160 ident = "";
161 name = "";
162 module = "";
163 min_value = NAN;
164 max_value = NAN;
165 default_value = NAN;
166 current_value = NAN;
167 min_value_text = "";
168 max_value_text = "";
169 default_value_text = "";
170 current_value_text = "";
171}
172
173String
174ClapParamInfo::hints_from_param_info_flags (clap_param_info_flags flags)
175{
176 static constexpr const struct { uint32_t bit; const char *const hint; } bits[] = {
177 { CLAP_PARAM_IS_STEPPED, "stepped" },
178 { CLAP_PARAM_IS_PERIODIC, "periodic" },
179 { CLAP_PARAM_IS_HIDDEN, "hidden" },
180 { CLAP_PARAM_IS_READONLY, "readonly" },
181 { CLAP_PARAM_IS_BYPASS, "bypass" },
182 { CLAP_PARAM_IS_AUTOMATABLE, "automatable" },
183 { CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID, "automatable-note-id" },
184 { CLAP_PARAM_IS_AUTOMATABLE_PER_KEY, "automatable-key" },
185 { CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL, "automatable-channel" },
186 { CLAP_PARAM_IS_AUTOMATABLE_PER_PORT, "automatable-port" },
187 { CLAP_PARAM_IS_MODULATABLE, "modulatable" },
188 { CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID, "modulatable-note-id" },
189 { CLAP_PARAM_IS_MODULATABLE_PER_KEY, "modulatable-key" },
190 { CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL, "modulatable-channel" },
191 { CLAP_PARAM_IS_MODULATABLE_PER_PORT, "modulatable-port" },
192 { CLAP_PARAM_REQUIRES_PROCESS, "requires-process" },
193 };
194 String hints = ":";
195 for (size_t i = 0; i < ASE_ARRAY_SIZE (bits); i++)
196 if (flags & bits[i].bit)
197 hints += bits[i].hint + String (":");
198 if (!(flags & CLAP_PARAM_IS_HIDDEN))
199 hints += "G:";
200 if (flags & CLAP_PARAM_IS_READONLY)
201 hints += "r:";
202 else
203 hints += "r:w:";
204 return hints;
205}
206
207// == ClapEventUnion ==
209 clap_event_header_t header; // size, time, space_id, type, flags
210 clap_event_note_t note; // CLAP_NOTE_DIALECT_CLAP
211 clap_event_note_expression_t expression; // CLAP_NOTE_DIALECT_CLAP
212 clap_event_param_value_t value;
213 clap_event_param_mod_t mod;
214 clap_event_param_gesture_t gesture;
215 clap_event_midi_t midi1; // CLAP_NOTE_DIALECT_MIDI
216 clap_event_midi_sysex_t sysex; // CLAP_NOTE_DIALECT_MIDI
217 clap_event_midi2_t midi2; // CLAP_NOTE_DIALECT_MIDI2
218};
219
220// == ClapAudioProcessor ==
222 ClapPluginHandle *handle_ = nullptr;
223 const clap_plugin *clapplugin_ = nullptr;
224 IBusId ibusid = {};
225 OBusId obusid = {};
226 uint imain_clapidx = ~0, omain_clapidx = ~0, iside_clapidx = ~0, oside_clapidx = ~0;
227 clap_note_dialect input_event_dialect = clap_note_dialect (0);
228 clap_note_dialect input_preferred_dialect = clap_note_dialect (0);
229 clap_note_dialect output_event_dialect = clap_note_dialect (0);
230 clap_note_dialect output_preferred_dialect = clap_note_dialect (0);
231 bool can_process_ = false;
232public:
233 static void
234 static_info (AudioProcessorInfo &info)
235 {
236 info.label = "Anklang.Devices.ClapAudioProcessor";
237 }
238 ClapAudioProcessor (const ProcessorSetup &psetup) :
239 AudioProcessor (psetup)
240 {}
242 {
243 while (enqueued_events_.size()) {
244 ClapEventParamS *pevents = enqueued_events_.back();
245 enqueued_events_.pop_back();
246 main_rt_jobs += RtCall (call_delete<ClapEventParamS>, pevents); // delete in main_thread
247 }
248 }
249 void
251 {
253 handle_ = &*ClapDeviceImpl::access_clap_handle (get_device());
254 assert_return (handle_ != nullptr);
255 clapplugin_ = access_clap_plugin (handle_);
256 assert_return (clapplugin_ != nullptr);
257
258 // find iports
259 const auto &audio_iport_infos = handle_->audio_iport_infos;
260 for (size_t i = 0; i < audio_iport_infos.size(); i++)
261 if (audio_iport_infos[i].port_type && strcmp (audio_iport_infos[i].port_type, CLAP_PORT_STEREO) == 0 && audio_iport_infos[i].channel_count == 2) {
262 if (audio_iport_infos[i].flags & CLAP_AUDIO_PORT_IS_MAIN && imain_clapidx == ~0)
263 imain_clapidx = i;
264 else if (!(audio_iport_infos[i].flags & CLAP_AUDIO_PORT_IS_MAIN) && iside_clapidx == ~0)
265 iside_clapidx = i;
266 }
267 // find oports
268 const clap_audio_port_info_t *main_oport = nullptr, *side_oport = nullptr;
269 const auto &audio_oport_infos = handle_->audio_oport_infos;
270 for (size_t i = 0; i < audio_oport_infos.size(); i++)
271 if (audio_oport_infos[i].port_type && strcmp (audio_oport_infos[i].port_type, CLAP_PORT_STEREO) == 0 && audio_oport_infos[i].channel_count == 2) {
272 if (audio_oport_infos[i].flags & CLAP_AUDIO_PORT_IS_MAIN && !main_oport)
273 omain_clapidx = i;
274 else if (!(audio_oport_infos[i].flags & CLAP_AUDIO_PORT_IS_MAIN) && !side_oport)
275 oside_clapidx = i;
276 }
277 // find event ports
278 input_event_dialect = clap_note_dialect (handle_->note_iport_infos.size() ? handle_->note_iport_infos[0].supported_dialects : 0);
279 input_preferred_dialect = clap_note_dialect (handle_->note_iport_infos.size() ? handle_->note_iport_infos[0].preferred_dialect : 0);
280 output_event_dialect = clap_note_dialect (handle_->note_oport_infos.size() ? handle_->note_oport_infos[0].supported_dialects : 0);
281 output_preferred_dialect = clap_note_dialect (handle_->note_oport_infos.size() ? handle_->note_oport_infos[0].preferred_dialect : 0);
282
283 // create busses
284 if (imain_clapidx < audio_iport_infos.size())
285 ibusid = add_input_bus (audio_iport_infos[imain_clapidx].name, SpeakerArrangement::STEREO);
286 if (omain_clapidx < audio_oport_infos.size())
287 obusid = add_output_bus (audio_oport_infos[omain_clapidx].name, SpeakerArrangement::STEREO);
288 // prepare event IO
289 if (input_event_dialect & (CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI)) {
291 input_events_.reserve (256); // avoid audio-thread allocations
292 }
293 if (output_event_dialect & (CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI)) {
295 output_events_.reserve (256); // avoid audio-thread allocations
296 }
297 enqueued_events_.reserve (8);
298
299 // workaround AudioProcessor asserting that a Processor should have *some* IO facilities
300 if (!has_event_output() && !has_event_input() && ibusid == 0 && obusid == 0)
302 }
303 void
304 reset (uint64 target_stamp) override
305 {}
306 void convert_clap_events (const clap_process_t &process, bool as_clapnotes);
307 std::vector<ClapEventUnion> input_events_;
308 std::vector<ClapEventUnion> output_events_;
309 static uint32_t
310 input_events_size (const clap_input_events *evlist)
311 {
312 ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx;
313 size_t param_events_size = 0;
314 for (const auto &pevents_b : self->enqueued_events_)
315 param_events_size += pevents_b->size();
316 return param_events_size + self->input_events_.size();
317 }
318 static const clap_event_header_t*
319 input_events_get (const clap_input_events *evlist, uint32_t index)
320 {
321 ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx;
322 for (const auto &pevents_b : self->enqueued_events_) {
323 if (index < pevents_b->size())
324 return &(*pevents_b)[index].header;
325 index -= pevents_b->size();
326 }
327 return index < self->input_events_.size() ? &self->input_events_[index].header : nullptr;
328 }
329 static bool
330 output_events_try_push (const clap_output_events *evlist, const clap_event_header_t *event)
331 {
332 ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx;
333 return event_unions_try_push (self->output_events_, event);
334 }
335 const clap_input_events_t plugin_input_events = {
336 .ctx = (ClapAudioProcessor*) this,
337 .size = input_events_size,
338 .get = input_events_get,
339 };
340 const clap_output_events_t plugin_output_events = {
341 .ctx = (ClapAudioProcessor*) this,
342 .try_push = output_events_try_push,
343 };
344 const ClapParamInfoMap *param_info_map_ = nullptr;
345 const ClapParamInfoImpl *param_info_map_start_ = nullptr;
346 clap_process_t processinfo = { 0, };
347 clap_event_transport_t transportinfo = { { 0, }, };
348 std::vector<ClapEventParamS*> enqueued_events_;
349 void
350 enqueue_events (ClapEventParamS *pevents)
351 {
352 // insert 0-time event list *before* other events
353 if (pevents && pevents->size() && pevents->back().header.time == 0)
354 for (size_t i = 0; i < enqueued_events_.size(); i++)
355 if (enqueued_events_[i]->size() && enqueued_events_[i]->back().header.time > 0) {
356 enqueued_events_.insert (enqueued_events_.begin() + i, pevents);
357 return;
358 }
359 // or add to existing queue
360 enqueued_events_.push_back (pevents);
361 }
362 bool
363 start_processing (const ClapParamInfoMap *param_info_map, const ClapParamInfoImpl *map_start, size_t map_size)
364 {
365 return_unless (!can_process_, true);
366 assert_return (clapplugin_, false);
367 param_info_map_ = param_info_map;
368 param_info_map_start_ = map_start;
369 atomic_bits_resize (map_size);
370 can_process_ = clapplugin_->start_processing (clapplugin_);
371 CDEBUG ("%s: %s: %d", handle_->clapid(), __func__, can_process_);
372 if (can_process_) {
373 processinfo = clap_process_t {
374 .steady_time = int64_t (engine().frame_counter()),
375 .frames_count = 0, .transport = &transportinfo,
376 .audio_inputs = &handle_->audio_inputs_[0], .audio_outputs = &handle_->audio_outputs_[0],
377 .audio_inputs_count = uint32_t (handle_->audio_inputs_.size()),
378 .audio_outputs_count = uint32_t (handle_->audio_outputs_.size()),
379 .in_events = &plugin_input_events, .out_events = &plugin_output_events,
380 };
381 transportinfo = clap_event_transport_t {
382 .header = clap_event_header_t {
383 .size = sizeof (clap_event_transport_t),
384 .space_id = CLAP_CORE_EVENT_SPACE_ID,
385 .type = CLAP_EVENT_TRANSPORT
386 }
387 };
388 input_events_.resize (0);
389 output_events_.resize (0);
390 }
391 return can_process_;
392 }
393 void
394 stop_processing()
395 {
396 return_unless (can_process_);
397 can_process_ = false;
398 clapplugin_->stop_processing (clapplugin_);
399 param_info_map_ = nullptr;
400 param_info_map_start_ = nullptr;
401 CDEBUG ("%s: %s", handle_->clapid(), __func__);
402 input_events_.resize (0);
403 output_events_.resize (0);
404 }
405 void
406 update_transportinfo()
407 {
408 const auto &trans = transport();
409 const auto &tick_sig = trans.tick_sig;
410 transportinfo.flags = CLAP_TRANSPORT_HAS_TEMPO |
411 CLAP_TRANSPORT_HAS_BEATS_TIMELINE |
412 CLAP_TRANSPORT_HAS_SECONDS_TIMELINE |
413 CLAP_TRANSPORT_HAS_TIME_SIGNATURE;
414 if (trans.running())
415 transportinfo.flags |= CLAP_TRANSPORT_IS_PLAYING;
416 double sec_pos = trans.current_seconds + trans.current_minutes * 60;
417 double beat_pos = trans.current_tick_d * (1.0 / TRANSPORT_PPQN);
418 transportinfo.song_pos_beats = llrint (beat_pos * CLAP_BEATTIME_FACTOR);
419 transportinfo.song_pos_seconds = llrint (sec_pos * CLAP_SECTIME_FACTOR);
420 transportinfo.tempo = tick_sig.bpm();
421 transportinfo.tempo_inc = 0;
422 transportinfo.loop_start_beats = 0;
423 transportinfo.loop_end_beats = 0;
424 transportinfo.loop_start_seconds = 0;
425 transportinfo.loop_end_seconds = 0;
426 double bar_start = trans.current_bar_tick * (1.0 / TRANSPORT_PPQN);
427 transportinfo.bar_start = llrint (bar_start * CLAP_BEATTIME_FACTOR);
428 transportinfo.bar_number = trans.current_bar;
429 transportinfo.tsig_num = tick_sig.beats_per_bar();
430 transportinfo.tsig_denom = tick_sig.beat_unit();
431 }
432 void
433 render (uint n_frames) override
434 {
435 const uint icount = ibusid != 0 ? this->n_ichannels (ibusid) : 0;
436 if (can_process_) {
437 update_transportinfo();
438 for (size_t i = 0; i < icount; i++) {
439 assert_return (processinfo.audio_inputs[imain_clapidx].channel_count == icount);
440 processinfo.audio_inputs[imain_clapidx].data32[i] = const_cast<float*> (ifloats (ibusid, i));
441 }
442 const uint ocount = obusid != 0 ? this->n_ochannels (obusid) : 0;
443 for (size_t i = 0; i < ocount; i++) {
444 assert_return (processinfo.audio_outputs[omain_clapidx].channel_count == ocount);
445 processinfo.audio_outputs[omain_clapidx].data32[i] = oblock (obusid, i);
446 }
447 processinfo.frames_count = n_frames;
448 convert_clap_events (processinfo, input_preferred_dialect & CLAP_NOTE_DIALECT_CLAP);
449 processinfo.steady_time += processinfo.frames_count;
450 const clap_process_status status = clapplugin_->process (clapplugin_, &processinfo);
451 (void) status;
452 bool need_wakeup = dequeue_events (n_frames);
453 for (const auto &e : output_events_)
454 need_wakeup |= apply_param_value_event (e.value);
455 output_events_.resize (0);
456 if (need_wakeup)
457 enotify_enqueue_mt (PARAMCHANGE);
458 if (0)
459 CDEBUG ("render: status=%d", status);
460 }
461 }
462 bool
463 apply_param_value_event (const clap_event_param_value &e)
464 {
465 bool need_wakeup = false;
466 if (e.header.type == CLAP_EVENT_PARAM_VALUE && e.header.size >= sizeof (clap_event_param_value))
467 {
468 const clap_event_param_value &event = e;
469 const auto it = param_info_map_->find (event.param_id);
470 if (it != param_info_map_->end())
471 {
472 ClapParamInfoImpl *pinfo = it->second;
473 pinfo->next_value_ = event.value;
474 atomic_bit_notify (pinfo - param_info_map_start_);
475 need_wakeup = true;
476 PDEBUG ("%s: PROCESS: %08x=%f: (%s)\n", clapplugin_->desc->name, pinfo->param_id, event.value, pinfo->name);
477 }
478 }
479 return need_wakeup;
480 }
481 bool
482 dequeue_events (size_t nframes)
483 {
484 return_unless (enqueued_events_.size(), false);
485 // TODO: need proper time stamp handling
486 bool need_wakeup = false;
487 while (enqueued_events_.size() && (enqueued_events_[0]->empty() || enqueued_events_[0]->back().header.time < nframes)) {
488 ClapEventParamS *const pevents = enqueued_events_[0];
489 enqueued_events_.erase (enqueued_events_.begin());
490 for (const auto &e : *pevents)
491 need_wakeup |= apply_param_value_event (e);
492 main_rt_jobs += RtCall (call_delete<ClapEventParamS>, pevents); // delete in main_thread
493 }
494 return need_wakeup;
495 }
496};
497static CString clap_audio_wrapper_aseid = register_audio_processor<ClapAudioProcessor>();
498
499static inline clap_event_midi*
500setup_midi1 (ClapEventUnion *evunion, uint32_t time, uint16_t port_index)
501{
502 clap_event_midi *midi1 = &evunion->midi1;
503 midi1->header.size = sizeof (*midi1);
504 midi1->header.time = time;
505 midi1->header.space_id = CLAP_CORE_EVENT_SPACE_ID;
506 midi1->header.type = CLAP_EVENT_MIDI;
507 midi1->header.flags = 0;
508 midi1->port_index = port_index;
509 return midi1;
510}
511
512static inline clap_event_note*
513setup_evnote (ClapEventUnion *evunion, uint32_t time, uint16_t port_index)
514{
515 clap_event_note *evnote = &evunion->note;
516 evnote->header.size = sizeof (*evnote);
517 evnote->header.type = 0;
518 evnote->header.time = time;
519 evnote->header.space_id = CLAP_CORE_EVENT_SPACE_ID;
520 evnote->header.flags = 0;
521 evnote->port_index = port_index;
522 return evnote;
523}
524
525static inline clap_event_note_expression*
526setup_expression (ClapEventUnion *evunion, uint32_t time, uint16_t port_index)
527{
528 clap_event_note_expression *expr = &evunion->expression;
529 expr->header.size = sizeof (*expr);
530 expr->header.type = CLAP_EVENT_NOTE_EXPRESSION;
531 expr->header.time = time;
532 expr->header.space_id = CLAP_CORE_EVENT_SPACE_ID;
533 expr->header.flags = 0;
534 expr->port_index = port_index;
535 return expr;
536}
537
538void
539ClapAudioProcessor::convert_clap_events (const clap_process_t &process, const bool as_clapnotes)
540{
541 MidiEventInput evinput = midi_event_input();
542 if (input_events_.capacity() < evinput.events_pending())
543 input_events_.reserve (evinput.events_pending() + 128);
544 input_events_.resize (evinput.events_pending());
545 uint j = 0;
546 for (const auto &ev : evinput)
547 switch (ev.message())
548 {
549 clap_event_note_expression *expr;
550 clap_event_note_t *evnote;
551 clap_event_midi_t *midi1;
552 int16_t i16;
553 case MidiMessage::NOTE_ON:
554 case MidiMessage::NOTE_OFF:
555 case MidiMessage::AFTERTOUCH:
556 if (as_clapnotes && ev.type == MidiEvent::AFTERTOUCH) {
557 expr = setup_expression (&input_events_[j++], ev.frame, 0);
558 expr->expression_id = CLAP_NOTE_EXPRESSION_PRESSURE;
559 expr->note_id = ev.noteid;
560 expr->channel = ev.channel;
561 expr->key = ev.key;
562 expr->value = ev.velocity;
563 } else if (as_clapnotes) {
564 evnote = setup_evnote (&input_events_[j++], ev.frame, 0);
565 evnote->header.type = ev.type == MidiEvent::NOTE_ON ? CLAP_EVENT_NOTE_ON : CLAP_EVENT_NOTE_OFF;
566 evnote->note_id = ev.noteid;
567 evnote->channel = ev.channel;
568 evnote->key = ev.key;
569 evnote->velocity = ev.velocity;
570 } else {
571 midi1 = setup_midi1 (&input_events_[j++], ev.frame, 0);
572 midi1->data[0] = uint8_t (ev.type) | (ev.channel & 0xf);
573 midi1->data[1] = ev.key;
574 midi1->data[2] = std::min (uint8_t (ev.velocity * 127), uint8_t (127));
575 }
576 break;
577 case MidiMessage::ALL_NOTES_OFF:
578 if (as_clapnotes) {
579 evnote = setup_evnote (&input_events_[j++], ev.frame, 0);
580 evnote->header.type = CLAP_EVENT_NOTE_CHOKE;
581 evnote->note_id = -1;
582 evnote->channel = -1;
583 evnote->key = -1;
584 evnote->velocity = 0;
585 } else {
586 midi1 = setup_midi1 (&input_events_[j++], ev.frame, 0);
587 midi1->data[0] = 0xB0 | (ev.channel & 0xf);
588 midi1->data[1] = 123;
589 midi1->data[2] = 0;
590 }
591 break;
592 case MidiMessage::CONTROL_CHANGE:
593 midi1 = setup_midi1 (&input_events_[j++], ev.frame, 0);
594 midi1->data[0] = 0xB0 | (ev.channel & 0xf);
595 midi1->data[1] = ev.param;
596 midi1->data[2] = ev.cval;
597 break;
598 case MidiMessage::CHANNEL_PRESSURE:
599 midi1 = setup_midi1 (&input_events_[j++], ev.frame, 0);
600 midi1->data[0] = 0xD0 | (ev.channel & 0xf);
601 midi1->data[1] = std::min (uint8_t (ev.velocity * 127), uint8_t (127));
602 midi1->data[2] = 0;
603 break;
604 case MidiMessage::PITCH_BEND:
605 midi1 = setup_midi1 (&input_events_[j++], ev.frame, 0);
606 midi1->data[0] = 0xE0 | (ev.channel & 0xf);
607 midi1->data[1] = std::min (uint8_t (ev.velocity * 127), uint8_t (127));
608 midi1->data[2] = 0;
609 i16 = ev.value < 0 ? ev.value * 8192.0 : ev.value * 8191.0;
610 i16 += 8192;
611 midi1->data[1] = i16 & 127;
612 midi1->data[2] = (i16 >> 7) & 127;
613 break;
614 default: ;
615 }
616 input_events_.resize (j);
617 if (debug_enabled()) // lock-free check
618 {
619 static bool evdebug = CLAPEVENT_ENABLED();
620 if (ASE_UNLIKELY (evdebug))
621 for (const auto &ev : input_events_) {
622 if (ev.header.type == CLAP_EVENT_MIDI)
623 printerr ("%+4d ch=%-2u %-14s %02X %02X %02X sz=%d spc=%d flags=%x port=%d\n",
624 ev.midi1.header.time, ev.midi1.data[0] & 0xf, "MIDI1",
625 ev.midi1.data[0], ev.midi1.data[1], ev.midi1.data[2],
626 ev.midi1.header.size, ev.midi1.header.space_id, ev.midi1.header.flags, ev.midi1.port_index);
627 else
628 printerr ("%s\n", clap_event_to_string (&ev.note));
629 }
630 }
631}
632
633// == ClapPluginHandleImpl ==
635public:
636 static String clapid (const clap_host *host) { return Ase::clapid (host); }
637 String clapid () const { return ClapPluginHandle::clapid(); }
638 clap_host_t phost = {
639 .clap_version = CLAP_VERSION,
640 .host_data = (ClapPluginHandleImpl*) this,
641 .name = anklang_host_name(), .vendor = "anklang.testbit.eu",
642 .url = "https://anklang.testbit.eu/", .version = ase_version(),
643 .get_extension = [] (const clap_host *host, const char *extension_id) {
644 const void *ext = host_get_extension_mt (host, extension_id);
645 CDEBUG ("%s: host_get_extension_mt(\"%s\"): %p", clapid (host), extension_id, ext);
646 return ext;
647 },
648 .request_restart = host_request_restart_mt,
649 .request_process = host_request_process_mt,
650 .request_callback = host_request_callback_mt,
651 };
652 ClapAudioProcessorP proc_;
653 const clap_plugin_t *plugin_ = nullptr;
654 const clap_plugin_gui *plugin_gui = nullptr;
655 const clap_plugin_state *plugin_state = nullptr;
656 const clap_plugin_file_reference *plugin_file_reference = nullptr;
657 const clap_plugin_params *plugin_params = nullptr;
658 const clap_plugin_timer_support *plugin_timer_support = nullptr;
659 const clap_plugin_audio_ports_config *plugin_audio_ports_config = nullptr;
660 const clap_plugin_audio_ports *plugin_audio_ports = nullptr;
661 const clap_plugin_note_ports *plugin_note_ports = nullptr;
662 const clap_plugin_posix_fd_support *plugin_posix_fd_support = nullptr;
663 ClapPluginHandleImpl (const ClapPluginDescriptor &descriptor_, AudioProcessorP aproc) :
664 ClapPluginHandle (descriptor_), proc_ (shared_ptr_cast<ClapAudioProcessor> (aproc))
665 {
666 assert_return (proc_ != nullptr);
667 const clap_plugin_entry *pluginentry = descriptor.entry();
668 if (pluginentry)
669 {
670 const auto *factory = (const clap_plugin_factory*) pluginentry->get_factory (CLAP_PLUGIN_FACTORY_ID);
671 if (factory)
672 plugin_ = factory->create_plugin (factory, &phost, clapid().c_str());
673 }
674 }
676 {
677 destroy();
679 }
680 bool
681 init_plugin ()
682 {
683 return_unless (plugin_, false);
684 if (!plugin_->init (plugin_)) {
685 CDEBUG ("%s: initialization failed", clapid());
686 destroy(); // destroy per spec and cleanup resources used by init()
687 return false;
688 }
689 CDEBUG ("%s: initialized", clapid());
690 auto plugin_get_extension = [this] (const char *extname) {
691 const void *ext = plugin_->get_extension (plugin_, extname);
692 CDEBUG ("%s: plugin_get_extension(\"%s\"): %p", clapid(), extname, ext);
693 return ext;
694 };
695 plugin_gui = (const clap_plugin_gui*) plugin_get_extension (CLAP_EXT_GUI);
696 plugin_params = (const clap_plugin_params*) plugin_get_extension (CLAP_EXT_PARAMS);
697 plugin_timer_support = (const clap_plugin_timer_support*) plugin_get_extension (CLAP_EXT_TIMER_SUPPORT);
698 plugin_audio_ports_config = (const clap_plugin_audio_ports_config*) plugin_get_extension (CLAP_EXT_AUDIO_PORTS_CONFIG);
699 plugin_audio_ports = (const clap_plugin_audio_ports*) plugin_get_extension (CLAP_EXT_AUDIO_PORTS);
700 plugin_note_ports = (const clap_plugin_note_ports*) plugin_get_extension (CLAP_EXT_NOTE_PORTS);
701 plugin_posix_fd_support = (const clap_plugin_posix_fd_support*) plugin_get_extension (CLAP_EXT_POSIX_FD_SUPPORT);
702 plugin_state = (const clap_plugin_state*) plugin_get_extension (CLAP_EXT_STATE);
703 plugin_file_reference = (const clap_plugin_file_reference*) plugin_get_extension (CLAP_EXT_FILE_REFERENCE);
704 const clap_plugin_render *plugin_render = nullptr;
705 plugin_render = (const clap_plugin_render*) plugin_get_extension (CLAP_EXT_RENDER);
706 (void) plugin_render;
707 const clap_plugin_latency *plugin_latency = nullptr;
708 plugin_latency = (const clap_plugin_latency*) plugin_get_extension (CLAP_EXT_LATENCY);
709 (void) plugin_latency;
710 const clap_plugin_tail *plugin_tail = nullptr;
711 plugin_tail = (const clap_plugin_tail*) plugin_get_extension (CLAP_EXT_TAIL);
712 (void) plugin_tail;
713 get_port_infos();
714 return true;
715 }
716 bool plugin_activated = false;
717 bool plugin_processing = false;
718 bool gui_visible_ = false;
719 bool gui_canresize = false;
720 ulong gui_windowid = 0;
721 std::vector<uint> timers_;
722 struct FdPoll { int fd = -1; uint source = 0; uint flags = 0; };
723 std::vector<FdPoll> fd_polls_;
725 ClapParamInfoMap param_ids_;
726 ClapParamUpdateS *loader_updates_ = nullptr;
727 void get_port_infos ();
728 String get_param_value_text (clap_id param_id, double value);
729 double get_param_value_double (clap_id param_id, const String &text);
730 ClapEventParamS* convert_param_updates (const ClapParamUpdateS &updates);
731 void flush_event_params (const ClapEventParamS &events, ClapEventUnionS &output_events);
732 void params_changed() override;
733 void scan_params();
734 void resolve_file_references (ClapResourceHashS loader_hashes);
736 find_param_info (clap_id clapid)
737 {
738 auto it = param_ids_.find (clapid);
739 return it != param_ids_.end() ? it->second : nullptr;
740 }
742 param_infos () override
743 {
744 ClapParamInfoS infos;
745 for (const auto &pinfo : param_infos_)
746 infos.push_back (pinfo);
747 return infos;
748 }
749 bool
750 param_set_property (clap_id param_id, PropertyP prop) override
751 {
752 ClapParamInfoImpl *info = find_param_info (param_id);
753 return_unless (info, false);
754 info->aseprop_ = prop;
755 return true;
756 }
757 PropertyP
758 param_get_property (clap_id param_id) override
759 {
760 ClapParamInfoImpl *info = find_param_info (param_id);
761 return_unless (info, nullptr);
762 PropertyP prop = info->aseprop_.lock();
763 return prop;
764 }
765 double
766 param_get_value (clap_id param_id, String *text) override
767 {
768 ClapParamInfoImpl *info = find_param_info (param_id);
769 const double v = info ? info->current_value : NAN;
770 if (text)
771 *text = !info ? "NAN" : get_param_value_text (param_id, v);
772 return v;
773 }
774 bool
775 param_set_value (clap_id param_id, const String &stringvalue) override
776 {
777 ClapParamInfoImpl *info = find_param_info (param_id);
778 const double value = info ? get_param_value_double (param_id, stringvalue) : NAN;
779 return isnan (value) ? false : param_set_value (param_id, value);
780 }
781 bool
782 param_set_value (clap_id param_id, double v) override
783 {
784 ClapParamInfoImpl *info = find_param_info (param_id);
785 return_unless (info, false);
786 return_unless (!(info->flags & CLAP_PARAM_IS_READONLY), false);
787 ClapParamUpdateS updates;
788 if (info->flags & CLAP_PARAM_IS_STEPPED)
789 v = round (v);
790 ClapParamUpdate update = {
791 .steady_time = 0, // NOW
792 .param_id = param_id,
793 .value = CLAMP (v, info->min_value, info->max_value),
794 };
795 updates.push_back (update);
796 enqueue_updates (updates);
797 return true;
798 }
799 void
800 load_state (WritNode &xs) override
801 {
802 assert_return (loader_updates_ == nullptr);
803 // queue parameter update events
804 if (!plugin_state)
805 {
806 ClapParamIdValueS params;
807 xs["param_values"] & params;
808 PDEBUG ("%s: LOAD: %s\n", clapid(), json_stringify (params));
809 loader_updates_ = new ClapParamUpdateS;
810 for (const auto &[id, value] : params)
811 loader_updates_->push_back ({
812 .steady_time = 0, .param_id = id, .value = value,
813 });
814 // TODO: flush loader_updates_ right away
815 }
816 // load saved blob
817 if (plugin_state)
818 {
819 String blobname;
820 xs["state_blob"] & blobname;
821 StreamReaderP blob = blobname.empty() ? nullptr : _project()->load_blob (blobname);
822 const clap_istream istream = {
823 .ctx = blob.get(),
824 .read = [] (const clap_istream *stream, void *buffer, uint64_t size) -> int64_t {
825 StreamReader *sr = (StreamReader*) stream->ctx;
826 return sr->read (buffer, size);
827 }
828 };
829 errno = ENOSYS;
830 bool ok = !blob ? false : plugin_state->load (plugin_, &istream);
831 ok &= !blob ? false : blob->close();
832 if (!ok && blobname.size())
833 printerr ("%s: blob read error: %s\n", clapid(), strerror (errno ? errno : EIO));
834 }
835 // update collected files
836 ClapResourceHashS loader_hashes;
837 xs["resource_hashes"] & loader_hashes;
838 resolve_file_references (loader_hashes);
839 }
840 void
841 save_state (WritNode &xs, const String &device_path) override
842 {
843 // first, flush plugin state to disk
844 bool need_save_resources = false;
845 if (plugin_file_reference && plugin_file_reference->save_resources)
846 need_save_resources = !plugin_file_reference->save_resources (plugin_);
847 // store params if plugin_state->save is unimplemented
848 if (!plugin_state)
849 {
850 ClapParamIdValueS params;
851 for (const auto &pinfo : param_infos_)
852 if (pinfo.param_id != CLAP_INVALID_ID)
853 params.push_back ({ pinfo.param_id, pinfo.current_value });
854 xs["param_values"] & params;
855 PDEBUG ("%s: SAVE: %s\n", clapid(), json_stringify (params));
856 }
857 // save state into blob file
858 if (plugin_state)
859 {
860 String blobname = string_format ("clap-%s.bin", device_path);
861 printerr ("SAVE: blobname: %s\n", blobname);
862 const String blobfile = _project()->writer_file_name (blobname) + ".zst";
863 StreamWriterP swp = stream_writer_zstd (stream_writer_create_file (blobfile));
864 const clap_ostream ostream = {
865 .ctx = swp.get(),
866 .write = [] (const clap_ostream *stream, const void *buffer, uint64_t size) -> int64_t {
867 StreamWriter *sw = (StreamWriter*) stream->ctx;
868 return sw->write (buffer, size);
869 }
870 };
871 errno = 0;
872 bool ok = plugin_state->save (plugin_, &ostream);
873 ok &= swp->close();
874 if (!ok) // TODO: user_note
875 printerr ("%s: %s: write error: %s\n", clapid(), blobfile, strerror (errno ? errno : EIO));
876 // keep state only if size >0
877 if (!ok || !Path::check (blobfile, "frs"))
878 Path::rmrf (blobfile);
879 else
880 {
881 Error err = _project()->writer_add_file (blobfile);
882 if (!!err)
883 printerr ("%s: %s: %s\n", program_alias(), blobfile, ase_error_blurb (err));
884 else
885 xs["state_blob"] & blobname;
886 }
887 }
888 // collect external files
889 if (plugin_file_reference && plugin_file_reference->count && plugin_file_reference->get)
890 {
891 ClapResourceHashS hashes;
892 if (need_save_resources) // retry if we got false previously
893 plugin_file_reference->save_resources (plugin_);
894 const size_t n_files = plugin_file_reference->count (plugin_);
895 for (size_t i = 0; i < n_files; i++)
896 {
897 char buffer[ASE_PATH_MAX + 2] = { 0, };
898 const size_t path_capacity = sizeof (buffer) - 1;
899 clap_file_reference pfile = {
900 .resource_id = CLAP_INVALID_ID,
901 .belongs_to_plugin_collection = false,
902 .path_capacity = path_capacity,
903 .path_size = 0, .path = buffer,
904 };
905 if (!plugin_file_reference->get (plugin_, i, &pfile) || pfile.path_size < 1 ||
906 !pfile.path || pfile.path_size >= path_capacity) // ignore excessive path lengths
907 continue;
908 pfile.path[pfile.path_size] = 0;
909 if (!Path::check (pfile.path, "r")) // must exist and be readable
910 continue;
911 ClapResourceHash rhash = { pfile.resource_id, "" };
912 Error err = _project()->writer_collect (pfile.path, &std::get<String> (rhash));
913 if (!err)
914 hashes.push_back (rhash);
915 else
916 ASE_SERVER.user_note (string_format ("## Note Missing File\n%s: \\\nFailed to store: `%s` \\\n%s",
917 clapid(), pfile.path, ase_error_blurb (err)));
918 }
919 xs["resource_hashes"] & hashes;
920 }
921 }
922 bool
923 clap_activated() const override
924 {
925 return plugin_activated;
926 }
927 bool
928 enqueue_updates (const ClapParamUpdateS &updates)
929 {
930 ClapPluginHandleImplP selfp = shared_ptr_cast<ClapPluginHandleImpl> (this);
931 return_unless (clap_activated(), false);
932 ClapEventParamS *pevents = convert_param_updates (updates); // allocated in main_thread
933 proc_->engine().async_jobs += [selfp, pevents] () {
934 selfp->proc_->enqueue_events (pevents);
935 };
936 return true;
937 }
938 bool
939 clap_activate() override
940 {
941 return_unless (plugin_ && !clap_activated(), clap_activated());
942 // initial param scan
943 if (plugin_params) {
944 scan_params(); // needed for convert_param_updates
945 }
946 // load parameter updates and rescan
947 if (plugin_params && loader_updates_) {
948 ClapEventParamS *pevents = convert_param_updates (*loader_updates_);
949 ClapEventUnionS output_events;
950 flush_event_params (*pevents, output_events);
951 output_events.clear(); // discard output_events, we just do a rescan
952 scan_params();
953 delete pevents;
954 }
955 if (loader_updates_) {
956 delete loader_updates_;
957 loader_updates_ = nullptr;
958 }
959 // activate, keeps param_ids_, param_infos_ locked now, start_processing
960 plugin_activated = plugin_->activate (plugin_, proc_->engine().sample_rate(), 32, 4096);
961 CDEBUG ("%s: %s: %d", clapid(), __func__, plugin_activated);
962 if (plugin_activated) {
963 ClapPluginHandleImplP selfp = shared_ptr_cast<ClapPluginHandleImpl> (this);
964 // synchronize with start_processing
965 ScopedSemaphore sem;
966 proc_->engine().async_jobs += [&sem, selfp] () {
967 selfp->proc_->start_processing (&selfp->param_ids_, &selfp->param_infos_[0], selfp->param_infos_.size());
968 sem.post();
969 };
970 sem.wait();
971 // active_state && processing_state
972 }
973 return clap_activated();
974 }
975 void
976 clap_deactivate() override
977 {
978 return_unless (plugin_ && clap_activated());
979 if (true) {
980 ClapPluginHandleImplP selfp = shared_ptr_cast<ClapPluginHandleImpl> (this);
981 ScopedSemaphore sem;
982 proc_->engine().async_jobs += [&sem, selfp] () {
983 selfp->proc_->stop_processing();
984 sem.post();
985 };
986 sem.wait();
987 // NOW: !processing && !clap_activated
988 }
989 plugin_activated = false;
990 plugin_->deactivate (plugin_);
991 CDEBUG ("%s: plugin->deactivated", clapid());
992 }
993 void show_gui () override;
994 void hide_gui () override;
995 void destroy_gui () override;
996 bool gui_visible () override;
997 bool supports_gui () override;
998 void
999 destroy () override
1000 {
1001 destroy_gui();
1002 if (plugin_) {
1003 if (clap_activated())
1004 clap_deactivate();
1005 CDEBUG ("%s: destroying", clapid());
1006 }
1007 param_ids_.clear();
1008 param_infos_.clear();
1009 while (fd_polls_.size())
1010 host_unregister_fd (&phost, fd_polls_.back().fd);
1011 while (timers_.size())
1012 host_unregister_timer (&phost, timers_.back());
1013 if (plugin_)
1014 plugin_->destroy (plugin_);
1015 plugin_ = nullptr;
1016 plugin_gui = nullptr;
1017 plugin_state = nullptr;
1018 plugin_file_reference = nullptr;
1019 plugin_params = nullptr;
1020 plugin_timer_support = nullptr;
1021 plugin_audio_ports_config = nullptr;
1022 plugin_audio_ports = nullptr;
1023 plugin_note_ports = nullptr;
1024 }
1025 AudioProcessorP
1026 audio_processor () override
1027 {
1028 return proc_;
1029 }
1030};
1031
1032// == get_param_value_text ==
1033String
1034ClapPluginHandleImpl::get_param_value_text (clap_id param_id, double value)
1035{
1036 constexpr uint LEN = 256;
1037 char buffer[LEN + 1] = { 0 };
1038 if (!plugin_params || !plugin_params->value_to_text (plugin_, param_id, value, buffer, LEN))
1039 buffer[0] = 0;
1040 buffer[LEN] = 0;
1041 return buffer;
1042}
1043
1044double
1045ClapPluginHandleImpl::get_param_value_double (clap_id param_id, const String &text)
1046{
1047 double value = NAN;
1048 if (!plugin_params || !plugin_params->text_to_value ||
1049 !plugin_params->text_to_value (plugin_, param_id, text.c_str(), &value))
1050 value = NAN;
1051 return value;
1052}
1053
1054// == scan_params ==
1055void
1056ClapPluginHandleImpl::scan_params()
1057{
1058 param_ids_.clear();
1059 param_infos_.clear();
1060 const uint32_t count = plugin_params->count (plugin_);
1061 param_infos_.resize (count);
1062 param_ids_.reserve (param_infos_.size());
1063 for (size_t i = 0; i < param_infos_.size(); i++) {
1064 ClapParamInfoImpl &pinfo = param_infos_[i];
1065 pinfo.unset();
1066 clap_param_info cinfo = { CLAP_INVALID_ID, 0, nullptr, {0}, {0}, NAN, NAN, NAN };
1067 if (plugin_params->get_info (plugin_, i, &cinfo) && cinfo.id != CLAP_INVALID_ID) {
1068 pinfo = cinfo;
1069 param_ids_[cinfo.id] = &param_infos_[i];
1070 }
1071 }
1072 for (size_t i = 0; i < param_infos_.size(); i++) {
1073 ClapParamInfoImpl &pinfo = param_infos_[i];
1074 if (pinfo.param_id == CLAP_INVALID_ID)
1075 continue;
1076 pinfo.min_value_text = get_param_value_text (pinfo.param_id, pinfo.min_value);
1077 pinfo.max_value_text = get_param_value_text (pinfo.param_id, pinfo.max_value);
1078 pinfo.default_value_text = get_param_value_text (pinfo.param_id, pinfo.default_value);
1079 if (!plugin_params->get_value (plugin_, pinfo.param_id, &pinfo.current_value)) {
1080 pinfo.current_value = pinfo.default_value;
1081 pinfo.current_value_text = pinfo.default_value_text;
1082 } else
1083 pinfo.current_value_text = get_param_value_text (pinfo.param_id, pinfo.current_value);
1084 PDEBUG ("%s: SCAN: %08x=%f: %s (%s)\n", clapid(), pinfo.param_id, pinfo.current_value, pinfo.current_value_text, pinfo.name);
1085 }
1086}
1087
1088// == params_changed ==
1089void
1090ClapPluginHandleImpl::params_changed ()
1091{
1092 for (AtomicBits::Iter it = proc_->atomic_bits_iter(); it.valid(); it.big_inc1())
1093 if (it.clear())
1094 {
1095 ClapParamInfoImpl &pinfo = param_infos_[it.position()];
1096 if (ASE_ISLIKELY (isnan (pinfo.next_value_)) || pinfo.param_id == CLAP_INVALID_ID)
1097 continue;
1098 pinfo.current_value = pinfo.next_value_;
1099 pinfo.next_value_ = NAN;
1100 pinfo.current_value_text = get_param_value_text (pinfo.param_id, pinfo.current_value);
1101 PDEBUG ("%s: UPDATE: %08x=%f: %s (%s)\n", clapid(), pinfo.param_id, pinfo.current_value, pinfo.current_value_text, pinfo.name);
1102 PropertyP prop = pinfo.aseprop_.lock();
1103 if (prop)
1104 dynamic_cast<EmittableImpl*> (prop.get())->emit_notify (pinfo.ident);
1105 }
1106}
1107
1108// == convert_param_updates ==
1109ClapEventParamS*
1110ClapPluginHandleImpl::convert_param_updates (const ClapParamUpdateS &updates)
1111{
1112 ClapEventParamS *param_events = new ClapEventParamS();
1113 for (size_t i = 0; i < updates.size(); i++)
1114 {
1115 const ClapParamInfoImpl *pinfo = find_param_info (updates[i].param_id);
1116 if (!pinfo)
1117 continue;
1118 const clap_event_param_value event = {
1119 .header = {
1120 .size = sizeof (clap_event_param_value),
1121 .time = 0, // updates[i].steady_time
1122 .space_id = CLAP_CORE_EVENT_SPACE_ID,
1123 .type = CLAP_EVENT_PARAM_VALUE,
1124 .flags = CLAP_EVENT_DONT_RECORD,
1125 },
1126 .param_id = pinfo->param_id,
1127 .cookie = pinfo->cookie_,
1128 .note_id = -1,
1129 .port_index = -1,
1130 .channel = -1,
1131 .key = -1,
1132 .value = updates[i].value
1133 };
1134 param_events->push_back (event);
1135 PDEBUG ("%s: CONVERT: %08x=%f: (%s)\n", clapid(), pinfo->param_id, event.value, pinfo->name);
1136 }
1137 return param_events;
1138}
1139
1140// == flush_event_params ==
1141void
1142ClapPluginHandleImpl::flush_event_params (const ClapEventParamS &inevents, ClapEventUnionS &outevents)
1143{
1144 const clap_output_events output_events = {
1145 .ctx = &outevents,
1146 .try_push = [] (const clap_output_events *list, const clap_event_header_t *event) {
1147 ClapEventUnionS *outevents = (ClapEventUnionS*) list->ctx;
1148 return event_unions_try_push (*outevents, event);
1149 }
1150 };
1151 const clap_input_events input_events = {
1152 .ctx = const_cast<ClapEventParamS*> (&inevents),
1153 .size = [] (const struct clap_input_events *list) {
1154 const ClapEventParamS *inevents = (const ClapEventParamS*) list->ctx;
1155 return uint32_t (inevents->size());
1156 },
1157 .get = [] (const clap_input_events *list, uint32_t index) -> const clap_event_header_t* {
1158 const ClapEventParamS *inevents = (const ClapEventParamS*) list->ctx;
1159 return index < inevents->size() ? &(*inevents)[index].header : nullptr;
1160 }
1161 };
1162 if (PDEBUG_ENABLED())
1163 for (const auto &p : inevents)
1164 PDEBUG ("%s: FLUSH: %08x=%f\n", clapid(), p.param_id, p.value);
1165 plugin_params->flush (plugin_, &input_events, &output_events);
1166}
1167
1168// == get_port_infos ==
1169void
1170ClapPluginHandleImpl::get_port_infos()
1171{
1172 assert_return (!clap_activated());
1173 uint total_channels = 0;
1174 // audio_ports_configs_
1175 audio_ports_configs_.resize (!plugin_audio_ports_config ? 0 : plugin_audio_ports_config->count (plugin_));
1176 for (size_t i = 0; i < audio_ports_configs_.size(); i++)
1177 if (!plugin_audio_ports_config->get (plugin_, i, &audio_ports_configs_[i]))
1178 audio_ports_configs_[i] = { CLAP_INVALID_ID, { 0, }, 0, 0, 0, 0, "", 0, 0, "" };
1179 if (audio_ports_configs_.size()) { // not encountered yet
1180 String s = string_format ("audio_configs:%u:", audio_ports_configs_.size());
1181 for (size_t i = 0; i < audio_ports_configs_.size(); i++)
1182 if (audio_ports_configs_[i].id != CLAP_INVALID_ID)
1183 s += string_format (" %u:%s:iports=%u:oports=%u:imain=%u,%s:omain=%u,%s",
1184 audio_ports_configs_[i].id,
1185 audio_ports_configs_[i].name,
1186 audio_ports_configs_[i].input_port_count,
1187 audio_ports_configs_[i].output_port_count,
1188 audio_ports_configs_[i].has_main_input * audio_ports_configs_[i].main_input_channel_count,
1189 audio_ports_configs_[i].main_input_port_type,
1190 audio_ports_configs_[i].has_main_output * audio_ports_configs_[i].main_output_channel_count,
1191 audio_ports_configs_[i].main_output_port_type);
1192 CDEBUG ("%s: %s", clapid(), s);
1193 }
1194 // note_iport_infos_
1195 note_iport_infos_.resize (!plugin_note_ports ? 0 : plugin_note_ports->count (plugin_, true));
1196 for (size_t i = 0; i < note_iport_infos_.size(); i++)
1197 if (!plugin_note_ports->get (plugin_, i, true, &note_iport_infos_[i]))
1198 note_iport_infos_[i] = { CLAP_INVALID_ID, 0, 0, { 0, }, };
1199 if (note_iport_infos_.size()) {
1200 String s = string_format ("note_iports=%u:", note_iport_infos_.size());
1201 for (size_t i = 0; i < note_iport_infos_.size(); i++)
1202 if (note_iport_infos_[i].id != CLAP_INVALID_ID)
1203 s += string_format (" %u:%s:can=%x:want=%x",
1204 note_iport_infos_[i].id,
1205 note_iport_infos_[i].name,
1206 note_iport_infos_[i].supported_dialects,
1207 note_iport_infos_[i].preferred_dialect);
1208 CDEBUG ("%s: %s", clapid(), s);
1209 }
1210 // note_oport_infos_
1211 note_oport_infos_.resize (!plugin_note_ports ? 0 : plugin_note_ports->count (plugin_, false));
1212 for (size_t i = 0; i < note_oport_infos_.size(); i++)
1213 if (!plugin_note_ports->get (plugin_, i, false, &note_oport_infos_[i]))
1214 note_oport_infos_[i] = { CLAP_INVALID_ID, 0, 0, { 0, }, };
1215 if (note_oport_infos_.size()) {
1216 String s = string_format ("note_oports=%u:", note_oport_infos_.size());
1217 for (size_t i = 0; i < note_oport_infos_.size(); i++)
1218 if (note_oport_infos_[i].id != CLAP_INVALID_ID)
1219 s += string_format (" %u:%s:can=%x:want=%x",
1220 note_oport_infos_[i].id,
1221 note_oport_infos_[i].name,
1222 note_oport_infos_[i].supported_dialects,
1223 note_oport_infos_[i].preferred_dialect);
1224 CDEBUG ("%s: %s", clapid(), s);
1225 }
1226 // audio_iport_infos_
1227 audio_iport_infos_.resize (!plugin_audio_ports ? 0 : plugin_audio_ports->count (plugin_, true));
1228 for (size_t i = 0; i < audio_iport_infos_.size(); i++)
1229 if (!plugin_audio_ports->get (plugin_, i, true, &audio_iport_infos_[i]))
1230 audio_iport_infos_[i] = { CLAP_INVALID_ID, { 0 }, 0, 0, "", CLAP_INVALID_ID };
1231 else
1232 total_channels += audio_iport_infos_[i].channel_count;
1233 if (audio_iport_infos_.size()) {
1234 String s = string_format ("audio_iports=%u:", audio_iport_infos_.size());
1235 for (size_t i = 0; i < audio_iport_infos_.size(); i++)
1236 if (audio_iport_infos_[i].id != CLAP_INVALID_ID && audio_iport_infos_[i].port_type)
1237 s += string_format (" %u:ch=%u:%s:m=%u:%s:",
1238 audio_iport_infos_[i].id,
1239 audio_iport_infos_[i].channel_count,
1240 audio_iport_infos_[i].name,
1241 audio_iport_infos_[i].flags & CLAP_AUDIO_PORT_IS_MAIN,
1242 audio_iport_infos_[i].port_type);
1243 CDEBUG ("%s: %s", clapid(), s);
1244 }
1245 // audio_oport_infos_
1246 audio_oport_infos_.resize (!plugin_audio_ports ? 0 : plugin_audio_ports->count (plugin_, false));
1247 for (size_t i = 0; i < audio_oport_infos_.size(); i++)
1248 if (!plugin_audio_ports->get (plugin_, i, false, &audio_oport_infos_[i]))
1249 audio_oport_infos_[i] = { CLAP_INVALID_ID, { 0 }, 0, 0, "", CLAP_INVALID_ID };
1250 else
1251 total_channels += audio_oport_infos_[i].channel_count;
1252 if (audio_oport_infos_.size()) {
1253 String s = string_format ("audio_oports=%u:", audio_oport_infos_.size());
1254 for (size_t i = 0; i < audio_oport_infos_.size(); i++)
1255 if (audio_oport_infos_[i].id != CLAP_INVALID_ID && audio_oport_infos_[i].port_type)
1256 s += string_format (" %u:ch=%u:%s:m=%u:%s:",
1257 audio_oport_infos_[i].id,
1258 audio_oport_infos_[i].channel_count,
1259 audio_oport_infos_[i].name,
1260 audio_oport_infos_[i].flags & CLAP_AUDIO_PORT_IS_MAIN,
1261 audio_oport_infos_[i].port_type);
1262 CDEBUG ("%s: %s", clapid(), s);
1263 }
1264 // allocate .data32 pointer arrays for all input/output port channels
1265 data32ptrs_.resize (total_channels);
1266 // audio_inputs_
1267 audio_inputs_.resize (audio_iport_infos_.size());
1268 for (size_t i = 0; i < audio_inputs_.size(); i++) {
1269 audio_inputs_[i] = { nullptr, nullptr, 0, 0, 0 };
1270 if (audio_iport_infos_[i].id == CLAP_INVALID_ID) continue;
1271 audio_inputs_[i].channel_count = audio_iport_infos_[i].channel_count;
1272 total_channels -= audio_inputs_[i].channel_count;
1273 audio_inputs_[i].data32 = &data32ptrs_[total_channels];
1274 for (size_t j = 0; j < audio_inputs_[i].channel_count; j++)
1275 audio_inputs_[i].data32[j] = const_cast<float*> (const_float_zeros);
1276 }
1277 // audio_outputs_
1278 audio_outputs_.resize (audio_oport_infos_.size());
1279 for (size_t i = 0; i < audio_outputs_.size(); i++) {
1280 audio_outputs_[i] = { nullptr, nullptr, 0, 0, 0 };
1281 if (audio_oport_infos_[i].id == CLAP_INVALID_ID) continue;
1282 audio_outputs_[i].channel_count = audio_oport_infos_[i].channel_count;
1283 total_channels -= audio_outputs_[i].channel_count;
1284 audio_outputs_[i].data32 = &data32ptrs_[total_channels];
1285 for (size_t j = 0; j < audio_outputs_[i].channel_count; j++)
1286 audio_outputs_[i].data32[j] = scratch_float_buffer;
1287 }
1288 assert_return (total_channels == 0);
1289}
1290
1291// == helpers ==
1292static const char*
1293anklang_host_name()
1294{
1295 static String name = "Anklang//" + executable_name();
1296 return name.c_str();
1297}
1298
1299static String
1300feature_canonify (const String &str)
1301{
1302 return string_canonify (str, string_set_a2z() + string_set_A2Z() + "-0123456789", "-");
1303}
1304
1305static String
1306clapid (const clap_host *host)
1307{
1308 ClapPluginHandleImpl *handle = handle_ptr (host);
1309 return handle->clapid();
1310}
1311
1312static ClapPluginHandleImpl*
1313handle_ptr (const clap_host *host)
1314{
1315 ClapPluginHandleImpl *handle = (ClapPluginHandleImpl*) host->host_data;
1316 return handle;
1317}
1318
1319static ClapPluginHandleImplP
1320handle_sptr (const clap_host *host)
1321{
1322 ClapPluginHandleImpl *handle = handle_ptr (host);
1323 return shared_ptr_cast<ClapPluginHandleImpl> (handle);
1324}
1325
1326static const clap_plugin*
1327access_clap_plugin (ClapPluginHandle *handle)
1328{
1329 ClapPluginHandleImpl *handle_ = dynamic_cast<ClapPluginHandleImpl*> (handle);
1330 return handle_ ? handle_->plugin_ : nullptr;
1331}
1332
1333static bool
1334event_unions_try_push (ClapEventUnionS &events, const clap_event_header_t *event)
1335{
1336 const size_t esize = event->size;
1337 if (esize <= sizeof (events[0]))
1338 {
1339 events.resize (events.size() + 1);
1340 memcpy (&events.back(), event, esize);
1341 return true;
1342 }
1343 return false;
1344}
1345
1346// == clap_host_log ==
1347static void
1348host_log (const clap_host_t *host, clap_log_severity severity, const char *msg)
1349{
1350 static const char *severtities[] = { "DEBUG", "INFO", "WARNING", "ERROR", "FATAL",
1351 "BADHOST", "BADPLUGIN", };
1352 const char *cls = severity < sizeof (severtities) / sizeof (severtities[0]) ? severtities[severity] : "MISC";
1353 if (severity == CLAP_LOG_DEBUG)
1354 CDEBUG ("%s: %s", clapid (host), msg);
1355 else // severity != CLAP_LOG_DEBUG
1356 printerr ("CLAP-%s:%s: %s\n", cls, clapid (host), msg);
1357}
1358static const clap_host_log host_ext_log = { .log = host_log };
1359
1360// == clap_host_timer_support ==
1361static bool
1362host_call_on_timer (ClapPluginHandleImplP handlep, clap_id timer_id)
1363{
1364 // gui_threads_enter();
1365 if (handlep->plugin_timer_support) // register_timer() runs too early for this check
1366 handlep->plugin_timer_support->on_timer (handlep->plugin_, timer_id);
1367 // gui_threads_leave();
1368 return true; // keep-alive
1369}
1370
1371static bool
1372host_register_timer (const clap_host *host, uint32_t period_ms, clap_id *timer_id)
1373{
1374 // Note, plugins (JUCE) may call this method during init(), when plugin_timer_support==NULL
1375 ClapPluginHandleImplP handlep = handle_sptr (host);
1376 period_ms = MAX (30, period_ms);
1377 auto timeridp = std::make_shared<uint> (0);
1378 *timeridp = main_loop->exec_timer ([handlep, timeridp] () {
1379 return host_call_on_timer (handlep, *timeridp);
1380 }, period_ms, period_ms, EventLoop::PRIORITY_UPDATE);
1381 *timer_id = *timeridp;
1382 handlep->timers_.push_back (*timeridp);
1383 CDEBUG ("%s: %s: ms=%u: id=%u", clapid (host), __func__, period_ms, *timer_id);
1384 return true;
1385}
1386
1387static bool
1388host_unregister_timer (const clap_host *host, clap_id timer_id)
1389{
1390 // NOTE: plugin_ might be destroying here
1391 ClapPluginHandleImpl *handle = handle_ptr (host);
1392 const bool deleted = Aux::erase_first (handle->timers_, [timer_id] (uint id) { return id == timer_id; });
1393 if (deleted)
1394 main_loop->remove (timer_id);
1395 CDEBUG ("%s: %s: deleted=%u: id=%u", clapid (host), __func__, deleted, timer_id);
1396 return deleted;
1397}
1398static const clap_host_timer_support host_ext_timer_support = {
1399 .register_timer = host_register_timer,
1400 .unregister_timer = host_unregister_timer,
1401};
1402
1403// == clap_host_thread_check ==
1404static bool
1405host_is_main_thread (const clap_host_t *host)
1406{
1407 return this_thread_is_ase();
1408}
1409
1410static bool
1411host_is_audio_thread (const clap_host_t *host)
1412{
1413 return AudioEngine::thread_is_engine();
1414}
1415
1416static const clap_host_thread_check host_ext_thread_check = {
1417 .is_main_thread = host_is_main_thread,
1418 .is_audio_thread = host_is_audio_thread,
1419};
1420
1421// == clap_host_audio_ports ==
1422static bool
1423host_is_rescan_flag_supported (const clap_host_t *host, uint32_t flag)
1424{
1425 const bool supported = false;
1426 CDEBUG ("%s: %s: %s", clapid (host), __func__, supported);
1427 return supported;
1428}
1429
1430static void
1431host_rescan (const clap_host_t *host, uint32_t flag)
1432{
1433 CDEBUG ("%s: %s", clapid (host), __func__);
1434 // TODO: implement host_rescan, this is a shorthand for all-params-changed
1435}
1436
1437static const clap_host_audio_ports host_ext_audio_ports = {
1438 .is_rescan_flag_supported = host_is_rescan_flag_supported,
1439 .rescan = host_rescan,
1440};
1441
1442// == clap_host_posix_fd_support ==
1443static bool
1444host_register_fd (const clap_host_t *host, int fd, clap_posix_fd_flags_t flags)
1445{
1446 ClapPluginHandleImplP handlep = handle_sptr (host);
1447 auto plugin_on_fd = [handlep] (PollFD &pfd) {
1448 uint revents = 0;
1449 revents |= bool (pfd.revents & PollFD::IN) * CLAP_POSIX_FD_READ;
1450 revents |= bool (pfd.revents & PollFD::OUT) * CLAP_POSIX_FD_WRITE;
1451 revents |= bool (pfd.revents & (PollFD::ERR | PollFD::HUP | PollFD::NVAL)) * CLAP_POSIX_FD_ERROR;
1452 if (0)
1453 CDEBUG ("%s: plugin_on_fd: fd=%d revents=%u: %u", handlep->clapid(), pfd.fd, revents,
1454 handlep->plugin_posix_fd_support && handlep->plugin_posix_fd_support->on_fd);
1455 if (handlep->plugin_posix_fd_support)
1456 handlep->plugin_posix_fd_support->on_fd (handlep->plugin_, pfd.fd, revents);
1457 return true; // keep alive
1458 };
1459 String mode;
1460 if (flags & CLAP_POSIX_FD_READ)
1461 mode += "r";
1462 if (flags & CLAP_POSIX_FD_WRITE)
1463 mode += "w";
1464 ClapPluginHandleImpl::FdPoll fdpoll = {
1465 .fd = fd,
1466 .source = main_loop->exec_io_handler (plugin_on_fd, fd, mode),
1467 .flags = flags,
1468 };
1469 if (fdpoll.source)
1470 handlep->fd_polls_.push_back (fdpoll);
1471 CDEBUG ("%s: %s: fd=%d flags=%u mode=\"%s\" (nfds=%u): id=%u", clapid (host), __func__, fd, flags, mode,
1472 handlep->fd_polls_.size(), fdpoll.source);
1473 return fdpoll.source != 0;
1474}
1475
1476static bool
1477host_unregister_fd (const clap_host_t *host, int fd)
1478{
1479 ClapPluginHandleImplP handlep = handle_sptr (host);
1480 for (size_t i = 0; i < handlep->fd_polls_.size(); i++)
1481 if (handlep->fd_polls_[i].fd == fd) {
1482 const bool deleted = main_loop->try_remove (handlep->fd_polls_[i].source);
1483 CDEBUG ("%s: %s: fd=%d id=%u (nfds=%u): deleted=%u", clapid (host), __func__, fd, handlep->fd_polls_[i].source,
1484 handlep->fd_polls_.size(), deleted);
1485 handlep->fd_polls_.erase (handlep->fd_polls_.begin() + i);
1486 return true;
1487 }
1488 CDEBUG ("%s: %s: fd=%d: deleted=0", clapid (host), __func__, fd);
1489 return false;
1490}
1491
1492static bool
1493host_modify_fd (const clap_host_t *host, int fd, clap_posix_fd_flags_t flags)
1494{
1495 ClapPluginHandleImplP handlep = handle_sptr (host);
1496 for (size_t i = 0; i < handlep->fd_polls_.size(); i++)
1497 if (handlep->fd_polls_[i].fd == fd) {
1498 CDEBUG ("%s: %s: fd=%d flags=%u", clapid (host), __func__, fd, flags);
1499 if (handlep->fd_polls_[i].flags == flags)
1500 return true;
1501 // the following calls invalidate fd_polls_
1502 host_unregister_fd (host, fd);
1503 return host_register_fd (host, fd, flags);
1504 }
1505 CDEBUG ("%s: %s: fd=%d flags=%u: false", clapid (host), __func__, fd, flags);
1506 return false;
1507}
1508
1509static const clap_host_posix_fd_support host_ext_posix_fd_support = {
1510 .register_fd = host_register_fd,
1511 .modify_fd = host_modify_fd,
1512 .unregister_fd = host_unregister_fd,
1513};
1514
1515// == clap_host_params ==
1516static void
1517host_params_rescan (const clap_host_t *host, clap_param_rescan_flags flags)
1518{
1519 CDEBUG ("%s: %s(0x%x)", clapid (host), __func__, flags);
1520}
1521
1522static void
1523host_params_clear (const clap_host_t *host, clap_id param_id, clap_param_clear_flags flags)
1524{
1525 CDEBUG ("%s: %s(%u,0x%x)", clapid (host), __func__, param_id, flags);
1526}
1527
1528static void
1529host_request_flush (const clap_host_t *host)
1530{
1531 CDEBUG ("%s: %s", clapid (host), __func__);
1532}
1533
1534static const clap_host_params host_ext_params = {
1535 .rescan = host_params_rescan,
1536 .clear = host_params_clear,
1537 .request_flush = host_request_flush,
1538};
1539
1540// == clap_host_gui ==
1541static void
1542host_gui_delete_request (ClapPluginHandleImplP handlep)
1543{
1544 CDEBUG ("%s: %s", handlep->clapid(), __func__);
1545 handlep->destroy_gui();
1546}
1547
1548static ulong
1549host_gui_create_x11_window (ClapPluginHandleImplP handlep, int width, int height)
1550{
1551 Gtk2WindowSetup wsetup {
1552 .title = handlep->clapid(), .width = width, .height = height,
1553 .deleterequest_mt = [handlep] () {
1554 main_loop->exec_callback ([handlep]() {
1555 host_gui_delete_request (handlep);
1556 });
1557 },
1558 };
1559 const ulong windowid = x11wrapper->create_window (wsetup);
1560 return windowid;
1561}
1562
1563bool
1564ClapPluginHandleImpl::gui_visible ()
1565{
1566 return gui_visible_;
1567}
1568
1569bool
1570ClapPluginHandleImpl::supports_gui ()
1571{
1572 return plugin_gui != nullptr;
1573}
1574
1575void
1576ClapPluginHandleImpl::show_gui()
1577{
1578 if (plugin_gui)
1579 try_load_x11wrapper();
1580 if (!gui_windowid && plugin_gui && x11wrapper)
1581 {
1582 ClapPluginHandleImplP handlep = handle_sptr (&phost);
1583 const bool floating = false;
1584 clap_window_t cwindow = {
1585 .api = CLAP_WINDOW_API_X11,
1586 .x11 = 0
1587 };
1588 if (plugin_gui->is_api_supported (plugin_, cwindow.api, floating))
1589 {
1590 bool created = plugin_gui->create (plugin_, cwindow.api, floating);
1591 CDEBUG ("%s: gui_create: %d\n", clapid(), created);
1592 gui_canresize = plugin_gui->can_resize (plugin_);
1593 CDEBUG ("%s: gui_can_resize: %d\n", clapid(), gui_canresize);
1594 const double scale = 1.0;
1595 const bool scaled = scale > 0 ? plugin_gui->set_scale (plugin_, scale) : false;
1596 CDEBUG ("%s: gui_set_scale(%f): %d\n", clapid(), scale, scaled);
1597 uint32_t width = 0, height = 0;
1598 const bool sized = plugin_gui->get_size (plugin_, &width, &height);
1599 CDEBUG ("%s: gui_get_size: %ux%u: %d\n", clapid(), width, height, sized);
1600 cwindow.x11 = host_gui_create_x11_window (handlep, width, height);
1601 const bool parentset = plugin_gui->set_parent (plugin_, &cwindow);
1602 CDEBUG ("%s: gui_set_parent: %d\n", clapid(), parentset);
1603 gui_windowid = cwindow.x11;
1604 }
1605 }
1606 if (gui_windowid && plugin_gui) {
1607 gui_visible_ = plugin_gui->show (plugin_);
1608 CDEBUG ("%s: gui_show: %d\n", clapid(), gui_visible_);
1609 if (!gui_visible_)
1610 {} // do nothing, early JUCE versions have a bug returning false here
1611 x11wrapper->show_window (gui_windowid);
1612 }
1613}
1614
1615void
1616ClapPluginHandleImpl::hide_gui()
1617{
1618 if (gui_windowid)
1619 {
1620 plugin_gui->hide (plugin_);
1621 x11wrapper->hide_window (gui_windowid);
1622 gui_visible_ = false;
1623 }
1624}
1625
1626void
1627ClapPluginHandleImpl::destroy_gui()
1628{
1629 hide_gui();
1630 if (gui_windowid) {
1631 plugin_gui->destroy (plugin_);
1632 x11wrapper->destroy_window (gui_windowid);
1633 gui_windowid = 0;
1634 }
1635}
1636
1637static void
1638host_resize_hints_changed (const clap_host_t *host)
1639{
1640 CDEBUG ("%s: %s", clapid (host), __func__);
1641}
1642
1643static bool
1644host_request_resize (const clap_host_t *host, uint32_t width, uint32_t height)
1645{
1646 ClapPluginHandleImpl *handle = handle_ptr (host);
1647 CDEBUG ("%s: %s(%u,%u)", clapid (host), __func__, width, height);
1648 if (handle->gui_windowid) {
1649 if (x11wrapper->resize_window (handle->gui_windowid, width, height)) {
1650 if (handle->plugin_gui->can_resize (handle->plugin_))
1651 handle->plugin_gui->set_size (handle->plugin_, width, height);
1652 return true;
1653 }
1654 }
1655 return false;
1656}
1657
1658static bool
1659host_request_show (const clap_host_t *host)
1660{
1661 const bool supported = false;
1662 CDEBUG ("%s: %s: %d", clapid (host), __func__, supported);
1663 return supported;
1664}
1665
1666static bool
1667host_request_hide (const clap_host_t *host)
1668{
1669 const bool supported = false;
1670 CDEBUG ("%s: %s: %d", clapid (host), __func__, supported);
1671 return supported;
1672}
1673
1674static void
1675host_gui_closed (const clap_host_t *host, bool was_destroyed)
1676{
1677 ClapPluginHandleImpl *handle = handle_ptr (host);
1678 CDEBUG ("%s: %s(was_destroyed=%u)", clapid (host), __func__, was_destroyed);
1679 handle->gui_visible_ = false;
1680 if (was_destroyed && handle->plugin_gui) {
1681 handle->gui_windowid = 0;
1682 handle->plugin_gui->destroy (handle->plugin_);
1683 }
1684}
1685
1686static const clap_host_gui host_ext_gui = {
1687 .resize_hints_changed = host_resize_hints_changed,
1688 .request_resize = host_request_resize,
1689 .request_show = host_request_show,
1690 .request_hide = host_request_hide,
1691 .closed = host_gui_closed,
1692};
1693
1694// == clap_host_file_reference ==
1695void
1696ClapPluginHandleImpl::resolve_file_references (ClapResourceHashS loader_hashes)
1697{
1698 if (!plugin_file_reference || !plugin_file_reference->update_path ||
1699 !plugin_file_reference->count || !plugin_file_reference->get)
1700 return;
1701 const size_t n_files = plugin_file_reference->count (plugin_);
1702 for (size_t i = 0; i < n_files; i++)
1703 {
1704 char buffer[ASE_PATH_MAX + 2] = { 0, };
1705 const size_t path_capacity = sizeof (buffer) - 1;
1706 clap_file_reference pfile = {
1707 .resource_id = CLAP_INVALID_ID,
1708 .belongs_to_plugin_collection = false,
1709 .path_capacity = path_capacity,
1710 .path_size = 0, .path = buffer,
1711 };
1712 if (!plugin_file_reference->get (plugin_, i, &pfile) ||
1713 pfile.path_size >= path_capacity) // ignore excessive path lengths
1714 continue;
1715 if (pfile.path && Path::check (pfile.path, "e"))
1716 continue; // path exists, nothing to resolve
1717 String hash;
1718 uint8_t digest[32] = { 0, };
1719 // fetch hash from plugin or cache
1720 if (plugin_file_reference->get_blake3_digest &&
1721 plugin_file_reference->get_blake3_digest (plugin_, pfile.resource_id, digest))
1722 hash = string_to_hex (String ((const char*) digest, sizeof (digest)));
1723 else
1724 for (size_t i = 0; i < loader_hashes.size(); i++)
1725 if (std::get<clap_id> (loader_hashes[i]) == pfile.resource_id)
1726 {
1727 hash = std::get<String> (loader_hashes[i]);
1728 break;
1729 }
1730 // resolve or warn user
1731 String content_file = hash.empty() ? "" : _project()->loader_resolve (hash);
1732 if (content_file.size())
1733 plugin_file_reference->update_path (plugin_, pfile.resource_id, content_file.c_str());
1734 else
1735 {
1736 String what = string_format ("id=%04x", pfile.resource_id);
1737 if (!hash.empty())
1738 what += " hash=" + hash;
1739 if (pfile.path && pfile.path[0])
1740 what += String (" ") + pfile.path;
1741 ASE_SERVER.user_note (string_format ("## Note Missing File\n%s: \\\nFailed to find file: \\\n%s",
1742 clapid(), what));
1743 }
1744 }
1745}
1746
1747static void
1748host_file_reference_changed (const clap_host_t *host)
1749{
1750 CDEBUG ("%s: %s", clapid (host), __func__);
1751}
1752
1753static void
1754host_file_reference_set_dirty (const clap_host_t *host, clap_id resource_id)
1755{
1756 CDEBUG ("%s: %s: %d", clapid (host), __func__, resource_id);
1757}
1758
1759static const clap_host_file_reference host_ext_file_reference = {
1760 .changed = host_file_reference_changed,
1761 .set_dirty = host_file_reference_set_dirty,
1762};
1763
1764// == clap_host extensions ==
1765static const void*
1766host_get_extension_mt (const clap_host *host, const char *extension_id)
1767{
1768 const String ext = extension_id;
1769 if (ext == CLAP_EXT_LOG) return &host_ext_log;
1770 if (ext == CLAP_EXT_GUI) return &host_ext_gui;
1771 if (ext == CLAP_EXT_FILE_REFERENCE) return &host_ext_file_reference;
1772 if (ext == CLAP_EXT_TIMER_SUPPORT) return &host_ext_timer_support;
1773 if (ext == CLAP_EXT_THREAD_CHECK) return &host_ext_thread_check;
1774 if (ext == CLAP_EXT_AUDIO_PORTS) return &host_ext_audio_ports;
1775 if (ext == CLAP_EXT_PARAMS) return &host_ext_params;
1776 if (ext == CLAP_EXT_POSIX_FD_SUPPORT) return &host_ext_posix_fd_support;
1777 else return nullptr;
1778}
1779
1780static void
1781host_request_restart_mt (const clap_host *host)
1782{
1783 CDEBUG ("%s: %s", clapid (host), __func__);
1784}
1785
1786static void
1787host_request_process_mt (const clap_host *host)
1788{
1789 CDEBUG ("%s: %s", clapid (host), __func__);
1790}
1791
1792static void
1793host_request_callback_mt (const clap_host *host)
1794{
1795 CDEBUG ("%s: %s", clapid (host), __func__);
1796 ClapPluginHandleImplP handlep = handle_sptr (host);
1797 main_loop->exec_callback ([handlep] () {
1798 if (handlep->plugin_) {
1799 // gui_threads_enter();
1800 handlep->plugin_->on_main_thread (handlep->plugin_);
1801 // gui_threads_leave();
1802 }
1803 });
1804}
1805
1806// == ClapPluginDescriptor ==
1807ClapPluginDescriptor::ClapPluginDescriptor (ClapFileHandle &clapfile) :
1808 clapfile_ (clapfile)
1809{}
1810
1811void
1812ClapPluginDescriptor::open() const
1813{
1814 clapfile_.open();
1815}
1816
1817void
1818ClapPluginDescriptor::close() const
1819{
1820 clapfile_.close();
1821}
1822
1823const clap_plugin_entry*
1824ClapPluginDescriptor::entry() const
1825{
1826 return clapfile_.opened() ? clapfile_.pluginentry : nullptr;
1827}
1828
1829void
1830ClapPluginDescriptor::add_descriptor (const String &pluginpath, Collection &infos)
1831{
1832 ClapFileHandle *filehandle = new ClapFileHandle (pluginpath);
1833 filehandle->open();
1834 if (!filehandle->opened()) {
1835 filehandle->close();
1836 delete filehandle;
1837 return;
1838 }
1839 const clap_plugin_factory *pluginfactory = (const clap_plugin_factory *) filehandle->pluginentry->get_factory (CLAP_PLUGIN_FACTORY_ID);
1840 const uint32_t plugincount = !pluginfactory ? 0 : pluginfactory->get_plugin_count (pluginfactory);
1841 for (size_t i = 0; i < plugincount; i++)
1842 {
1843 const clap_plugin_descriptor_t *pdesc = pluginfactory->get_plugin_descriptor (pluginfactory, i);
1844 if (!pdesc || !pdesc->id || !pdesc->id[0])
1845 continue;
1846 const std::string clapversion = string_format ("clap-%u.%u.%u", pdesc->clap_version.major, pdesc->clap_version.minor, pdesc->clap_version.revision);
1847 if (!clap_version_is_compatible (pdesc->clap_version)) {
1848 CDEBUG ("invalid plugin: %s (%s)", pdesc->id, clapversion);
1849 continue;
1850 }
1851 ClapPluginDescriptor *descriptor = new ClapPluginDescriptor (*filehandle);
1852 descriptor->id = pdesc->id;
1853 descriptor->name = pdesc->name ? pdesc->name : pdesc->id;
1854 descriptor->version = pdesc->version ? pdesc->version : "0.0.0-unknown";
1855 descriptor->vendor = pdesc->vendor ? pdesc->vendor : "";
1856 descriptor->url = pdesc->url ? pdesc->url : "";
1857 descriptor->manual_url = pdesc->manual_url ? pdesc->manual_url : "";
1858 descriptor->support_url = pdesc->support_url ? pdesc->support_url : "";
1859 descriptor->description = pdesc->description ? pdesc->description : "";
1860 StringS features;
1861 if (pdesc->features)
1862 for (size_t ft = 0; pdesc->features[ft]; ft++)
1863 if (pdesc->features[ft][0])
1864 features.push_back (feature_canonify (pdesc->features[ft]));
1865 descriptor->features = ":" + string_join (":", features) + ":";
1866 infos.push_back (descriptor);
1867 CDEBUG ("Plugin: %s %s %s (%s, %s)%s", descriptor->name, descriptor->version,
1868 descriptor->vendor.empty() ? "" : "- " + descriptor->vendor,
1869 descriptor->id, clapversion,
1870 descriptor->features.empty() ? "" : ": " + descriptor->features);
1871 }
1872 filehandle->close();
1873}
1874
1875const ClapPluginDescriptor::Collection&
1876ClapPluginDescriptor::collect_descriptors ()
1877{
1878 static Collection collection;
1879 if (collection.empty()) {
1880 for (const auto &clapfile : list_clap_files())
1881 add_descriptor (clapfile, collection);
1882 }
1883 return collection;
1884}
1885
1886// == CLAP utilities ==
1887StringS
1888list_clap_files()
1889{
1891 Path::rglob ("~/.clap", "*.clap", files);
1892 Path::rglob ("/usr/lib/clap", "*.clap", files);
1893 const char *clapsearchpath = getenv ("CLAP_PATH");
1894 if (clapsearchpath)
1895 for (const String &spath : Path::searchpath_split (clapsearchpath))
1896 Path::rglob (spath, "*.clap", files);
1897 Path::unique_realpaths (files);
1898 return files;
1899}
1900
1901const char*
1902clap_event_type_string (int etype)
1903{
1904 switch (etype)
1905 {
1906 case CLAP_EVENT_NOTE_ON: return "NOTE_ON";
1907 case CLAP_EVENT_NOTE_OFF: return "NOTE_OFF";
1908 case CLAP_EVENT_NOTE_CHOKE: return "NOTE_CHOKE";
1909 case CLAP_EVENT_NOTE_END: return "NOTE_END";
1910 case CLAP_EVENT_NOTE_EXPRESSION: return "NOTE_EXPRESSION";
1911 case CLAP_EVENT_PARAM_VALUE: return "PARAM_VALUE";
1912 case CLAP_EVENT_PARAM_MOD: return "PARAM_MOD";
1913 case CLAP_EVENT_PARAM_GESTURE_BEGIN: return "PARAM_GESTURE_BEGIN";
1914 case CLAP_EVENT_PARAM_GESTURE_END: return "PARAM_GESTURE_END";
1915 case CLAP_EVENT_TRANSPORT: return "TRANSPORT";
1916 case CLAP_EVENT_MIDI: return "MIDI";
1917 case CLAP_EVENT_MIDI_SYSEX: return "MIDI_SYSEX";
1918 case CLAP_EVENT_MIDI2: return "MIDI2";
1919 default: return "<UNKNOWN>";
1920 }
1921}
1922
1923String
1924clap_event_to_string (const clap_event_note_t *enote)
1925{
1926 const char *et = clap_event_type_string (enote->header.type);
1927 switch (enote->header.type)
1928 {
1929 case CLAP_EVENT_NOTE_ON:
1930 case CLAP_EVENT_NOTE_OFF:
1931 case CLAP_EVENT_NOTE_CHOKE:
1932 case CLAP_EVENT_NOTE_END:
1933 return string_format ("%+4d ch=%-2u %-14s pitch=%d vel=%f id=%x sz=%d spc=%d flags=%x port=%d",
1934 enote->header.time, enote->channel, et, enote->key, enote->velocity, enote->note_id,
1935 enote->header.size, enote->header.space_id, enote->header.flags, enote->port_index);
1936 default:
1937 return string_format ("%+4d %-20s sz=%d spc=%d flags=%x port=%d",
1938 enote->header.time, et, enote->header.size, enote->header.space_id, enote->header.flags);
1939 }
1940}
1941
1942DeviceInfo
1943clap_device_info (const ClapPluginDescriptor &descriptor)
1944{
1945 DeviceInfo di;
1946 di.uri = "CLAP:" + descriptor.id;
1947 di.name = descriptor.name;
1948 di.description = descriptor.description;
1949 di.website_url = descriptor.url;
1950 di.creator_name = descriptor.vendor;
1951 di.creator_url = descriptor.manual_url;
1952 const char *const cfeatures = descriptor.features.c_str();
1953 if (strstr (cfeatures, ":instrument:")) // CLAP_PLUGIN_FEATURE_INSTRUMENT
1954 di.category = "Instrument";
1955 else if (strstr (cfeatures, ":analyzer:")) // CLAP_PLUGIN_FEATURE_ANALYZER
1956 di.category = "Analyzer";
1957 else if (strstr (cfeatures, ":note-effect:")) // CLAP_PLUGIN_FEATURE_NOTE_EFFECT
1958 di.category = "Note FX";
1959 else if (strstr (cfeatures, ":audio-effect:")) // CLAP_PLUGIN_FEATURE_AUDIO_EFFECT
1960 di.category = "Audio FX";
1961 else if (strstr (cfeatures, ":effect:")) // CLAP_PLUGIN_FEATURE_AUDIO_EFFECT
1962 di.category = "Audio FX";
1963 else
1964 di.category = "Clap Device";
1965 return di;
1966}
1967
1968// == Gtk2DlWrapEntry ==
1969static void
1970try_load_x11wrapper()
1971{
1972 return_unless (x11wrapper == nullptr);
1973 static Gtk2DlWrapEntry *gtk2wrapentry = [] () {
1974 String gtk2wrapso = anklang_runpath (RPath::LIBDIR, "gtk2wrap.so");
1975 void *gtkdlhandle_ = dlopen (gtk2wrapso.c_str(), RTLD_LOCAL | RTLD_NOW);
1976 const char *symname = "Ase__Gtk2__wrapentry";
1977 void *ptr = gtkdlhandle_ ? dlsym (gtkdlhandle_, symname) : nullptr;
1978 return (Gtk2DlWrapEntry*) ptr;
1979 } ();
1980 x11wrapper = gtk2wrapentry;
1981}
1982
1983// == ClapPluginHandle ==
1984ClapPluginHandle::ClapPluginHandle (const ClapPluginDescriptor &descriptor_) :
1985 descriptor (descriptor_)
1986{
1987 descriptor.open();
1988}
1989
1990ClapPluginHandle::~ClapPluginHandle()
1991{
1992 descriptor.close();
1993}
1994
1995CString
1996ClapPluginHandle::audio_processor_type()
1997{
1998 return clap_audio_wrapper_aseid;
1999}
2000
2001ClapPluginHandleP
2002ClapPluginHandle::make_clap_handle (const ClapPluginDescriptor &descriptor, AudioProcessorP audio_processor)
2003{
2004 ClapPluginHandleImplP handlep = std::make_shared<ClapPluginHandleImpl> (descriptor, audio_processor);
2005 handlep->init_plugin();
2006 return handlep;
2007}
2008
2009} // Ase
#define ENOSYS
T c_str(T... args)
Audio signal AudioProcessor base class, implemented by all effects and instruments.
Definition processor.hh:76
void enotify_enqueue_mt(uint32 pushmask)
void remove_all_buses()
Remove existing bus configurations, useful at the start of configure().
Definition processor.cc:646
const float * ifloats(IBusId b, uint c) const
Access readonly float buffer of input bus b, channel c, see also ofloats().
Definition processor.hh:546
MidiEventInput midi_event_input()
Access the current MidiEvent inputs during render(), needs prepare_event_input().
Definition processor.cc:844
uint n_ochannels(OBusId busid) const
Number of channels of output bus busid configured for this AudioProcessor.
Definition processor.hh:509
const AudioTransport & transport() const
Sample rate mixing frequency in Hz as unsigned, used for render().
Definition processor.hh:432
OBusId add_output_bus(CString uilabel, SpeakerArrangement speakerarrangement, const String &hints="", const String &blurb="")
Add an output bus with uilabel and channels configured via speakerarrangement.
Definition processor.cc:530
bool has_event_input() const
Returns true if this AudioProcessor has an event input stream.
Definition processor.hh:459
void atomic_bits_resize(size_t count)
Prepare count bits for atomic notifications.
Definition processor.cc:380
void prepare_event_output()
Definition processor.cc:467
bool atomic_bit_notify(size_t nth)
Set the nth atomic notification bit, return if enotify_enqueue_mt() is needed.
Definition processor.cc:389
AudioEngine & engine() const
Retrieve AudioEngine handle for this AudioProcessor.
Definition processor.hh:425
IBusId add_input_bus(CString uilabel, SpeakerArrangement speakerarrangement, const String &hints="", const String &blurb="")
Add an input bus with uilabel and channels configured via speakerarrangement.
Definition processor.cc:510
bool has_event_output() const
Returns true if this AudioProcessor has an event output stream.
Definition processor.hh:466
uint n_ichannels(IBusId busid) const
Number of channels of input bus busid configured for this AudioProcessor.
Definition processor.hh:501
float * oblock(OBusId b, uint c)
Definition processor.hh:561
DeviceP get_device() const
Gain access to the Device handle of this AudioProcessor.
Definition processor.cc:130
void initialize(SpeakerArrangement busses) override
void render(uint n_frames) override
void reset(uint64 target_stamp) override
Reset all state variables.
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
Definition object.cc:164
static const int16 PRIORITY_UPDATE
Mildly important, used for GUI updates or user information.
Definition loop.hh:94
GadgetImpl * _parent() const override
Retrieve parent container.
Definition gadget.hh:30
ProjectImpl * _project() const
Find Project in parent ancestry.
Definition gadget.cc:168
String loader_resolve(const String &hexhash)
Find file from hash code, returns fspath.
Definition project.cc:428
T clear(T... args)
T count(T... args)
#define ASE_ARRAY_SIZE(array)
Yield the number of C array elements.
Definition cxxaux.hh:51
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
Definition cxxaux.hh:46
#define ASE_ISLIKELY(expr)
Compiler hint to optimize for expr evaluating to true.
Definition cxxaux.hh:45
dlclose
dlerror
dlopen
dlsym
T empty(T... args)
T end(T... args)
errno
T find(T... args)
getenv
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:58
#define MAX(a, b)
Yield maximum of a and b.
Definition internal.hh:52
isnan
llrint
memcpy
T min(T... args)
size_t erase_first(C &container, const std::function< bool(typename C::value_type const &value)> &pred)
Erase first element for which pred() is true in vector or list.
Definition utils.hh:337
void unique_realpaths(StringS &pathnames)
Convert all pathnames via realpath() and eliminate duplicates.
Definition path.cc:869
bool check(const String &file, const String &mode)
Definition path.cc:625
void rmrf(const String &dir)
Recursively delete directory tree.
Definition path.cc:236
void rglob(const String &basedir, const String &pattern, StringS &matches)
Recursively match files with glob pattern under basedir.
Definition path.cc:844
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
constexpr const uint AUDIO_BLOCK_FLOAT_ZEROS_SIZE
Maximum number of values in the const_float_zeros block.
Definition datautils.hh:10
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
IBusId
ID type for AudioProcessor input buses, buses are numbered with increasing index.
Definition processor.hh:21
String string_join(const String &junctor, const StringS &strvec)
Definition strings.cc:452
SpeakerArrangement
Definition transport.hh:11
OBusId
ID type for AudioProcessor output buses, buses are numbered with increasing index.
Definition processor.hh:25
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
Definition strings.cc:1171
float const_float_zeros[AUDIO_BLOCK_FLOAT_ZEROS_SIZE]
Block of const floats allof value 0.
Definition datautils.cc:7
String uri
Unique identifier for de-/serialization.
Definition api.hh:204
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
Definition cxxaux.hh:36
Error
Enum representing Error states.
Definition api.hh:22
const char * ase_error_blurb(Error error)
Describe Error condition.
Definition server.cc:227
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
Definition platform.cc:58
bool debug_enabled() ASE_PURE
Check if any kind of debugging is enabled by $ASE_DEBUG.
Definition utils.hh:175
CString label
Preferred user interface name.
Definition processor.hh:30
String program_alias()
Retrieve the program name as used for logging or debug messages.
Definition platform.cc:849
const char * ase_version()
Provide a string containing the package version.
Definition platform.cc:835
const String & string_set_a2z()
Returns a string containing all of a-z.
Definition strings.cc:102
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
std::string executable_name()
Retrieve the name part of executable_path().
Definition platform.cc:742
String string_canonify(const String &string, const String &valid_chars, const String &substitute)
Definition strings.cc:68
const String & string_set_A2Z()
Returns a string containing all of A-Z.
Definition strings.cc:110
constexpr const int64 TRANSPORT_PPQN
Maximum number of sample frames to calculate in Processor::render().
Definition transport.hh:53
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
RtJobQueue main_rt_jobs
Queue a callback for the main_loop without invoking malloc(), addition is obstruction free.
Definition main.cc:65
String json_stringify(const T &source, Writ::Flags flags=Writ::Flags(0))
Create JSON string from source.
Definition serialize.hh:530
Detailed information and common properties of AudioProcessor subclasses.
Definition processor.hh:29
T push_back(T... args)
T reserve(T... args)
T resize(T... args)
round
T size(T... args)
typedef uint32_t
strcmp
@ HUP
file descriptor closed
Definition loop.hh:26
@ IN
RDNORM || RDBAND.
Definition loop.hh:17
@ NVAL
invalid PollFD
Definition loop.hh:27
@ OUT
writing data will not block
Definition loop.hh:19
@ ERR
error condition
Definition loop.hh:25
Wrap simple callback pointers, without using malloc (obstruction free).
Definition callback.hh:91
time