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