1 /**
2 * LV2 Client implementation
3 *
4 * Copyright: Ethan Reker 2018-2019.
5 *            Guillaume Piolat 2019.
6 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 */
8 /*
9  * DISTRHO Plugin Framework (DPF)
10  * Copyright (C) 2012-2018 Filipe Coelho <falktx@falktx.com>
11  *
12  * Permission to use, copy, modify, and/or distribute this software for any purpose with
13  * or without fee is hereby granted, provided that the above copyright notice and this
14  * permission notice appear in all copies.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
17  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
18  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
19  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
20  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 module dplug.lv2.lv2_init;
24 
25 version(LV2):
26 
27 import core.stdc.stdint;
28 import core.stdc.string;
29 
30 import dplug.core.nogc;
31 import dplug.core.runtime;
32 
33 import dplug.client.client;
34 
35 import dplug.lv2.lv2;
36 import dplug.lv2.ui;
37 import dplug.lv2.state;
38 import dplug.lv2.lv2client;
39 import dplug.lv2.ttl;
40 
41 //debug = debugLV2Client;
42 
43 
44 
45 /**
46  * Main entry point for LV2 plugins.
47  */
48 template LV2EntryPoint(alias ClientClass)
49 {
50     static immutable enum lv2_descriptor =
51         "export extern(C) const(void)* lv2_descriptor(uint index) nothrow @nogc" ~
52         "{" ~
53         "    return lv2_descriptor_templated!" ~ ClientClass.stringof ~ "(index);" ~
54         "}\n";
55 
56     static immutable enum lv2_ui_descriptor =
57         "export extern(C) const(void)* lv2ui_descriptor(uint index)nothrow @nogc" ~
58         "{" ~
59         "    return lv2ui_descriptor_templated!" ~ ClientClass.stringof ~ "(index);" ~
60         "}\n";
61 
62     static immutable enum generate_manifest_from_client =
63         "export extern(C) int GenerateManifestFromClient(char* manifestBuf, int manifestBufLen, const(char)* binaryFileName, int binaryFileNameLen)"  ~
64         "{" ~
65         "    return GenerateManifestFromClient_templated!" ~ ClientClass.stringof ~ "(manifestBuf[0..manifestBufLen], binaryFileName[0..binaryFileNameLen]);" ~
66         "}\n";
67 
68     const char[] LV2EntryPoint = lv2_descriptor ~ lv2_ui_descriptor ~ generate_manifest_from_client;
69 }
70 
71 const(LV2_Descriptor)* lv2_descriptor_templated(ClientClass)(uint index) nothrow @nogc
72 {
73     debug(debugLV2Client) debugLog(">lv2_descriptor_templated");
74     ScopedForeignCallback!(false, true) scopedCallback;
75     scopedCallback.enter();
76     
77     build_all_lv2_descriptors!ClientClass();
78     if(index >= cast(int)(lv2Descriptors.length))
79         return null;
80 
81     debug(debugLV2Client) debugLog("<lv2_descriptor_templated");
82     return &lv2Descriptors[index];
83 }
84 
85 const (LV2UI_Descriptor)* lv2ui_descriptor_templated(ClientClass)(uint index) nothrow @nogc
86 {
87     debug(debugLV2Client) debugLog(">lv2ui_descriptor_templated");
88     ScopedForeignCallback!(false, true) scopedCallback;
89     scopedCallback.enter();
90 
91     
92     build_all_lv2_descriptors!ClientClass();
93     if (hasUI && index == 0)
94     {
95         debug(debugLV2Client) debugLog("<lv2ui_descriptor_templated");
96         return &lv2UIDescriptor;
97     }
98     else
99         return null;
100 }
101 
102 extern(C) static LV2_Handle instantiate(ClientClass)(const(LV2_Descriptor)* descriptor,
103                                                      double rate,
104                                                      const(char)* bundle_path,
105                                                      const(LV2_Feature*)* features)
106 {
107     debug(debugLV2Client) debugLog(">instantiate");
108     ScopedForeignCallback!(false, true) scopedCallback;
109     scopedCallback.enter();
110     
111     LV2_Handle handle = cast(LV2_Handle)myLV2EntryPoint!ClientClass(descriptor, rate, bundle_path, features);
112     debug(debugLV2Client) debugLog("<instantiate");
113     return handle;
114 }
115 
116 
117 private:
118 
119 // These are initialized lazily by `build_all_lv2_descriptors`
120 __gshared bool descriptorsAreInitialized = false;
121 __gshared LV2_Descriptor[] lv2Descriptors;
122 __gshared bool hasUI;
123 __gshared LV2UI_Descriptor lv2UIDescriptor;
124 
125 // build all needed LV2_Descriptors and LV2UI_Descriptor lazily
126 void build_all_lv2_descriptors(ClientClass)() nothrow @nogc
127 {
128     if (descriptorsAreInitialized)
129         return;
130 
131     debug(debugLV2Client) debugLog(">build_all_lv2_descriptors");
132 
133     // Build a client
134     auto client = mallocNew!ClientClass();
135     scope(exit) client.destroyFree();
136 
137     LegalIO[] legalIOs = client.legalIOs();
138 
139     lv2Descriptors = mallocSlice!LV2_Descriptor(legalIOs.length); // Note: leaked
140 
141     char[256] uriBuf;
142 
143     for(int io = 0; io < cast(int)(legalIOs.length); io++)
144     {
145         // Make an URI for this I/O configuration
146         sprintPluginURI_IO(uriBuf.ptr, 256, client.pluginHomepage(), client.getPluginUniqueID(), legalIOs[io]);
147 
148         lv2Descriptors[io] = LV2_Descriptor.init;
149 
150         lv2Descriptors[io].URI = stringDup(uriBuf.ptr).ptr;
151         lv2Descriptors[io].instantiate = &instantiate!ClientClass;
152         lv2Descriptors[io].connect_port = &connect_port;
153         lv2Descriptors[io].activate = &activate;
154         lv2Descriptors[io].run = &run;
155         lv2Descriptors[io].deactivate = &deactivate;
156         lv2Descriptors[io].cleanup = &cleanup;
157         lv2Descriptors[io].extension_data = &extensionData;
158     }
159 
160 
161     if (client.hasGUI())
162     {
163         hasUI = true;
164 
165         // Make an URI for this the UI
166         sprintPluginURI_UI(uriBuf.ptr, 256, client.pluginHomepage(), client.getPluginUniqueID());
167 
168         LV2UI_Descriptor descriptor =
169         {
170             URI:            stringDup(uriBuf.ptr).ptr,
171             instantiate:    &instantiateUI,
172             cleanup:        &cleanupUI,
173             port_event:     &port_eventUI,
174             extension_data: &extensionDataUI
175         };
176         lv2UIDescriptor = descriptor;
177     }
178     else
179     {
180         hasUI = false;
181     }
182     descriptorsAreInitialized = true;
183     debug(debugLV2Client) debugLog("<build_all_lv2_descriptors");
184 }
185 
186 
187 
188 LV2Client myLV2EntryPoint(alias ClientClass)(const LV2_Descriptor* descriptor,
189                                              double rate,
190                                              const char* bundle_path,
191                                              const(LV2_Feature*)* features) nothrow @nogc
192 {
193     debug(debugLV2Client) debugLog(">myLV2EntryPoint");
194     auto client = mallocNew!ClientClass();
195 
196     // Find which decsriptor was used using pointer offset
197     int legalIOIndex = cast(int)(descriptor - lv2Descriptors.ptr);
198     auto lv2client = mallocNew!LV2Client(client, legalIOIndex);
199 
200     lv2client.instantiate(descriptor, rate, bundle_path, features);
201     debug(debugLV2Client) debugLog("<myLV2EntryPoint");
202     return lv2client;
203 }
204 
205 /*
206     LV2 Callback function implementations
207 */
208 extern(C) nothrow @nogc
209 {
210     void connect_port(LV2_Handle instance, uint32_t   port, void* data)
211     {
212         ScopedForeignCallback!(false, true) scopedCallback;
213         scopedCallback.enter();
214         LV2Client lv2client = cast(LV2Client)instance;
215         lv2client.connect_port(port, data);
216     }
217 
218     void activate(LV2_Handle instance)
219     {
220         debug(debugLV2Client) debugLog(">activate");
221         ScopedForeignCallback!(false, true) scopedCallback;
222         scopedCallback.enter();
223         LV2Client lv2client = cast(LV2Client)instance;
224         lv2client.activate();
225         debug(debugLV2Client) debugLog("<activate");
226     }
227 
228     void run(LV2_Handle instance, uint32_t n_samples)
229     {
230         ScopedForeignCallback!(false, true) scopedCallback;
231         scopedCallback.enter();
232         LV2Client lv2client = cast(LV2Client)instance;
233         lv2client.run(n_samples);
234     }
235 
236     void deactivate(LV2_Handle instance)
237     {
238         debug(debugLV2Client) debugLog(">deactivate");
239         debug(debugLV2Client) debugLog("<deactivate");
240     }
241 
242     void cleanup(LV2_Handle instance)
243     {
244         debug(debugLV2Client) debugLog(">cleanup");
245         ScopedForeignCallback!(false, true) scopedCallback;
246         scopedCallback.enter();
247         LV2Client lv2client = cast(LV2Client)instance;
248         lv2client.destroyFree();
249         debug(debugLV2Client) debugLog("<cleanup");
250     }
251 
252     const (void)* extensionData(const char* uri)
253     {
254         void* feature = null;
255         debug(debugLV2Client) debugLogf(">extension_data: %s", uri);
256 
257         version(futureBinState)
258         {
259             static immutable LV2_State_Interface lv2StateInterface = LV2_State_Interface(&state_save, &state_restore);
260             if (!strcmp(uri, LV2_STATE__interface)) {
261                 feature = cast(void*)&lv2StateInterface;
262             }
263         }
264 
265         debug(debugLV2Client) debugLog("<extension_dataUI");
266         return feature;
267     }
268 
269     const (void)* extensionDataUI(const char* uri)
270     {
271         void* feature = null;
272         debug(debugLV2Client) debugLogf(">extension_dataUI: %s", uri);
273         static const LV2UI_Resize lv2UIResize = LV2UI_Resize(cast(void*)null, &uiResize);
274         if (!strcmp(uri, LV2_UI__resize)) {
275             feature = cast(void*)&lv2UIResize;
276         }
277 
278         debug(debugLV2Client) debugLog("<extension_dataUI");
279         return feature;
280     }
281     
282     /// This is currently not fully implemented
283     /// According to the LV2 IRC channel, this extension is planned to be
284     /// phased out.  The only known host that uses this extension is
285     /// synthpod.  LV2 plug-ins should respond directly to resize
286     /// events from the window.
287     /// Note: is it used at all?
288     int uiResize(LV2UI_Feature_Handle handle, int width, int height)
289     {
290         ScopedForeignCallback!(false, true) scopedCallback;
291         scopedCallback.enter();
292         LV2Client lv2client = cast(LV2Client)handle;
293         return 0;
294     }
295 
296     LV2UI_Handle instantiateUI(const LV2UI_Descriptor* descriptor,
297                                const char*             plugin_uri,
298                                const char*             bundle_path,
299                                LV2UI_Write_Function    write_function,
300                                LV2UI_Controller        controller,
301                                LV2UI_Widget*           widget,
302                                const (LV2_Feature*)*   features)
303     {
304         debug(debugLV2Client) debugLog(">instantiateUI");
305         ScopedForeignCallback!(false, true) scopedCallback;
306         scopedCallback.enter();
307         void* instance_access = lv2_features_data(features, "http://lv2plug.in/ns/ext/instance-access");
308         if (instance_access)
309         {
310             LV2Client lv2client = cast(LV2Client)instance_access;
311             lv2client.instantiateUI(descriptor, plugin_uri, bundle_path, write_function, controller, widget, features);
312             debug(debugLV2Client) debugLog("<instantiateUI");
313             return cast(LV2UI_Handle)instance_access;
314         }
315         else
316         {
317             debug(debugLV2Client) debugLog("Error: Instance access is not available\n");
318             return null;
319         }
320     }
321 
322     void write_function(LV2UI_Controller controller,
323                               uint32_t         port_index,
324                               uint32_t         buffer_size,
325                               uint32_t         port_protocol,
326                               const void*      buffer)
327     {
328         debug(debugLV2Client) debugLog(">write_function");
329         debug(debugLV2Client) debugLog("<write_function");
330     }
331 
332     void cleanupUI(LV2UI_Handle ui)
333     {
334         debug(debugLV2Client) debugLog(">cleanupUI");
335         ScopedForeignCallback!(false, true) scopedCallback;
336         scopedCallback.enter();
337         LV2Client lv2client = cast(LV2Client)ui;
338         lv2client.cleanupUI();
339         debug(debugLV2Client) debugLog("<cleanupUI");
340     }
341 
342     void port_eventUI(LV2UI_Handle ui,
343                       uint32_t     port_index,
344                       uint32_t     buffer_size,
345                       uint32_t     format,
346                       const void*  buffer)
347     {
348         //debug(debugLV2Client) debugLog(">port_event");
349         ScopedForeignCallback!(false, true) scopedCallback;
350         scopedCallback.enter();
351         LV2Client lv2client = cast(LV2Client)ui;
352         lv2client.portEventUI(port_index, buffer_size, format, buffer);
353         //debug(debugLV2Client) debugLog("<port_event");
354     }
355 
356     version(futureBinState)
357     {
358 
359         // Save plugin state (beyond the port values).
360         LV2_State_Status state_save (LV2_Handle               instance,
361                                      LV2_State_Store_Function store,
362                                      LV2_State_Handle         handle,
363                                      uint                     flags,
364                                      const(LV2_Feature*)*     features)
365         {
366             debug(debugLV2Client) debugLog(">state_save");
367 
368             LV2Client lv2client = cast(LV2Client)instance;
369 
370             // Get the most current base64-encoded state + terminal zero.
371             const(ubyte)[] lastChunk = lv2client.getBase64EncodedStateZ();
372 
373             LV2_State_Status res = store(handle,
374                                          lv2client.getStateBinaryURID(),
375                                          lastChunk.ptr,
376                                          lastChunk.length, // this includes a terminal zero
377                                          lv2client.getAtomStringURID(),
378                                          LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
379 
380             debug(debugLV2Client) debugLogf("  * store returned %d\n", res);
381 
382             debug(debugLV2Client) debugLog("<state_save");
383             return res;
384         }
385 
386 
387         LV2_State_Status state_restore(LV2_Handle                  instance,
388                                        LV2_State_Retrieve_Function retrieve,
389                                        LV2_State_Handle            handle,
390                                        uint                        flags, // those flags currently unused by LV2
391                                        const(LV2_Feature*)*        features)
392         {
393             debug(debugLV2Client) debugLog(">state_restore\n");
394             LV2Client lv2client = cast(LV2Client)instance;
395 
396             // Try to restore from a vendor:stateBinary URI
397             {
398                 size_t len;
399                 uint type;
400                 uint rflags;
401                 const(ubyte)* pStateBinary = cast(const(ubyte)*) 
402                     retrieve(handle, lv2client.getStateBinaryURID(), &len, &type, &rflags);
403 
404                 debug(debugLV2Client) debugLogf("  * retrieve returned %p\n", pStateBinary);
405 
406                 if (pStateBinary == null)
407                     return LV2_STATE_ERR_NO_PROPERTY;
408 
409                 if (type != lv2client.getAtomStringURID())
410                     return LV2_STATE_ERR_BAD_TYPE;
411 
412                 if (len == 0) // Empty chunk => error
413                     return LV2_STATE_ERR_BAD_TYPE;
414 
415                 // WORKAROUND.
416                 // Now for an interesting story... 
417                 // With the same saveState, some hosts like Carla return a string length 
418                 // that includes the zero-terminal character.
419                 // But other hosts, like REAPER, instead return the string length you can 
420                 // expect from the atom spec.
421                 // Check if the last return character is '\0', in which case we can safely
422                 // remove it from the base64 decoding.
423                 {
424                     if (pStateBinary[len-1] == '\0')
425                         len = len - 1;
426 
427                     if (len == 0) // We just got a '\0' character, so an empty chunk => error
428                         return LV2_STATE_ERR_BAD_TYPE;
429                 }
430 
431                 const(ubyte)[] chunk = pStateBinary[0..len];
432                 bool success = lv2client.restoreStateBinaryBase64(chunk);
433                 debug(debugLV2Client) debugLog("<state_restore\n");
434                 return success ? LV2_STATE_SUCCESS : LV2_STATE_ERR_BAD_TYPE;
435             }
436         }
437     }
438 }