1 /*
2 * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
18 */
19 /*
20 Copyright 2007-2016 David Robillard <http://drobilla.net>
21
22 Permission to use, copy, modify, and/or distribute this software for any
23 purpose with or without fee is hereby granted, provided that the above
24 copyright notice and this permission notice appear in all copies.
25
26 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
27 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
28 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
29 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
30 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
31 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33 */
34
35 #include "plugins/lv2_plugin.h"
36 #include "plugins/lv2/lv2_gtk.h"
37 #include "plugins/lv2/lv2_ui.h"
38 #include "plugins/plugin.h"
39 #include "plugins/plugin_manager.h"
40 #include "zrythm.h"
41 #include "zrythm_app.h"
42
43 #include <lv2/instance-access/instance-access.h>
44
45 static const bool debug = true;
46
47 /**
48 * Returns if the UI of the plugin is resizable.
49 */
50 bool
lv2_ui_is_resizable(Lv2Plugin * plugin)51 lv2_ui_is_resizable (
52 Lv2Plugin* plugin)
53 {
54 g_return_val_if_fail (
55 plugin->plugin->setting->ui_uri, false);
56
57 LilvNode * s =
58 lilv_new_uri (
59 LILV_WORLD, plugin->plugin->setting->ui_uri);
60 LilvNode * p =
61 lilv_new_uri (
62 LILV_WORLD, LV2_CORE__optionalFeature);
63 LilvNode * fs =
64 lilv_new_uri (
65 LILV_WORLD, LV2_UI__fixedSize);
66 LilvNode * nrs =
67 lilv_new_uri (
68 LILV_WORLD, LV2_UI__noUserResize);
69
70 LilvNodes * fs_matches =
71 lilv_world_find_nodes (LILV_WORLD, s, p, fs);
72 LilvNodes * nrs_matches =
73 lilv_world_find_nodes (LILV_WORLD, s, p, nrs);
74
75 lilv_nodes_free (nrs_matches);
76 lilv_nodes_free (fs_matches);
77 lilv_node_free (nrs);
78 lilv_node_free (fs);
79 lilv_node_free (p);
80 lilv_node_free (s);
81
82 return !fs_matches && !nrs_matches;
83 }
84
85 /**
86 * Suil callback to get the index of the Lv2Port
87 * corresponding to the given symbol.
88 */
89 static uint32_t
get_port_index(SuilController controller,const char * symbol)90 get_port_index (
91 SuilController controller,
92 const char* symbol)
93 {
94 Lv2Plugin* const plugin = (Lv2Plugin*)controller;
95 Port * port =
96 plugin_get_port_by_symbol (
97 plugin->plugin, symbol);
98
99 return
100 port && port->lilv_port_index >= 0 ?
101 (uint32_t) port->lilv_port_index :
102 LV2UI_INVALID_PORT_INDEX;
103 }
104
105 /**
106 * Read and apply control change events from UI,
107 * for plugins that have their own UIs.
108 *
109 * Called in the real-time audio thread during
110 * plugin processing.
111 *
112 * @param nframes Used for event ports.
113 */
114 REALTIME
115 void
lv2_ui_read_and_apply_events(Lv2Plugin * plugin,uint32_t nframes)116 lv2_ui_read_and_apply_events (
117 Lv2Plugin * plugin,
118 uint32_t nframes)
119 {
120 /*if (!lv2_plugin_has_custom_ui (plugin))*/
121 /*{*/
122 /*return;*/
123 /*}*/
124
125 Lv2ControlChange ev;
126 const size_t space =
127 zix_ring_read_space (
128 plugin->ui_to_plugin_events);
129 for (size_t i = 0; i < space;
130 i += sizeof(ev) + ev.size)
131 {
132 zix_ring_read (
133 plugin->ui_to_plugin_events,
134 (char*)&ev, sizeof(ev));
135 char body[ev.size];
136 if (zix_ring_read (
137 plugin->ui_to_plugin_events, body,
138 ev.size) !=
139 ev.size)
140 {
141 g_critical (
142 "Error reading from UI ring buffer");
143 break;
144 }
145 g_return_if_fail (
146 (int) ev.index >= 0 &&
147 (int) ev.index <
148 plugin->plugin->num_lilv_ports);
149 Port * port =
150 plugin->plugin->lilv_ports[ev.index];
151
152 bool have_custom_ui =
153 !plugin->plugin->setting->force_generic_ui;
154
155 /* float control change - this is only for
156 * plugins with custom UIs */
157 if (ev.protocol == 0 && have_custom_ui)
158 {
159 assert (ev.size == sizeof (float));
160 g_return_if_fail (port);
161 port_set_control_value (
162 port, * (float *) body, 0, 0);
163 port->received_ui_event = 1;
164
165 #if 0
166 /* note: should not be printing in the
167 * realtime thread */
168 g_debug (
169 "plugin %s float control '%s' change: "
170 "%f - has custom UI %d",
171 plugin->plugin->setting->descr->name,
172 port->id.label,
173 (double) * (float *) body,
174 have_custom_ui);
175 #endif
176 }
177 else if (ev.protocol ==
178 PM_URIDS.atom_eventTransfer)
179 {
180 LV2_Evbuf_Iterator e =
181 lv2_evbuf_end (port->evbuf);
182 const LV2_Atom* const atom =
183 (const LV2_Atom*)body;
184 lv2_evbuf_write (
185 &e, nframes, 0, atom->type, atom->size,
186 (const uint8_t*)
187 LV2_ATOM_BODY_CONST(atom));
188
189 #if 0
190 /* note: should not be printing in the
191 * realtime thread */
192 g_debug (
193 "%s: plugin %s event transfer "
194 "event for %s - has custom UI %d",
195 __func__, plugin->plugin->setting->descr->name,
196 port->id.label,
197 have_custom_ui);
198 #endif
199 }
200 else
201 {
202 g_critical (
203 "Unknown control change protocol %d",
204 ev.protocol);
205 }
206 }
207 }
208
209 /**
210 * Similar to lv2_ui_send_event_from_plugin_to_ui
211 * except that it passes a float instead of an
212 * LV2 atom.
213 *
214 * @param lv2_port The port to pass the value of.
215 */
216 void
lv2_ui_send_control_val_event_from_plugin_to_ui(Lv2Plugin * lv2_plugin,Port * port)217 lv2_ui_send_control_val_event_from_plugin_to_ui (
218 Lv2Plugin * lv2_plugin,
219 Port * port)
220 {
221 if (!lv2_plugin->plugin->visible ||
222 lv2_plugin->plugin->instantiation_failed)
223 return;
224
225 #if 0
226 g_debug ("%s: %s: %s (%d)",
227 __func__,
228 lv2_plugin->plugin->setting->descr->name,
229 port->id.sym,
230 port->lilv_port_index);
231 #endif
232
233 char buf[sizeof(Lv2ControlChange) +
234 sizeof(float)];
235 Lv2ControlChange* ev =
236 (Lv2ControlChange*)buf;
237 ev->index = (uint32_t) port->lilv_port_index;
238 ev->protocol = 0;
239 ev->size = sizeof(float);
240 *(float*)ev->body = port->control;
241 port->automating = 0;
242
243 if (zix_ring_write (
244 lv2_plugin->plugin_to_ui_events,
245 buf, sizeof(buf)) <
246 sizeof(buf))
247 {
248 PluginDescriptor * descr =
249 lv2_plugin->plugin->setting->descr;
250 g_warning (
251 "Buffer overflow when writing "
252 "events from plugin %s (%s) to "
253 "its UI",
254 descr->name, descr->uri);
255 }
256
257 port->last_sent_control = port->control;
258 }
259
260 /**
261 * Send event to UI, called during the real time
262 * audio thread when processing the plugin.
263 *
264 * @param type Atom type.
265 */
266 REALTIME
267 int
lv2_ui_send_event_from_plugin_to_ui(Lv2Plugin * plugin,uint32_t port_index,uint32_t type,uint32_t size,const void * body)268 lv2_ui_send_event_from_plugin_to_ui (
269 Lv2Plugin * plugin,
270 uint32_t port_index,
271 uint32_t type,
272 uint32_t size,
273 const void * body)
274 {
275 /* TODO: Be more disciminate about what to send */
276 char evbuf[
277 sizeof (Lv2ControlChange) +
278 sizeof (LV2_Atom)];
279 Lv2ControlChange* ev = (Lv2ControlChange*) evbuf;
280 ev->index = port_index;
281 ev->protocol = PM_URIDS.atom_eventTransfer;
282 ev->size =
283 (uint32_t) sizeof (LV2_Atom) + size;
284
285 LV2_Atom* atom = (LV2_Atom*)ev->body;
286 atom->type = type;
287 atom->size = (uint32_t) size;
288
289 if (zix_ring_write_space (
290 plugin->plugin_to_ui_events) >=
291 sizeof(evbuf) + size)
292 {
293 zix_ring_write (
294 plugin->plugin_to_ui_events, evbuf,
295 sizeof(evbuf));
296 zix_ring_write (
297 plugin->plugin_to_ui_events,
298 (const char*)body, size);
299 return 1;
300 }
301 else
302 {
303 PluginDescriptor * descr =
304 plugin->plugin->setting->descr;
305 g_warning (
306 "Buffer overflow when sending plugin %s "
307 "(%s) event to its UI",
308 descr->name, descr->uri);
309 return 0;
310 }
311 }
312
313 /**
314 * Write events from the plugin's UI to the plugin.
315 *
316 * @param protocol Format, 0 for control-port event,
317 * > 0 for message.
318 */
319 void
lv2_ui_send_event_from_ui_to_plugin(Lv2Plugin * plugin,uint32_t port_index,uint32_t buffer_size,uint32_t protocol,const void * buffer)320 lv2_ui_send_event_from_ui_to_plugin (
321 Lv2Plugin * plugin,
322 uint32_t port_index,
323 uint32_t buffer_size,
324 uint32_t protocol,
325 const void* buffer)
326 {
327 if ((int) port_index < 0 ||
328 (int) port_index >=
329 plugin->plugin->num_lilv_ports)
330 {
331 g_warning (
332 "UI write to out of range port index %d",
333 port_index);
334 return;
335 }
336
337 #if 0
338 Port * port =
339 plugin->plugin->lilv_ports[port_index];
340 if (port->lilv_port_index > -1)
341 {
342 g_debug ("port %d", port_index);
343 }
344 else
345 {
346 g_debug (
347 "param %d (%s)", port_index, port->id.sym);
348 }
349 #endif
350
351 if (protocol != 0 &&
352 protocol != PM_URIDS.atom_eventTransfer)
353 {
354 g_warning (
355 "UI write with unsupported protocol %d (%s)",
356 protocol,
357 lv2_urid_unmap_uri (plugin, protocol));
358 return;
359 }
360
361 /* also see https://git.open-music-kontrollers.ch/lv2/sherlock.lv2/tree/atom_inspector_nk.c#n39 */
362 if (debug &&
363 protocol == PM_URIDS.atom_eventTransfer)
364 {
365 const LV2_Atom* atom =
366 (const LV2_Atom*)buffer;
367 g_debug (
368 "[atom] type <%s>, %d bytes",
369 plugin->unmap.unmap (
370 plugin->unmap.handle, atom->type),
371 atom->size);
372 char * str =
373 sratom_to_turtle (
374 plugin->sratom,
375 &plugin->unmap,
376 "plugin:",
377 NULL, NULL,
378 atom->type,
379 atom->size,
380 LV2_ATOM_BODY_CONST (atom));
381 g_message (
382 "## UI => Plugin (%u bytes) ##\n%s",
383 atom->size, str);
384 free(str);
385 }
386
387 char buf[sizeof(Lv2ControlChange) + buffer_size];
388 Lv2ControlChange* ev = (Lv2ControlChange*)buf;
389 ev->index = port_index;
390 ev->protocol = protocol;
391 ev->size = buffer_size;
392 memcpy (ev->body, buffer, buffer_size);
393 zix_ring_write (
394 plugin->ui_to_plugin_events, buf,
395 (uint32_t) sizeof(buf));
396 }
397
398 /**
399 * Instantiates the plugin UI.
400 */
401 void
lv2_ui_instantiate(Lv2Plugin * plugin)402 lv2_ui_instantiate (
403 Lv2Plugin * plugin)
404 {
405 plugin->suil_host =
406 suil_host_new (
407 (SuilPortWriteFunc)
408 lv2_ui_send_event_from_ui_to_plugin,
409 get_port_index, NULL, NULL);
410 plugin->external_ui_widget = NULL;
411
412 char * bundle_uri =
413 lv2_plugin_get_ui_bundle_uri (
414 plugin->plugin->setting->descr->uri,
415 plugin->plugin->setting->ui_uri);
416 g_return_if_fail (bundle_uri);
417 char * binary_uri =
418 lv2_plugin_get_ui_binary_uri (
419 plugin->plugin->setting->descr->uri,
420 plugin->plugin->setting->ui_uri);
421 char* bundle_path =
422 lilv_file_uri_parse (bundle_uri, NULL);
423 char* binary_path =
424 lilv_file_uri_parse (binary_uri, NULL);
425
426 /*
427 * Data access feature.
428 *
429 * Pass the LV2_Extension_Data_Feature initialized
430 * during plugin instantiation. This allows the UI
431 * to call the data_access method in ext_data to
432 * receive data from the plugin instance.
433 */
434 const LV2_Feature data_feature =
435 {
436 LV2_DATA_ACCESS_URI, &plugin->ext_data_feature,
437 };
438
439 const LV2_Feature idle_feature =
440 {
441 LV2_UI__idleInterface, NULL
442 };
443
444 /**
445 * Instance access feature.
446 *
447 * Pass the plugin instance handle.
448 */
449 const LV2_Feature instance_feature = {
450 LV2_INSTANCE_ACCESS_URI,
451 lilv_instance_get_handle (plugin->instance)
452 };
453
454 /*
455 * Instantiate plugin UI.
456 *
457 * Note: suil appends and implements the following
458 * features:
459 * * LV2_UI__portMap
460 * * LV2_UI__portSubscribe
461 * * LV2_UI__touch
462 * * LV2_UI__parent
463 * * LV2_UI__resize
464 * * LV2_UI__idleInterface
465 */
466 if (plugin->has_external_ui)
467 {
468 /* deprecated URI for backwards
469 * compatibility */
470 const LV2_Feature external_ui_feature = {
471 .URI = LV2_EXTERNAL_UI_DEPRECATED_URI,
472 .data = &plugin->extui,
473 };
474
475 const LV2_Feature external_kxui_feature = {
476 .URI = LV2_EXTERNAL_UI__Host,
477 .data = &plugin->extui,
478 };
479
480 const LV2_Feature* ui_features[] = {
481 &plugin->map_feature,
482 &plugin->unmap_feature,
483 &instance_feature,
484 &data_feature,
485 &idle_feature,
486 &plugin->log_feature,
487 &external_ui_feature,
488 &external_kxui_feature,
489 &plugin->options_feature,
490 NULL
491 };
492
493 char * ui_class =
494 lv2_plugin_get_ui_class (
495 plugin->plugin->setting->descr->uri,
496 plugin->plugin->setting->ui_uri);
497 plugin->suil_instance =
498 suil_instance_new (
499 plugin->suil_host, plugin,
500 ui_class,
501 plugin->plugin->setting->descr->uri,
502 plugin->plugin->setting->ui_uri,
503 ui_class,
504 bundle_path, binary_path, ui_features);
505 g_free (ui_class);
506
507 if (plugin->suil_instance)
508 {
509 plugin->external_ui_widget =
510 suil_instance_get_widget (
511 plugin->suil_instance);
512 }
513 else
514 {
515 plugin->has_external_ui = 0;
516 }
517 }
518 else
519 {
520 const LV2_Feature* ui_features[] = {
521 &plugin->map_feature,
522 &plugin->unmap_feature,
523 &instance_feature,
524 &data_feature,
525 &idle_feature,
526 &plugin->log_feature,
527 &plugin->options_feature,
528 NULL,
529 };
530
531 char * ui_class =
532 lv2_plugin_get_ui_class (
533 plugin->plugin->setting->descr->uri,
534 plugin->plugin->setting->ui_uri);
535 plugin->suil_instance =
536 suil_instance_new (
537 plugin->suil_host,
538 plugin,
539 LV2_UI__Gtk3UI,
540 plugin->plugin->setting->descr->uri,
541 plugin->plugin->setting->ui_uri,
542 ui_class,
543 bundle_path,
544 binary_path,
545 ui_features);
546 g_free (ui_class);
547 }
548
549 lilv_free (binary_path);
550 lilv_free (bundle_path);
551 g_free (binary_uri);
552 g_free (bundle_uri);
553
554 if (!plugin->suil_instance)
555 {
556 g_warning (
557 "Failed to get UI instance for %s",
558 plugin->plugin->setting->descr->name);
559 }
560 }
561
562 /**
563 * Inits the LV2 plugin UI.
564 *
565 * To be called for generic, suil-wrapped and
566 * external UIs.
567 */
568 void
lv2_ui_init(Lv2Plugin * plugin)569 lv2_ui_init (
570 Lv2Plugin* plugin)
571 {
572 // Set initial control port values
573 for (int i = 0;
574 i < plugin->plugin->num_lilv_ports; ++i)
575 {
576 Port * port = plugin->plugin->lilv_ports[i];
577 if (port->id.type == TYPE_CONTROL &&
578 !(port->id.flags & PORT_FLAG_IS_PROPERTY))
579 {
580 lv2_gtk_ui_port_event (
581 plugin, (uint32_t) i,
582 sizeof(float), 0,
583 &port->control);
584 }
585 }
586
587 if (plugin->control_in != -1)
588 {
589 /* Send patch:Get message for initial
590 * parameters/etc */
591 LV2_Atom_Forge * forge = &plugin->main_forge;
592 LV2_Atom_Forge_Frame frame;
593 uint8_t buf[1024];
594 lv2_atom_forge_set_buffer (
595 forge, buf, sizeof(buf));
596 lv2_atom_forge_object (
597 forge, &frame, 0, PM_URIDS.patch_Get);
598
599 const LV2_Atom* atom =
600 lv2_atom_forge_deref (forge, frame.ref);
601 lv2_ui_send_event_from_ui_to_plugin (
602 plugin,
603 (uint32_t) plugin->control_in,
604 lv2_atom_total_size (atom),
605 PM_URIDS.atom_eventTransfer,
606 atom);
607 lv2_atom_forge_pop (forge, &frame);
608 }
609 }
610