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