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 }