1 /** 2 * LV2 Client implementation 3 * 4 * Copyright: Ethan Reker 2018. 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 24 module dplug.lv2.lv2client; 25 26 version(LV2): 27 28 import std.string, 29 std.algorithm.comparison; 30 31 import core.stdc.stdlib, 32 core.stdc.string, 33 core.stdc.stdio, 34 core.stdc.math, 35 core.stdc.stdint; 36 37 import dplug.core.vec, 38 dplug.core.nogc, 39 dplug.core.math, 40 dplug.core.lockedqueue, 41 dplug.core.runtime, 42 dplug.core.fpcontrol, 43 dplug.core.thread, 44 dplug.core.sync; 45 46 import dplug.client.client, 47 dplug.client.daw, 48 dplug.client.preset, 49 dplug.client.graphics, 50 dplug.client.midi, 51 dplug.client.params; 52 53 import dplug.lv2.lv2, 54 dplug.lv2.midi, 55 dplug.lv2.ui, 56 dplug.lv2.options, 57 dplug.lv2.urid, 58 dplug.lv2.atom; 59 60 //debug = debugLV2Client; 61 62 nothrow: 63 @nogc: 64 65 class LV2Client : IHostCommand 66 { 67 nothrow: 68 @nogc: 69 70 Client _client; 71 72 this(Client client, int legalIOIndex) 73 { 74 _client = client; 75 76 // Implement IHostCommand itself 77 _client.setHostCommand(this); 78 _graphicsMutex = makeMutex(); 79 _legalIOIndex = legalIOIndex; 80 _latencyOutput = null; 81 _eventsInput = null; 82 _eventsOutput = null; 83 _latencySamples = 0; 84 version(futureBinState) 85 initializeStateChunkTypeURI(); 86 } 87 88 ~this() 89 { 90 _client.destroyFree(); 91 92 _paramsPointers.reallocBuffer(0); 93 _paramsLastValue.reallocBuffer(0); 94 95 _inputPointersProvided.freeSlice(); 96 _outputPointersProvided.freeSlice(); 97 _inputPointersProcessing.freeSlice(); 98 _outputPointersProcessing.freeSlice(); 99 100 // destroy scratch buffers 101 for (int i = 0; i < _numInputs; ++i) 102 _inputScratchBuffer[i].destroy(); 103 for (int i = 0; i < _numOutputs; ++i) 104 _outputScratchBuffer[i].destroy(); 105 _inputScratchBuffer.freeSlice(); 106 _outputScratchBuffer.freeSlice(); 107 } 108 109 void instantiate(const LV2_Descriptor* descriptor, double rate, const char* bundle_path, const(LV2_Feature*)* features) 110 { 111 LV2_Options_Option* options = null; 112 LV2_URID_Map* uridMap = null; 113 114 assert(features !is null); // by-spec, always point to at least one item 115 116 for(int i = 0; features[i] != null; ++i) 117 { 118 debug(debugLV2Client) debugLogf(" * host supports feature: %s\n", features[i].URI); 119 120 if (strcmp(features[i].URI, "http://lv2plug.in/ns/ext/options#options") == 0) 121 options = cast(LV2_Options_Option*)features[i].data; 122 else if (strcmp(features[i].URI, "http://lv2plug.in/ns/ext/urid#map") == 0) 123 uridMap = cast(LV2_URID_Map*)features[i].data; 124 } 125 126 // Some default value to initialize with in case we don't find an option with the max buffer size 127 _maxBufferSize = 512; 128 129 // Retrieve max buffer size from options 130 if (options && uridMap) 131 { 132 for (int i = 0; options[i].key != 0; ++i) 133 { 134 if (options[i].key == uridMap.map(uridMap.handle, LV2_BUF_SIZE__maxBlockLength)) 135 _maxBufferSize = *cast(const(int)*)options[i].value; 136 } 137 } 138 139 _callResetOnNextRun = true; 140 141 const(char)* stateChunkURIZ = "unused".ptr; 142 version(futureBinState) 143 { 144 stateChunkURIZ = stateChunkTypeURI.ptr; 145 } 146 _mappedURIs.initialize(uridMap, stateChunkURIZ); 147 148 LegalIO selectedIO = _client.legalIOs()[_legalIOIndex]; 149 150 _numInputs = selectedIO.numInputChannels; 151 _numOutputs = selectedIO.numOutputChannels; 152 _sampleRate = cast(float)rate; 153 154 _numParams = cast(int)(_client.params.length); 155 156 _paramsPointers.reallocBuffer(_numParams); 157 _paramsPointers[] = null; 158 159 _paramsLastValue.reallocBuffer(_numParams); 160 for (int i = 0; i < _numParams; ++i) 161 _paramsLastValue[i] = _client.param(i).getNormalized(); 162 163 164 _inputPointersProcessing = mallocSlice!(float*)(_numInputs); 165 _outputPointersProcessing = mallocSlice!(float*)(_numOutputs); 166 _inputPointersProvided = mallocSlice!(float*)(_numInputs); 167 _outputPointersProvided = mallocSlice!(float*)(_numOutputs); 168 169 _inputScratchBuffer = mallocSlice!(Vec!float)(_numInputs); 170 _outputScratchBuffer = mallocSlice!(Vec!float)(_numOutputs); 171 for (int i = 0; i < _numInputs; ++i) 172 _inputScratchBuffer[i] = makeVec!float(); 173 for (int i = 0; i < _numOutputs; ++i) 174 _outputScratchBuffer[i] = makeVec!float(); 175 176 resizeScratchBuffers(_maxBufferSize); 177 178 _write_function = null; 179 180 _currentTimeInfo = TimeInfo.init; 181 _currentTimeInfo.hostIsPlaying = true; 182 183 if (_numOutputs > 0) 184 _latencySamples = _client.latencySamples(rate); 185 } 186 187 void connect_port(uint32_t port, void* data) 188 { 189 // Parameters by index: 190 // - first goes all parameters by index 191 // - then audio inputs 192 // - then audio outputs 193 // - (optional) then a latency port if there is one audio output 194 // - then an events port for timings and MIDI input 195 // - (optional) then an output event port for MIDI output 196 197 198 int numParams = cast(int)(_client.params.length); 199 if(port < numParams) 200 { 201 _paramsPointers[port] = cast(float*)data; 202 return; 203 } 204 port -= numParams; 205 if(port < _numInputs) 206 { 207 _inputPointersProvided[port] = cast(float*)data; 208 return; 209 } 210 port -= _numInputs; 211 if(port < _numOutputs) 212 { 213 _outputPointersProvided[port] = cast(float*)data; 214 return; 215 } 216 port -= _numOutputs; 217 if (_numOutputs > 0) 218 { 219 if (port == 0) 220 { 221 _latencyOutput = cast(float*)data; 222 return; 223 } 224 --port; 225 } 226 if(port == 0) 227 { 228 _eventsInput = cast(LV2_Atom_Sequence*)data; 229 return; 230 } 231 --port; 232 if(port == 0) 233 { 234 _eventsOutput = cast(LV2_Atom_Sequence*)data; 235 return; 236 } 237 assert(false, "Error unknown port index"); 238 } 239 240 void activate() 241 { 242 _callResetOnNextRun = true; 243 } 244 245 void resizeScratchBuffers(int numSamples) 246 { 247 for (int i = 0; i < _numInputs; ++i) 248 { 249 _inputScratchBuffer[i].resize(numSamples); 250 _inputScratchBuffer[i].fill(0); 251 } 252 for (int i = 0; i < _numOutputs; ++i) 253 { 254 _outputScratchBuffer[i].resize(numSamples); 255 } 256 } 257 258 void run(uint32_t n_samples) 259 { 260 if (_maxBufferSize < n_samples) 261 { 262 _callResetOnNextRun = true; 263 _maxBufferSize = n_samples; // in case the max buffer value wasn't found within options 264 resizeScratchBuffers(_maxBufferSize); 265 } 266 267 if(_callResetOnNextRun) 268 { 269 _callResetOnNextRun = false; 270 _client.resetFromHost(_sampleRate, _maxBufferSize, _numInputs, _numOutputs); 271 } 272 273 if (_eventsInput !is null) 274 { 275 for(LV2_Atom_Event* event = lv2_atom_sequence_begin(&_eventsInput.body_); 276 !lv2_atom_sequence_is_end(&_eventsInput.body_, _eventsInput.atom.size, event); 277 event = lv2_atom_sequence_next(event)) 278 { 279 if (event is null) 280 break; 281 282 if (_client.receivesMIDI() && event.body_.type == _mappedURIs.midiEvent) 283 { 284 // Get offset of MIDI message in that buffer 285 int offset = cast(int)(event.time.frames); 286 if (offset < 0) 287 offset = 0; 288 int bytes = event.body_.size; 289 ubyte* data = cast(ubyte*)(event + 1); 290 291 if (bytes >= 1 && bytes <= 3) // else doesn't fit in a Dplug MidiMessage 292 { 293 ubyte byte0 = data[0]; 294 ubyte byte1 = (bytes >= 2) ? data[1] : 0; 295 ubyte byte2 = (bytes >= 3) ? data[2] : 0; 296 MidiMessage message = MidiMessage(offset, byte0, byte1, byte2); 297 _client.enqueueMIDIFromHost(message); 298 } 299 } 300 else if (event.body_.type == _mappedURIs.atomBlank || event.body_.type == _mappedURIs.atomObject) 301 { 302 303 const (LV2_Atom_Object*) obj = cast(LV2_Atom_Object*)&event.body_; 304 305 if (obj.body_.otype == _mappedURIs.timePosition) 306 { 307 LV2_Atom* beatsPerMinute = null; 308 LV2_Atom* frame = null; 309 LV2_Atom* speed = null; 310 311 lv2AtomObjectExtractTimeInfo(obj, 312 _mappedURIs.timeBPM, &beatsPerMinute, 313 _mappedURIs.timeFrame, &frame, 314 _mappedURIs.timeSpeed, &speed); 315 316 if (beatsPerMinute != null) 317 { 318 if (beatsPerMinute.type == _mappedURIs.atomDouble) 319 _currentTimeInfo.tempo = (cast(LV2_Atom_Double*)beatsPerMinute).body_; 320 else if (beatsPerMinute.type == _mappedURIs.atomFloat) 321 _currentTimeInfo.tempo = (cast(LV2_Atom_Float*)beatsPerMinute).body_; 322 else if (beatsPerMinute.type == _mappedURIs.atomInt) 323 _currentTimeInfo.tempo = (cast(LV2_Atom_Int*)beatsPerMinute).body_; 324 else if (beatsPerMinute.type == _mappedURIs.atomLong) 325 _currentTimeInfo.tempo = (cast(LV2_Atom_Long*)beatsPerMinute).body_; 326 } 327 if (frame != null) 328 { 329 if (frame.type == _mappedURIs.atomDouble) 330 _currentTimeInfo.timeInSamples = cast(long)(cast(LV2_Atom_Double*)frame).body_; 331 else if (frame.type == _mappedURIs.atomFloat) 332 _currentTimeInfo.timeInSamples = cast(long)(cast(LV2_Atom_Float*)frame).body_; 333 else if (frame.type == _mappedURIs.atomInt) 334 _currentTimeInfo.timeInSamples = (cast(LV2_Atom_Int*)frame).body_; 335 else if (frame.type == _mappedURIs.atomLong) 336 _currentTimeInfo.timeInSamples = (cast(LV2_Atom_Long*)frame).body_; 337 } 338 if (speed != null) 339 { 340 if (speed.type == _mappedURIs.atomDouble) 341 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Double*)speed).body_ > 0.0f; 342 else if (speed.type == _mappedURIs.atomFloat) 343 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Float*)speed).body_ > 0.0f; 344 else if (speed.type == _mappedURIs.atomInt) 345 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Int*)speed).body_ > 0.0f; 346 else if (speed.type == _mappedURIs.atomLong) 347 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Long*)speed).body_ > 0.0f; 348 } 349 } 350 } 351 } 352 } 353 354 // Update changed parameters 355 { 356 for (int i = 0; i < _numParams; ++i) 357 { 358 if (_paramsPointers[i]) 359 { 360 float currentValue = *_paramsPointers[i]; 361 362 // Force normalization in case host sends invalid parameter values 363 if (currentValue < 0) currentValue = 0; 364 if (currentValue > 1) currentValue = 1; 365 366 if (currentValue != _paramsLastValue[i]) 367 { 368 _paramsLastValue[i] = currentValue; 369 _client.setParameterFromHost(i, currentValue); 370 } 371 } 372 } 373 } 374 375 // Fill input and output pointers for this block, based on what we have received 376 for(int input = 0; input < _numInputs; ++input) 377 { 378 // Copy each available input to a scrach buffer, because some hosts (Mixbus/Ardour) 379 // give identical pointers for input and output buffers. 380 if (_inputPointersProvided[input]) 381 { 382 const(float)* source = _inputPointersProvided[input]; 383 float* dest = _inputScratchBuffer[input].ptr; 384 dest[0..n_samples] = source[0..n_samples]; 385 } 386 _inputPointersProcessing[input] = _inputScratchBuffer[input].ptr; 387 } 388 for(int output = 0; output < _numOutputs; ++output) 389 { 390 _outputPointersProcessing[output] = _outputPointersProvided[output] ? _outputPointersProvided[output] : _outputScratchBuffer[output].ptr; 391 } 392 393 if (_client.sendsMIDI) 394 _client.clearAccumulatedOutputMidiMessages(); 395 396 // Process audio 397 _client.processAudioFromHost(_inputPointersProcessing, _outputPointersProcessing, n_samples, _currentTimeInfo); 398 399 _currentTimeInfo.timeInSamples += n_samples; 400 401 if (_client.sendsMIDI() && _eventsOutput !is null) 402 { 403 uint capacity = _eventsOutput.atom.size; 404 405 _eventsOutput.atom.size = LV2_Atom_Sequence_Body.sizeof; 406 _eventsOutput.atom.type = _mappedURIs.atomSequence; 407 _eventsOutput.body_.unit = 0; 408 _eventsOutput.body_.pad = 0; 409 410 const(MidiMessage)[] outMsgs = _client.getAccumulatedOutputMidiMessages(); 411 if (outMsgs.length > 0) 412 { 413 const(ubyte)* midiEventData; 414 uint totalOffset = 0; 415 416 foreach(MidiMessage msg; outMsgs) 417 { 418 assert(msg.offset >= 0 && msg.offset <= n_samples); 419 420 int midiMsgSize = msg.lengthInBytes(); 421 422 // Unknown length, drop message. 423 if (midiMsgSize == -1) 424 continue; 425 426 if ( LV2_Atom_Event.sizeof + midiMsgSize > capacity - totalOffset) 427 break; // Note: some MIDI messages will get dropped in that case 428 429 LV2_Atom_Event* event = cast(LV2_Atom_Event*)(cast(char*)LV2_ATOM_CONTENTS!LV2_Atom_Sequence(&_eventsOutput.atom) + totalOffset); 430 event.time.frames = msg.offset; 431 event.body_.type = _mappedURIs.midiEvent; 432 event.body_.size = midiMsgSize; 433 int written = msg.toBytes( cast(ubyte*) LV2_ATOM_BODY(&event.body_), midiMsgSize /* enough room */); 434 assert(written == midiMsgSize); 435 uint size = lv2_atom_pad_size(cast(uint)(LV2_Atom_Event.sizeof) + midiMsgSize); 436 totalOffset += size; 437 _eventsOutput.atom.size += size; 438 } 439 } 440 } 441 442 // Report latency to host, expressed in frames 443 if (_latencyOutput) 444 *_latencyOutput = _latencySamples; 445 } 446 447 void instantiateUI(const LV2UI_Descriptor* descriptor, 448 const char* plugin_uri, 449 const char* bundle_path, 450 LV2UI_Write_Function write_function, 451 LV2UI_Controller controller, 452 LV2UI_Widget* widget, 453 const (LV2_Feature*)* features) 454 { 455 debug(debugLV2Client) debugLog(">instantiateUI"); 456 457 int transientWinId; 458 void* parentId = null; 459 LV2_Options_Option* options = null; 460 _uiResize = null; 461 LV2_URID_Map* uridMap = null; 462 463 // Useful to record automation 464 _write_function = write_function; 465 _controller = controller; 466 _uiTouch = null; 467 468 if (features !is null) 469 { 470 for (int i=0; features[i] != null; ++i) 471 { 472 debug(debugLV2Client) debugLogf(" * host UI supports feature: %s\n", features[i].URI); 473 if (strcmp(features[i].URI, LV2_UI__parent) == 0) 474 parentId = cast(void*)features[i].data; 475 else if (strcmp(features[i].URI, LV2_UI__resize) == 0) 476 _uiResize = cast(LV2UI_Resize*)features[i].data; 477 else if (strcmp(features[i].URI, LV2_OPTIONS__options) == 0) 478 options = cast(LV2_Options_Option*)features[i].data; 479 else if (strcmp(features[i].URI, LV2_URID__map) == 0) 480 uridMap = cast(LV2_URID_Map*)features[i].data; 481 else if (strcmp(features[i].URI, LV2_UI__touch) == 0) 482 _uiTouch = cast(LV2UI_Touch*)features[i].data; 483 } 484 } 485 486 // Not transmitted yet 487 /* 488 if (options && uridMap) 489 { 490 for (int i = 0; options[i].key != 0; ++i) 491 { 492 if (options[i].key == uridTransientWinId) 493 { 494 transientWin = cast(void*)(options[i].value); // sound like it lacks a dereferencing 495 } 496 } 497 }*/ 498 499 if (widget != null) 500 { 501 _graphicsMutex.lock(); 502 void* pluginWindow = cast(LV2UI_Widget)_client.openGUI(parentId, null, GraphicsBackend.autodetect); 503 _graphicsMutex.unlock(); 504 505 int widthLogicalPixels, heightLogicalPixels; 506 if (_client.getGUISize(&widthLogicalPixels, &heightLogicalPixels)) 507 { 508 _graphicsMutex.lock(); 509 _uiResize.ui_resize(_uiResize.handle, widthLogicalPixels, heightLogicalPixels); 510 _graphicsMutex.unlock(); 511 } 512 513 *widget = cast(LV2UI_Widget)pluginWindow; 514 } 515 debug(debugLV2Client) debugLog("<instantiateUI"); 516 } 517 518 void portEventUI(uint32_t port_index, 519 uint32_t buffer_size, 520 uint32_t format, 521 const void* buffer) 522 { 523 // Nothing to do since parameter changes already dirty the UI? 524 } 525 526 void cleanupUI() 527 { 528 debug(debugLV2Client) debugLog(">cleanupUI"); 529 _graphicsMutex.lock(); 530 assert(_client.hasGUI()); 531 _client.closeGUI(); 532 _graphicsMutex.unlock(); 533 debug(debugLV2Client) debugLog("<cleanupUI"); 534 } 535 536 override void beginParamEdit(int paramIndex) 537 { 538 // Note: this is untested, since it appears DISTRHO doesn't really ask 539 // for this interface, and Carla doesn't provide one. 540 if ( (_uiTouch !is null) && (_uiTouch.touch !is null) ) 541 _uiTouch.touch(_uiTouch.handle, paramIndex, true); 542 } 543 544 override void paramAutomate(int paramIndex, float value) 545 { 546 // write back automation to host 547 assert(_write_function !is null); 548 _write_function(_controller, paramIndex, float.sizeof, 0, &value); 549 } 550 551 override void endParamEdit(int paramIndex) 552 { 553 // Note: this is untested, since it appears DISTRHO doesn't really ask 554 // for this interface, and Carla doesn't provide one. 555 if ( (_uiTouch !is null) && (_uiTouch.touch !is null) ) 556 _uiTouch.touch(_uiTouch.handle, paramIndex, false); 557 } 558 559 override bool requestResize(int width, int height) 560 { 561 int result = _uiResize.ui_resize(_uiResize.handle, width, height); 562 return result == 0; 563 } 564 565 override bool notifyResized() 566 { 567 return false; 568 } 569 570 // Not properly implemented yet. LV2 should have an extension to get DAW information. 571 override DAW getDAW() 572 { 573 return DAW.Unknown; 574 } 575 576 override PluginFormat getPluginFormat() 577 { 578 return PluginFormat.lv2; 579 } 580 581 /// Get the URI used for state chunk type. 582 /// The slice has a terminal zero afterwards. 583 /// eg: "https://www.wittyaudio.com/Destructatorizer57694469#stateBinary" 584 version(futureBinState) 585 { 586 const(char)[] getStateBinaryURI() 587 { 588 return stateChunkTypeURI[0..strlen(stateChunkTypeURI.ptr)]; 589 } 590 591 LV2_URID getStateBinaryURID() 592 { 593 return _mappedURIs.stateBinary; 594 } 595 596 LV2_URID getAtomStringURID() 597 { 598 return _mappedURIs.atomString; 599 } 600 601 const(ubyte)[] getBase64EncodedStateZ() 602 { 603 // Fetch latest state. 604 _lastStateBinary.clearContents(); 605 _client.saveState(_lastStateBinary); 606 607 // PERF: compare to last seen state, skip base64 if unchanged. 608 609 // Encode to base64 610 _lastStateBinaryBase64.clearContents(); 611 encodeBase64(_lastStateBinary[], _lastStateBinaryBase64); 612 _lastStateBinaryBase64.pushBack(0); // Add a terminal zero, since LV2 wants a zero-terminated Atom String. 613 614 return _lastStateBinaryBase64[]; 615 } 616 617 // Base64-decode the input extra state chunk, and gives it to plug-in client. 618 // Empty chunk is considered a success. 619 // Return: true on success. 620 bool restoreStateBinaryBase64(const(ubyte)[] base64StateBinary) 621 { 622 debug(debugLV2Client) debugLogf(">restoreStateBinaryBase64\n"); 623 624 if (base64StateBinary.length == 0) 625 { 626 // Note: zero-length state binary not passed to loadState. Considered a success. 627 return true; 628 } 629 630 debug(debugLV2Client) debugLogf(" * len = %llu\n", cast(int)base64StateBinary.length); 631 632 _lastDecodedStateBinary.clearContents; 633 bool err; 634 decodeBase64(base64StateBinary, _lastDecodedStateBinary, '+', '/', &err); 635 if (err) 636 { 637 debug(debugLV2Client) debugLogf("chunk didn't decode\n"); 638 return false; // wrong base64 data 639 } 640 641 debug(debugLV2Client) debugLogf("decoded\n"); 642 643 bool parsed = _client.loadState(_lastDecodedStateBinary[]); 644 645 debug(debugLV2Client) debugLogf(" * parsed = %d\n", cast(int)parsed); 646 return parsed; 647 } 648 } 649 650 private: 651 652 uint _numInputs; 653 uint _numOutputs; 654 655 // the maximum buffer size we've found, from either the options or reality of incoming buffers 656 int _maxBufferSize; 657 658 // whether the plugin should call resetFromHost at next `run()` 659 bool _callResetOnNextRun; 660 661 int _numParams; 662 float*[] _paramsPointers; 663 float[] _paramsLastValue; 664 665 // Scratch input buffers in case the host doesn't provide ones. 666 Vec!float[] _inputScratchBuffer; 667 668 // Scratch output buffers in case the host doesn't provide ones. 669 Vec!float[] _outputScratchBuffer; 670 671 // Input pointers that were provided by the host, `null` if not provided. 672 float*[] _inputPointersProvided; 673 674 // Output input used by processing, recomputed at each run(). 675 float*[] _inputPointersProcessing; 676 677 // Output pointers that were provided by the host, `null` if not provided. 678 float*[] _outputPointersProvided; 679 680 // Output pointers used by processing, recomputed at each run(). 681 float*[] _outputPointersProcessing; 682 683 // Output pointer for latency reporting, `null` if not provided. 684 float* _latencyOutput; 685 686 LV2_Atom_Sequence* _eventsInput; // may be null 687 LV2_Atom_Sequence* _eventsOutput; 688 689 float _sampleRate; 690 691 // The latency value expressed in frames 692 int _latencySamples; 693 694 UncheckedMutex _graphicsMutex; 695 696 // Set at instantiation 697 MappedURIs _mappedURIs; 698 699 int _legalIOIndex; 700 701 LV2UI_Write_Function _write_function; 702 LV2UI_Controller _controller; 703 LV2UI_Touch* _uiTouch; 704 LV2UI_Resize* _uiResize; 705 706 // Current time info, eventually extrapolated when data is missing. 707 TimeInfo _currentTimeInfo; 708 709 version(futureBinState) 710 { 711 Vec!ubyte _lastStateBinary; 712 Vec!ubyte _lastStateBinaryBase64; 713 Vec!ubyte _lastDecodedStateBinary; 714 715 // A zero-terminated buffer holding the full URI to vendor:stateChunk. 716 char[256] stateChunkTypeURI; 717 718 void initializeStateChunkTypeURI() 719 { 720 char[4] pluginID = _client.getPluginUniqueID(); 721 CString pluginHomepageZ = CString(_client.pluginHomepage()); 722 snprintf(stateChunkTypeURI.ptr, 256, "%s%2X%2X%2X%2X#%s", 723 pluginHomepageZ.storage, pluginID[0], pluginID[1], pluginID[2], pluginID[3], 724 "stateBinary".ptr); 725 stateChunkTypeURI[$-1] = '\0'; 726 } 727 } 728 729 } 730 731 struct MappedURIs 732 { 733 nothrow: 734 @nogc: 735 736 LV2_URID atomDouble; 737 LV2_URID atomFloat; 738 LV2_URID atomInt; 739 LV2_URID atomLong; 740 LV2_URID atomBlank; 741 LV2_URID atomObject; 742 LV2_URID atomSequence; 743 LV2_URID atomString; 744 LV2_URID midiEvent; 745 LV2_URID timePosition; 746 LV2_URID timeFrame; 747 LV2_URID timeBPM; 748 LV2_URID timeSpeed; 749 750 version(futureBinState) 751 LV2_URID stateBinary; 752 753 void initialize(LV2_URID_Map* uridMap, const(char)* stateBinaryURIZ) 754 { 755 atomDouble = uridMap.map(uridMap.handle, LV2_ATOM__Double); 756 atomFloat = uridMap.map(uridMap.handle, LV2_ATOM__Float); 757 atomInt = uridMap.map(uridMap.handle, LV2_ATOM__Int); 758 atomLong = uridMap.map(uridMap.handle, LV2_ATOM__Long); 759 atomBlank = uridMap.map(uridMap.handle, LV2_ATOM__Blank); 760 atomObject = uridMap.map(uridMap.handle, LV2_ATOM__Object); 761 atomSequence = uridMap.map(uridMap.handle, LV2_ATOM__Sequence); 762 atomString = uridMap.map(uridMap.handle, LV2_ATOM__String); 763 midiEvent = uridMap.map(uridMap.handle, LV2_MIDI__MidiEvent); 764 timePosition = uridMap.map(uridMap.handle, LV2_TIME__Position); 765 timeFrame = uridMap.map(uridMap.handle, LV2_TIME__frame); 766 timeBPM = uridMap.map(uridMap.handle, LV2_TIME__beatsPerMinute); 767 timeSpeed = uridMap.map(uridMap.handle, LV2_TIME__speed); 768 version(futureBinState) 769 stateBinary = uridMap.map(uridMap.handle, stateBinaryURIZ); 770 } 771 } 772 773 774 int lv2AtomObjectExtractTimeInfo(const (LV2_Atom_Object*) object, 775 LV2_URID tempoURID, LV2_Atom** tempoAtom, 776 LV2_URID frameURID, LV2_Atom** frameAtom, 777 LV2_URID speedURID, LV2_Atom** speedAtom) 778 { 779 int n_queries = 3; 780 int matches = 0; 781 // #define LV2_ATOM_OBJECT_FOREACH(obj, iter) 782 for (LV2_Atom_Property_Body* prop = lv2_atom_object_begin(&object.body_); 783 !lv2_atom_object_is_end(&object.body_, object.atom.size, prop); 784 prop = lv2_atom_object_next(prop)) 785 { 786 for (int i = 0; i < n_queries; ++i) { 787 // uint32_t qkey = va_arg!(uint32_t)(args); 788 // LV2_Atom** qval = va_arg!(LV2_Atom**)(args); 789 // if (qkey == prop.key && !*qval) { 790 // *qval = &prop.value; 791 // if (++matches == n_queries) { 792 // return matches; 793 // } 794 // break; 795 // } 796 if(tempoURID == prop.key && tempoAtom) { 797 *tempoAtom = &prop.value; 798 ++matches; 799 } 800 else if(frameURID == prop.key && frameAtom) { 801 *frameAtom = &prop.value; 802 ++matches; 803 } 804 else if(speedURID == prop.key && speedAtom) { 805 *speedAtom = &prop.value; 806 ++matches; 807 } 808 } 809 } 810 return matches; 811 }