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 }