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