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