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 597 bool err; 598 presetBank.loadStateChunk(chunk, &err); 599 if (err) 600 return 0; // Chunk didn't parse 601 else 602 return 1; // success 603 } 604 } 605 606 case effProcessEvents: // opcode 25, "host usually call ProcessEvents just before calling ProcessReplacing" 607 VstEvents* pEvents = cast(VstEvents*) ptr; 608 if (pEvents != null) 609 { 610 VstEvent** allEvents = pEvents.events.ptr; 611 for (int i = 0; i < pEvents.numEvents; ++i) 612 { 613 VstEvent* pEvent = allEvents[i]; 614 if (pEvent) 615 { 616 if (pEvent.type == kVstMidiType) 617 { 618 VstMidiEvent* pME = cast(VstMidiEvent*) pEvent; 619 620 // Enqueue midi message to be processed by the audio thread. 621 // Note that not all information is kept, some is discarded like in IPlug. 622 MidiMessage msg = MidiMessage(pME.deltaFrames, 623 pME.midiData[0], 624 pME.midiData[1], 625 pME.midiData[2]); 626 _messageQueue.pushBack(makeMIDIMessage(msg)); 627 } 628 else 629 { 630 // FUTURE handle sysex 631 } 632 } 633 } 634 return 1; 635 } 636 return 0; 637 638 case effCanBeAutomated: // opcode 26 639 { 640 if (!isValidParamIndex(index)) 641 return 0; 642 if (_client.param(index).isAutomatable) 643 return 1; 644 return 0; 645 } 646 647 case effString2Parameter: // opcode 27 648 { 649 if (!isValidParamIndex(index)) 650 return 0; 651 652 if (ptr == null) 653 return 0; 654 655 // MAYDO: Sounds a bit insufficient? Will return 0 in case of error. 656 // also it will run into C locale problems. 657 double parsed = atof(cast(char*)ptr); 658 _client.setParameterFromHost(index, parsed); 659 return 1; 660 } 661 662 case DEPRECATED_effGetNumProgramCategories: // opcode 28 663 return 1; // no real program categories 664 665 case effGetProgramNameIndexed: // opcode 29 666 { 667 char* p = cast(char*)ptr; 668 if (p !is null) 669 { 670 PresetBank bank = _client.presetBank(); 671 if (!bank.isValidPresetIndex(index)) 672 return 0; 673 const(char)[] name = bank.preset(index).name(); 674 stringNCopy(p, 24, name); 675 return (name.length > 0) ? 1 : 0; 676 } 677 else 678 return 0; 679 } 680 681 case effGetInputProperties: // opcode 33 682 { 683 if (ptr == null) 684 return 0; 685 686 if (!isValidInputIndex(index)) 687 return 0; 688 689 VstPinProperties* pp = cast(VstPinProperties*) ptr; 690 pp.flags = kVstPinIsActive; 691 692 if ( (index % 2) == 0 && index < _maxInputs) 693 pp.flags |= kVstPinIsStereo; 694 695 sprintf(pp.label.ptr, "Input %d", index); 696 return 1; 697 } 698 699 case effGetOutputProperties: // opcode 34 700 { 701 if (ptr == null) 702 return 0; 703 704 if (!isValidOutputIndex(index)) 705 return 0; 706 707 VstPinProperties* pp = cast(VstPinProperties*) ptr; 708 pp.flags = kVstPinIsActive; 709 710 if ( (index % 2) == 0 && index < _maxOutputs) 711 pp.flags |= kVstPinIsStereo; 712 713 sprintf(pp.label.ptr, "Output %d", index); 714 return 1; 715 } 716 717 case effGetPlugCategory: // opcode 35 718 if ( _client.isSynth() ) 719 return kPlugCategSynth; 720 else 721 return kPlugCategEffect; 722 723 case effSetSpeakerArrangement: // opcode 42 724 { 725 VstSpeakerArrangement* pInputArr = cast(VstSpeakerArrangement*) value; 726 VstSpeakerArrangement* pOutputArr = cast(VstSpeakerArrangement*) ptr; 727 if (pInputArr !is null && pOutputArr !is null ) 728 { 729 int numInputs = pInputArr.numChannels; 730 int numOutputs = pOutputArr.numChannels; 731 chooseIOArrangement(numInputs, numOutputs); 732 sendResetMessage(); 733 return 0; // MAYDO: this looks very wrong 734 } 735 return 1; 736 } 737 738 case effSetBypass: // opcode 44 739 // Unfortunately, we were unable to find any VST2 hsot that would use effSetBypass 740 // So disable it since it can't be out untested. 741 return 0; 742 743 case effGetEffectName: // opcode 45 744 { 745 char* p = cast(char*)ptr; 746 if (p !is null) 747 { 748 stringNCopy(p, 32, _client.pluginName()); 749 return 1; 750 } 751 return 0; 752 } 753 754 case effGetVendorString: // opcode 47 755 { 756 char* p = cast(char*)ptr; 757 if (p !is null) 758 { 759 stringNCopy(p, 64, _client.vendorName()); 760 return 1; 761 } 762 return 0; 763 } 764 765 case effGetProductString: // opcode 48 766 { 767 char* p = cast(char*)ptr; 768 if (p !is null) 769 { 770 _client.getPluginFullName(p, 64); 771 return 1; 772 } 773 return 0; 774 } 775 776 case effCanDo: // opcode 51 777 { 778 char* str = cast(char*)ptr; 779 if (str is null) 780 return 0; 781 782 if (strcmp(str, "receiveVstTimeInfo") == 0) 783 return 1; 784 785 // Unable to find a host that will actually support it. 786 // Have to disable it to avoid being untested. 787 /* 788 if (strcmp(str, "bypass") == 0) 789 { 790 return _client.hasBypass() ? 1 : 0; 791 } 792 */ 793 794 if (_client.sendsMIDI()) 795 { 796 if (strcmp(str, "sendVstEvents") == 0) 797 return 1; 798 if (strcmp(str, "sendVstMidiEvent") == 0) 799 return 1; 800 if (strcmp(str, "sendVstMidiEvents") == 0) 801 return 1; 802 } 803 804 if (_client.receivesMIDI()) 805 { 806 if (strcmp(str, "receiveVstEvents") == 0) 807 return 1; 808 809 // Issue #198, Bitwig Studio need this 810 if (strcmp(str, "receiveVstMidiEvent") == 0) 811 return 1; 812 813 if (strcmp(str, "receiveVstMidiEvents") == 0) 814 return 1; 815 } 816 817 return 0; 818 } 819 820 case effGetVstVersion: // opcode 58 821 return 2400; // version 2.4 822 823 default: 824 return 0; // unknown opcode, should never happen 825 } 826 } 827 828 // 829 // Processing buffers and callbacks 830 // 831 832 // Resize copy buffers according to maximum block size. 833 void resizeScratchBuffers(int nFrames) nothrow @nogc 834 { 835 for (int i = 0; i < _maxInputs; ++i) 836 _inputScratchBuffer[i].resize(nFrames); 837 838 for (int i = 0; i < _maxOutputs; ++i) 839 _outputScratchBuffer[i].resize(nFrames); 840 841 _zeroesBuffer.resize(nFrames); 842 _zeroesBuffer.fill(0); 843 } 844 845 846 void processMessages() nothrow @nogc 847 { 848 // Race condition here. 849 // Being a tryPop, there is a tiny chance that we miss a message from the queue. 850 // Thankfully it isn't that bad: 851 // - we are going to read it next buffer 852 // - not clearing the state for a buffer duration does no harm 853 // - plugin is initialized first with the maximum amount of input and outputs 854 // so missing such a message isn't that bad: the audio callback will have some outputs that are untouched 855 // (a third thread might start a collect while the UI thread takes the queue lock) which is another unlikely race condition. 856 // Perhaps it's the one to favor, I don't know. 857 // TODO: Objectionable decision, for MIDI input, think about impact. 858 859 AudioThreadMessage msg = void; 860 while(_messageQueue.tryPopFront(msg)) 861 { 862 final switch(msg.type) with (AudioThreadMessage.Type) 863 { 864 case resetState: 865 resizeScratchBuffers(msg.maxFrames); 866 867 _hostIOFromAudioThread = msg.hostIO; 868 _processingIOFromAudioThread = msg.processingIO; 869 870 _client.resetFromHost(msg.samplerate, 871 msg.maxFrames, 872 _processingIOFromAudioThread.inputs, 873 _processingIOFromAudioThread.outputs); 874 break; 875 876 case midi: 877 _client.enqueueMIDIFromHost(msg.midiMessage); 878 } 879 } 880 } 881 882 void process(float **inputs, float **outputs, int sampleFrames) nothrow @nogc 883 { 884 processMessages(); 885 int hostInputs = _hostIOFromAudioThread.inputs; 886 int hostOutputs = _hostIOFromAudioThread.outputs; 887 int usedInputs = _processingIOFromAudioThread.inputs; 888 int usedOutputs = _processingIOFromAudioThread.outputs; 889 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 890 891 // Not sure if the hosts would support an overwriting of these pointers, so copy them 892 for (int i = 0; i < usedInputs; ++i) 893 { 894 // Points to zeros if the host provides a buffer, or the host buffer otherwise. 895 // Note: all input channels point on same buffer, but it's ok since input channels are const 896 _inputPointers[i] = (i < hostInputs) ? inputs[i] : _zeroesBuffer.ptr; 897 } 898 899 for (int i = 0; i < usedOutputs; ++i) 900 { 901 _outputPointers[i] = _outputScratchBuffer[i].ptr; 902 } 903 904 clearMidiOutBuffer(); 905 _client.processAudioFromHost(_inputPointers[0..usedInputs], 906 _outputPointers[0..usedOutputs], 907 sampleFrames, 908 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 909 _samplesAlreadyProcessed += sampleFrames; 910 911 // accumulate on available host output channels 912 for (int i = 0; i < minOutputs; ++i) 913 { 914 float* source = _outputScratchBuffer[i].ptr; 915 float* dest = outputs[i]; 916 for (int f = 0; f < sampleFrames; ++f) 917 dest[f] += source[f]; 918 } 919 sendMidiEvents(); 920 } 921 922 void processReplacing(float **inputs, float **outputs, int sampleFrames) nothrow @nogc 923 { 924 processMessages(); 925 int hostInputs = _hostIOFromAudioThread.inputs; 926 int hostOutputs = _hostIOFromAudioThread.outputs; 927 int usedInputs = _processingIOFromAudioThread.inputs; 928 int usedOutputs = _processingIOFromAudioThread.outputs; 929 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 930 931 // Some hosts (Live, Orion, and others) send identical input and output pointers. 932 // This is actually legal in VST. 933 // We copy them to a scratch buffer to keep the constness guarantee of input buffers. 934 for (int i = 0; i < usedInputs; ++i) 935 { 936 if (i < hostInputs) 937 { 938 float* source = inputs[i]; 939 float* dest = _inputScratchBuffer[i].ptr; 940 dest[0..sampleFrames] = source[0..sampleFrames]; 941 _inputPointers[i] = dest; 942 } 943 else 944 { 945 _inputPointers[i] = _zeroesBuffer.ptr; 946 } 947 } 948 949 for (int i = 0; i < usedOutputs; ++i) 950 { 951 if (i < hostOutputs) 952 _outputPointers[i] = outputs[i]; 953 else 954 _outputPointers[i] = _outputScratchBuffer[i].ptr; // dummy output 955 } 956 957 clearMidiOutBuffer(); 958 _client.processAudioFromHost(_inputPointers[0..usedInputs], 959 _outputPointers[0..usedOutputs], 960 sampleFrames, 961 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 962 _samplesAlreadyProcessed += sampleFrames; 963 964 // Fills remaining host channels (if any) with zeroes 965 for (int i = minOutputs; i < hostOutputs; ++i) 966 { 967 float* dest = outputs[i]; 968 for (int f = 0; f < sampleFrames; ++f) 969 dest[f] = 0; 970 } 971 sendMidiEvents(); 972 } 973 974 void processDoubleReplacing(double **inputs, double **outputs, int sampleFrames) nothrow @nogc 975 { 976 processMessages(); 977 int hostInputs = _hostIOFromAudioThread.inputs; 978 int hostOutputs = _hostIOFromAudioThread.outputs; 979 int usedInputs = _processingIOFromAudioThread.inputs; 980 int usedOutputs = _processingIOFromAudioThread.outputs; 981 int minOutputs = (usedOutputs < hostOutputs) ? usedOutputs : hostOutputs; 982 983 // Existing inputs gets converted to float 984 // Non-connected inputs are zeroes 985 // 986 // Note about converting double to float: 987 // on both white noise and sinusoids, a conversion from 988 // double to float yield a relative RMS difference of 989 // -152dB. It would really extraordinary if anyone can tell 990 // the difference, as -110 dB RMS already exercise the limits 991 // of audition. 992 for (int i = 0; i < usedInputs; ++i) 993 { 994 if (i < hostInputs) 995 { 996 double* source = inputs[i]; 997 float* dest = _inputScratchBuffer[i].ptr; 998 for (int f = 0; f < sampleFrames; ++f) 999 dest[f] = source[f]; 1000 _inputPointers[i] = dest; 1001 } 1002 else 1003 _inputPointers[i] = _zeroesBuffer.ptr; 1004 } 1005 1006 for (int i = 0; i < usedOutputs; ++i) 1007 { 1008 _outputPointers[i] = _outputScratchBuffer[i].ptr; 1009 } 1010 1011 clearMidiOutBuffer(); 1012 _client.processAudioFromHost(_inputPointers[0..usedInputs], 1013 _outputPointers[0..usedOutputs], 1014 sampleFrames, 1015 _host.getVSTTimeInfo(_samplesAlreadyProcessed)); 1016 _samplesAlreadyProcessed += sampleFrames; 1017 1018 // Converts back to double on available host output channels 1019 for (int i = 0; i < minOutputs; ++i) 1020 { 1021 float* source = _outputScratchBuffer[i].ptr; 1022 double* dest = outputs[i]; 1023 for (int f = 0; f < sampleFrames; ++f) 1024 dest[f] = cast(double)source[f]; 1025 } 1026 1027 // Fills remaining host channels (if any) with zeroes 1028 for (int i = minOutputs; i < hostOutputs; ++i) 1029 { 1030 double* dest = outputs[i]; 1031 for (int f = 0; f < sampleFrames; ++f) 1032 dest[f] = 0; 1033 } 1034 sendMidiEvents(); 1035 } 1036 1037 void clearMidiOutBuffer() 1038 { 1039 if (!_client.sendsMIDI()) 1040 return; 1041 _client.clearAccumulatedOutputMidiMessages(); 1042 } 1043 1044 void sendMidiEvents() 1045 { 1046 if (!_client.sendsMIDI()) 1047 return; 1048 1049 const(MidiMessage)[] messages = _client.getAccumulatedOutputMidiMessages(); 1050 foreach(MidiMessage msg; messages) 1051 { 1052 VstMidiEvent event; 1053 event.type = kVstMidiType; 1054 event.byteSize = VstMidiEvent.sizeof; 1055 event.deltaFrames = msg.offset; 1056 event.flags = 0; // not played live, doesn't need specially high-priority 1057 event.noteLength = 0; // not available 1058 event.noteOffset = 0; // not available 1059 event.midiData[0] = 0; 1060 event.midiData[1] = 0; 1061 event.midiData[2] = 0; 1062 event.midiData[3] = 0; 1063 1064 int written = msg.toBytes(cast(ubyte*)(event.midiData.ptr), 3); 1065 if (written == 0) 1066 { 1067 // nothing written, do not send this message. 1068 // which means we must support more message types. 1069 continue; 1070 } 1071 1072 event.detune = 0; 1073 event.noteOffVelocity = 0; // why it's here? 1074 event.reserved1 = 0; 1075 event.reserved2 = 0; 1076 _host.sendVstMidiEvent(cast(VstEvent*)&event); 1077 } 1078 } 1079 } 1080 1081 // This look-up table speed-up unimplemented opcodes 1082 private static immutable ubyte[64] opcodeShouldReturn0Immediately = 1083 [ 1, 0, 0, 0, 0, 0, 0, 0, // opcodes 0 to 7 1084 0, 1, 0, 0, 0, 0, 0, 0, // opcodes 8 to 15 1085 1, 1, 1, 1, 1, 1, 0, 0, // opcodes 16 to 23 1086 0, 0, 0, 0, 0, 0, 1, 1, // opcodes 24 to 31 1087 1, 0, 0, 0, 1, 1, 1, 1, // opcodes 32 to 39 1088 1, 1, 0, 1, 0, 0, 1, 0, // opcodes 40 to 47 1089 0, 1, 1, 0, 1, 1, 1, 1, // opcodes 48 to 55 1090 1, 1, 0, 1, 1, 1, 1, 1 ]; // opcodes 56 to 63 1091 1092 // 1093 // VST callbacks 1094 // 1095 extern(C) private nothrow 1096 { 1097 VstIntPtr dispatcherCallback(AEffect *effect, int opcode, int index, ptrdiff_t value, void *ptr, float opt) nothrow @nogc 1098 { 1099 VstIntPtr result = 0; 1100 1101 // Short-circuit inconsequential opcodes to gain speed 1102 if (cast(uint)opcode >= 64) 1103 return 0; 1104 if (opcodeShouldReturn0Immediately[opcode]) 1105 return 0; 1106 1107 ScopedForeignCallback!(true, true) scopedCallback; 1108 scopedCallback.enter(); 1109 1110 version(logVSTDispatcher) 1111 { 1112 char[128] buf; 1113 snprintf(buf.ptr, 128, "dispatcher effect %p opcode %d".ptr, effect, opcode); 1114 debugLog(buf.ptr); 1115 } 1116 1117 auto plugin = cast(VST2Client)(effect.object); 1118 result = plugin.dispatcher(opcode, index, value, ptr, opt); 1119 if (opcode == effClose) 1120 { 1121 destroyFree(plugin); 1122 } 1123 return result; 1124 } 1125 1126 // VST callback for DEPRECATED_process 1127 void processCallback(AEffect *effect, float **inputs, float **outputs, int sampleFrames) nothrow @nogc 1128 { 1129 FPControl fpctrl; 1130 fpctrl.initialize(); 1131 1132 auto plugin = cast(VST2Client)effect.object; 1133 plugin.process(inputs, outputs, sampleFrames); 1134 } 1135 1136 // VST callback for processReplacing 1137 void processReplacingCallback(AEffect *effect, float **inputs, float **outputs, int sampleFrames) nothrow @nogc 1138 { 1139 FPControl fpctrl; 1140 fpctrl.initialize(); 1141 1142 auto plugin = cast(VST2Client)effect.object; 1143 plugin.processReplacing(inputs, outputs, sampleFrames); 1144 } 1145 1146 // VST callback for processDoubleReplacing 1147 void processDoubleReplacingCallback(AEffect *effect, double **inputs, double **outputs, int sampleFrames) nothrow @nogc 1148 { 1149 FPControl fpctrl; 1150 fpctrl.initialize(); 1151 1152 auto plugin = cast(VST2Client)effect.object; 1153 plugin.processDoubleReplacing(inputs, outputs, sampleFrames); 1154 } 1155 1156 // VST callback for setParameter 1157 void setParameterCallback(AEffect *effect, int index, float parameter) nothrow @nogc 1158 { 1159 FPControl fpctrl; 1160 fpctrl.initialize(); 1161 1162 auto plugin = cast(VST2Client)effect.object; 1163 Client client = plugin._client; 1164 1165 if (!plugin.isValidParamIndex(index)) 1166 return; 1167 1168 client.setParameterFromHost(index, parameter); 1169 } 1170 1171 // VST callback for getParameter 1172 float getParameterCallback(AEffect *effect, int index) nothrow @nogc 1173 { 1174 FPControl fpctrl; 1175 fpctrl.initialize(); 1176 1177 auto plugin = cast(VST2Client)(effect.object); 1178 Client client = plugin._client; 1179 1180 if (!plugin.isValidParamIndex(index)) 1181 return 0.0f; 1182 1183 float value; 1184 value = client.param(index).getForHost(); 1185 return value; 1186 } 1187 } 1188 1189 /// Access to VST host from the VST client perspective. 1190 /// The IHostCommand subset is accessible from the plugin client with no knowledge of the format 1191 final class VSTHostFromClientPOV : IHostCommand 1192 { 1193 public: 1194 nothrow: 1195 @nogc: 1196 1197 this(HostCallbackFunction hostCallback, AEffect* effect) 1198 { 1199 _hostCallback = hostCallback; 1200 _effect = effect; 1201 } 1202 1203 /** 1204 * Deprecated: This call is Deprecated, but was added to support older hosts (like MaxMSP). 1205 * Plugins (VSTi2.0 thru VSTi2.3) call this to tell the host that the plugin is an instrument. 1206 */ 1207 void wantEvents() nothrow @nogc 1208 { 1209 callback(DEPRECATED_audioMasterWantMidi, 0, 1, null, 0); 1210 } 1211 1212 /// Request plugin window resize. 1213 override bool requestResize(int width, int height) nothrow @nogc 1214 { 1215 bool isAbletonLive = getDAW() == DAW.AbletonLive; // #DAW-specific 1216 if (canDo(HostCaps.SIZE_WINDOW) || isAbletonLive) 1217 { 1218 return (callback(audioMasterSizeWindow, width, height, null, 0.0f) != 0); 1219 } 1220 else 1221 return false; 1222 } 1223 1224 override bool notifyResized() 1225 { 1226 return false; 1227 } 1228 1229 override void beginParamEdit(int paramIndex) nothrow @nogc 1230 { 1231 callback(audioMasterBeginEdit, paramIndex, 0, null, 0.0f); 1232 } 1233 1234 override void paramAutomate(int paramIndex, float value) nothrow @nogc 1235 { 1236 callback(audioMasterAutomate, paramIndex, 0, null, value); 1237 } 1238 1239 override void endParamEdit(int paramIndex) nothrow @nogc 1240 { 1241 callback(audioMasterEndEdit, paramIndex, 0, null, 0.0f); 1242 } 1243 1244 override DAW getDAW() nothrow @nogc 1245 { 1246 return identifyDAW(productString()); 1247 } 1248 1249 override PluginFormat getPluginFormat() 1250 { 1251 return PluginFormat.vst2; 1252 } 1253 1254 const(char)* vendorString() nothrow @nogc 1255 { 1256 int res = cast(int)callback(audioMasterGetVendorString, 0, 0, _vendorStringBuf.ptr, 0.0f); 1257 if (res == 1) 1258 { 1259 return _vendorStringBuf.ptr; 1260 } 1261 else 1262 return "unknown"; 1263 } 1264 1265 const(char)* productString() nothrow @nogc 1266 { 1267 int res = cast(int)callback(audioMasterGetProductString, 0, 0, _productStringBuf.ptr, 0.0f); 1268 if (res == 1) 1269 { 1270 // Force lowercase 1271 for (char* p = _productStringBuf.ptr; *p != '\0'; ++p) 1272 { 1273 if (*p >= 'A' && *p <= 'Z') 1274 *p += ('a' - 'A'); 1275 } 1276 return _productStringBuf.ptr; 1277 } 1278 else 1279 return "unknown"; 1280 } 1281 1282 /// Gets VSTTimeInfo structure, null if not all flags are supported 1283 TimeInfo getVSTTimeInfo(long fallbackTimeInSamples) nothrow @nogc 1284 { 1285 TimeInfo info; 1286 int filters = kVstTempoValid; 1287 VstTimeInfo* ti = cast(VstTimeInfo*) callback(audioMasterGetTime, 0, filters, null, 0); 1288 if (ti && ti.sampleRate > 0) 1289 { 1290 info.timeInSamples = cast(long)(0.5f + ti.samplePos); 1291 if ((ti.flags & kVstTempoValid) && ti.tempo > 0) 1292 info.tempo = ti.tempo; 1293 info.hostIsPlaying = (ti.flags & kVstTransportPlaying) != 0; 1294 } 1295 else 1296 { 1297 // probably a very simple host, fake time 1298 info.timeInSamples = fallbackTimeInSamples; 1299 } 1300 return info; 1301 } 1302 1303 /// Capabilities 1304 1305 enum HostCaps 1306 { 1307 SEND_VST_EVENTS, // Host supports send of Vst events to plug-in. 1308 SEND_VST_MIDI_EVENTS, // Host supports send of MIDI events to plug-in. 1309 SEND_VST_TIME_INFO, // Host supports send of VstTimeInfo to plug-in. 1310 RECEIVE_VST_EVENTS, // Host can receive Vst events from plug-in. 1311 RECEIVE_VST_MIDI_EVENTS, // Host can receive MIDI events from plug-in. 1312 REPORT_CONNECTION_CHANGES, // Host will indicates the plug-in when something change in plug-in´s routing/connections with suspend()/resume()/setSpeakerArrangement(). 1313 ACCEPT_IO_CHANGES, // Host supports ioChanged(). 1314 SIZE_WINDOW, // used by VSTGUI 1315 OFFLINE, // Host supports offline feature. 1316 OPEN_FILE_SELECTOR, // Host supports function openFileSelector(). 1317 CLOSE_FILE_SELECTOR, // Host supports function closeFileSelector(). 1318 START_STOP_PROCESS, // Host supports functions startProcess() and stopProcess(). 1319 SHELL_CATEGORY, // 'shell' handling via uniqueID. If supported by the Host and the Plug-in has the category kPlugCategShell 1320 SEND_VST_MIDI_EVENT_FLAG_IS_REALTIME, // Host supports flags for VstMidiEvent. 1321 SUPPLY_IDLE // ??? 1322 } 1323 1324 bool canDo(HostCaps caps) nothrow 1325 { 1326 const(char)* capsString = hostCapsString(caps); 1327 assert(capsString !is null); 1328 1329 // note: const is casted away here 1330 return callback(audioMasterCanDo, 0, 0, cast(void*)capsString, 0.0f) == 1; 1331 } 1332 1333 bool sendVstMidiEvent(VstEvent* event) 1334 { 1335 VstEvents events; 1336 memset(&events, 0, VstEvents.sizeof); 1337 events.numEvents = 1; 1338 events.events[0] = event; // PERF: could use the VLA in VstEvents to pass more at once. 1339 return callback(audioMasterProcessEvents, 0, 0, &events, 0.0f) == 1; 1340 } 1341 1342 private: 1343 1344 AEffect* _effect; 1345 HostCallbackFunction _hostCallback; 1346 char[65] _vendorStringBuf; 1347 char[96] _productStringBuf; 1348 int _vendorVersion; 1349 1350 VstIntPtr callback(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) nothrow @nogc 1351 { 1352 // Saves FP state 1353 FPControl fpctrl; 1354 fpctrl.initialize(); 1355 return _hostCallback(_effect, opcode, index, value, ptr, opt); 1356 } 1357 1358 static const(char)* hostCapsString(HostCaps caps) pure nothrow 1359 { 1360 switch (caps) 1361 { 1362 case HostCaps.SEND_VST_EVENTS: return "sendVstEvents"; 1363 case HostCaps.SEND_VST_MIDI_EVENTS: return "sendVstMidiEvent"; 1364 case HostCaps.SEND_VST_TIME_INFO: return "sendVstTimeInfo"; 1365 case HostCaps.RECEIVE_VST_EVENTS: return "receiveVstEvents"; 1366 case HostCaps.RECEIVE_VST_MIDI_EVENTS: return "receiveVstMidiEvent"; 1367 case HostCaps.REPORT_CONNECTION_CHANGES: return "reportConnectionChanges"; 1368 case HostCaps.ACCEPT_IO_CHANGES: return "acceptIOChanges"; 1369 case HostCaps.SIZE_WINDOW: return "sizeWindow"; 1370 case HostCaps.OFFLINE: return "offline"; 1371 case HostCaps.OPEN_FILE_SELECTOR: return "openFileSelector"; 1372 case HostCaps.CLOSE_FILE_SELECTOR: return "closeFileSelector"; 1373 case HostCaps.START_STOP_PROCESS: return "startStopProcess"; 1374 case HostCaps.SHELL_CATEGORY: return "shellCategory"; 1375 case HostCaps.SEND_VST_MIDI_EVENT_FLAG_IS_REALTIME: return "sendVstMidiEventFlagIsRealtime"; 1376 case HostCaps.SUPPLY_IDLE: return "supplyIdle"; 1377 default: 1378 assert(false); 1379 } 1380 } 1381 } 1382 1383 1384 /** Four Character Constant (for AEffect->uniqueID) */ 1385 private int CCONST(int a, int b, int c, int d) pure nothrow @nogc 1386 { 1387 return (a << 24) | (b << 16) | (c << 8) | (d << 0); 1388 } 1389 1390 struct IO 1391 { 1392 int inputs; /// number of input channels 1393 int outputs; /// number of output channels 1394 } 1395 1396 // 1397 // Message queue 1398 // 1399 1400 private: 1401 1402 /// A message for the audio thread. 1403 /// Intended to be passed from a non critical thread to the audio thread. 1404 struct AudioThreadMessage 1405 { 1406 enum Type 1407 { 1408 resetState, // reset plugin state, set samplerate and buffer size (samplerate = fParam, buffersize in frames = iParam) 1409 midi 1410 } 1411 1412 this(Type type_, int maxFrames_, float samplerate_, IO hostIO_, IO processingIO_) pure const nothrow @nogc 1413 { 1414 type = type_; 1415 maxFrames = maxFrames_; 1416 samplerate = samplerate_; 1417 hostIO = hostIO_; 1418 processingIO = processingIO_; 1419 } 1420 1421 Type type; 1422 int maxFrames; 1423 float samplerate; 1424 IO hostIO; 1425 IO processingIO; 1426 MidiMessage midiMessage; 1427 } 1428 1429 AudioThreadMessage makeMIDIMessage(MidiMessage midiMessage) pure nothrow @nogc 1430 { 1431 AudioThreadMessage msg; 1432 msg.type = AudioThreadMessage.Type.midi; 1433 msg.midiMessage = midiMessage; 1434 return msg; 1435 }