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 }