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