Fourth set of 50 GPL attribution for CV-Contributors added +
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / pluginlv2.C
1 /*
2  * CINELERRA
3  * Copyright (C) 2016-2020 William Morrow
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published
7  * by the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #ifdef HAVE_LV2
22
23 #include "bctrace.h"
24 #include "bcwindowbase.h"
25 #include "pluginlv2.h"
26 #include "samples.h"
27
28 #include <sys/shm.h>
29 #include <sys/mman.h>
30
31 PluginLV2::PluginLV2()
32 {
33         shm_bfr = 0;
34         use_shm = 1;
35         shmid = -1;
36         in_buffers = 0;   iport = 0;
37         out_buffers = 0;  oport = 0;
38         nb_inputs = 0;
39         nb_outputs = 0;
40         max_bufsz = 0;
41         ui_features = 0;
42
43         samplerate = 44100;
44         refreshrate = 30.;
45         min_block_length = 1;
46         block_length = 4096;
47         midi_buf_size = 8192;
48
49         world = 0;
50         lilv = 0;
51         lilv_uis = 0;
52         inst = 0;
53         sinst = 0;
54         ui_host = 0;
55
56         lv2_InputPort = 0;
57         lv2_OutputPort = 0;
58         lv2_AudioPort = 0;
59         lv2_ControlPort = 0;
60         lv2_CVPort = 0;
61         lv2_Optional = 0;
62         atom_AtomPort = 0;
63         atom_Sequence = 0;
64         powerOf2BlockLength = 0;
65         fixedBlockLength = 0;
66         boundedBlockLength = 0;
67         seq_out = 0;
68
69         worker_thread = 0;
70         memset(&schedule, 0, sizeof(schedule));
71         schedule.handle = (LV2_Worker_Schedule_Handle)this;
72         schedule.schedule_work = lv2_worker_schedule;
73         worker_iface = 0;  worker_done = -1;
74         pthread_mutex_init(&worker_lock, 0);
75         pthread_mutex_init(&startup_lock, 0);
76         pthread_mutex_lock(&startup_lock);
77         pthread_cond_init(&worker_ready, 0);
78         work_avail = 0;   work_input = 0;
79         work_output = 0;  work_tail = &work_output;
80 }
81
82 PluginLV2::~PluginLV2()
83 {
84         reset_lv2();
85         if( world )     lilv_world_free(world);
86         pthread_mutex_destroy(&worker_lock);
87         pthread_cond_destroy(&worker_ready);
88 }
89
90 void PluginLV2::reset_lv2()
91 {
92         worker_stop();
93         if( inst ) lilv_instance_deactivate(inst);
94         lilv_instance_free(inst);             inst = 0;
95         lilv_uis_free(lilv_uis);              lilv_uis = 0;
96
97         lilv_node_free(lv2_InputPort);        lv2_InputPort = 0;
98         lilv_node_free(lv2_OutputPort);       lv2_OutputPort = 0;
99         lilv_node_free(lv2_AudioPort);        lv2_AudioPort = 0;
100         lilv_node_free(lv2_ControlPort);      lv2_ControlPort = 0;
101         lilv_node_free(lv2_CVPort);           lv2_CVPort = 0;
102
103         lilv_node_free(lv2_Optional);         lv2_Optional = 0;
104         lilv_node_free(atom_AtomPort);        atom_AtomPort = 0;
105         lilv_node_free(atom_Sequence);        atom_Sequence = 0;
106         lilv_node_free(boundedBlockLength);   boundedBlockLength = 0;
107         lilv_node_free(fixedBlockLength);     fixedBlockLength = 0;
108         lilv_node_free(powerOf2BlockLength);  powerOf2BlockLength = 0;
109
110         delete [] (char *)seq_out;            seq_out = 0;
111         uri_table.remove_all_objects();
112         features.remove_all_objects();        ui_features = 0;
113         del_buffer();
114 }
115
116
117 int PluginLV2::load_lv2(const char *path, char *title)
118 {
119         if( !world ) {
120                 world = lilv_world_new();
121                 if( !world ) {
122                         printf("lv2: lilv_world_new failed\n");
123                         return 1;
124                 }
125                 lilv_world_load_all(world);
126         }
127
128         LilvNode *uri = lilv_new_uri(world, path);
129         if( !uri ) {
130                 printf("lv2: lilv_new_uri(%s) failed\n", path);
131                 return 1;
132         }
133
134         const LilvPlugins *all_plugins = lilv_world_get_all_plugins(world);
135         lilv = lilv_plugins_get_by_uri(all_plugins, uri);
136         lilv_node_free(uri);
137         if( !lilv ) {
138                 printf("lv2: lilv_plugins_get_by_uri (%s) failed\n", path);
139                 return 1;
140         }
141
142         if( title ) {
143                 LilvNode *name = lilv_plugin_get_name(lilv);
144                 const char *nm = lilv_node_as_string(name);
145                 sprintf(title, "L2_%s", nm);
146                 lilv_node_free(name);
147         }
148         return 0;
149 }
150
151 int PluginLV2::init_lv2(PluginLV2ClientConfig &conf, int sample_rate, int bfrsz)
152 {
153         reset_lv2();
154         double bps = 2. * sample_rate / bfrsz;
155         if( bps > refreshrate ) refreshrate = bps;
156
157         lv2_AudioPort       = lilv_new_uri(world, LV2_CORE__AudioPort);
158         lv2_ControlPort     = lilv_new_uri(world, LV2_CORE__ControlPort);
159         lv2_CVPort          = lilv_new_uri(world, LV2_CORE__CVPort);
160         lv2_InputPort       = lilv_new_uri(world, LV2_CORE__InputPort);
161         lv2_OutputPort      = lilv_new_uri(world, LV2_CORE__OutputPort);
162         lv2_Optional        = lilv_new_uri(world, LV2_CORE__connectionOptional);
163         atom_AtomPort       = lilv_new_uri(world, LV2_ATOM__AtomPort);
164         atom_Sequence       = lilv_new_uri(world, LV2_ATOM__Sequence);
165         powerOf2BlockLength = lilv_new_uri(world, LV2_BUF_SIZE__powerOf2BlockLength);
166         fixedBlockLength    = lilv_new_uri(world, LV2_BUF_SIZE__fixedBlockLength);
167         boundedBlockLength  = lilv_new_uri(world, LV2_BUF_SIZE__boundedBlockLength);
168         seq_out = (LV2_Atom_Sequence *) new char[sizeof(LV2_Atom_Sequence) + LV2_SEQ_SIZE];
169
170         conf.init_lv2(lilv, this);
171         nb_inputs = nb_outputs = 0;
172
173         for( int i=0; i<conf.nb_ports; ++i ) {
174                 const LilvPort *lp = lilv_plugin_get_port_by_index(lilv, i);
175                 if( !lp ) continue;
176                 int is_input = lilv_port_is_a(lilv, lp, lv2_InputPort);
177                 if( !is_input && !lilv_port_is_a(lilv, lp, lv2_OutputPort) &&
178                     !lilv_port_has_property(lilv, lp, lv2_Optional) ) {
179                         printf("lv2: not input, not output, and not optional: %s\n", conf.names[i]);
180                         continue;
181                 }
182                 if( is_input && lilv_port_is_a(lilv, lp, lv2_ControlPort) ) {
183                         conf.append(new PluginLV2Client_Opt(&conf, i));
184                         continue;
185                 }
186                 if( lilv_port_is_a(lilv, lp, lv2_AudioPort) ||
187                     lilv_port_is_a(lilv, lp, lv2_CVPort ) ) {
188                         if( is_input ) ++nb_inputs; else ++nb_outputs;
189                         continue;
190                 }
191         }
192
193         if( !nb_inputs || !nb_outputs ) {
194                 printf(": Unsupported lv2 plugin, missing audio input or output\n");
195                 reset_lv2();
196                 return 1;
197         }
198
199         uri_map.handle = (LV2_URID_Map_Handle)this;
200         uri_map.map = map_uri;
201         features.append(new Lv2Feature(LV2_URID__map, &uri_map));
202         uri_unmap.handle = (LV2_URID_Unmap_Handle)this;
203         uri_unmap.unmap = unmap_uri;
204         features.append(new Lv2Feature(LV2_URID__unmap, &uri_unmap));
205         features.append(new Lv2Feature(LV2_BUF_SIZE__powerOf2BlockLength, 0));
206         features.append(new Lv2Feature(LV2_BUF_SIZE__fixedBlockLength,    0));
207         features.append(new Lv2Feature(LV2_BUF_SIZE__boundedBlockLength,  0));
208         features.append(new Lv2Feature(LV2_WORKER__schedule, &schedule));
209
210         if( sample_rate < 64 ) sample_rate = samplerate;
211
212         atom_int   = uri_table.map(LV2_ATOM__Int);
213         atom_float = uri_table.map(LV2_ATOM__Float);
214         param_sampleRate = uri_table.map(LV2_PARAMETERS__sampleRate);
215         bufsz_minBlockLength =  uri_table.map(LV2_BUF_SIZE__minBlockLength);
216         bufsz_maxBlockLength = uri_table.map(LV2_BUF_SIZE__maxBlockLength);
217         bufsz_sequenceSize =  uri_table.map(LV2_BUF_SIZE__sequenceSize);
218         ui_updateRate = uri_table.map(LV2_UI__updateRate);
219
220         samplerate = sample_rate;
221         options.add(param_sampleRate, sizeof(float), atom_float, &samplerate);
222         if( min_block_length > bfrsz ) min_block_length = bfrsz;
223         options.add(bufsz_minBlockLength, sizeof(int), atom_int, &min_block_length);
224         block_length = bfrsz;
225         options.add(bufsz_maxBlockLength, sizeof(int), atom_int, &block_length);
226         options.add(bufsz_sequenceSize, sizeof(int), atom_int, &midi_buf_size);
227         options.add(ui_updateRate, sizeof(float),  atom_float, &refreshrate);
228         options.add(0, 0, 0, 0);
229
230         features.append(new Lv2Feature(LV2_OPTIONS__options,  &options[0]));
231         features.append(0);
232
233         inst = lilv_plugin_instantiate(lilv, sample_rate, features);
234         if( !inst ) {
235                 printf("lv2: lilv_plugin_instantiate failed\n");
236                 return 1;
237         }
238         
239 // After instantiate, some plugins require fields to be filled in before
240 // activate is called. This is done via ConnectPort, which connects a
241 // port to a data structure in the host (CinGG). So these have to be
242 // allocated first.
243
244         init_buffer(bfrsz);
245         connect_ports(conf, PORTS_ALL);
246         
247         const LV2_Descriptor *lilv_desc = inst->lv2_descriptor;
248         worker_iface = !lilv_desc->extension_data ? 0 :
249                 (LV2_Worker_Interface*)lilv_desc->extension_data(LV2_WORKER__interface);
250         if( worker_iface )
251                 worker_start();
252
253         lilv_instance_activate(inst);
254 // not sure what to do with these
255         max_bufsz = nb_inputs &&
256                 (lilv_plugin_has_feature(lilv, powerOf2BlockLength) ||
257                  lilv_plugin_has_feature(lilv, fixedBlockLength) ||
258                  lilv_plugin_has_feature(lilv, boundedBlockLength)) ? 4096 : 0;
259
260         return 0;
261 }
262
263 uint32_t PluginLV2::map_uri(LV2_URID_Map_Handle handle, const char *uri)
264 {
265         PluginLV2 *the = (PluginLV2 *)handle;
266         return the->uri_table.map(uri);
267 }
268
269 const char *PluginLV2::unmap_uri(LV2_URID_Unmap_Handle handle, LV2_URID urid)
270 {
271         PluginLV2 *the = (PluginLV2 *)handle;
272         return the->uri_table.unmap(urid);
273 }
274
275 void PluginLV2::connect_ports(PluginLV2ClientConfig &conf, int ports)
276 {
277         int ich = 0, och = 0;
278         for( int i=0; i<conf.nb_ports; ++i ) {
279                 const LilvPort *lp = lilv_plugin_get_port_by_index(lilv, i);
280                 if( !lp ) continue;
281                 int port = conf.ports[i];
282                 if( !(port & ports) ) continue;
283                 if( (port & PORTS_CONTROL) ) {
284                         lilv_instance_connect_port(inst, i, &conf.ctls[i]);
285                         continue;
286                 }
287                 if( (port & PORTS_AUDIO) ) {
288                         if( (port & PORTS_INPUT) ) {
289                                 lilv_instance_connect_port(inst, i, in_buffers[ich]);
290                                 iport[ich++] = i;
291                         }
292                         else if( (port & PORTS_OUTPUT) ) {
293                                 lilv_instance_connect_port(inst, i, out_buffers[och]);
294                                 oport[och++] = i;
295                         }
296                         continue;
297                 }
298                 if( (port & PORTS_ATOM) ) {
299                         if( (port & PORTS_INPUT) )
300                                 lilv_instance_connect_port(inst, i, &seq_in);
301                         else
302                                 lilv_instance_connect_port(inst, i, seq_out);
303                         continue;
304                 }
305         }
306
307         seq_in[0].atom.size = sizeof(LV2_Atom_Sequence_Body);
308         seq_in[0].atom.type = uri_table.map(LV2_ATOM__Sequence);
309         seq_in[1].atom.size = 0;
310         seq_in[1].atom.type = 0;
311         seq_out->atom.size  = LV2_SEQ_SIZE;
312         seq_out->atom.type  = uri_table.map(LV2_ATOM__Chunk);
313 }
314
315 void PluginLV2::del_buffer()
316 {
317         if( shmid >= 0 )
318                 shm_buffer(-1);
319
320         delete [] in_buffers;  in_buffers = 0;
321         delete [] out_buffers;  out_buffers = 0;
322 }
323
324 void PluginLV2::new_buffer(int64_t sz)
325 {
326         uint8_t *bp = 0;
327         if( use_shm ) {  // currently, always uses shm
328                 shmid = shmget(IPC_PRIVATE, sz, IPC_CREAT | 0777);
329                 if( shmid >= 0 ) {
330                         bp = (unsigned char*)shmat(shmid, NULL, 0);
331                         if( bp == (void *) -1 ) { perror("shmat"); bp = 0; }
332                         shmctl(shmid, IPC_RMID, 0); // delete when last ref gone
333                 }
334                 else {
335                         perror("PluginLV2::allocate_buffer: shmget failed\n");
336                         BC_Trace::dump_shm_stats(stdout);
337                 }
338         }
339
340         shm_bfr = (shm_bfr_t *) bp;
341         if( shm_bfr ) shm_bfr->sz = sz;
342 }
343
344 shm_bfr_t *PluginLV2::shm_buffer(int shmid)
345 {
346         if( this->shmid != shmid ) {
347                 if( this->shmid >= 0 ) {
348                         shmdt(shm_bfr);  this->shmid = -1;
349                         shm_bfr = 0;
350                 }
351                 if( shmid >= 0 ) {
352                         shm_bfr = (shm_bfr_t *)shmat(shmid, NULL, 0);
353                         if( shm_bfr == (void *)-1 ) { perror("shmat");  shm_bfr = 0; }
354                         this->shmid = shm_bfr ? shmid : -1;
355                 }
356         }
357         return shm_bfr;
358 }
359
360 void PluginLV2::init_buffer(int samples)
361 {
362         int64_t sz = sizeof(shm_bfr_t) +
363                 sizeof(iport[0])*nb_inputs + sizeof(oport[0])*nb_outputs +
364                 sizeof(*in_buffers[0]) *samples * nb_inputs +
365                 sizeof(*out_buffers[0])*samples * nb_outputs;
366
367         if( shm_bfr ) {
368                 if( shm_bfr->sz < sz ||
369                     shm_bfr->nb_inputs != nb_inputs ||
370                     shm_bfr->nb_outputs != nb_outputs )
371                         del_buffer();
372         }
373
374         if( !shm_bfr )
375                 new_buffer(sz);
376
377         shm_bfr->samples = samples;
378         shm_bfr->done = 0;
379         shm_bfr->nb_inputs = nb_inputs;
380         shm_bfr->nb_outputs = nb_outputs;
381
382         map_buffer();
383 }
384
385 // shm_bfr layout:
386 // struct shm_bfr {
387 //   int64_t sz;
388 //   int samples, done;
389 //   int nb_inputs, nb_outputs;
390 //   int iport[nb_inputs], 
391 //   float in_buffers[samples][nb_inputs];
392 //   int oport[nb_outputs];
393 //   float out_buffers[samples][nb_outputs];
394 // };
395
396 void PluginLV2::map_buffer()
397 {
398         uint8_t *bp = (uint8_t *)(shm_bfr + 1);
399
400         nb_inputs = shm_bfr->nb_inputs;
401         iport = (int *)bp;
402         bp += sizeof(iport[0])*nb_inputs;
403         in_buffers = new float*[nb_inputs];
404         int samples = shm_bfr->samples;
405         for(int i=0; i<nb_inputs; ++i ) {
406                 in_buffers[i] = (float *)bp;
407                 bp += sizeof(*in_buffers[0])*samples;
408         }
409
410         nb_outputs = shm_bfr->nb_outputs;
411         oport = (int *)bp;
412         bp += sizeof(oport[0])*nb_outputs;
413         out_buffers = new float*[nb_outputs];
414         for( int i=0; i<nb_outputs; ++i ) {
415                 out_buffers[i] = (float *)bp;
416                 bp += sizeof(*out_buffers[0])*samples;
417         }
418 }
419
420
421 // LV2 Worker
422
423 PluginLV2Work::PluginLV2Work()
424 {
425         next = 0;
426         alloc = used = 0;
427         data = 0;
428 }
429
430 PluginLV2Work::~PluginLV2Work()
431 {
432         delete [] data;
433 }
434
435 void PluginLV2Work::load(const void *vp, unsigned size)
436 {
437         if( alloc < size ) {
438                 delete [] data;
439                 data = new char[alloc=size];
440         }
441         memcpy(data, vp, used=size);
442 }
443
444 PluginLV2Work *PluginLV2::get_work()
445 {
446 // must hold worker_lock
447         if( !work_avail ) return new PluginLV2Work();
448         PluginLV2Work *wp = work_avail;
449         work_avail = wp->next;  wp->next = 0;
450         return wp;
451 }
452
453 void *PluginLV2::worker_func()
454 {
455         pthread_mutex_lock(&worker_lock);
456         pthread_mutex_unlock(&startup_lock);
457         for(;;) {
458                 while( !worker_done && !work_input )
459                         pthread_cond_wait(&worker_ready, &worker_lock);
460                 if( worker_done ) break;
461                 PluginLV2Work *wp = work_input;  work_input = wp->next;
462
463                 pthread_mutex_unlock(&worker_lock);
464                 worker_iface->work(inst, lv2_worker_respond, this, wp->used, wp->data);
465                 pthread_mutex_lock(&worker_lock);
466                 wp->next = work_avail;  work_avail = wp;
467         }
468         pthread_mutex_unlock(&worker_lock);
469         return NULL;
470 }
471 void *PluginLV2::worker_func(void* vp)
472 {
473         PluginLV2 *the = (PluginLV2 *)vp;
474         return the->worker_func();
475 }
476
477 void PluginLV2::worker_start()
478 {
479         pthread_create(&worker_thread, 0, worker_func, this);
480         pthread_mutex_lock(&startup_lock);
481 }
482
483 void PluginLV2::worker_stop()
484 {
485         if( !worker_done ) {
486                 worker_done = 1;
487                 pthread_mutex_lock(&worker_lock);
488                 pthread_cond_signal(&worker_ready);
489                 pthread_mutex_unlock(&worker_lock);
490                 pthread_join(worker_thread, 0);
491                 worker_thread = 0;
492         }
493         work_stop(work_avail);
494         work_stop(work_input);
495         work_stop(work_output);
496         work_tail = &work_output;
497 }
498
499 void PluginLV2::work_stop(PluginLV2Work *&work)
500 {
501         while( work ) {
502                 PluginLV2Work *wp = work;
503                 work = wp->next;
504                 delete wp;
505         }
506 }
507
508 LV2_Worker_Status PluginLV2::worker_schedule(uint32_t inp_size, const void *inp_data)
509 {
510         if( is_forked() ) {
511                 if( !pthread_mutex_trylock(&worker_lock) ) {
512                         PluginLV2Work *wp = get_work();
513                         wp->load(inp_data, inp_size);
514                         wp->next = work_input;  work_input = wp;
515                         pthread_cond_signal(&worker_ready);
516                         pthread_mutex_unlock(&worker_lock);
517                 }
518         }
519         else if( worker_iface )
520                 worker_iface->work(inst, lv2_worker_respond, this, inp_size, inp_data);
521         return LV2_WORKER_SUCCESS;
522 }
523 LV2_Worker_Status PluginLV2::lv2_worker_schedule(LV2_Worker_Schedule_Handle vp,
524                     uint32_t inp_size, const void *inp_data)
525 {
526         PluginLV2 *the = (PluginLV2 *)vp;
527         return the->worker_schedule(inp_size, inp_data);
528 }
529
530 LV2_Worker_Status PluginLV2::worker_respond(uint32_t out_size, const void *out_data)
531 {
532         pthread_mutex_lock(&worker_lock);
533         PluginLV2Work *wp = get_work();
534         wp->load(out_data, out_size);
535         *work_tail = wp;  work_tail = &wp->next;
536         pthread_mutex_unlock(&worker_lock);
537         return LV2_WORKER_SUCCESS;
538 }
539 LV2_Worker_Status PluginLV2::lv2_worker_respond(LV2_Worker_Respond_Handle vp,
540                 uint32_t out_size, const void *out_data)
541 {
542         PluginLV2 *the = (PluginLV2 *)vp;
543         return the->worker_respond(out_size, out_data);
544 }
545
546 void PluginLV2::worker_responses()
547 {
548         pthread_mutex_lock(&worker_lock);
549         while( work_output ) {
550                 PluginLV2Work *rp = work_output;
551                 if( !(work_output=rp->next) ) work_tail = &work_output;
552                 pthread_mutex_unlock(&worker_lock);
553                 worker_iface->work_response(inst, rp->used, rp->data);
554                 pthread_mutex_lock(&worker_lock);
555                 rp->next = work_avail;  work_avail = rp;
556         }
557         pthread_mutex_unlock(&worker_lock);
558 }
559
560 #include "file.h"
561 #include "pluginlv2ui.h"
562
563 PluginLV2ChildUI::PluginLV2ChildUI()
564 {
565         ac = 0;
566         av = 0;
567         gui = 0;
568         hidden = 1;
569 }
570
571 PluginLV2ChildUI::~PluginLV2ChildUI()
572 {
573         delete gui;
574 }
575
576 void PluginLV2ChildUI::run()
577 {
578         ArrayList<char *> av;
579         av.set_array_delete();
580         char arg[BCTEXTLEN];
581         const char *exec_path = File::get_cinlib_path();
582         snprintf(arg, sizeof(arg), "%s/%s", exec_path, "lv2ui");
583         av.append(cstrdup(arg));
584         sprintf(arg, "%d", child_fd);
585         av.append(cstrdup(arg));
586         sprintf(arg, "%d", parent_fd);
587         av.append(cstrdup(arg));
588         sprintf(arg, "%d", ppid);
589         av.append(cstrdup(arg));
590         av.append(0);
591         execv(av[0], &av.values[0]);
592         fprintf(stderr, "execv failed: %s\n %m\n", av.values[0]);
593         av.remove_all_objects();
594         _exit(1);
595 }
596
597 #define LV2_EXTERNAL_UI_URI__KX__Widget "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget"
598
599 PluginLV2UI::PluginLV2UI()
600 {
601         lilv_ui = 0;
602         lilv_type = 0;
603
604         done = -1;
605         running = 0;
606         updates = 0;
607         hidden = 1;
608         title[0] = 0;
609
610         memset(&uri_map, 0, sizeof(uri_map));
611         memset(&uri_unmap, 0, sizeof(uri_unmap));
612         memset(&extui_host, 0, sizeof(extui_host));
613         wgt_type = LV2_EXTERNAL_UI_URI__KX__Widget;
614         gtk_type = LV2_UI__GtkUI;
615         ui_type = 0;
616 }
617
618 PluginLV2UI::~PluginLV2UI ()
619 {
620 }
621
622 #endif