1 /* 2 Cockos WDL License 3 4 Copyright (C) 2005-2015 Cockos Incorporated 5 Copyright (C) 2015-2022 Guillaume Piolat 6 7 Portions copyright other contributors, see each source file for more information 8 9 This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 10 11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 15 1. This notice may not be removed or altered from any source distribution. 16 */ 17 /+ 18 VST plug-in client implementation. 19 20 Copyright: Cockos Incorporated 2005-2015. 21 Copyright: Guillaume Piolat 2015-2022. 22 +/ 23 module dplug.vst2.client; 24 25 import std.string; 26 27 import core.stdc.stdlib, 28 core.stdc.string, 29 core.stdc.stdio; 30 31 import dplug.core.vec, 32 dplug.core.nogc, 33 dplug.core.math, 34 dplug.core.lockedqueue, 35 dplug.core.runtime, 36 dplug.core.fpcontrol, 37 dplug.core.thread, 38 dplug.core.sync; 39 40 import dplug.client.client, 41 dplug.client.daw, 42 dplug.client.preset, 43 dplug.client.graphics, 44 dplug.client.midi; 45 46 import dplug.vst2.translatesdk; 47 48 // Only does a semantic pass on this if the VST version identifier is defined. 49 // This allows building dplug:vst even without a VST2 SDK (though nothing will be defined in this case) 50 version(VST2): 51 52 template VST2EntryPoint(alias ClientClass) 53 { 54 enum entry_VSTPluginMain = 55 "export extern(C) nothrow AEffect* VSTPluginMain(HostCallbackFunction hostCallback) " ~ 56 "{" ~ 57 " return myVSTEntryPoint!" ~ ClientClass.stringof ~ "(hostCallback);" ~ 58 "}\n"; 59 enum entry_main_macho = 60 "export extern(C) nothrow AEffect* main_macho(HostCallbackFunction hostCallback) " ~ 61 "{" ~ 62 " return myVSTEntryPoint!" ~ ClientClass.stringof ~ "(hostCallback);" ~ 63 "}\n"; 64 65 // Workaround LDC #3505 66 // https://github.com/ldc-developers/ldc/issues/3505 67 // Not possible to return a pointer from a "main" function. 68 // Using pragma mangle, this symbol should be "main" in Win32 and "_main" on Linux. 69 static if (__VERSION__ >= 2079) 70 { 71 enum entry_main = 72 "pragma(mangle, \"main\") export extern(C) nothrow AEffect* main_renamed(HostCallbackFunction hostCallback) " ~ 73 "{" ~ 74 " return myVSTEntryPoint!" ~ ClientClass.stringof ~ "(hostCallback);" ~ 75 "}\n"; 76 } 77 else 78 { 79 enum entry_main = // earlier compilers didn't have a problem 80 "export extern(C) nothrow AEffect* main(HostCallbackFunction hostCallback) " ~ 81 "{" ~ 82 " return myVSTEntryPoint!" ~ ClientClass.stringof ~ "(hostCallback);" ~ 83 "}\n"; 84 } 85 86 version(unittest) 87 enum entry_main_if_no_unittest = ""; 88 else 89 enum entry_main_if_no_unittest = entry_main; 90 91 version(OSX) 92 // OSX has two VST entry points 93 const char[] VST2EntryPoint = entry_VSTPluginMain ~ entry_main_macho; 94 else version(Windows) 95 { 96 static if (size_t.sizeof == int.sizeof) 97 // 32-bit Windows needs a legacy "main" entry point 98 const char[] VST2EntryPoint = entry_VSTPluginMain ~ entry_main_if_no_unittest; 99 else 100 // 64-bit Windows does not 101 const char[] VST2EntryPoint = entry_VSTPluginMain; 102 103 } 104 else version(linux) 105 const char[] VST2EntryPoint = entry_VSTPluginMain ~ entry_main_if_no_unittest; 106 else 107 static assert(false); 108 } 109 110 // This is the main VST entrypoint 111 nothrow AEffect* myVSTEntryPoint(alias ClientClass)(HostCallbackFunction hostCallback) 112 { 113 if (hostCallback is null) 114 return null; 115 116 ScopedForeignCallback!(false, true) scopedCallback; 117 scopedCallback.enter(); 118 auto client = mallocNew!ClientClass(); 119 120 // malloc'd else the GC would not register roots for some reason! 121 VST2Client plugin = mallocNew!VST2Client(client, hostCallback); 122 return &plugin._effect; 123 }; 124 125 126 //version = logVSTDispatcher; 127 128 /// VST client wrapper 129 class VST2Client 130 { 131 public: 132 nothrow: 133 @nogc: 134 135 AEffect _effect; 136 137 this(Client client, HostCallbackFunction hostCallback) 138 { 139 int queueSize = 2048; 140 _messageQueue = makeLockedQueue!AudioThreadMessage(queueSize); 141 142 _client = client; 143 _effect = _effect.init; 144 _effect.magic = CCONST('V', 's', 't', 'P'); 145 146 int flags = effFlagsCanReplacing | effFlagsCanDoubleReplacing; 147 148 version(legacyVST2Chunks) 149 {} 150 else 151 { 152 flags |= effFlagsProgramChunks; 153 } 154 155 if ( client.hasGUI() ) 156 flags |= effFlagsHasEditor; 157 158 if ( client.isSynth() ) 159 flags |= effFlagsIsSynth; 160 161 _effect.flags = flags; 162 _maxParams = cast(int)(client.params().length); 163 _maxInputs = _effect.numInputs = _client.maxInputs(); 164 _maxOutputs = _effect.numOutputs = _client.maxOutputs(); 165 assert(_maxParams >= 0 && _maxInputs >= 0 && _maxOutputs >= 0); 166 _effect.numParams = cast(int)(client.params().length); 167 _effect.numPrograms = cast(int)(client.presetBank().numPresets()); 168 _effect.version_ = client.getPublicVersion().toVSTVersion(); 169 char[4] uniqueID = client.getPluginUniqueID(); 170 _effect.uniqueID = CCONST(uniqueID[0], uniqueID[1], uniqueID[2], uniqueID[3]); 171 _effect.processReplacing = &processReplacingCallback; 172 _effect.dispatcher = &dispatcherCallback; 173 _effect.setParameter = &setParameterCallback; 174 _effect.getParameter = &getParameterCallback; 175 _effect.object = cast(void*)(this); 176 177 _effect.initialDelay = _client.latencySamples(44100); // Note: we can't have a sample-rate yet 178 _effect.object = cast(void*)(this); 179 _effect.processDoubleReplacing = &processDoubleReplacingCallback; 180 181 _effect.DEPRECATED_ioRatio = 1.0; 182 _effect.DEPRECATED_process = &processCallback; 183 184 // dummmy values 185 _sampleRate = 44100.0f; 186 _maxFrames = 128; 187 188 _maxFramesInProcess = _client.maxFramesInProcess(); 189 190 _samplesAlreadyProcessed = 0; 191 192 193 // GUI thread can allocate 194 _inputScratchBuffer = mallocSlice!(Vec!float)(_maxInputs); 195 _outputScratchBuffer = mallocSlice!(Vec!float)(_maxOutputs); 196 197 for (int i = 0; i < _maxInputs; ++i) 198 _inputScratchBuffer[i] = makeVec!float(); 199 200 for (int i = 0; i < _maxOutputs; ++i) 201 _outputScratchBuffer[i] = makeVec!float(); 202 203 _zeroesBuffer = makeVec!float(); 204 205 _inputPointers = mallocSlice!(float*)(_maxInputs); 206 _outputPointers = mallocSlice!(float*)(_maxOutputs); 207 208 // because effSetSpeakerArrangement might never come, take a default 209 chooseIOArrangement(_maxInputs, _maxOutputs); 210 sendResetMessage(); 211 212 // Create host callback wrapper 213 _host = mallocNew!VSTHostFromClientPOV(hostCallback, &_effect); 214 client.setHostCommand(_host); 215 216 if ( client.receivesMIDI() ) 217 { 218 _host.wantEvents(); 219 } 220 221 _graphicsMutex = makeMutex(); 222 } 223 224 ~this() 225 { 226 _client.destroyFree(); 227 228 for (int i = 0; i < _maxInputs; ++i) 229 _inputScratchBuffer[i].destroy(); 230 231 for (int i = 0; i < _maxOutputs; ++i) 232 _outputScratchBuffer[i].destroy(); 233 234 _inputScratchBuffer.freeSlice(); 235 _outputScratchBuffer.freeSlice(); 236 237 _zeroesBuffer.destroy(); 238 239 _inputPointers.freeSlice(); 240 _outputPointers.freeSlice(); 241 242 _host.destroyFree(); 243 244 _messageQueue.destroy(); 245 246 version(legacyVST2Chunks) 247 {} 248 else 249 { 250 if (_lastStateChunk) 251 { 252 free(_lastStateChunk.ptr); 253 _lastStateChunk = null; 254 } 255 } 256 } 257 258 private: 259 260 VSTHostFromClientPOV _host; 261 Client _client; 262 263 float _sampleRate; // samplerate from opcode thread POV 264 int _maxFrames; // max frames from opcode thread POV 265 int _maxFramesInProcess; // max frames supported by the plugin, buffers will be splitted to follow this. 266 int _maxInputs; 267 int _maxOutputs; 268 int _maxParams; 269 270 // Actual channels the host will give. 271 IO _hostIOFromOpcodeThread; 272 273 // Logical number of channels the plugin will use. 274 // This might be different with hosts that call effSetSpeakerArrangement with 275 // an invalid number of channels (like Audition which uses 1-1 even if not available). 276 IO _processingIOFromOpcodeThread; 277 278 // Fills _hostIOFromOpcodeThread and _processingIOFromOpcodeThread 279 final void chooseIOArrangement(int numInputs, int numOutputs) nothrow @nogc 280 { 281 _hostIOFromOpcodeThread = IO(numInputs, numOutputs); 282 283 // Note: _hostIOFromOpcodeThread may contain invalid stuff 284 // Compute acceptable number of channels based on I/O legality. 285 286 // Find the legal I/O combination with the highest score. 287 int bestScore = -10000; 288 IO bestProcessingIO = _hostIOFromOpcodeThread; 289 bool found = false; 290 291 foreach(LegalIO io; _client.legalIOs()) 292 { 293 // The reasoning is: try to match exactly inputs and outputs. 294 // If this isn't possible, better have the largest number of channels, 295 // all other things being equal. 296 // Note: this heuristic will prefer 1-2 to 2-1 if 1-1 was asked. 297 int score = 0; 298 if (io.numInputChannels == numInputs) 299 score += 2000; 300 else 301 score += (io.numInputChannels - numInputs); 302 303 if (io.numOutputChannels == numOutputs) 304 score += 1000; 305 else 306 score += (io.numOutputChannels - numOutputs); 307 308 if (score > bestScore) 309 { 310 bestScore = score; 311 bestProcessingIO = IO(io.numInputChannels, io.numOutputChannels); 312 } 313 } 314 _processingIOFromOpcodeThread = bestProcessingIO; 315 } 316 317 // Same data, but on the audio thread point of view. 318 IO _hostIOFromAudioThread; 319 IO _processingIOFromAudioThread; 320 321 long _samplesAlreadyProcessed; // For hosts that don't provide time info, fake it by counting samples. 322 323 ERect _editRect; // structure holding the UI size 324 325 Vec!float[] _inputScratchBuffer; // input buffer, one per possible input 326 Vec!float[] _outputScratchBuffer; // input buffer, one per output 327 Vec!float _zeroesBuffer; // used for disconnected inputs 328 float*[] _inputPointers; // where processAudio will take its audio input, one per possible input 329 float*[] _outputPointers; // where processAudio will output audio, one per possible output 330 331 // stores the last asked state chunk 332 version(legacyVST2Chunks) 333 {} 334 else 335 { 336 337 ubyte[] _lastStateChunk = null; 338 } 339 340 // Inter-locked message queue from opcode thread to audio thread 341 LockedQueue!AudioThreadMessage _messageQueue; 342 343 UncheckedMutex _graphicsMutex; 344 345 final bool isValidParamIndex(int i) pure const nothrow @nogc 346 { 347 return i >= 0 && i < _maxParams; 348 } 349 350 final bool isValidInputIndex(int index) pure const nothrow @nogc 351 { 352 return index >= 0 && index < _maxInputs; 353 } 354 355 final bool isValidOutputIndex(int index) pure const nothrow @nogc 356 { 357 return index >= 0 && index < _maxOutputs; 358 } 359 360 void sendResetMessage() 361 { 362 AudioThreadMessage msg = AudioThreadMessage(AudioThreadMessage.Type.resetState, 363 _maxFrames, 364 _sampleRate, 365 _hostIOFromOpcodeThread, 366 _processingIOFromOpcodeThread); 367 _messageQueue.pushBack(msg); 368 } 369 370 /// VST opcode dispatcher 371 final VstIntPtr dispatcher(int opcode, int index, ptrdiff_t value, void *ptr, float opt) nothrow @nogc 372 { 373 // Important message from Cockos: 374 // "Assume everything can (and WILL) run at the same time as your 375 // process/processReplacing, except: 376 // - effOpen/effClose 377 // - effSetChunk -- while effGetChunk can run at the same time as audio 378 // (user saves project, or for automatic undo state tracking), effSetChunk 379 // is guaranteed to not run while audio is processing. 380 // So nearly everything else should be threadsafe." 381 382 switch(opcode) 383 { 384 case effClose: // opcode 1 385 return 0; 386 387 case effSetProgram: // opcode 2 388 { 389 int presetIndex = cast(int)value; 390 PresetBank bank = _client.presetBank(); 391 if (bank.isValidPresetIndex(presetIndex)) 392 bank.loadPresetFromHost(presetIndex); 393 return 0; 394 } 395 396 case effGetProgram: // opcode 3 397 { 398 // FUTURE: will probably need to be zero with internal preset management 399 return _client.presetBank.currentPresetIndex(); 400 } 401 402 case effSetProgramName: // opcode 4 403 { 404 char* p = cast(char*)ptr; 405 int len = cast(int)strlen(p); 406 PresetBank bank = _client.presetBank(); 407 Preset current = bank.currentPreset(); 408 if (current !is null) 409 { 410 current.setName(p[0..len]); 411 } 412 return 0; 413 } 414 415 case effGetProgramName: // opcode 5 416 { 417 char* p = cast(char*)ptr; 418 if (p !is null) 419 { 420 PresetBank bank = _client.presetBank(); 421 Preset current = bank.currentPreset(); 422 if (current !is null) 423 { 424 stringNCopy(p, 24, current.name()); 425 } 426 } 427 return 0; 428 } 429 430 case effGetParamLabel: // opcode 6 431 { 432 char* p = cast(char*)ptr; 433 if (!isValidParamIndex(index)) 434 *p = '\0'; 435 else 436 { 437 stringNCopy(p, 8, _client.param(index).label()); 438 } 439 return 0; 440 } 441 442 case effGetParamDisplay: // opcode 7 443 { 444 char* p = cast(char*)ptr; 445 if (!isValidParamIndex(index)) 446 *p = '\0'; 447 else 448 { 449 _client.param(index).toDisplayN(p, 8); 450 } 451 return 0; 452 } 453 454 case effGetParamName: // opcode 8 455 { 456 char* p = cast(char*)ptr; 457 if (!isValidParamIndex(index)) 458 *p = '\0'; 459 else 460 { 461 stringNCopy(p, 32, _client.param(index).name()); 462 } 463 return 0; 464 } 465 466 case effSetSampleRate: // opcode 10 467 { 468 _sampleRate = opt; 469 sendResetMessage(); 470 return 0; 471 } 472 473 case effSetBlockSize: // opcode 11 474 { 475 if (value < 0) 476 return 1; 477 478 _maxFrames = cast(int)value; 479 sendResetMessage(); 480 return 0; 481 } 482 483 case effMainsChanged: // opcode 12 484 { 485 if (value == 0) 486 { 487 // Audio processing was switched off. 488 // The plugin must flush its state because otherwise pending data 489 // would sound again when the effect is switched on next time. 490 // MAYDO: this sounds backwards... 491 sendResetMessage(); 492 } 493 else // "resume()" in VST parlance 494 { 495 // Audio processing was switched on. Update the latency. #154 496 int latency = _client.latencySamples(_sampleRate); 497 _effect.initialDelay = latency; 498 } 499 return 0; 500 } 501 502 case effEditGetRect: // opcode 13 503 { 504 if ( _client.hasGUI() && ptr) 505 { 506 // Cubase may call effEditOpen and effEditGetRect simultaneously 507 _graphicsMutex.lock(); 508 509 int widthLogicalPixels, heightLogicalPixels; 510 if (_client.getGUISize(&widthLogicalPixels, &heightLogicalPixels)) 511 { 512 _graphicsMutex.unlock(); 513 _editRect.top = 0; 514 _editRect.left = 0; 515 _editRect.right = cast(short)(widthLogicalPixels); 516 _editRect.bottom = cast(short)(heightLogicalPixels); 517 *cast(ERect**)(ptr) = &_editRect; 518 519 return 1; 520 } 521 else 522 { 523 _graphicsMutex.unlock(); 524 ptr = null; 525 return 0; 526 } 527 } 528 ptr = null; 529 return 0; 530 } 531 532 case effEditOpen: // opcode 14 533 { 534 if ( _client.hasGUI() ) 535 { 536 // Cubase may call effEditOpen and effEditGetRect simultaneously 537 _graphicsMutex.lock(); 538 _client.openGUI(ptr, null, GraphicsBackend.autodetect); 539 _graphicsMutex.unlock(); 540 return 1; 541 } 542 else 543 return 0; 544 } 545 546 case effEditClose: // opcode 15 547 { 548 if ( _client.hasGUI() ) 549 { 550 _graphicsMutex.lock(); 551 _client.closeGUI(); 552 _graphicsMutex.unlock(); 553 return 1; 554 } 555 else 556 return 0; 557 } 558 559 case DEPRECATED_effIdentify: // opcode 22 560 return CCONST('N', 'v', 'E', 'f'); 561 562 case effGetChunk: // opcode 23 563 { 564 version(legacyVST2Chunks) 565 {} 566 else 567 { 568 ubyte** ppData = cast(ubyte**) ptr; 569 bool wantBank = (index == 0); 570 if (ppData) 571 { 572 // Note: we have no concern whether the host demanded a bank or preset chunk here. 573 auto presetBank = _client.presetBank(); 574 _lastStateChunk = presetBank.getStateChunkFromCurrentState(); 575 *ppData = _lastStateChunk.ptr; 576 return cast(int)_lastStateChunk.length; 577 } 578 } 579 return 0; 580 } 581 582 case effSetChunk: // opcode 24 583 { 584 version(legacyVST2Chunks) 585 { 586 return 0; 587 } 588 else 589 { 590 if (!ptr) 591 return 0; 592 593 bool isBank = (index == 0); 594 ubyte[] chunk = (cast(ubyte*)ptr)[0..value]; 595 auto presetBank = _client.presetBank(); 596 try 597 { 598 presetBank.loadStateChunk(chunk); 599 return 1; // success 600 } 601 catch(Exception e) 602 { 603 // Chunk didn't parse 604 e.destroyFree(); 605 return 0; 606 } 607 } 608 } 609 610 case effProcessEvents: // opcode 25, "host usually call ProcessEvents just before calling ProcessReplacing" 611 VstEvents* pEvents = cast(VstEvents*) ptr; 612 if (pEvents != null) 613 { 614 VstEvent** allEvents = pEvents.events.ptr; 615 for (int i = 0; i < pEvents.numEvents; ++i) 616 { 617 VstEvent* pEvent = allEvents[i]; 618 if (pEvent) 619 { 620 if (pEvent.type == kVstMidiType) 621 { 622 VstMidiEvent* pME = cast(VstMidiEvent*) pEvent; 623 624 // Enqueue midi message to be processed by the audio thread. 625 // Note that not all information is kept, some is discarded like in IPlug. 626 MidiMessage msg = MidiMessage(pME.deltaFrames, 627 pME.midiData[0], 628 pME.midiData[1], 629 pME.midiData[2]); 630 _messageQueue.pushBack(makeMIDIMessage(msg)); 631 } 632 else 633 { 634 // FUTURE handle sysex 635 } 636 } 637 } 638 return 1; 639 } 640 return 0; 641 642 case effCanBeAutomated: // opcode 26 643 { 644 if (!isValidParamIndex(index)) 645 return 0; 646 if (_client.param(index).isAutomatable) 647 return 1; 648 return 0; 649 } 650 651 case effString2Parameter: // opcode 27 652 { 653 if (!isValidParamIndex(index)) 654 return 0; 655 656 if (ptr == null) 657 return 0; 658 659 // MAYDO: Sounds a bit insufficient? Will return 0 in case of error. 660 // also it will run into C locale problems. 661 double parsed = atof(cast(char*)ptr); 662 _client.setParameterFromHost(index, parsed); 663 return 1; 664 } 665 666 case DEPRECATED_effGetNumProgramCategories: // opcode 28 667 return 1; // no real program categories 668 669 case effGetProgramNameIndexed: // opcode 29 670 { 671 char* p = cast(char*)ptr; 672 if (p !is null) 673 { 674 PresetBank bank = _client.presetBank(); 675 if (!bank.isValidPresetIndex(index)) 676 return 0; 677 const(char)[] name = bank.preset(index).name(); 678 stringNCopy(p, 24, name); 679 return (name.length > 0) ? 1 : 0; 680 } 681 else 682 return 0; 683 } 684 685 case effGetInputProperties: // opcode 33 686 { 687 if (ptr == null) 688 return 0; 689 690 if (!isValidInputIndex(index)) 691 return 0; 692 693 VstPinProperties* pp = cast(VstPinProperties*) ptr; 694 pp.flags = kVstPinIsActive; 695 696 if ( (index % 2) == 0 && index < _maxInputs) 697 pp.flags |= kVstPinIsStereo; 698 699 sprintf(pp.label.ptr, "Input %d", index); 700 return 1; 701 } 702 703 case effGetOutputProperties: // opcode 34 704 { 705 if (ptr == null) 706 return 0; 707 708 if (!isValidOutputIndex(index)) 709 return 0; 710 711 VstPinProperties* pp = cast(VstPinProperties*) ptr; 712 pp.flags = kVstPinIsActive; 713 714 if ( (index % 2) == 0 && index < _maxOutputs) 715 pp.flags |= kVstPinIsStereo; 716 717 sprintf(pp.label.ptr, "Output %d", index); 718 return 1; 719 } 720 721 case effGetPlugCategory: // opcode 35 722 if ( _client.isSynth() ) 723 return kPlugCategSynth; 724 else 725 return kPlugCategEffect; 726 727 case effSetSpeakerArrangement: // opcode 42 728 { 729 VstSpeakerArrangement* pInputArr = cast(VstSpeakerArrangement*) value; 730 VstSpeakerArrangement* pOutputArr = cast(VstSpeakerArrangement*) ptr; 731 if (pInputArr !is null && pOutputArr !is null ) 732 { 733 int numInputs = pInputArr.numChannels; 734 int numOutputs = pOutputArr.numChannels; 735 chooseIOArrangement(numInputs, numOutputs); 736 sendResetMessage(); 737 return 0; // MAYDO: this looks very wrong 738 } 739 return 1; 740 } 741 742 case effSetBypass: // opcode 44 743 // Unfortunately, we were unable to find any VST2 hsot that would use effSetBypass 744 // So disable it since it can't be out untested. 745 return 0; 746 747 case effGetEffectName: // opcode 45 748 { 749 char* p = cast(char*)ptr; 750 if (p !is null) 751 { 752 stringNCopy(p, 32, _client.pluginName()); 753 return 1; 754 } 755 return 0; 756 } 757 758 case effGetVendorString: // opcode 47 759 { 760 char* p = cast(char*)ptr; 761 if (p !is null) 762 { 763 stringNCopy(p, 64, _client.vendorName()); 764 return 1; 765 } 766 return 0; 767 } 768 769 case effGetProductString: // opcode 48 770 { 771 char* p = cast(char*)ptr; 772 if (p !is null) 773 { 774 _client.getPluginFullName(p, 64); 775 return 1; 776 } 777 return 0; 778 } 779 780 case effCanDo: // opcode 51 781 { 782 char* str = cast(char*)ptr; 783 if (str is null) 784 return 0; 785 786 if (strcmp(str, "receiveVstTimeInfo") == 0) 787 return 1; 788 789 // Unable to find a host that will actually support it. 790 // Have to disable it to avoid being untested. 791 /* 792 if (strcmp(str, "bypass") == 0) 793 { 794 return _client.hasBypass() ? 1 : 0; 795 } 796 */ 797 798 if (_client.sendsMIDI()) 799 { 800 if (strcmp(str, "sendVstEvents") == 0) 801 return 1; 802 if (strcmp(str, "sendVstMidiEvent") == 0) 803 return 1; 804 if (strcmp(str, "sendVstMidiEvents") == 0) 805 return 1; 806 } 807 808 if (_client.receivesMIDI()) 809 { 810 if (strcmp(str, "receiveVstEvents") == 0) 811 return 1; 812 813 // Issue #198, Bitwig Studio need this 814 if (strcmp(str, "receiveVstMidiEvent") == 0) 815 return 1; 816 817 if (strcmp(str, "receiveVstMidiEvents") == 0) 818 return 1; 819 } 820 821 return 0; 822 } 823 824 case effGetVstVersion: // opcode 58 825 return 2400; // version 2.4 826 827 default: 828 return 0; // unknown opcode, should never happen 829 } 830 } 831 832 // 833 // Processing buffers and callbacks 834 // 835 836 // Resize copy buffers according to maximum block size. 837 void resizeScratchBuffers(int nFrames) nothrow @nogc 838 { 839 for (int i = 0; i < _maxInputs; ++i) 840 _inputScratchBuffer[i].resize(nFrames); 841 842 for (int i = 0; i < _maxOutputs; ++i) 843 _outputScratchBuffer[i].resize(nFrames); 844 845 _zeroesBuffer.resize(nFrames); 846 _zeroesBuffer.fill(0); 847 } 848 849 850 void processMessages() nothrow @nogc 851 { 852 // Race condition here. 853 // Being a tryPop, there is a tiny chance that we miss a message from the queue. 854 // Thankfully it isn't that bad: 855 // - we are going to read it next buffer 856 // - not clearing the state for a buffer duration does no harm 857 // - plugin is initialized first with the maximum amount of input and outputs 858 // so missing such a message isn't that bad: the audio callback will have some outputs that are untouched 859 // (a third thread might start a collect while the UI thread takes the queue lock) which is another unlikely race condition. 860 // Perhaps it's the one to favor, I don't know. 861 // TODO: Objectionable decision, for MIDI input, think about impact. 862 863 AudioThreadMessage msg = void; 864 while(_messageQueue.tryPopFront(msg)) 865 { 866 final switch(msg.type) with (AudioThreadMessage.Type) 867 { 868 case resetState: 869 resizeScratchBuffers(msg.maxFrames); 870 871 _hostIOFromAudioThread = msg.hostIO; 872 _processingIOFromAudioThread = msg.processingIO; 873 874 _client.resetFromHost(msg.samplerate, 875 msg.maxFrames, 876 _processingIOFromAudioThread.inputs, 877 _processingIOFromAudioThread.outputs); 878 break; 879 880 case midi: 881 _client.enqueueMIDIFromHost(msg.midiMessage); 882 } 883 } 884 } 885 886 void process(float **inputs, float **outputs, int sampleFrames) nothrow @nogc 887 { 888 processMessages(); 889 int hostInputs = _hostIOFromAudioThread.inputs; 890 int hostOutputs = _hostIOFromAudioThread.outputs; 891 int usedInputs = _processingIOFromAudioThread.inputs; 892 int usedOutputs = _processingIOFromAudioThread.outputs; 893 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 894 895 // Not sure if the hosts would support an overwriting of these pointers, so copy them 896 for (int i = 0; i < usedInputs; ++i) 897 { 898 // Points to zeros if the host provides a buffer, or the host buffer otherwise. 899 // Note: all input channels point on same buffer, but it's ok since input channels are const 900 _inputPointers[i] = (i < hostInputs) ? inputs[i] : _zeroesBuffer.ptr; 901 } 902 903 for (int i = 0; i < usedOutputs; ++i) 904 { 905 _outputPointers[i] = _outputScratchBuffer[i].ptr; 906 } 907 908 clearMidiOutBuffer(); 909 _client.processAudioFromHost(_inputPointers[0..usedInputs], 910 _outputPointers[0..usedOutputs], 911 sampleFrames, 912 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 913 _samplesAlreadyProcessed += sampleFrames; 914 915 // accumulate on available host output channels 916 for (int i = 0; i < minOutputs; ++i) 917 { 918 float* source = _outputScratchBuffer[i].ptr; 919 float* dest = outputs[i]; 920 for (int f = 0; f < sampleFrames; ++f) 921 dest[f] += source[f]; 922 } 923 sendMidiEvents(); 924 } 925 926 void processReplacing(float **inputs, float **outputs, int sampleFrames) nothrow @nogc 927 { 928 processMessages(); 929 int hostInputs = _hostIOFromAudioThread.inputs; 930 int hostOutputs = _hostIOFromAudioThread.outputs; 931 int usedInputs = _processingIOFromAudioThread.inputs; 932 int usedOutputs = _processingIOFromAudioThread.outputs; 933 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 934 935 // Some hosts (Live, Orion, and others) send identical input and output pointers. 936 // This is actually legal in VST. 937 // We copy them to a scratch buffer to keep the constness guarantee of input buffers. 938 for (int i = 0; i < usedInputs; ++i) 939 { 940 if (i < hostInputs) 941 { 942 float* source = inputs[i]; 943 float* dest = _inputScratchBuffer[i].ptr; 944 dest[0..sampleFrames] = source[0..sampleFrames]; 945 _inputPointers[i] = dest; 946 } 947 else 948 { 949 _inputPointers[i] = _zeroesBuffer.ptr; 950 } 951 } 952 953 for (int i = 0; i < usedOutputs; ++i) 954 { 955 if (i < hostOutputs) 956 _outputPointers[i] = outputs[i]; 957 else 958 _outputPointers[i] = _outputScratchBuffer[i].ptr; // dummy output 959 } 960 961 clearMidiOutBuffer(); 962 _client.processAudioFromHost(_inputPointers[0..usedInputs], 963 _outputPointers[0..usedOutputs], 964 sampleFrames, 965 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 966 _samplesAlreadyProcessed += sampleFrames; 967 968 // Fills remaining host channels (if any) with zeroes 969 for (int i = minOutputs; i < hostOutputs; ++i) 970 { 971 float* dest = outputs[i]; 972 for (int f = 0; f < sampleFrames; ++f) 973 dest[f] = 0; 974 } 975 sendMidiEvents(); 976 } 977 978 void processDoubleReplacing(double **inputs, double **outputs, int sampleFrames) nothrow @nogc 979 { 980 processMessages(); 981 int hostInputs = _hostIOFromAudioThread.inputs; 982 int hostOutputs = _hostIOFromAudioThread.outputs; 983 int usedInputs = _processingIOFromAudioThread.inputs; 984 int usedOutputs = _processingIOFromAudioThread.outputs; 985 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 986 987 // Existing inputs gets converted to float 988 // Non-connected inputs are zeroes 989 // 990 // Note about converting double to float: 991 // on both white noise and sinusoids, a conversion from 992 // double to float yield a relative RMS difference of 993 // -152dB. It would really extraordinary if anyone can tell 994 // the difference, as -110 dB RMS already exercise the limits 995 // of audition. 996 for (int i = 0; i < usedInputs; ++i) 997 { 998 if (i < hostInputs) 999 { 1000 double* source = inputs[i]; 1001 float* dest = _inputScratchBuffer[i].ptr; 1002 for (int f = 0; f < sampleFrames; ++f) 1003 dest[f] = source[f]; 1004 _inputPointers[i] = dest; 1005 } 1006 else 1007 _inputPointers[i] = _zeroesBuffer.ptr; 1008 } 1009 1010 for (int i = 0; i < usedOutputs; ++i) 1011 { 1012 _outputPointers[i] = _outputScratchBuffer[i].ptr; 1013 } 1014 1015 clearMidiOutBuffer(); 1016 _client.processAudioFromHost(_inputPointers[0..usedInputs], 1017 _outputPointers[0..usedOutputs], 1018 sampleFrames, 1019 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 1020 _samplesAlreadyProcessed += sampleFrames; 1021 1022 // Converts back to double on available host output channels 1023 for (int i = 0; i < minOutputs; ++i) 1024 { 1025 float* source = _outputScratchBuffer[i].ptr; 1026 double* dest = outputs[i]; 1027 for (int f = 0; f < sampleFrames; ++f) 1028 dest[f] = cast(double)source[f]; 1029 } 1030 1031 // Fills remaining host channels (if any) with zeroes 1032 for (int i = minOutputs; i < hostOutputs; ++i) 1033 { 1034 double* dest = outputs[i]; 1035 for (int f = 0; f < sampleFrames; ++f) 1036 dest[f] = 0; 1037 } 1038 sendMidiEvents(); 1039 } 1040 1041 void clearMidiOutBuffer() 1042 { 1043 if (!_client.sendsMIDI()) 1044 return; 1045 _client.clearAccumulatedOutputMidiMessages(); 1046 } 1047 1048 void sendMidiEvents() 1049 { 1050 if (!_client.sendsMIDI()) 1051 return; 1052 1053 const(MidiMessage)[] messages = _client.getAccumulatedOutputMidiMessages(); 1054 foreach(MidiMessage msg; messages) 1055 { 1056 VstMidiEvent event; 1057 event.type = kVstMidiType; 1058 event.byteSize = VstMidiEvent.sizeof; 1059 event.deltaFrames = msg.offset; 1060 event.flags = 0; // not played live, doesn't need specially high-priority 1061 event.noteLength = 0; // not available 1062 event.noteOffset = 0; // not available 1063 event.midiData[0] = 0; 1064 event.midiData[1] = 0; 1065 event.midiData[2] = 0; 1066 event.midiData[3] = 0; 1067 msg.toBytes(cast(ubyte*)(event.midiData.ptr), 3); // Warning: doesn't handle longer MIDI message than 3 bytes. 1068 event.detune = 0; 1069 event.noteOffVelocity = 0; // why it's here? 1070 event.reserved1 = 0; 1071 event.reserved2 = 0; 1072 _host.sendVstMidiEvent(cast(VstEvent*)&event); 1073 } 1074 } 1075 } 1076 1077 // This look-up table speed-up unimplemented opcodes 1078 private static immutable ubyte[64] opcodeShouldReturn0Immediately = 1079 [ 1, 0, 0, 0, 0, 0, 0, 0, // opcodes 0 to 7 1080 0, 1, 0, 0, 0, 0, 0, 0, // opcodes 8 to 15 1081 1, 1, 1, 1, 1, 1, 0, 0, // opcodes 16 to 23 1082 0, 0, 0, 0, 0, 0, 1, 1, // opcodes 24 to 31 1083 1, 0, 0, 0, 1, 1, 1, 1, // opcodes 32 to 39 1084 1, 1, 0, 1, 0, 0, 1, 0, // opcodes 40 to 47 1085 0, 1, 1, 0, 1, 1, 1, 1, // opcodes 48 to 55 1086 1, 1, 0, 1, 1, 1, 1, 1 ]; // opcodes 56 to 63 1087 1088 // 1089 // VST callbacks 1090 // 1091 extern(C) private nothrow 1092 { 1093 VstIntPtr dispatcherCallback(AEffect *effect, int opcode, int index, ptrdiff_t value, void *ptr, float opt) nothrow @nogc 1094 { 1095 VstIntPtr result = 0; 1096 1097 // Short-circuit inconsequential opcodes to gain speed 1098 if (cast(uint)opcode >= 64) 1099 return 0; 1100 if (opcodeShouldReturn0Immediately[opcode]) 1101 return 0; 1102 1103 ScopedForeignCallback!(true, true) scopedCallback; 1104 scopedCallback.enter(); 1105 1106 version(logVSTDispatcher) 1107 { 1108 char[128] buf; 1109 snprintf(buf.ptr, 128, "dispatcher effect %p opcode %d".ptr, effect, opcode); 1110 debugLog(buf.ptr); 1111 } 1112 1113 auto plugin = cast(VST2Client)(effect.object); 1114 result = plugin.dispatcher(opcode, index, value, ptr, opt); 1115 if (opcode == effClose) 1116 { 1117 destroyFree(plugin); 1118 } 1119 return result; 1120 } 1121 1122 // VST callback for DEPRECATED_process 1123 void processCallback(AEffect *effect, float **inputs, float **outputs, int sampleFrames) nothrow @nogc 1124 { 1125 FPControl fpctrl; 1126 fpctrl.initialize(); 1127 1128 auto plugin = cast(VST2Client)effect.object; 1129 plugin.process(inputs, outputs, sampleFrames); 1130 } 1131 1132 // VST callback for processReplacing 1133 void processReplacingCallback(AEffect *effect, float **inputs, float **outputs, int sampleFrames) nothrow @nogc 1134 { 1135 FPControl fpctrl; 1136 fpctrl.initialize(); 1137 1138 auto plugin = cast(VST2Client)effect.object; 1139 plugin.processReplacing(inputs, outputs, sampleFrames); 1140 } 1141 1142 // VST callback for processDoubleReplacing 1143 void processDoubleReplacingCallback(AEffect *effect, double **inputs, double **outputs, int sampleFrames) nothrow @nogc 1144 { 1145 FPControl fpctrl; 1146 fpctrl.initialize(); 1147 1148 auto plugin = cast(VST2Client)effect.object; 1149 plugin.processDoubleReplacing(inputs, outputs, sampleFrames); 1150 } 1151 1152 // VST callback for setParameter 1153 void setParameterCallback(AEffect *effect, int index, float parameter) nothrow @nogc 1154 { 1155 FPControl fpctrl; 1156 fpctrl.initialize(); 1157 1158 auto plugin = cast(VST2Client)effect.object; 1159 Client client = plugin._client; 1160 1161 if (!plugin.isValidParamIndex(index)) 1162 return; 1163 1164 client.setParameterFromHost(index, parameter); 1165 } 1166 1167 // VST callback for getParameter 1168 float getParameterCallback(AEffect *effect, int index) nothrow @nogc 1169 { 1170 FPControl fpctrl; 1171 fpctrl.initialize(); 1172 1173 auto plugin = cast(VST2Client)(effect.object); 1174 Client client = plugin._client; 1175 1176 if (!plugin.isValidParamIndex(index)) 1177 return 0.0f; 1178 1179 float value; 1180 value = client.param(index).getForHost(); 1181 return value; 1182 } 1183 } 1184 1185 /// Access to VST host from the VST client perspective. 1186 /// The IHostCommand subset is accessible from the plugin client with no knowledge of the format 1187 final class VSTHostFromClientPOV : IHostCommand 1188 { 1189 public: 1190 nothrow: 1191 @nogc: 1192 1193 this(HostCallbackFunction hostCallback, AEffect* effect) 1194 { 1195 _hostCallback = hostCallback; 1196 _effect = effect; 1197 } 1198 1199 /** 1200 * Deprecated: This call is Deprecated, but was added to support older hosts (like MaxMSP). 1201 * Plugins (VSTi2.0 thru VSTi2.3) call this to tell the host that the plugin is an instrument. 1202 */ 1203 void wantEvents() nothrow @nogc 1204 { 1205 callback(DEPRECATED_audioMasterWantMidi, 0, 1, null, 0); 1206 } 1207 1208 /// Request plugin window resize. 1209 override bool requestResize(int width, int height) nothrow @nogc 1210 { 1211 bool isAbletonLive = getDAW() == DAW.AbletonLive; // #DAW-specific 1212 if (canDo(HostCaps.SIZE_WINDOW) || isAbletonLive) 1213 { 1214 return (callback(audioMasterSizeWindow, width, height, null, 0.0f) != 0); 1215 } 1216 else 1217 return false; 1218 } 1219 1220 override void beginParamEdit(int paramIndex) nothrow @nogc 1221 { 1222 callback(audioMasterBeginEdit, paramIndex, 0, null, 0.0f); 1223 } 1224 1225 override void paramAutomate(int paramIndex, float value) nothrow @nogc 1226 { 1227 callback(audioMasterAutomate, paramIndex, 0, null, value); 1228 } 1229 1230 override void endParamEdit(int paramIndex) nothrow @nogc 1231 { 1232 callback(audioMasterEndEdit, paramIndex, 0, null, 0.0f); 1233 } 1234 1235 override DAW getDAW() nothrow @nogc 1236 { 1237 return identifyDAW(productString()); 1238 } 1239 1240 override PluginFormat getPluginFormat() 1241 { 1242 return PluginFormat.vst2; 1243 } 1244 1245 const(char)* vendorString() nothrow @nogc 1246 { 1247 int res = cast(int)callback(audioMasterGetVendorString, 0, 0, _vendorStringBuf.ptr, 0.0f); 1248 if (res == 1) 1249 { 1250 return _vendorStringBuf.ptr; 1251 } 1252 else 1253 return "unknown"; 1254 } 1255 1256 const(char)* productString() nothrow @nogc 1257 { 1258 int res = cast(int)callback(audioMasterGetProductString, 0, 0, _productStringBuf.ptr, 0.0f); 1259 if (res == 1) 1260 { 1261 // Force lowercase 1262 for (char* p = _productStringBuf.ptr; *p != '\0'; ++p) 1263 { 1264 if (*p >= 'A' && *p <= 'Z') 1265 *p += ('a' - 'A'); 1266 } 1267 return _productStringBuf.ptr; 1268 } 1269 else 1270 return "unknown"; 1271 } 1272 1273 /// Gets VSTTimeInfo structure, null if not all flags are supported 1274 TimeInfo getVSTTimeInfo(long fallbackTimeInSamples) nothrow @nogc 1275 { 1276 TimeInfo info; 1277 int filters = kVstTempoValid; 1278 VstTimeInfo* ti = cast(VstTimeInfo*) callback(audioMasterGetTime, 0, filters, null, 0); 1279 if (ti && ti.sampleRate > 0) 1280 { 1281 info.timeInSamples = cast(long)(0.5f + ti.samplePos); 1282 if ((ti.flags & kVstTempoValid) && ti.tempo > 0) 1283 info.tempo = ti.tempo; 1284 info.hostIsPlaying = (ti.flags & kVstTransportPlaying) != 0; 1285 } 1286 else 1287 { 1288 // probably a very simple host, fake time 1289 info.timeInSamples = fallbackTimeInSamples; 1290 } 1291 return info; 1292 } 1293 1294 /// Capabilities 1295 1296 enum HostCaps 1297 { 1298 SEND_VST_EVENTS, // Host supports send of Vst events to plug-in. 1299 SEND_VST_MIDI_EVENTS, // Host supports send of MIDI events to plug-in. 1300 SEND_VST_TIME_INFO, // Host supports send of VstTimeInfo to plug-in. 1301 RECEIVE_VST_EVENTS, // Host can receive Vst events from plug-in. 1302 RECEIVE_VST_MIDI_EVENTS, // Host can receive MIDI events from plug-in. 1303 REPORT_CONNECTION_CHANGES, // Host will indicates the plug-in when something change in plug-in´s routing/connections with suspend()/resume()/setSpeakerArrangement(). 1304 ACCEPT_IO_CHANGES, // Host supports ioChanged(). 1305 SIZE_WINDOW, // used by VSTGUI 1306 OFFLINE, // Host supports offline feature. 1307 OPEN_FILE_SELECTOR, // Host supports function openFileSelector(). 1308 CLOSE_FILE_SELECTOR, // Host supports function closeFileSelector(). 1309 START_STOP_PROCESS, // Host supports functions startProcess() and stopProcess(). 1310 SHELL_CATEGORY, // 'shell' handling via uniqueID. If supported by the Host and the Plug-in has the category kPlugCategShell 1311 SEND_VST_MIDI_EVENT_FLAG_IS_REALTIME, // Host supports flags for VstMidiEvent. 1312 SUPPLY_IDLE // ??? 1313 } 1314 1315 bool canDo(HostCaps caps) nothrow 1316 { 1317 const(char)* capsString = hostCapsString(caps); 1318 assert(capsString !is null); 1319 1320 // note: const is casted away here 1321 return callback(audioMasterCanDo, 0, 0, cast(void*)capsString, 0.0f) == 1; 1322 } 1323 1324 bool sendVstMidiEvent(VstEvent* event) 1325 { 1326 VstEvents events; 1327 memset(&events, 0, VstEvents.sizeof); 1328 events.numEvents = 1; 1329 events.events[0] = event; // PERF: could use the VLA in VstEvents to pass more at once. 1330 return callback(audioMasterProcessEvents, 0, 0, &events, 0.0f) == 1; 1331 } 1332 1333 private: 1334 1335 AEffect* _effect; 1336 HostCallbackFunction _hostCallback; 1337 char[65] _vendorStringBuf; 1338 char[96] _productStringBuf; 1339 int _vendorVersion; 1340 1341 VstIntPtr callback(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) nothrow @nogc 1342 { 1343 // Saves FP state 1344 FPControl fpctrl; 1345 fpctrl.initialize(); 1346 return _hostCallback(_effect, opcode, index, value, ptr, opt); 1347 } 1348 1349 static const(char)* hostCapsString(HostCaps caps) pure nothrow 1350 { 1351 switch (caps) 1352 { 1353 case HostCaps.SEND_VST_EVENTS: return "sendVstEvents"; 1354 case HostCaps.SEND_VST_MIDI_EVENTS: return "sendVstMidiEvent"; 1355 case HostCaps.SEND_VST_TIME_INFO: return "sendVstTimeInfo"; 1356 case HostCaps.RECEIVE_VST_EVENTS: return "receiveVstEvents"; 1357 case HostCaps.RECEIVE_VST_MIDI_EVENTS: return "receiveVstMidiEvent"; 1358 case HostCaps.REPORT_CONNECTION_CHANGES: return "reportConnectionChanges"; 1359 case HostCaps.ACCEPT_IO_CHANGES: return "acceptIOChanges"; 1360 case HostCaps.SIZE_WINDOW: return "sizeWindow"; 1361 case HostCaps.OFFLINE: return "offline"; 1362 case HostCaps.OPEN_FILE_SELECTOR: return "openFileSelector"; 1363 case HostCaps.CLOSE_FILE_SELECTOR: return "closeFileSelector"; 1364 case HostCaps.START_STOP_PROCESS: return "startStopProcess"; 1365 case HostCaps.SHELL_CATEGORY: return "shellCategory"; 1366 case HostCaps.SEND_VST_MIDI_EVENT_FLAG_IS_REALTIME: return "sendVstMidiEventFlagIsRealtime"; 1367 case HostCaps.SUPPLY_IDLE: return "supplyIdle"; 1368 default: 1369 assert(false); 1370 } 1371 } 1372 } 1373 1374 1375 /** Four Character Constant (for AEffect->uniqueID) */ 1376 private int CCONST(int a, int b, int c, int d) pure nothrow @nogc 1377 { 1378 return (a << 24) | (b << 16) | (c << 8) | (d << 0); 1379 } 1380 1381 struct IO 1382 { 1383 int inputs; /// number of input channels 1384 int outputs; /// number of output channels 1385 } 1386 1387 // 1388 // Message queue 1389 // 1390 1391 private: 1392 1393 /// A message for the audio thread. 1394 /// Intended to be passed from a non critical thread to the audio thread. 1395 struct AudioThreadMessage 1396 { 1397 enum Type 1398 { 1399 resetState, // reset plugin state, set samplerate and buffer size (samplerate = fParam, buffersize in frames = iParam) 1400 midi 1401 } 1402 1403 this(Type type_, int maxFrames_, float samplerate_, IO hostIO_, IO processingIO_) pure const nothrow @nogc 1404 { 1405 type = type_; 1406 maxFrames = maxFrames_; 1407 samplerate = samplerate_; 1408 hostIO = hostIO_; 1409 processingIO = processingIO_; 1410 } 1411 1412 Type type; 1413 int maxFrames; 1414 float samplerate; 1415 IO hostIO; 1416 IO processingIO; 1417 MidiMessage midiMessage; 1418 } 1419 1420 AudioThreadMessage makeMIDIMessage(MidiMessage midiMessage) pure nothrow @nogc 1421 { 1422 AudioThreadMessage msg; 1423 msg.type = AudioThreadMessage.Type.midi; 1424 msg.midiMessage = midiMessage; 1425 return msg; 1426 }