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 } 85 86 ~this() 87 { 88 _client.destroyFree(); 89 90 _paramsPointers.reallocBuffer(0); 91 _paramsLastValue.reallocBuffer(0); 92 93 _inputPointersProvided.freeSlice(); 94 _outputPointersProvided.freeSlice(); 95 _inputPointersProcessing.freeSlice(); 96 _outputPointersProcessing.freeSlice(); 97 98 // destroy scratch buffers 99 for (int i = 0; i < _numInputs; ++i) 100 _inputScratchBuffer[i].destroy(); 101 for (int i = 0; i < _numOutputs; ++i) 102 _outputScratchBuffer[i].destroy(); 103 _inputScratchBuffer.freeSlice(); 104 _outputScratchBuffer.freeSlice(); 105 } 106 107 void instantiate(const LV2_Descriptor* descriptor, double rate, const char* bundle_path, const(LV2_Feature*)* features) 108 { 109 LV2_Options_Option* options = null; 110 LV2_URID_Map* uridMap = null; 111 112 for(int i = 0; features[i] != null; ++i) 113 { 114 if (strcmp(features[i].URI, "http://lv2plug.in/ns/ext/options#options") == 0) 115 options = cast(LV2_Options_Option*)features[i].data; 116 else if (strcmp(features[i].URI, "http://lv2plug.in/ns/ext/urid#map") == 0) 117 uridMap = cast(LV2_URID_Map*)features[i].data; 118 } 119 120 // Some default value to initialize with in case we don't find an option with the max buffer size 121 _maxBufferSize = 512; 122 123 // Retrieve max buffer size from options 124 if (options && uridMap) 125 { 126 for (int i = 0; options[i].key != 0; ++i) 127 { 128 if (options[i].key == uridMap.map(uridMap.handle, LV2_BUF_SIZE__maxBlockLength)) 129 _maxBufferSize = *cast(const(int)*)options[i].value; 130 } 131 } 132 133 _callResetOnNextRun = true; 134 135 _mappedURIs.initialize(uridMap); 136 137 LegalIO selectedIO = _client.legalIOs()[_legalIOIndex]; 138 139 _numInputs = selectedIO.numInputChannels; 140 _numOutputs = selectedIO.numOutputChannels; 141 _sampleRate = cast(float)rate; 142 143 _numParams = cast(int)(_client.params.length); 144 145 _paramsPointers.reallocBuffer(_numParams); 146 _paramsPointers[] = null; 147 148 _paramsLastValue.reallocBuffer(_numParams); 149 for (int i = 0; i < _numParams; ++i) 150 _paramsLastValue[i] = _client.param(i).getNormalized(); 151 152 153 _inputPointersProcessing = mallocSlice!(float*)(_numInputs); 154 _outputPointersProcessing = mallocSlice!(float*)(_numOutputs); 155 _inputPointersProvided = mallocSlice!(float*)(_numInputs); 156 _outputPointersProvided = mallocSlice!(float*)(_numOutputs); 157 158 _inputScratchBuffer = mallocSlice!(Vec!float)(_numInputs); 159 _outputScratchBuffer = mallocSlice!(Vec!float)(_numOutputs); 160 for (int i = 0; i < _numInputs; ++i) 161 _inputScratchBuffer[i] = makeVec!float(); 162 for (int i = 0; i < _numOutputs; ++i) 163 _outputScratchBuffer[i] = makeVec!float(); 164 165 resizeScratchBuffers(_maxBufferSize); 166 167 _write_function = null; 168 169 _currentTimeInfo = TimeInfo.init; 170 _currentTimeInfo.hostIsPlaying = true; 171 172 if (_numOutputs > 0) 173 _latencySamples = _client.latencySamples(rate); 174 } 175 176 void connect_port(uint32_t port, void* data) 177 { 178 // Parameters by index: 179 // - first goes all parameters by index 180 // - then audio inputs 181 // - then audio outputs 182 // - (optional) then a latency port if there is one audio output 183 // - then an events port for timings and MIDI input 184 // - (optional) then an output event port for MIDI output 185 186 187 int numParams = cast(int)(_client.params.length); 188 if(port < numParams) 189 { 190 _paramsPointers[port] = cast(float*)data; 191 return; 192 } 193 port -= numParams; 194 if(port < _numInputs) 195 { 196 _inputPointersProvided[port] = cast(float*)data; 197 return; 198 } 199 port -= _numInputs; 200 if(port < _numOutputs) 201 { 202 _outputPointersProvided[port] = cast(float*)data; 203 return; 204 } 205 port -= _numOutputs; 206 if (_numOutputs > 0) 207 { 208 if (port == 0) 209 { 210 _latencyOutput = cast(float*)data; 211 return; 212 } 213 --port; 214 } 215 if(port == 0) 216 { 217 _eventsInput = cast(LV2_Atom_Sequence*)data; 218 return; 219 } 220 --port; 221 if(port == 0) 222 { 223 _eventsOutput = cast(LV2_Atom_Sequence*)data; 224 return; 225 } 226 assert(false, "Error unknown port index"); 227 } 228 229 void activate() 230 { 231 _callResetOnNextRun = true; 232 } 233 234 void resizeScratchBuffers(int numSamples) 235 { 236 for (int i = 0; i < _numInputs; ++i) 237 { 238 _inputScratchBuffer[i].resize(numSamples); 239 _inputScratchBuffer[i].fill(0); 240 } 241 for (int i = 0; i < _numOutputs; ++i) 242 { 243 _outputScratchBuffer[i].resize(numSamples); 244 } 245 } 246 247 void run(uint32_t n_samples) 248 { 249 if (_maxBufferSize < n_samples) 250 { 251 _callResetOnNextRun = true; 252 _maxBufferSize = n_samples; // in case the max buffer value wasn't found within options 253 resizeScratchBuffers(_maxBufferSize); 254 } 255 256 if(_callResetOnNextRun) 257 { 258 _callResetOnNextRun = false; 259 _client.resetFromHost(_sampleRate, _maxBufferSize, _numInputs, _numOutputs); 260 } 261 262 if (_eventsInput !is null) 263 { 264 for(LV2_Atom_Event* event = lv2_atom_sequence_begin(&_eventsInput.body_); 265 !lv2_atom_sequence_is_end(&_eventsInput.body_, _eventsInput.atom.size, event); 266 event = lv2_atom_sequence_next(event)) 267 { 268 if (event is null) 269 break; 270 271 if (_client.receivesMIDI() && event.body_.type == _mappedURIs.midiEvent) 272 { 273 // Get offset of MIDI message in that buffer 274 int offset = cast(int)(event.time.frames); 275 if (offset < 0) 276 offset = 0; 277 int bytes = event.body_.size; 278 ubyte* data = cast(ubyte*)(event + 1); 279 280 if (bytes >= 1 && bytes <= 3) // else doesn't fit in a Dplug MidiMessage 281 { 282 ubyte byte0 = data[0]; 283 ubyte byte1 = (bytes >= 2) ? data[1] : 0; 284 ubyte byte2 = (bytes >= 3) ? data[2] : 0; 285 MidiMessage message = MidiMessage(offset, byte0, byte1, byte2); 286 _client.enqueueMIDIFromHost(message); 287 } 288 } 289 else if (event.body_.type == _mappedURIs.atomBlank || event.body_.type == _mappedURIs.atomObject) 290 { 291 292 const (LV2_Atom_Object*) obj = cast(LV2_Atom_Object*)&event.body_; 293 294 if (obj.body_.otype == _mappedURIs.timePosition) 295 { 296 LV2_Atom* beatsPerMinute = null; 297 LV2_Atom* frame = null; 298 LV2_Atom* speed = null; 299 300 lv2AtomObjectExtractTimeInfo(obj, 301 _mappedURIs.timeBeatsPerMinute, &beatsPerMinute, 302 _mappedURIs.timeFrame, &frame, 303 _mappedURIs.timeSpeed, &speed); 304 305 if (beatsPerMinute != null) 306 { 307 if (beatsPerMinute.type == _mappedURIs.atomDouble) 308 _currentTimeInfo.tempo = (cast(LV2_Atom_Double*)beatsPerMinute).body_; 309 else if (beatsPerMinute.type == _mappedURIs.atomFloat) 310 _currentTimeInfo.tempo = (cast(LV2_Atom_Float*)beatsPerMinute).body_; 311 else if (beatsPerMinute.type == _mappedURIs.atomInt) 312 _currentTimeInfo.tempo = (cast(LV2_Atom_Int*)beatsPerMinute).body_; 313 else if (beatsPerMinute.type == _mappedURIs.atomLong) 314 _currentTimeInfo.tempo = (cast(LV2_Atom_Long*)beatsPerMinute).body_; 315 } 316 if (frame != null) 317 { 318 if (frame.type == _mappedURIs.atomDouble) 319 _currentTimeInfo.timeInSamples = cast(long)(cast(LV2_Atom_Double*)frame).body_; 320 else if (frame.type == _mappedURIs.atomFloat) 321 _currentTimeInfo.timeInSamples = cast(long)(cast(LV2_Atom_Float*)frame).body_; 322 else if (frame.type == _mappedURIs.atomInt) 323 _currentTimeInfo.timeInSamples = (cast(LV2_Atom_Int*)frame).body_; 324 else if (frame.type == _mappedURIs.atomLong) 325 _currentTimeInfo.timeInSamples = (cast(LV2_Atom_Long*)frame).body_; 326 } 327 if (speed != null) 328 { 329 if (speed.type == _mappedURIs.atomDouble) 330 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Double*)speed).body_ > 0.0f; 331 else if (speed.type == _mappedURIs.atomFloat) 332 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Float*)speed).body_ > 0.0f; 333 else if (speed.type == _mappedURIs.atomInt) 334 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Int*)speed).body_ > 0.0f; 335 else if (speed.type == _mappedURIs.atomLong) 336 _currentTimeInfo.hostIsPlaying = (cast(LV2_Atom_Long*)speed).body_ > 0.0f; 337 } 338 } 339 } 340 } 341 } 342 343 // Update changed parameters 344 { 345 for (int i = 0; i < _numParams; ++i) 346 { 347 if (_paramsPointers[i]) 348 { 349 float currentValue = *_paramsPointers[i]; 350 351 // Force normalization in case host sends invalid parameter values 352 if (currentValue < 0) currentValue = 0; 353 if (currentValue > 1) currentValue = 1; 354 355 if (currentValue != _paramsLastValue[i]) 356 { 357 _paramsLastValue[i] = currentValue; 358 _client.setParameterFromHost(i, currentValue); 359 } 360 } 361 } 362 } 363 364 // Fill input and output pointers for this block, based on what we have received 365 for(int input = 0; input < _numInputs; ++input) 366 { 367 // Copy each available input to a scrach buffer, because some hosts (Mixbus/Ardour) 368 // give identical pointers for input and output buffers. 369 if (_inputPointersProvided[input]) 370 { 371 const(float)* source = _inputPointersProvided[input]; 372 float* dest = _inputScratchBuffer[input].ptr; 373 dest[0..n_samples] = source[0..n_samples]; 374 } 375 _inputPointersProcessing[input] = _inputScratchBuffer[input].ptr; 376 } 377 for(int output = 0; output < _numOutputs; ++output) 378 { 379 _outputPointersProcessing[output] = _outputPointersProvided[output] ? _outputPointersProvided[output] : _outputScratchBuffer[output].ptr; 380 } 381 382 if (_client.sendsMIDI) 383 _client.clearAccumulatedOutputMidiMessages(); 384 385 // Process audio 386 _client.processAudioFromHost(_inputPointersProcessing, _outputPointersProcessing, n_samples, _currentTimeInfo); 387 388 _currentTimeInfo.timeInSamples += n_samples; 389 390 if (_client.sendsMIDI() && _eventsOutput !is null) 391 { 392 uint capacity = _eventsOutput.atom.size; 393 394 _eventsOutput.atom.size = LV2_Atom_Sequence_Body.sizeof; 395 _eventsOutput.atom.type = _mappedURIs.atomSequence; 396 _eventsOutput.body_.unit = 0; 397 _eventsOutput.body_.pad = 0; 398 399 const(MidiMessage)[] outMsgs = _client.getAccumulatedOutputMidiMessages(); 400 if (outMsgs.length > 0) 401 { 402 const(ubyte)* midiEventData; 403 uint totalOffset = 0; 404 405 foreach(MidiMessage msg; outMsgs) 406 { 407 assert(msg.offset >= 0 && msg.offset <= n_samples); 408 409 int len = msg.lengthInBytes(); 410 411 if ( LV2_Atom_Event.sizeof + len > capacity - totalOffset) 412 break; // Note: some MIDI messages will get dropped in that case 413 414 int midiMsgSize = cast(int) msg.lengthInBytes(); 415 LV2_Atom_Event* event = cast(LV2_Atom_Event*)(cast(char*)LV2_ATOM_CONTENTS!LV2_Atom_Sequence(&_eventsOutput.atom) + totalOffset); 416 event.time.frames = msg.offset; 417 event.body_.type = _mappedURIs.midiEvent; 418 event.body_.size = midiMsgSize; 419 int written = msg.toBytes( cast(ubyte*) LV2_ATOM_BODY(&event.body_), midiMsgSize /* enough room */); 420 assert(written == midiMsgSize); 421 uint size = lv2_atom_pad_size(cast(uint)(LV2_Atom_Event.sizeof) + midiMsgSize); 422 totalOffset += size; 423 _eventsOutput.atom.size += size; 424 } 425 } 426 } 427 428 // Report latency to host, expressed in frames 429 if (_latencyOutput) 430 *_latencyOutput = _latencySamples; 431 } 432 433 void instantiateUI(const LV2UI_Descriptor* descriptor, 434 const char* plugin_uri, 435 const char* bundle_path, 436 LV2UI_Write_Function write_function, 437 LV2UI_Controller controller, 438 LV2UI_Widget* widget, 439 const (LV2_Feature*)* features) 440 { 441 debug(debugLV2Client) debugLog(">instantiateUI"); 442 443 int transientWinId; 444 void* parentId = null; 445 LV2_Options_Option* options = null; 446 _uiResize = null; 447 LV2_URID_Map* uridMap = null; 448 449 // Useful to record automation 450 _write_function = write_function; 451 _controller = controller; 452 _uiTouch = null; 453 454 for (int i=0; features[i] != null; ++i) 455 { 456 if (strcmp(features[i].URI, LV2_UI__parent) == 0) 457 parentId = cast(void*)features[i].data; 458 else if (strcmp(features[i].URI, LV2_UI__resize) == 0) 459 _uiResize = cast(LV2UI_Resize*)features[i].data; 460 else if (strcmp(features[i].URI, LV2_OPTIONS__options) == 0) 461 options = cast(LV2_Options_Option*)features[i].data; 462 else if (strcmp(features[i].URI, LV2_URID__map) == 0) 463 uridMap = cast(LV2_URID_Map*)features[i].data; 464 else if (strcmp(features[i].URI, LV2_UI__touch) == 0) 465 _uiTouch = cast(LV2UI_Touch*)features[i].data; 466 } 467 468 // Not transmitted yet 469 /* 470 if (options && uridMap) 471 { 472 for (int i = 0; options[i].key != 0; ++i) 473 { 474 if (options[i].key == uridTransientWinId) 475 { 476 transientWin = cast(void*)(options[i].value); // sound like it lacks a dereferencing 477 } 478 } 479 }*/ 480 481 if (widget != null) 482 { 483 _graphicsMutex.lock(); 484 void* pluginWindow = cast(LV2UI_Widget)_client.openGUI(parentId, null, GraphicsBackend.autodetect); 485 _graphicsMutex.unlock(); 486 487 int widthLogicalPixels, heightLogicalPixels; 488 if (_client.getGUISize(&widthLogicalPixels, &heightLogicalPixels)) 489 { 490 _graphicsMutex.lock(); 491 _uiResize.ui_resize(_uiResize.handle, widthLogicalPixels, heightLogicalPixels); 492 _graphicsMutex.unlock(); 493 } 494 495 *widget = cast(LV2UI_Widget)pluginWindow; 496 } 497 debug(debugLV2Client) debugLog("<instantiateUI"); 498 } 499 500 void portEventUI(uint32_t port_index, 501 uint32_t buffer_size, 502 uint32_t format, 503 const void* buffer) 504 { 505 // Nothing to do since parameter changes already dirty the UI? 506 } 507 508 void cleanupUI() 509 { 510 debug(debugLV2Client) debugLog(">cleanupUI"); 511 _graphicsMutex.lock(); 512 assert(_client.hasGUI()); 513 _client.closeGUI(); 514 _graphicsMutex.unlock(); 515 debug(debugLV2Client) debugLog("<cleanupUI"); 516 } 517 518 override void beginParamEdit(int paramIndex) 519 { 520 // Note: this is untested, since it appears DISTRHO doesn't really ask 521 // for this interface, and Carla doesn't provide one. 522 if ( (_uiTouch !is null) && (_uiTouch.touch !is null) ) 523 _uiTouch.touch(_uiTouch.handle, paramIndex, true); 524 } 525 526 override void paramAutomate(int paramIndex, float value) 527 { 528 // write back automation to host 529 assert(_write_function !is null); 530 _write_function(_controller, paramIndex, float.sizeof, 0, &value); 531 } 532 533 override void endParamEdit(int paramIndex) 534 { 535 // Note: this is untested, since it appears DISTRHO doesn't really ask 536 // for this interface, and Carla doesn't provide one. 537 if ( (_uiTouch !is null) && (_uiTouch.touch !is null) ) 538 _uiTouch.touch(_uiTouch.handle, paramIndex, false); 539 } 540 541 override bool requestResize(int width, int height) 542 { 543 int result = _uiResize.ui_resize(_uiResize.handle, width, height); 544 return result == 0; 545 } 546 547 // Not properly implemented yet. LV2 should have an extension to get DAW information. 548 override DAW getDAW() 549 { 550 return DAW.Unknown; 551 } 552 553 override PluginFormat getPluginFormat() 554 { 555 return PluginFormat.lv2; 556 } 557 558 private: 559 560 uint _numInputs; 561 uint _numOutputs; 562 563 // the maximum buffer size we've found, from either the options or reality of incoming buffers 564 int _maxBufferSize; 565 566 // whether the plugin should call resetFromHost at next `run()` 567 bool _callResetOnNextRun; 568 569 int _numParams; 570 float*[] _paramsPointers; 571 float[] _paramsLastValue; 572 573 // Scratch input buffers in case the host doesn't provide ones. 574 Vec!float[] _inputScratchBuffer; 575 576 // Scratch output buffers in case the host doesn't provide ones. 577 Vec!float[] _outputScratchBuffer; 578 579 // Input pointers that were provided by the host, `null` if not provided. 580 float*[] _inputPointersProvided; 581 582 // Output input used by processing, recomputed at each run(). 583 float*[] _inputPointersProcessing; 584 585 // Output pointers that were provided by the host, `null` if not provided. 586 float*[] _outputPointersProvided; 587 588 // Output pointers used by processing, recomputed at each run(). 589 float*[] _outputPointersProcessing; 590 591 // Output pointer for latency reporting, `null` if not provided. 592 float* _latencyOutput; 593 594 LV2_Atom_Sequence* _eventsInput; // may be null 595 LV2_Atom_Sequence* _eventsOutput; 596 597 float _sampleRate; 598 599 // The latency value expressed in frames 600 int _latencySamples; 601 602 UncheckedMutex _graphicsMutex; 603 604 // Set at instantiation 605 MappedURIs _mappedURIs; 606 607 int _legalIOIndex; 608 609 LV2UI_Write_Function _write_function; 610 LV2UI_Controller _controller; 611 LV2UI_Touch* _uiTouch; 612 LV2UI_Resize* _uiResize; 613 614 // Current time info, eventually extrapolated when data is missing. 615 TimeInfo _currentTimeInfo; 616 } 617 618 struct MappedURIs 619 { 620 nothrow: 621 @nogc: 622 623 LV2_URID atomDouble; 624 LV2_URID atomFloat; 625 LV2_URID atomInt; 626 LV2_URID atomLong; 627 LV2_URID atomBlank; 628 LV2_URID atomObject; 629 LV2_URID atomSequence; 630 LV2_URID midiEvent; 631 LV2_URID timePosition; 632 LV2_URID timeFrame; 633 LV2_URID timeBeatsPerMinute; 634 LV2_URID timeSpeed; 635 636 void initialize(LV2_URID_Map* uridMap) 637 { 638 atomDouble = uridMap.map(uridMap.handle, LV2_ATOM__Double); 639 atomFloat = uridMap.map(uridMap.handle, LV2_ATOM__Float); 640 atomInt = uridMap.map(uridMap.handle, LV2_ATOM__Int); 641 atomLong = uridMap.map(uridMap.handle, LV2_ATOM__Long); 642 atomBlank = uridMap.map(uridMap.handle, LV2_ATOM__Blank); 643 atomObject = uridMap.map(uridMap.handle, LV2_ATOM__Object); 644 atomSequence = uridMap.map(uridMap.handle, LV2_ATOM__Sequence); 645 midiEvent = uridMap.map(uridMap.handle, LV2_MIDI__MidiEvent); 646 timePosition = uridMap.map(uridMap.handle, LV2_TIME__Position); 647 timeFrame = uridMap.map(uridMap.handle, LV2_TIME__frame); 648 timeBeatsPerMinute = uridMap.map(uridMap.handle, LV2_TIME__beatsPerMinute); 649 timeSpeed = uridMap.map(uridMap.handle, LV2_TIME__speed); 650 } 651 } 652 653 654 int lv2AtomObjectExtractTimeInfo(const (LV2_Atom_Object*) object, 655 LV2_URID tempoURID, LV2_Atom** tempoAtom, 656 LV2_URID frameURID, LV2_Atom** frameAtom, 657 LV2_URID speedURID, LV2_Atom** speedAtom) 658 { 659 int n_queries = 3; 660 int matches = 0; 661 // #define LV2_ATOM_OBJECT_FOREACH(obj, iter) 662 for (LV2_Atom_Property_Body* prop = lv2_atom_object_begin(&object.body_); 663 !lv2_atom_object_is_end(&object.body_, object.atom.size, prop); 664 prop = lv2_atom_object_next(prop)) 665 { 666 for (int i = 0; i < n_queries; ++i) { 667 // uint32_t qkey = va_arg!(uint32_t)(args); 668 // LV2_Atom** qval = va_arg!(LV2_Atom**)(args); 669 // if (qkey == prop.key && !*qval) { 670 // *qval = &prop.value; 671 // if (++matches == n_queries) { 672 // return matches; 673 // } 674 // break; 675 // } 676 if(tempoURID == prop.key && tempoAtom) { 677 *tempoAtom = &prop.value; 678 ++matches; 679 } 680 else if(frameURID == prop.key && frameAtom) { 681 *frameAtom = &prop.value; 682 ++matches; 683 } 684 else if(speedURID == prop.key && speedAtom) { 685 *speedAtom = &prop.value; 686 ++matches; 687 } 688 } 689 } 690 return matches; 691 }