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