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(legacyBinState)
258         {}
259         else
260         {
261             static immutable LV2_State_Interface lv2StateInterface = LV2_State_Interface(&state_save, &state_restore);
262             if (!strcmp(uri, LV2_STATE__interface)) {
263                 feature = cast(void*)&lv2StateInterface;
264             }
265         }
266 
267         debug(debugLV2Client) debugLog("<extension_dataUI");
268         return feature;
269     }
270 
271     const (void)* extensionDataUI(const char* uri)
272     {
273         void* feature = null;
274         debug(debugLV2Client) debugLogf(">extension_dataUI: %s", uri);
275         static const LV2UI_Resize lv2UIResize = LV2UI_Resize(cast(void*)null, &uiResize);
276         if (!strcmp(uri, LV2_UI__resize)) {
277             feature = cast(void*)&lv2UIResize;
278         }
279 
280         debug(debugLV2Client) debugLog("<extension_dataUI");
281         return feature;
282     }
283     
284     /// This is currently not fully implemented
285     /// According to the LV2 IRC channel, this extension is planned to be
286     /// phased out.  The only known host that uses this extension is
287     /// synthpod.  LV2 plug-ins should respond directly to resize
288     /// events from the window.
289     /// Note: is it used at all?
290     int uiResize(LV2UI_Feature_Handle handle, int width, int height)
291     {
292         ScopedForeignCallback!(false, true) scopedCallback;
293         scopedCallback.enter();
294         LV2Client lv2client = cast(LV2Client)handle;
295         return 0;
296     }
297 
298     LV2UI_Handle instantiateUI(const LV2UI_Descriptor* descriptor,
299                                const char*             plugin_uri,
300                                const char*             bundle_path,
301                                LV2UI_Write_Function    write_function,
302                                LV2UI_Controller        controller,
303                                LV2UI_Widget*           widget,
304                                const (LV2_Feature*)*   features)
305     {
306         debug(debugLV2Client) debugLog(">instantiateUI");
307         ScopedForeignCallback!(false, true) scopedCallback;
308         scopedCallback.enter();
309         void* instance_access = lv2_features_data(features, "http://lv2plug.in/ns/ext/instance-access");
310         if (instance_access)
311         {
312             LV2Client lv2client = cast(LV2Client)instance_access;
313             lv2client.instantiateUI(descriptor, plugin_uri, bundle_path, write_function, controller, widget, features);
314             debug(debugLV2Client) debugLog("<instantiateUI");
315             return cast(LV2UI_Handle)instance_access;
316         }
317         else
318         {
319             debug(debugLV2Client) debugLog("Error: Instance access is not available\n");
320             return null;
321         }
322     }
323 
324     void write_function(LV2UI_Controller controller,
325                               uint32_t         port_index,
326                               uint32_t         buffer_size,
327                               uint32_t         port_protocol,
328                               const void*      buffer)
329     {
330         debug(debugLV2Client) debugLog(">write_function");
331         debug(debugLV2Client) debugLog("<write_function");
332     }
333 
334     void cleanupUI(LV2UI_Handle ui)
335     {
336         debug(debugLV2Client) debugLog(">cleanupUI");
337         ScopedForeignCallback!(false, true) scopedCallback;
338         scopedCallback.enter();
339         LV2Client lv2client = cast(LV2Client)ui;
340         lv2client.cleanupUI();
341         debug(debugLV2Client) debugLog("<cleanupUI");
342     }
343 
344     void port_eventUI(LV2UI_Handle ui,
345                       uint32_t     port_index,
346                       uint32_t     buffer_size,
347                       uint32_t     format,
348                       const void*  buffer)
349     {
350         //debug(debugLV2Client) debugLog(">port_event");
351         ScopedForeignCallback!(false, true) scopedCallback;
352         scopedCallback.enter();
353         LV2Client lv2client = cast(LV2Client)ui;
354         lv2client.portEventUI(port_index, buffer_size, format, buffer);
355         //debug(debugLV2Client) debugLog("<port_event");
356     }
357 
358     version(legacyBinState)
359     {}
360     else
361     {
362 
363         // Save plugin state (beyond the port values).
364         LV2_State_Status state_save (LV2_Handle               instance,
365                                      LV2_State_Store_Function store,
366                                      LV2_State_Handle         handle,
367                                      uint                     flags,
368                                      const(LV2_Feature*)*     features)
369         {
370             debug(debugLV2Client) debugLog(">state_save");
371 
372             LV2Client lv2client = cast(LV2Client)instance;
373 
374             // Get the most current base64-encoded state + terminal zero.
375             const(ubyte)[] lastChunk = lv2client.getBase64EncodedStateZ();
376 
377             LV2_State_Status res = store(handle,
378                                          lv2client.getStateBinaryURID(),
379                                          lastChunk.ptr,
380                                          lastChunk.length, // this includes a terminal zero
381                                          lv2client.getAtomStringURID(),
382                                          LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
383 
384             debug(debugLV2Client) debugLogf("  * store returned %d\n", res);
385 
386             debug(debugLV2Client) debugLog("<state_save");
387             return res;
388         }
389 
390 
391         LV2_State_Status state_restore(LV2_Handle                  instance,
392                                        LV2_State_Retrieve_Function retrieve,
393                                        LV2_State_Handle            handle,
394                                        uint                        flags, // those flags currently unused by LV2
395                                        const(LV2_Feature*)*        features)
396         {
397             debug(debugLV2Client) debugLog(">state_restore\n");
398             LV2Client lv2client = cast(LV2Client)instance;
399 
400             // Try to restore from a vendor:stateBinary URI
401             {
402                 size_t len;
403                 uint type;
404                 uint rflags;
405                 const(ubyte)* pStateBinary = cast(const(ubyte)*) 
406                     retrieve(handle, lv2client.getStateBinaryURID(), &len, &type, &rflags);
407 
408                 debug(debugLV2Client) debugLogf("  * retrieve returned %p\n", pStateBinary);
409 
410                 if (pStateBinary == null)
411                     return LV2_STATE_ERR_NO_PROPERTY;
412 
413                 if (type != lv2client.getAtomStringURID())
414                     return LV2_STATE_ERR_BAD_TYPE;
415 
416                 if (len == 0) // Empty chunk => error
417                     return LV2_STATE_ERR_BAD_TYPE;
418 
419                 // WORKAROUND.
420                 // Now for an interesting story... 
421                 // With the same saveState, some hosts like Carla return a string length 
422                 // that includes the zero-terminal character.
423                 // But other hosts, like REAPER, instead return the string length you can 
424                 // expect from the atom spec.
425                 // Check if the last return character is '\0', in which case we can safely
426                 // remove it from the base64 decoding.
427                 {
428                     if (pStateBinary[len-1] == '\0')
429                         len = len - 1;
430 
431                     if (len == 0) // We just got a '\0' character, so an empty chunk => error
432                         return LV2_STATE_ERR_BAD_TYPE;
433                 }
434 
435                 const(ubyte)[] chunk = pStateBinary[0..len];
436                 bool success = lv2client.restoreStateBinaryBase64(chunk);
437                 debug(debugLV2Client) debugLog("<state_restore\n");
438                 return success ? LV2_STATE_SUCCESS : LV2_STATE_ERR_BAD_TYPE;
439             }
440         }
441     }
442 }