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