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