1 //-----------------------------------------------------------------------------
2 // LICENSE
3 // (c) 2005, Steinberg Media Technologies GmbH, All Rights Reserved
4 // (c) 2018-2021, Guillaume Piolat (contact@auburnsounds.com)
5 //-----------------------------------------------------------------------------
6 //
7 // This Software Development Kit is licensed under the terms of the General
8 // Public License (GPL) Version 3.
9 //
10 // Details of that license can be found at: www.gnu.org/licenses/gpl-3.0.html
11 //-----------------------------------------------------------------------------
12 module dplug.vst3.client;
13 
14 version(VST3):
15 
16 import core.atomic;
17 import core.stdc.stdlib: free;
18 import core.stdc.string: strcmp;
19 
20 import dplug.client.client;
21 import dplug.client.params;
22 import dplug.client.graphics;
23 import dplug.client.daw;
24 import dplug.client.midi;
25 
26 import dplug.core.nogc;
27 import dplug.core.sync;
28 import dplug.core.vec;
29 import dplug.core.runtime;
30 import dplug.vst3.ftypes;
31 import dplug.vst3.ivstaudioprocessor;
32 import dplug.vst3.ivsteditcontroller;
33 import dplug.vst3.iplugview;
34 import dplug.vst3.ivstcomponent;
35 import dplug.vst3.ipluginbase;
36 import dplug.vst3.ibstream;
37 import dplug.vst3.ivstunit;
38 
39 //debug = logVST3Client;
40 
41 
42 // Note: the VST3 client assumes shared memory
43 class VST3Client : IAudioProcessor, IComponent, IEditController, IEditController2, IUnitInfo
44 {
45 public:
46 nothrow:
47 @nogc:
48 
49     this(Client client)
50     {
51         debug(logVST3Client) debugLog(">VST3Client.this()");
52         debug(logVST3Client) scope(exit) debugLog("<VST3Client.this()");
53         _client = client;
54         _hostCommand = mallocNew!VST3HostCommand(this);
55         _client.setHostCommand(_hostCommand);
56 
57         // if no preset, pretend to be a continuous parameter
58         _presetStepCount = _client.presetBank.numPresets() - 1;
59         if (_presetStepCount < 0) _presetStepCount = 0;
60 
61         _maxInputs = client.maxInputs();
62         _inputScratchBuffers = mallocSlice!(Vec!float)(_maxInputs);
63         for (int i = 0; i < _maxInputs; ++i)
64             _inputScratchBuffers[i] = makeVec!float();
65     }
66 
67     ~this()
68     {
69         debug(logVST3Client) debugLog(">VST3Client.~this()");
70         debug(logVST3Client) scope(exit) debugLog("<VST3Client.~this()");
71         destroyFree(_client);
72         _client = null;
73 
74         for (int i = 0; i < _maxInputs; ++i)
75             _inputScratchBuffers[i].destroy();
76         _inputScratchBuffers.freeSlice();
77 
78         _zeroesBuffer.reallocBuffer(0);
79         _outputScratchBuffer.reallocBuffer(0);
80 
81         destroyFree(_hostCommand);
82         _hostCommand = null;
83 
84         _inputPointers.reallocBuffer(0);
85         _outputPointers.reallocBuffer(0);
86     }
87 
88     // Implements all COM interfaces needed
89     mixin QUERY_INTERFACE_SPECIAL_CASE_IUNKNOWN!(IAudioProcessor,
90                                                  IComponent,
91                                                  IEditController,
92                                                  IEditController2,
93                                                  IPluginBase,
94                                                  IUnitInfo);
95 
96     mixin IMPLEMENT_REFCOUNT;
97 
98 
99     // Implements IPluginBase
100 
101     /** The host passes a number of interfaces as context to initialize the Plug-in class.
102     @note Extensive memory allocations etc. should be performed in this method rather than in the class' constructor!
103     If the method does NOT return kResultOk, the object is released immediately. In this case terminate is not called! */
104     extern(Windows) override tresult initialize(FUnknown context)
105     {
106         debug(logVST3Client) debugLog(">initialize()".ptr);
107         debug(logVST3Client) scope(exit) debugLog("<initialize()".ptr);
108 
109         setHostApplication(context);
110 
111         // Create buses
112         int maxInputs = _client.maxInputs();
113         int maxOutputs = _client.maxOutputs();
114         bool receivesMIDI = _client.receivesMIDI();
115         bool sendsMIDI = _client.sendsMIDI();
116 
117         _audioInputs = makeVec!Bus;
118         _audioOutputs = makeVec!Bus;
119         _eventInputs = makeVec!Bus;
120 
121         _sampleRate = -42.0f; // so that a latency change is sent at next `setupProcessing`
122 
123         if (maxInputs)
124         {
125             Bus busAudioIn;
126             busAudioIn.active = true;
127             busAudioIn.speakerArrangement = getSpeakerArrangement(maxInputs);
128             with(busAudioIn.info)
129             {
130                 mediaType = kAudio;
131                 direction = kInput;
132                 channelCount = maxInputs;
133                 setName("Audio Input"w);
134                 busType = kMain;
135                 flags = BusInfo.BusFlags.kDefaultActive;
136             }
137             _audioInputs.pushBack(busAudioIn);
138         }
139 
140         if (maxOutputs)
141         {
142             Bus busAudioOut;
143             busAudioOut.active = true;
144             busAudioOut.speakerArrangement = getSpeakerArrangement(maxOutputs);
145             with(busAudioOut.info)
146             {
147                 mediaType = kAudio;
148                 direction = kOutput;
149                 channelCount = maxOutputs;
150                 setName("Audio Output"w);
151                 busType = kMain;
152                 flags = BusInfo.BusFlags.kDefaultActive;
153             }
154             _audioOutputs.pushBack(busAudioOut);
155         }
156 
157         if (receivesMIDI)
158         {
159             Bus busEventsIn;
160             busEventsIn.active = true;
161             busEventsIn.speakerArrangement = 0; // whatever
162             with(busEventsIn.info)
163             {
164                 mediaType = kEvent;
165                 direction = kInput;
166                 channelCount = 1;
167                 setName("MIDI Input"w);
168                 busType = kMain;
169                 flags = BusInfo.BusFlags.kDefaultActive;
170             }
171             _eventInputs.pushBack(busEventsIn);
172         }
173 
174         if (sendsMIDI)
175         {
176             Bus busEventsOut;
177             busEventsOut.active = true;
178             busEventsOut.speakerArrangement = 0; // whatever
179             with(busEventsOut.info)
180             {
181                 mediaType = kEvent;
182                 direction = kOutput;
183                 channelCount = 1;
184                 setName("MIDI Output"w);
185                 busType = kMain;
186                 flags = BusInfo.BusFlags.kDefaultActive;
187             }
188             _eventOutputs.pushBack(busEventsOut);
189         }
190 
191         return kResultOk;
192     }
193 
194     /** This function is called before the Plug-in is unloaded and can be used for
195     cleanups. You have to release all references to any host application interfaces. */
196     extern(Windows) override tresult terminate()
197     {
198         debug(logVST3Client) debugLog("terminate()".ptr);
199         debug(logVST3Client) scope(exit) debugLog("terminate()".ptr);
200         if (_hostApplication !is null)
201         {
202             _hostApplication.release();
203             _hostApplication = null;
204         }
205         return kResultOk;
206     }
207 
208     // Implements IComponent
209 
210     extern(Windows) override tresult getControllerClassId (TUID* classId)
211     {
212         // No need to implement since we "did not succeed to separate component from controller"
213         return kNotImplemented;
214     }
215 
216     extern(Windows) override tresult setIoMode (IoMode mode)
217     {
218         // Unused in every VST3 SDK example
219         return kNotImplemented;
220     }
221 
222     extern(Windows) override int32 getBusCount (MediaType type, BusDirection dir)
223     {
224         Vec!Bus* busList = getBusList(type, dir);
225         if (busList is null)
226             return 0;
227         return cast(int)( busList.length );
228     }
229 
230     extern(Windows) override tresult getBusInfo (MediaType type, BusDirection dir, int32 index, ref BusInfo bus /*out*/)
231     {
232         Vec!Bus* busList = getBusList(type, dir);
233         if (busList is null)
234             return kInvalidArgument;
235         if (index >= busList.length)
236             return kResultFalse;
237         bus = (*busList)[index].info;
238         return kResultTrue;
239     }
240 
241     extern(Windows) override tresult getRoutingInfo (ref RoutingInfo inInfo, ref RoutingInfo outInfo /*out*/)
242     {
243         // Apparently not needed in any SDK examples
244         return kNotImplemented;
245     }
246 
247     extern(Windows) override tresult activateBus (MediaType type, BusDirection dir, int32 index, TBool state)
248     {
249         debug(logVST3Client) debugLog(">activateBus".ptr);
250         debug(logVST3Client) scope(exit) debugLog("<activateBus".ptr);
251         Vec!Bus* busList = getBusList(type, dir);
252         if (busList is null)
253             return kInvalidArgument;
254         if (index >= busList.length)
255             return kResultFalse;
256         Bus* buses = (*busList).ptr;
257         buses[index].active = (state != 0);
258         return kResultTrue;
259     }
260 
261     extern(Windows) override tresult setActive (TBool state)
262     {
263         // In some VST3 examples, this place is used to initialize buffers.
264         return kResultOk;
265     }
266 
267     extern(Windows) override tresult setStateController (IBStream state)
268     {
269         debug(logVST3Client) debugLog(">setStateController".ptr);
270         debug(logVST3Client) scope(exit) debugLog("<setStateController".ptr);
271         return kNotImplemented;
272     }
273 
274     extern(Windows) override tresult getStateController (IBStream state)
275     {
276         debug(logVST3Client) debugLog(">getStateController".ptr);
277         debug(logVST3Client) scope(exit) debugLog("<getStateController".ptr);
278         return kNotImplemented;
279     }
280 
281     // Implements IAudioProcessor
282 
283     extern(Windows) override tresult setBusArrangements (SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts)
284     {
285         debug(logVST3Client) debugLog(">setBusArrangements".ptr);
286         debug(logVST3Client) scope(exit) debugLog("<setBusArrangements".ptr);
287 
288         if (numIns < 0 || numOuts < 0)
289             return kInvalidArgument;
290         int busIn = cast(int) (_audioInputs.length);   // 0 or 1
291         int busOut = cast(int) (_audioOutputs.length); // 0 or 1
292         if (numIns > busIn || numOuts > busOut)
293             return kResultFalse;
294         assert(numIns == 0 || numIns == 1);
295         assert(numOuts == 0 || numOuts == 1);
296 
297         int reqInputs = 0;
298         int reqOutputs = 0;
299 
300         if (numIns == 1)
301             reqInputs = getChannelCount(inputs[0]);
302         if (numOuts == 1)
303             reqOutputs = getChannelCount(outputs[0]);
304 
305         if (!_client.isLegalIO(reqInputs, reqOutputs))
306             return kResultFalse;
307 
308         if (numIns == 1)
309         {
310             Bus* pbus = _audioInputs.ptr;
311             pbus[0].speakerArrangement = inputs[0];
312             pbus[0].info.channelCount = reqInputs;
313         }
314         if (numOuts == 1)
315         {
316             Bus* pbus = _audioOutputs.ptr;
317             pbus[0].speakerArrangement = outputs[0];
318             pbus[0].info.channelCount = reqOutputs;
319         }
320         return kResultTrue;
321     }
322 
323     extern(Windows) override tresult getBusArrangement (BusDirection dir, int32 index, ref SpeakerArrangement arr)
324     {
325         debug(logVST3Client) debugLog(">getBusArrangement".ptr);
326         debug(logVST3Client) scope(exit) debugLog("<getBusArrangement".ptr);
327 
328         Vec!Bus* busList = getBusList(kAudio, dir);
329         if (busList is null || index >= cast(int)(busList.length))
330             return kInvalidArgument;
331         arr = (*busList)[index].speakerArrangement;
332         return kResultTrue;
333     }
334 
335     extern(Windows) override tresult canProcessSampleSize (int32 symbolicSampleSize)
336     {
337         return symbolicSampleSize == kSample32 ? kResultTrue : kResultFalse;
338     }
339 
340     extern(Windows) override uint32 getLatencySamples ()
341     {
342         ScopedForeignCallback!(false, true) scopedCallback;
343         scopedCallback.enter();
344         return _client.latencySamples(_sampleRateHostPOV);
345     }
346 
347     extern(Windows) override tresult setupProcessing (ref ProcessSetup setup)
348     {
349         debug(logVST3Client) debugLog(">setupProcessing".ptr);
350         debug(logVST3Client) scope(exit) debugLog("<setupProcessing".ptr);
351 
352         ScopedForeignCallback!(false, true) scopedCallback;
353         scopedCallback.enter();
354 
355         // Find out if this is a new latency, and inform host of latency change if yes.
356         // That is an implicit assumption in Dplug that latency is dependent upon sample-rate.
357         bool sampleRateChanged = (_sampleRate != setup.sampleRate);
358         _sampleRate = setup.sampleRate;
359         atomicStore(_sampleRateHostPOV, cast(float)(setup.sampleRate));
360         if (sampleRateChanged && _handler)
361             _handler.restartComponent(kLatencyChanged);
362 
363         // Pass these new values to the audio thread
364         atomicStore(_sampleRateAudioThreadPOV, cast(float)(setup.sampleRate));
365         atomicStore(_maxSamplesPerBlockHostPOV, setup.maxSamplesPerBlock);
366         if (setup.symbolicSampleSize != kSample32)
367             return kResultFalse;
368         return kResultOk;
369     }
370 
371     extern(Windows) override tresult setProcessing (TBool state)
372     {
373         debug(logVST3Client) debugLog(">setProcessing".ptr);
374         debug(logVST3Client) scope(exit) debugLog("<setProcessing".ptr);
375         if (state)
376         {
377             atomicStore(_shouldInitialize, true);
378         }
379         return kResultOk;
380     }
381 
382     extern(Windows) override tresult process (ref ProcessData data)
383     {
384         ScopedForeignCallback!(false, true) scopedCallback;
385         scopedCallback.enter();
386 
387         assert(data.symbolicSampleSize == kSample32); // no conversion to 64-bit supported
388 
389         // Call initialize if needed
390         float newSampleRate = atomicLoad!(MemoryOrder.raw)(_sampleRateAudioThreadPOV);
391         int newMaxSamplesPerBlock = atomicLoad!(MemoryOrder.raw)(_maxSamplesPerBlockHostPOV);
392         // find current number of inputs audio channels
393         int numInputs = 0;
394         if (data.numInputs != 0) // 0 or 1 output audio bus in a Dplug plugin
395             numInputs = data.inputs[0].numChannels;
396 
397         // find current number of inputs audio channels
398         int numOutputs = 0;
399         if (data.numOutputs != 0) // 0 or 1 output audio bus in a Dplug plugin
400             numOutputs = data.outputs[0].numChannels;
401 
402         bool shouldReinit = cas(&_shouldInitialize, true, false);
403         bool sampleRateChanged = (newSampleRate != _sampleRateDSPPOV);
404         bool maxSamplesChanged = (newMaxSamplesPerBlock != _maxSamplesPerBlockDSPPOV);
405         bool inputChanged = (_numInputChannels != numInputs);
406         bool outputChanged = (_numOutputChannels != numOutputs);
407 
408         if (shouldReinit || sampleRateChanged || maxSamplesChanged || inputChanged || outputChanged)
409         {
410             _sampleRateDSPPOV = newSampleRate;
411             _maxSamplesPerBlockDSPPOV = newMaxSamplesPerBlock;
412             _numInputChannels = numInputs;
413             _numOutputChannels = numOutputs;
414             _client.resetFromHost(_sampleRateDSPPOV, _maxSamplesPerBlockDSPPOV, _numInputChannels, _numOutputChannels);
415             resizeScratchBuffers(_maxSamplesPerBlockDSPPOV);
416 
417             _inputPointers.reallocBuffer(_numInputChannels);
418             _outputPointers.reallocBuffer(_numOutputChannels);
419         }
420 
421         // Gather all I/O pointers
422         foreach(chan; 0.._numInputChannels)
423         {
424             float* pInput = data.inputs[0].channelBuffers32[chan];
425 
426             // May be null in case of deactivated bus, in which case we feed zero instead
427             if (pInput is null)
428                 pInput = _zeroesBuffer.ptr;
429             _inputPointers[chan] = pInput;
430         }
431 
432         foreach(chan; 0.._numOutputChannels)
433         {
434             float* pOutput = data.outputs[0].channelBuffers32[chan];
435 
436             // May be null in case of deactivated bus, in which case we use a garbage buffer instead
437             if (pOutput is null)
438                 pOutput = _outputScratchBuffer.ptr;
439 
440             _outputPointers[chan] = pOutput;
441         }
442 
443         // Deal with input MIDI events (only note on, note off, CC and pitch bend supported so far)
444         // We can pull all MIDI events at once since there is a priority queue to store them.
445         if (data.inputEvents !is null && _client.receivesMIDI())
446         {
447             IEventList eventList = data.inputEvents;
448             int numEvents = eventList.getEventCount();
449             foreach(index; 0..numEvents)
450             {
451                 Event e;
452                 if (eventList.getEvent(index, e) == kResultOk)
453                 {
454                     int offset = e.sampleOffset;
455                     switch(e.type)
456                     {
457                         case Event.EventTypes.kNoteOnEvent:
458                         {
459                             ubyte velocity = cast(ubyte)(0.5f + 127.0f * e.noteOn.velocity);
460                             ubyte noteNumber = cast(ubyte)(e.noteOn.pitch);
461                             _client.enqueueMIDIFromHost( makeMidiMessageNoteOn(offset, e.noteOn.channel, noteNumber, velocity));
462                             break;
463                         }
464 
465                         case Event.EventTypes.kNoteOffEvent:
466                         {
467                             ubyte noteNumber = cast(ubyte)(e.noteOff.pitch);
468                             _client.enqueueMIDIFromHost( makeMidiMessageNoteOff(offset, e.noteOff.channel, noteNumber));
469                             break;
470                         }
471 
472                         case Event.EventTypes.kLegacyMIDICCOutEvent:
473                         {
474                             if (e.midiCCOut.controlNumber <= 127)
475                             {
476                                 _client.enqueueMIDIFromHost(
477                                     makeMidiMessage(offset, e.midiCCOut.channel, MidiStatus.controlChange, e.midiCCOut.controlNumber, e.midiCCOut.value));
478                             }
479                             else if (e.midiCCOut.controlNumber == 129)
480                             {
481                                 _client.enqueueMIDIFromHost(
482                                     makeMidiMessage(offset, e.midiCCOut.channel, MidiStatus.pitchBend, e.midiCCOut.value, e.midiCCOut.value2));
483                             }
484                             break;
485                         }
486 
487                         default:
488                             // unsupported events
489                     }
490                 }
491             }
492         }
493 
494 
495         int totalFrames = data.numSamples;
496         int bufferSplitMaxFrames = _client.getBufferSplitMaxFrames();
497         assert(bufferSplitMaxFrames > 0); // TODO: does this even work when no buffer splitting?
498 
499         // How many split buffers do we need this buffer?
500         int numSubBuffers = (totalFrames + (bufferSplitMaxFrames - 1)) / bufferSplitMaxFrames;
501 
502         updateTimeInfo(data.processContext);
503 
504         // Hosts like Cubase gives input and output buffer which are identical.
505         // This creates problems since Dplug assumes the contrary.
506         // Copy the input to scratch buffers to avoid overwrite.
507         for (int chan = 0; chan < numInputs; ++chan)
508         {
509             float* pCopy = _inputScratchBuffers[chan].ptr;
510             float* pInput = _inputPointers[chan];
511             pCopy[0..totalFrames] = pInput[0..totalFrames];
512             _inputPointers[chan] = pCopy;
513         }
514 
515         // Read parameter changes, sets them.
516         // For changed parameters, fills an array of successive values for each split period + 1.
517         //
518         // Store changes of a single parameter over the current buffer.
519 
520         IParameterChanges paramChanges = data.inputParameterChanges;
521         _tracks.clearContents();
522         if (paramChanges !is null)
523         {
524             // How many paramter tracks do we need this buffer?
525             int numParamChanges = paramChanges.getParameterCount();
526 
527             // For each varying parameter, store values for each subbuffer + 1 for the end value.
528             int numParamValues = numSubBuffers + 1;
529 
530             _sharedValues.resize(numParamValues * numParamChanges);
531 
532             foreach(index; 0..numParamChanges)
533             {
534                 IParamValueQueue queue = paramChanges.getParameterData(index);
535                 ParamID id = queue.getParameterId();
536                 int pointCount = queue.getPointCount();
537 
538                 ParameterTracks track;
539                 track.id = id;
540                 track.values = (_sharedValues.ptr + (index * numParamValues));
541 
542                 double x1 = -1; // position of last point related to current buffer
543                 double y1 = getParamNormalized(id); // last known parameter value
544 
545                 int time = 0;
546                 int subbuf = 0;
547                 for (int pt = 0; pt < pointCount; ++pt)
548                 {
549                     int pointTime;
550                     ParamValue pointValue;
551                     if (kResultTrue == queue.getPoint(pt, pointTime, pointValue))
552                     {
553                     }
554                     else
555                         continue;
556 
557                     double x2 = pointTime;
558                     double y2 = pointValue;
559                     double slope = 0;
560                     double offset = y1;
561                     if (x2 != x1)
562                     {
563                         slope = (y2 - y1) / (x2 - x1);
564                         offset = y1 - (slope * x1);
565                     }
566 
567                     // Fill values for subbuffers in this linear ramp
568                     while (time < x2)
569                     {
570                         assert(time >= x1);
571                         double curveValue = (slope * time) + offset; // bufferTime is any position in buffer
572                         track.values[subbuf++] = curveValue;
573                         time += bufferSplitMaxFrames;
574                     }
575                     x1 = x2;
576                     y1 = y2;
577                 }
578 
579                 // All remaining points are set to be y1 (the last known value)
580                 while (subbuf < numParamValues)
581                 {
582                     track.values[subbuf++] = y1;
583                 }
584 
585                 _tracks.pushBack(track);
586             }
587         }
588 
589         if (_client.sendsMIDI)
590             _client.clearAccumulatedOutputMidiMessages();
591 
592         int processedFrames = 0;
593         int subbuf = 0;
594         while(processedFrames < totalFrames)
595         {
596             int blockFrames = totalFrames - processedFrames;
597             if (blockFrames > bufferSplitMaxFrames)
598                 blockFrames = bufferSplitMaxFrames;
599 
600             for (int track = 0; track < _tracks.length; ++track)
601             {
602                 setParamValue(_tracks[track].id, _tracks[track].values[subbuf]);
603             }
604 
605             // Support bypass
606             bool bypassed = atomicLoad!(MemoryOrder.raw)(_bypassed);
607             if (bypassed)
608             {
609                 int minIO = numInputs;
610                 if (minIO > numOutputs) minIO = numOutputs;
611 
612                 for (int chan = 0; chan < minIO; ++chan)
613                 {
614                     float* pOut = _outputPointers[chan];
615                     float* pIn = _inputPointers[chan];
616                     for(int i = 0; i < blockFrames; ++i)
617                     {
618                         pOut[i] = pIn[i];
619                     }
620                 }
621 
622                 for (int chan = minIO; chan < numOutputs; ++chan)
623                 {
624                     float* pOut = _outputPointers[chan];
625                     for(int i = 0; i < blockFrames; ++i)
626                     {
627                         pOut[i] = 0.0f;
628                     }
629                 }
630             }
631             else
632             {
633                 // Regular processing
634                 bool doNotSplit = true;
635                 _client.processAudioFromHost(_inputPointers[0..numInputs],
636                                              _outputPointers[0..numOutputs],
637                                              blockFrames,
638                                              _timeInfo,
639                                              doNotSplit);
640 
641                 // Accumulate MIDI output for this sub-buffer, if any
642                 if (_client.sendsMIDI())
643                     _client.accumulateOutputMIDI(blockFrames);
644             }
645 
646             // advance split buffers
647             for (int chan = 0; chan < numInputs; ++chan)
648             {
649                 _inputPointers[chan] = _inputPointers[chan] + blockFrames;
650             }
651             for (int chan = 0; chan < numOutputs; ++chan)
652             {
653                 _outputPointers[chan] = _outputPointers[chan] + blockFrames;
654             }
655 
656             // In case the next process block has no ProcessContext
657             _timeInfo.timeInSamples += blockFrames;
658 
659             processedFrames += blockFrames;
660             subbuf = subbuf + 1;
661         }
662 
663         // Send last values for varying parameters
664         for (int track = 0; track < _tracks.length; ++track)
665         {
666             setParamValue(_tracks[track].id, _tracks[track].values[subbuf]);
667         }
668 
669         assert(processedFrames == totalFrames);
670 
671         // Send MIDI message in bulk
672         if (_client.sendsMIDI)
673         {
674             const(MidiMessage)[] toSend = _client.getAccumulatedOutputMidiMessages();
675             IEventList outEvents = data.outputEvents;
676 
677             if (outEvents !is null && toSend.length != 0)
678             {
679                 foreach(ref const(MidiMessage) msg; toSend)
680                 {
681                     Event e;
682                     e.busIndex = 0;
683                     e.sampleOffset = msg.offset;
684                     e.ppqPosition = 0;
685                     e.flags = 0;
686 
687                     // TODO support the following events:
688                     // - MIDI poly after touch
689                     // - MIDI channel after touch
690                     // - MIDI program change
691 
692                     if (msg.isNoteOn())
693                     {
694                         e.type = Event.EventTypes.kNoteOnEvent;
695                         e.noteOn.channel = cast(short) msg.channel();
696                         e.noteOn.pitch = cast(short) msg.noteNumber();
697                         e.noteOn.velocity = msg.noteVelocity() / 127.0f;
698                         e.noteOn.length = 0;
699                         e.noteOn.tuning = 0;
700                         e.noteOn.noteId = -1;
701                         outEvents.addEvent(e);
702                     }
703                     else if (msg.isNoteOff())
704                     {
705                         e.type = Event.EventTypes.kNoteOffEvent;
706                         e.noteOff.channel = cast(short) msg.channel();
707                         e.noteOff.pitch = cast(short) msg.noteNumber();
708                         e.noteOff.velocity = msg.noteVelocity() / 127.0f;
709                         e.noteOff.tuning = 0;
710                         e.noteOff.noteId = -1;
711                         outEvents.addEvent(e);
712                     }
713                     else if (msg.isPitchBend())
714                     {
715                         e.type = Event.EventTypes.kLegacyMIDICCOutEvent;
716                         e.midiCCOut.channel = cast(byte) msg.channel();
717                         e.midiCCOut.controlNumber = 129 /* kPitchBend */;
718                         e.midiCCOut.value = msg.data1();
719                         e.midiCCOut.value2 = msg.data2();
720                         outEvents.addEvent(e);
721                     }
722                     else if (msg.isControlChange())
723                     {
724                         e.type = Event.EventTypes.kLegacyMIDICCOutEvent;
725                         e.midiCCOut.channel = cast(byte) msg.channel();
726                         e.midiCCOut.controlNumber = cast(ubyte) msg.controlChangeControl();
727                         e.midiCCOut.value = cast(byte) msg.controlChangeValue();
728                         e.midiCCOut.value2 = 0; // TODO: special handling for pitch bend and poly pressure                        
729                     }
730                 }
731             }
732         }
733         return kResultOk;
734     }
735 
736     void setParamValue(ParamID id, ParamValue value)
737     {
738         if (id == PARAM_ID_BYPASS)
739         {
740             atomicStore(_bypassed, (value >= 0.5f));
741         }
742         else if (id == PARAM_ID_PROGRAM_CHANGE)
743         {
744             int presetIndex;
745             if (convertPresetParamToPlain(value, &presetIndex))
746             {
747                 _client.presetBank.loadPresetFromHost(presetIndex);
748             }
749         }
750         else
751         {
752             // Note: VSTHost sends _param indices_ instead of ParamID, which is completely wrong.
753             int paramIndex;
754             if (_hostMaySendBadParameterIDs)
755             {
756                 paramIndex = convertParamIDToClientParamIndex(convertVST3ParamIndexToParamID(id));
757                 if (!_client.isValidParamIndex(paramIndex))
758                     return;
759             }
760             else
761                 paramIndex = convertParamIDToClientParamIndex(id);
762 
763             _client.setParameterFromHost(paramIndex, value);
764         }
765     }
766 
767     void updateTimeInfo(ProcessContext* context)
768     {
769         if (context !is null)
770         {
771             if (context.state & ProcessContext.kTempoValid)
772                 _timeInfo.tempo = context.tempo;
773             _timeInfo.timeInSamples = context.projectTimeSamples;
774             _timeInfo.hostIsPlaying = (context.state & ProcessContext.kPlaying) != 0;
775         }
776     }
777 
778     extern(Windows) override uint32 getTailSamples()
779     {
780         float tailSeconds = _client.tailSizeInSeconds();
781         assert(tailSeconds >= 0);
782 
783         double tailInSamples = tailSeconds * cast(double) atomicLoad(_sampleRateHostPOV);
784 
785         // Large or infinity? Return infinite tail, which should disable "smart disable".
786         if (tailInSamples + 0.5 >= kInfiniteTail)
787             return kInfiniteTail;
788 
789         long samples = cast(long)(tailInSamples + 0.5);
790         assert(samples >= 0 && samples <= kInfiniteTail);
791 
792         if (samples < 2)
793         {
794             // In case some hosts do not understand that zero there doesn't mean the same thing that in VST2
795             // (where it meant infinity tail), we avoid zero.
796             // Because in VST2 1 was a special value that means "no tail", we also avoid 1.
797             // So, we return 2 so that even a buggy host would accept our value.
798             // The number of plugin that actually have a zero tail size is really super small.
799             return 2;
800         }
801 
802         return cast(uint) samples;
803     }
804 
805     // Implements IEditController
806 
807     extern(Windows) override tresult setComponentState (IBStream state)
808     {
809         return kNotImplemented;
810     }
811 
812     extern(Windows) override tresult setState(IBStream state)
813     {
814         debug(logVST3Client) debugLog(">setState".ptr);
815         debug(logVST3Client) scope(exit) debugLog("<setState".ptr);
816 
817         // Manage VST3 state versionning.
818         // First byte of the state is the major VST3 chunk parsing method (we need versionning just in case)
819         ubyte version_;
820         int bytesRead;
821         if (state.read (&version_, 1, &bytesRead) != kResultOk)
822             return kResultFalse;
823         if (version_ != 0)
824             return kResultFalse; // Only version zero is supported
825 
826         // (version 0) Second byte of the state is the bypass parameter
827         ubyte bypass;
828         if (state.read (&bypass, 1, &bytesRead) != kResultOk)
829             return kResultFalse;
830         atomicStore(_bypassed, bypass != 0);
831 
832         // Try to find current position with seeking to the end
833         int size;
834         {
835             long curPos;
836             if (state.tell(&curPos) != kResultOk)
837                 return kResultFalse;
838 
839             long newPos;
840             if (state.seek(0, IBStream.kIBSeekEnd, &newPos) != kResultOk)
841                 return kResultFalse;
842 
843             size = cast(int)(newPos - curPos);
844 
845             if (state.seek(curPos, IBStream.kIBSeekSet, null) != kResultOk)
846                 return kResultFalse;
847         }
848 
849         ubyte[] chunk = mallocSlice!ubyte(size);
850         scope(exit) chunk.freeSlice();
851 
852         if (state.read (chunk.ptr, size, &bytesRead) != kResultOk)
853             return kResultFalse;
854 
855         try
856         {
857             auto presetBank = _client.presetBank();
858             presetBank.loadStateChunk(chunk);
859             return kResultTrue;
860         }
861         catch(Exception e)
862         {
863             e.destroyFree();
864             return kResultFalse;
865         }
866     }
867 
868     extern(Windows) override tresult getState(IBStream state)
869     {
870         debug(logVST3Client) debugLog(">getState".ptr);
871         debug(logVST3Client) scope(exit) debugLog("<getState".ptr);
872 
873         // First byte of the state is the major VST3 chunk parsing method (we need versionning just in case)
874         ubyte CURRENT_VST3_STATE_VERSION = 0;
875         if (state.write(&CURRENT_VST3_STATE_VERSION, 1, null) != kResultTrue)
876             return kResultFalse;
877 
878         // (version 0) Second byte of the state is the bypass parameter
879         ubyte bypass = atomicLoad(_bypassed) ? 1 : 0;
880         if (state.write(&bypass, 1, null) != kResultTrue)
881             return kResultFalse;
882 
883         auto presetBank = _client.presetBank();
884         ubyte[] chunk = presetBank.getStateChunkFromCurrentState();
885         scope(exit) free(chunk.ptr);
886         return state.write(chunk.ptr, cast(int)(chunk.length), null);
887     }
888 
889     extern(Windows) override int32 getParameterCount()
890     {
891         return cast(int)(_client.params.length) + 2; // 2 because bypass and program change fake parameters
892     }
893 
894     extern(Windows) override tresult getParameterInfo (int32 paramIndex, ref ParameterInfo info)
895     {
896         if (paramIndex >= (cast(uint)(_client.params.length) + 2))
897             return kResultFalse;
898 
899         if (paramIndex == 0)
900         {
901             info.id = PARAM_ID_BYPASS;
902             str8ToStr16(info.title.ptr, "Bypass".ptr, 128);
903             str8ToStr16(info.shortTitle.ptr, "Byp".ptr, 128);
904             str8ToStr16(info.units.ptr, "".ptr, 128);
905             info.stepCount = 1;
906             info.defaultNormalizedValue = 0.0f;
907             info.unitId = 0; // root, unit 0 is always here
908             info.flags = ParameterInfo.ParameterFlags.kCanAutomate
909                        | ParameterInfo.ParameterFlags.kIsBypass
910                        | ParameterInfo.ParameterFlags.kIsList;
911             return kResultTrue;
912         }
913         else if (paramIndex == 1)
914         {
915             info.id = PARAM_ID_PROGRAM_CHANGE;
916             str8ToStr16(info.title.ptr, "Preset".ptr, 128);
917             str8ToStr16(info.shortTitle.ptr, "Pre".ptr, 128);
918             str8ToStr16(info.units.ptr, "".ptr, 128);
919             info.stepCount = _presetStepCount;
920             info.defaultNormalizedValue = 0.0f;
921             info.unitId = 0; // root, unit 0 is always here
922             info.flags = ParameterInfo.ParameterFlags.kIsProgramChange;
923             return kResultTrue;
924         }
925         else
926         {
927             info.id = convertVST3ParamIndexToParamID(paramIndex);
928             Parameter param = _client.param(convertParamIDToClientParamIndex(info.id));
929             str8ToStr16(info.title.ptr, param.name, 128);
930             str8ToStr16(info.shortTitle.ptr, param.name(), 128);
931             str8ToStr16(info.units.ptr, param.label(), 128);
932             info.stepCount = 0; // continuous
933             info.defaultNormalizedValue = param.getNormalizedDefault();
934             info.unitId = 0; // root, unit 0 is always here
935             info.flags = 0;
936 
937             if (param.isAutomatable)
938             {
939                 info.flags |= ParameterInfo.ParameterFlags.kCanAutomate;
940             }
941             return kResultTrue;
942         }
943     }
944 
945     /** Gets for a given paramID and normalized value its associated string representation. */
946     extern(Windows) override tresult getParamStringByValue (ParamID id, ParamValue valueNormalized, String128* string_ )
947     {
948         debug(logVST3Client) debugLog(">getParamStringByValue".ptr);
949         if (id == PARAM_ID_BYPASS)
950         {
951             if (valueNormalized < 0.5f)
952                 str8ToStr16(string_.ptr, "No".ptr, 128);
953             else
954                 str8ToStr16(string_.ptr, "Yes".ptr, 128);
955             return kResultTrue;
956         }
957 
958         if (id == PARAM_ID_PROGRAM_CHANGE)
959         {
960             int presetIndex;
961             if (convertPresetParamToPlain(valueNormalized, &presetIndex))
962             {
963                 // Gives back name of preset
964                 str8ToStr16(string_.ptr, _client.presetBank.preset(presetIndex).name, 128);
965                 return kResultTrue;
966             }
967             else
968             {
969                 str8ToStr16(string_.ptr, "None".ptr, 128);
970                 return kResultTrue;
971             }
972         }
973 
974         int paramIndex = convertParamIDToClientParamIndex(id);
975         if (!_client.isValidParamIndex(paramIndex))
976         {
977             return kResultFalse;
978         }
979 
980         if (string_ is null)
981             return kResultFalse;
982 
983         Parameter param = _client.param(paramIndex);
984         char[128] buf;
985         param.stringFromNormalizedValue(valueNormalized, buf.ptr, 128);
986         str8ToStr16(string_.ptr, buf.ptr, 128);
987 
988         debug(logVST3Client) debugLog("<getParamStringByValue".ptr);
989         return kResultTrue;
990     }
991 
992     /** Gets for a given paramID and string its normalized value. */
993     extern(Windows) override tresult getParamValueByString (ParamID id, TChar* string_, ref ParamValue valueNormalized )
994     {
995         debug(logVST3Client) debugLog(">getParamValueByString".ptr);
996 
997         if (id == PARAM_ID_BYPASS || id == PARAM_ID_PROGRAM_CHANGE)
998             return kResultFalse; // MAYDO, eventually
999 
1000         int paramIndex = convertParamIDToClientParamIndex(id);
1001         if (!_client.isValidParamIndex(paramIndex))
1002         {
1003             debug(logVST3Client) debugLog("getParamValueByString got a wrong parameter index".ptr);
1004             return kResultFalse;
1005         }
1006         Parameter param = _client.param(paramIndex);
1007 
1008         char[128] valueUTF8;
1009         int len = 0;
1010         for(int i = 0; i < 128; ++i)
1011         {
1012             // Note: no surrogates supported in this UTF-16 to UTF8 conversion
1013             valueUTF8[i] = cast(char)string_[i];
1014             if (!string_[i])
1015                 break;
1016             else
1017                 len++;
1018         }
1019 
1020         if (param.normalizedValueFromString( valueUTF8[0..len], valueNormalized))
1021         {
1022             debug(logVST3Client) scope(exit) debugLog("<getParamValueByString".ptr);
1023             return kResultTrue;
1024         }
1025         else
1026         {
1027             debug(logVST3Client) scope(exit) debugLog("<getParamValueByString".ptr);
1028             return kResultFalse;
1029         }
1030     }
1031 
1032     /** Returns for a given paramID and a normalized value its plain representation
1033     (for example 90 for 90db - see \ref vst3AutomationIntro). */
1034     extern(Windows) override ParamValue normalizedParamToPlain (ParamID id, ParamValue valueNormalized)
1035     {
1036         debug(logVST3Client) debugLog(">normalizedParamToPlain".ptr);
1037         debug(logVST3Client) debugLog("<normalizedParamToPlain".ptr);
1038 
1039         if (id == PARAM_ID_BYPASS)
1040         {
1041             return convertBypassParamToPlain(valueNormalized);
1042         }
1043         else if (id == PARAM_ID_PROGRAM_CHANGE)
1044         {
1045             int presetIndex = 0;
1046             convertPresetParamToPlain(valueNormalized, &presetIndex);
1047             return presetIndex;
1048         }
1049         else
1050         {
1051             int paramIndex = convertParamIDToClientParamIndex(id);
1052             if (!_client.isValidParamIndex(paramIndex))
1053                 return 0;
1054             Parameter param = _client.param(paramIndex);
1055             return valueNormalized; // Note: the host don't need to know we do not deal with normalized values internally
1056         }
1057     }
1058 
1059     /** Returns for a given paramID and a plain value its normalized value. (see \ref vst3AutomationIntro) */
1060     extern(Windows) override ParamValue plainParamToNormalized (ParamID id, ParamValue plainValue)
1061     {
1062         debug(logVST3Client) debugLog(">plainParamToNormalized".ptr);
1063         debug(logVST3Client) scope(exit) debugLog("<plainParamToNormalized".ptr);
1064 
1065         if (id == PARAM_ID_BYPASS)
1066         {
1067             return convertBypassParamToNormalized(plainValue);
1068         }
1069         else if (id == PARAM_ID_PROGRAM_CHANGE)
1070         {
1071             return convertPresetParamToNormalized(plainValue);
1072         }
1073         else
1074         {
1075             int paramIndex = convertParamIDToClientParamIndex(id);
1076             if (!_client.isValidParamIndex(paramIndex))
1077                 return 0;
1078             Parameter param = _client.param(paramIndex);
1079             return plainValue; // Note: the host don't need to know we do not deal with normalized values internally
1080         }
1081     }
1082 
1083     /** Returns the normalized value of the parameter associated to the paramID. */
1084     extern(Windows) override ParamValue getParamNormalized (ParamID id)
1085     {
1086         debug(logVST3Client) debugLog(">getParamNormalized".ptr);
1087         debug(logVST3Client) scope(exit) debugLog("<getParamNormalized".ptr);
1088 
1089         if (id == PARAM_ID_BYPASS)
1090         {
1091             return atomicLoad(_bypassed) ? 1.0f : 0.0f;
1092         }
1093         else if (id == PARAM_ID_PROGRAM_CHANGE)
1094         {
1095             int currentPreset = _client.presetBank.currentPresetIndex();
1096             return convertPresetParamToNormalized(currentPreset);
1097         }
1098         else
1099         {
1100             int paramIndex = convertParamIDToClientParamIndex(id);
1101             if (!_client.isValidParamIndex(paramIndex))
1102                 return 0;
1103             Parameter param = _client.param(paramIndex);
1104             return param.getForHost();
1105         }
1106     }
1107 
1108     /** Sets the normalized value to the parameter associated to the paramID. The controller must never
1109     pass this value-change back to the host via the IComponentHandler. It should update the according
1110     GUI element(s) only!*/
1111     extern(Windows) override tresult setParamNormalized (ParamID id, ParamValue value)
1112     {
1113         debug(logVST3Client) debugLog(">setParamNormalized".ptr);
1114         debug(logVST3Client) scope(exit) debugLog("<setParamNormalized".ptr);
1115 
1116         if (id == PARAM_ID_BYPASS)
1117         {
1118             atomicStore(_bypassed, (value >= 0.5f));
1119             return kResultTrue;
1120         }
1121         else if (id == PARAM_ID_PROGRAM_CHANGE)
1122         {
1123             int presetIndex;
1124             if (convertPresetParamToPlain(value, &presetIndex))
1125             {
1126                 _client.presetBank.loadPresetFromHost(presetIndex);
1127             }
1128             return kResultTrue;
1129         }
1130         else
1131         {
1132             int paramIndex = convertParamIDToClientParamIndex(id);
1133             if (!_client.isValidParamIndex(paramIndex))
1134                 return kResultFalse;
1135             Parameter param = _client.param(paramIndex);
1136             param.setFromHost(value);
1137             return kResultTrue;
1138         }
1139     }
1140 
1141     /** Gets from host a handler. */
1142     extern(Windows) override tresult setComponentHandler (IComponentHandler handler)
1143     {
1144         debug(logVST3Client) debugLog(">setComponentHandler".ptr);
1145         debug(logVST3Client) scope(exit) debugLog("<setComponentHandler".ptr);
1146         if (_handler is handler)
1147             return kResultTrue;
1148 
1149         if (_handler)
1150         {
1151             _handler.release();
1152             _handler = null;
1153         }
1154 
1155         _handler = handler;
1156         if (_handler)
1157         {
1158             _handler.addRef();
1159         }
1160         return kResultTrue;
1161     }
1162 
1163     // view
1164     /** Creates the editor view of the Plug-in, currently only "editor" is supported, see \ref ViewType.
1165     The life time of the editor view will never exceed the life time of this controller instance. */
1166     extern(Windows) override IPlugView createView (FIDString name)
1167     {
1168         debug(logVST3Client) debugLog(">createView".ptr);
1169         debug(logVST3Client) scope(exit) debugLog("<createView".ptr);
1170         if (!_client.hasGUI)
1171             return null;
1172         if (name !is null && strcmp(name, "editor") == 0)
1173             return mallocNew!DplugView(this);
1174         return null;
1175     }
1176 
1177     // implements IEditController2
1178 
1179     extern(Windows) override tresult setKnobMode (KnobMode mode)
1180     {
1181         return (mode == kLinearMode) ? kResultTrue : kResultFalse;
1182     }
1183 
1184     extern(Windows) override tresult openHelp (TBool onlyCheck)
1185     {
1186         return kResultFalse;
1187     }
1188 
1189     extern(Windows) override tresult openAboutBox (TBool onlyCheck)
1190     {
1191         return kResultFalse;
1192     }
1193 
1194 
1195     // implements IUnitInfo
1196 
1197     extern(Windows) override int32 getUnitCount ()
1198     {
1199         return 1;
1200     }
1201 
1202     /** Gets UnitInfo for a given index in the flat list of unit. */
1203     extern(Windows) override tresult getUnitInfo (int32 unitIndex, ref UnitInfo info /*out*/)
1204     {
1205         if (unitIndex == 0)
1206         {
1207             info.id = kRootUnitId;
1208             info.parentUnitId = kNoParentUnitId;
1209             str8ToStr16(info.name.ptr, "Root Unit".ptr, 128);
1210             info.programListId = PARAM_ID_PROGRAM_CHANGE;
1211             return kResultTrue;
1212         }
1213         return kResultFalse;
1214     }
1215 
1216     /** Component intern program structure. */
1217     /** Gets the count of Program List. */
1218     extern(Windows) override int32 getProgramListCount ()
1219     {
1220         return 1;
1221     }
1222 
1223     /** Gets for a given index the Program List Info. */
1224     extern(Windows) override tresult getProgramListInfo (int32 listIndex, ref ProgramListInfo info /*out*/)
1225     {
1226         ProgramListInfo result;
1227         result.id = PARAM_ID_PROGRAM_CHANGE;
1228         result.programCount = _client.presetBank().numPresets();
1229         str8ToStr16(result.name.ptr, "Factory Presets".ptr, 128);
1230         info = result;
1231         return kResultTrue;
1232     }
1233 
1234     /** Gets for a given program list ID and program index its program name. */
1235     extern(Windows) override tresult getProgramName (ProgramListID listId, int32 programIndex, String128* name /*out*/)
1236     {
1237         if (listId != PARAM_ID_PROGRAM_CHANGE)
1238             return kResultFalse;
1239         auto presetBank = _client.presetBank();
1240         if (!presetBank.isValidPresetIndex(programIndex))
1241             return kResultFalse;
1242         str8ToStr16((*name).ptr, presetBank.preset(programIndex).name, 128);
1243         return kResultTrue;
1244     }
1245 
1246     /** Gets for a given program list ID, program index and attributeId the associated attribute value. */
1247     extern(Windows) override tresult getProgramInfo (ProgramListID listId, int32 programIndex,
1248                             const(wchar)* attributeId /*in*/, String128* attributeValue /*out*/)
1249     {
1250         return kNotImplemented; // I don't understand what these "attributes" could be
1251     }
1252 
1253     /** Returns kResultTrue if the given program index of a given program list ID supports PitchNames. */
1254     extern(Windows) override tresult hasProgramPitchNames (ProgramListID listId, int32 programIndex)
1255     {
1256         return kResultFalse;
1257     }
1258 
1259     /** Gets the PitchName for a given program list ID, program index and pitch.
1260     If PitchNames are changed the Plug-in should inform the host with IUnitHandler::notifyProgramListChange. */
1261     extern(Windows) override tresult getProgramPitchName (ProgramListID listId, int32 programIndex,
1262                                  int16 midiPitch, String128* name /*out*/)
1263     {
1264         return kResultFalse;
1265     }
1266 
1267     // units selection --------------------
1268     /** Gets the current selected unit. */
1269     extern(Windows) override UnitID getSelectedUnit ()
1270     {
1271         return 0;
1272     }
1273 
1274     /** Sets a new selected unit. */
1275     extern(Windows) override tresult selectUnit (UnitID unitId)
1276     {
1277         return (unitId == 0) ? kResultTrue : kResultFalse;
1278     }
1279 
1280     /** Gets the according unit if there is an unambiguous relation between a channel or a bus and a unit.
1281     This method mainly is intended to find out which unit is related to a given MIDI input channel. */
1282     extern(Windows) override tresult getUnitByBus (MediaType type, BusDirection dir, int32 busIndex,
1283                                                    int32 channel, ref UnitID unitId /*out*/)
1284     {
1285         unitId = 0;
1286         return kResultTrue;
1287     }
1288 
1289     /** Receives a preset data stream.
1290     - If the component supports program list data (IProgramListData), the destination of the data
1291     stream is the program specified by list-Id and program index (first and second parameter)
1292     - If the component supports unit data (IUnitData), the destination is the unit specified by the first
1293     parameter - in this case parameter programIndex is < 0). */
1294     extern(Windows) tresult setUnitProgramData (int32 listOrUnitId, int32 programIndex, IBStream data)
1295     {
1296         return kNotImplemented;
1297     }
1298 
1299 private:
1300     Client _client;
1301     IComponentHandler _handler;
1302     IHostCommand _hostCommand;
1303 
1304     // Assigned when UI is opened, nulled when closed. This allow to request a host parent window resize.
1305     IPlugFrame _plugFrame = null; 
1306     IPlugView _currentView = null;
1307 
1308     shared(bool) _shouldInitialize = true;
1309     shared(bool) _bypassed = false;
1310 
1311     // This is number of preset - 1, but 0 if no presets
1312     // "stepcount" is offset by 1 in VST3 Parameter parlance
1313     // stepcount = 1 gives 2 different values
1314     int _presetStepCount;
1315 
1316     float _sampleRate;
1317     shared(float) _sampleRateHostPOV = 44100.0f;
1318     shared(float) _sampleRateAudioThreadPOV = 44100.0f;
1319     float _sampleRateDSPPOV = 0.0f;
1320 
1321     shared(int) _maxSamplesPerBlockHostPOV = -1;
1322     int _maxSamplesPerBlockDSPPOV = -1;
1323 
1324     int _numInputChannels = -1; /// Number of input channels from the DSP point of view
1325     int _numOutputChannels = -1; /// Number of output channels from the DSP point of view
1326 
1327     float*[] _inputPointers;
1328     float*[] _outputPointers;
1329 
1330     DAW _daw = DAW.Unknown;
1331     char[128] _hostName;
1332 
1333     TimeInfo _timeInfo;
1334 
1335     static struct Bus
1336     {
1337         bool active;
1338         SpeakerArrangement speakerArrangement;
1339         BusInfo info;
1340     }
1341 
1342     Vec!Bus _audioInputs;
1343     Vec!Bus _audioOutputs;
1344     Vec!Bus _eventInputs;
1345     Vec!Bus _eventOutputs;
1346 
1347     Vec!Bus* getBusList(MediaType type, BusDirection dir)
1348     {
1349         if (type == kAudio)
1350         {
1351             if (dir == kInput) return &_audioInputs;
1352             if (dir == kOutput) return &_audioOutputs;
1353         }
1354         else if (type == kEvent)
1355         {
1356             if (dir == kInput) return &_eventInputs;
1357             if (dir == kOutput) return &_eventOutputs;
1358         }
1359         return null;
1360     }
1361 
1362     // Scratch buffers
1363     float[] _zeroesBuffer; // for deactivated input bus
1364     float[] _outputScratchBuffer; // for deactivated output bus
1365     Vec!float[] _inputScratchBuffers; // for input safe copy
1366     int _maxInputs;
1367 
1368     void resizeScratchBuffers(int maxFrames) nothrow @nogc
1369     {
1370         for (int i = 0; i < _maxInputs; ++i)
1371             _inputScratchBuffers[i].resize(maxFrames);
1372         _outputScratchBuffer.reallocBuffer(maxFrames);
1373         _zeroesBuffer.reallocBuffer(maxFrames);
1374         _zeroesBuffer[0..maxFrames] = 0;
1375     }
1376 
1377     // host application reference
1378     IHostApplication _hostApplication = null;
1379 
1380     Vec!ParameterTracks _tracks;
1381     Vec!double _sharedValues;
1382 
1383     // Workaround a VSTHost bug, see Issue #583.
1384     // VSTHost send parameters updates to inexisting parameters.
1385     bool _hostMaySendBadParameterIDs = false;
1386 
1387     void setHostApplication(FUnknown context)
1388     {
1389         debug(logVST3Client) debugLog(">setHostApplication".ptr);
1390         debug(logVST3Client) scope(exit) debugLog("<setHostApplication".ptr);
1391         IHostApplication hostApplication = null;
1392         if (context.queryInterface(IHostApplication.iid, cast(void**)(&hostApplication)) != kResultOk)
1393             hostApplication = null;
1394 
1395         // clean-up _hostApplication former if any
1396         if (_hostApplication !is null)
1397         {
1398             _hostApplication.release();
1399             _hostApplication = null;
1400         }
1401 
1402         if (hostApplication !is null)
1403         {
1404             hostApplication.addRef();
1405             _hostApplication = hostApplication;
1406 
1407             // Identify host
1408             String128 name;
1409             if (_hostApplication.getName(&name) == kResultOk)
1410             {
1411                 str16ToStr8(_hostName.ptr, name.ptr, 128);
1412 
1413                 // Force lowercase
1414                 for (int n = 0; n < 128; ++n)
1415                 {
1416                     if (_hostName[n] >= 'A' && _hostName[n] <= 'Z')
1417                         _hostName[n] += ('a' - 'A');
1418                 }
1419 
1420                 _daw = identifyDAW(_hostName.ptr);
1421                 _hostMaySendBadParameterIDs = (_daw == DAW.VSTHost);
1422             }
1423         }
1424     }
1425 
1426     double convertBypassParamToNormalized(double plainValue) const
1427     {
1428         return plainValue / cast(double)1;
1429     }
1430 
1431     double convertBypassParamToPlain(double normalizedValue) const
1432     {
1433         double v = cast(int)(normalizedValue * 2);
1434         if (v > 1)
1435             v = 1;
1436         return v;
1437     }
1438 
1439     double convertPresetParamToNormalized(double presetPlainValue) const
1440     {
1441         if (_presetStepCount == 0) // 0 or 1 preset
1442             return 0;
1443         return presetPlainValue / cast(double)_presetStepCount;
1444     }
1445 
1446     bool convertPresetParamToPlain(double presetNormalizedValue, int* presetIndex)
1447     {
1448         int index = cast(int)(presetNormalizedValue * (_presetStepCount + 1));
1449         if (index > _presetStepCount)
1450             index = _presetStepCount;
1451         *presetIndex = index;
1452         return _client.presetBank.isValidPresetIndex(_presetStepCount);
1453     }
1454 }
1455 
1456 private:
1457 nothrow:
1458 pure:
1459 @nogc:
1460 
1461 enum int PARAM_ID_BYPASS = 998;
1462 enum int PARAM_ID_PROGRAM_CHANGE = 999;
1463 
1464 // Convert from VST3 index to VST3 ParamID
1465 int convertVST3ParamIndexToParamID(int index)
1466 {
1467     // Parameter with VST3 index 0 is a fake Bypass parameter
1468     if (index == 0)
1469         return PARAM_ID_BYPASS;
1470 
1471     // Parameter with VST3 index 1 is a fake Program Change parameter
1472     if (index == 1)
1473         return PARAM_ID_PROGRAM_CHANGE;
1474 
1475     // Parameter with VST3 index 2 is the first client Parameter
1476     return index - 2;
1477 }
1478 
1479 // Convert from VST3 ParamID to VST3 index
1480 int convertParamIDToVST3ParamIndex(int index)
1481 {
1482     // Parameter with VST3 index 0 is a fake Bypass parameter
1483     if (index == PARAM_ID_BYPASS)
1484         return 0;
1485 
1486     // Parameter with VST3 index 1 is a fake Program Change parameter
1487     if (index == PARAM_ID_PROGRAM_CHANGE)
1488         return 1;
1489 
1490     // Parameter with VST3 index 2 is the first client Parameter
1491     return index + 2;
1492 }
1493 
1494 /// Convert from VST3 ParamID to Client Parameter index
1495 int convertParamIDToClientParamIndex(int paramID)
1496 {
1497     // Shouldn't be here if this is a fake parameter
1498     assert (paramID != PARAM_ID_BYPASS);
1499     assert (paramID != PARAM_ID_PROGRAM_CHANGE);
1500 
1501     // Parameter with VST3 index 2 is the first client Parameter
1502     return paramID;
1503 }
1504 
1505 /// Convert from Client Parameter index to VST3 ParamID
1506 int convertClientParamIndexToParamID(int clientIndex)
1507 {
1508     return clientIndex;
1509 }
1510 
1511 class DplugView : IPlugView
1512 {
1513 public:
1514 nothrow:
1515 @nogc:
1516 
1517     this(VST3Client vst3Client)
1518     {
1519         _vst3Client = vst3Client;
1520         _graphicsMutex = makeMutex();
1521     }
1522 
1523     ~this()
1524     {
1525     }
1526 
1527     // Implements FUnknown
1528     mixin QUERY_INTERFACE_SPECIAL_CASE_IUNKNOWN!(IPlugView);
1529     mixin IMPLEMENT_REFCOUNT;
1530 
1531     // IPlugView
1532 
1533     /** Is Platform UI Type supported
1534     \param type : IDString of \ref platformUIType */
1535     // MAYDO: there is considerable coupling with dplug:window here.
1536     extern(Windows) override tresult isPlatformTypeSupported (FIDString type)
1537     {
1538         debug(logVST3Client) debugLog(">isPlatformTypeSupported".ptr);
1539         debug(logVST3Client) scope(exit) debugLog("<isPlatformTypeSupported".ptr);
1540         GraphicsBackend backend;
1541         if (convertPlatformToGraphicsBackend(type, &backend))
1542             return isGraphicsBackendSupported(backend) ? kResultTrue : kResultFalse;
1543         return kResultFalse;
1544     }
1545 
1546     /** The parent window of the view has been created, the (platform) representation of the view
1547     should now be created as well.
1548     Note that the parent is owned by the caller and you are not allowed to alter it in any way
1549     other than adding your own views.
1550     Note that in this call the Plug-in could call a IPlugFrame::resizeView ()!
1551     \param parent : platform handle of the parent window or view
1552     \param type : \ref platformUIType which should be created */
1553     extern(Windows) tresult attached (void* parent, FIDString type)
1554     {
1555         debug(logVST3Client) debugLog(">attached".ptr);
1556         debug(logVST3Client) scope(exit) debugLog("<attached".ptr);
1557 
1558         if (_vst3Client._client.hasGUI() )
1559         {
1560             if (kResultTrue != isPlatformTypeSupported(type))
1561                 return kResultFalse;
1562 
1563             GraphicsBackend backend = GraphicsBackend.autodetect;
1564             if (!convertPlatformToGraphicsBackend(type, &backend))
1565                 return kResultFalse;
1566 
1567             _graphicsMutex.lock();
1568             scope(exit) _graphicsMutex.unlock();
1569 
1570             _vst3Client._client.openGUI(parent, null, cast(GraphicsBackend)backend);
1571             return kResultTrue;
1572         }
1573         return kResultFalse;
1574 
1575     }
1576 
1577     /** The parent window of the view is about to be destroyed.
1578     You have to remove all your own views from the parent window or view. */
1579     extern(Windows) tresult removed ()
1580     {
1581         debug(logVST3Client) debugLog(">removed".ptr);
1582 
1583         if (_vst3Client._client.hasGUI() )
1584         {
1585             _graphicsMutex.lock();
1586             scope(exit) _graphicsMutex.unlock();
1587             _vst3Client._plugFrame = null;
1588             _vst3Client._currentView = null;
1589             _vst3Client._client.closeGUI();
1590             debug(logVST3Client) debugLog("<removed".ptr);
1591             return kResultTrue;
1592         }
1593         return kResultFalse;
1594     }
1595 
1596     /** Handling of mouse wheel. */
1597     extern(Windows) tresult onWheel (float distance)
1598     {
1599         debug(logVST3Client) debugLog(">onWheel".ptr);
1600         debug(logVST3Client) scope(exit) debugLog("<onWheel".ptr);
1601         return kResultFalse;
1602     }
1603 
1604     /** Handling of keyboard events : Key Down.
1605     \param key : unicode code of key
1606     \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h
1607     \param modifiers : any combination of modifiers - see \ref KeyModifier in keycodes.h
1608     \return kResultTrue if the key is handled, otherwise kResultFalse. \n
1609     <b> Please note that kResultTrue must only be returned if the key has really been
1610     handled. </b> Otherwise key command handling of the host might be blocked! */
1611     extern(Windows) tresult onKeyDown (char16 key, int16 keyCode, int16 modifiers)
1612     {
1613         debug(logVST3Client) debugLog(">onKeyDown".ptr);
1614         debug(logVST3Client) scope(exit) debugLog("<onKeyDown".ptr);
1615         return kResultFalse;
1616     }
1617 
1618     /** Handling of keyboard events : Key Up.
1619     \param key : unicode code of key
1620     \param keyCode : virtual keycode for non ascii keys - see \ref VirtualKeyCodes in keycodes.h
1621     \param modifiers : any combination of KeyModifier - see \ref KeyModifier in keycodes.h
1622     \return kResultTrue if the key is handled, otherwise return kResultFalse. */
1623     extern(Windows) tresult onKeyUp (char16 key, int16 keyCode, int16 modifiers)
1624     {
1625         debug(logVST3Client) debugLog(">onKeyUp".ptr);
1626         debug(logVST3Client) scope(exit) debugLog("<onKeyUp".ptr);
1627         return kResultFalse;
1628     }
1629 
1630     /** Returns the size of the platform representation of the view. */
1631     extern(Windows) tresult getSize (ViewRect* size)
1632     {
1633         debug(logVST3Client) debugLog(">getSize".ptr);
1634         debug(logVST3Client) scope(exit) debugLog("<getSize".ptr);
1635         if (!_vst3Client._client.hasGUI())
1636             return kResultFalse;
1637 
1638         _graphicsMutex.lock();
1639         scope(exit) _graphicsMutex.unlock();
1640 
1641         int widthLogicalPixels, heightLogicalPixels;
1642         if (_vst3Client._client.getGUISize(&widthLogicalPixels, &heightLogicalPixels))
1643         {
1644             size.left = 0;
1645             size.top = 0;
1646             size.right = widthLogicalPixels;
1647             size.bottom = heightLogicalPixels;
1648             return kResultTrue;
1649         }
1650         return kResultFalse;
1651     }
1652 
1653     /** Resizes the platform representation of the view to the given rect. Note that if the Plug-in
1654     *  requests a resize (IPlugFrame::resizeView ()) onSize has to be called afterward. */
1655     extern(Windows) tresult onSize (ViewRect* newSize)
1656     {
1657         _graphicsMutex.lock();
1658         scope(exit) _graphicsMutex.unlock();
1659 
1660         auto graphics = _vst3Client._client.getGraphics();
1661         assert(graphics !is null);
1662 
1663         graphics.nativeWindowResize(newSize.getWidth(), newSize.getHeight()); // Tell the IWindow to position itself at newSize.
1664 
1665         return kResultOk;
1666     }
1667 
1668     /** Focus changed message. */
1669     extern(Windows) tresult onFocus (TBool state)
1670     {
1671         return kResultOk;
1672     }
1673 
1674     /** Sets IPlugFrame object to allow the Plug-in to inform the host about resizing. */
1675     extern(Windows) tresult setFrame (IPlugFrame frame)
1676     {
1677         _vst3Client._plugFrame = frame;
1678         _vst3Client._currentView = cast(IPlugView)this;
1679         return kResultTrue;
1680     }
1681 
1682     /** Is view sizable by user. */
1683     extern(Windows) tresult canResize ()
1684     {
1685         _graphicsMutex.lock();
1686         scope(exit) _graphicsMutex.unlock();
1687         auto graphics = _vst3Client._client.getGraphics();
1688         assert(graphics !is null);
1689 
1690         tresult result = graphics.isResizeable() ? kResultTrue : kResultFalse;
1691         return result;
1692     }
1693 
1694     /** On live resize this is called to check if the view can be resized to the given rect, if not
1695     *  adjust the rect to the allowed size. */
1696     extern(Windows) tresult checkSizeConstraint (ViewRect* rect)
1697     {
1698         _graphicsMutex.lock();
1699         scope(exit) _graphicsMutex.unlock();
1700         auto graphics = _vst3Client._client.getGraphics();
1701         assert(graphics !is null);
1702 
1703         int W = rect.getWidth();
1704         int H = rect.getHeight();
1705 
1706         DAW daw = _vst3Client._hostCommand.getDAW();
1707         bool reaper = (daw == DAW.Reaper);
1708         bool avoidLargerSizeInCheckConstraint = reaper;
1709 
1710         // Force the default size on first opening in REAPER.
1711         // See Issue #559.
1712         // This is because REAPER will try another size for no apparent reason, in VST3 + (Linux or macOS).
1713         bool avoidLiveResize = false;
1714         version(linux)
1715             if (reaper) avoidLiveResize = true;
1716         version(OSX)
1717             if (reaper) avoidLiveResize = true;
1718         if (avoidLiveResize)
1719         {
1720             graphics.getGUISize(&W, &H);
1721         }
1722         else if (avoidLargerSizeInCheckConstraint)
1723         {
1724             // REAPER want instead the max size that fits.
1725             graphics.getMaxSmallerValidSize(&W, &H);
1726         }
1727         else
1728         {
1729             // Most other host accomodate with a larger size.
1730             // Note: It is important to take the nearest here rather than the max size that fits, but is smaller?
1731             // it is to allow an upsize when dragging only one size of the window.
1732             // Else, the window can only be shrink in VST3 hosts (tested in Cubase, FL, Waveform, the test host...) unless the 
1733             // window corner is used.
1734             // FUTURE: allow any size, since GUIGraphics does the best it can.
1735             //         Check it on Studio One! which is more demanding.
1736             graphics.getNearestValidSize(&W, &H);
1737         }
1738 
1739         rect.right = rect.left + W;
1740         rect.bottom = rect.top + H;
1741 
1742         return kResultTrue;
1743     }
1744    
1745 private:
1746     VST3Client _vst3Client;
1747     UncheckedMutex _graphicsMutex;
1748 
1749     static bool convertPlatformToGraphicsBackend(FIDString type, GraphicsBackend* backend)
1750     {
1751         if (strcmp(type, kPlatformTypeHWND.ptr) == 0)
1752         {
1753             *backend = GraphicsBackend.win32;
1754             return true;
1755         }
1756         if (strcmp(type, kPlatformTypeNSView.ptr) == 0)
1757         {
1758             *backend = GraphicsBackend.cocoa;
1759             return true;
1760         }
1761         if (strcmp(type, kPlatformTypeX11EmbedWindowID.ptr) == 0)
1762         {
1763             *backend = GraphicsBackend.x11;
1764             return true;
1765         }
1766         return false;
1767     }
1768 }
1769 
1770 
1771 
1772 // Host commands
1773 
1774 class VST3HostCommand : IHostCommand
1775 {
1776 public:
1777 nothrow:
1778 @nogc:
1779 
1780     this(VST3Client vst3Client)
1781     {
1782         _vst3Client = vst3Client;
1783     }
1784 
1785     override void beginParamEdit(int paramIndex)
1786     {
1787         auto handler = _vst3Client._handler;
1788         if (handler)
1789             handler.beginEdit(convertClientParamIndexToParamID(paramIndex));
1790     }
1791 
1792     override void paramAutomate(int paramIndex, float value)
1793     {
1794         auto handler = _vst3Client._handler;
1795         if (handler)
1796             handler.performEdit(convertClientParamIndexToParamID(paramIndex), value);
1797     }
1798 
1799     override void endParamEdit(int paramIndex)
1800     {
1801         auto handler = _vst3Client._handler;
1802         if (handler)
1803             handler.endEdit(convertClientParamIndexToParamID(paramIndex));
1804     }
1805 
1806     override bool requestResize(int width, int height)
1807     {
1808         IPlugFrame frame = _vst3Client._plugFrame;
1809         IPlugView view = _vst3Client._currentView;
1810         if (frame is null || view is null)
1811             return false;
1812         ViewRect rect;
1813         rect.left = 0;
1814         rect.top = 0;
1815         rect.right = width;
1816         rect.bottom = height;
1817         return frame.resizeView(view, &rect) == kResultOk;
1818     }
1819 
1820     DAW getDAW()
1821     {
1822         return _vst3Client._daw;
1823     }
1824 
1825     override PluginFormat getPluginFormat()
1826     {
1827         return PluginFormat.vst3;
1828     }
1829 
1830 private:
1831     VST3Client _vst3Client;
1832 }
1833 
1834 /// Returns: `true` if that graphics backend is supported on this platform in VST3
1835 private bool isGraphicsBackendSupported(GraphicsBackend backend) nothrow @nogc
1836 {
1837     version(Windows)
1838         return (backend == GraphicsBackend.win32);
1839     else version(OSX)
1840     {
1841         return (backend == GraphicsBackend.cocoa);
1842     }
1843     else version(linux)
1844         return (backend == GraphicsBackend.x11);
1845     else
1846         static assert(false, "Unsupported OS");
1847 }
1848 
1849 // Store changes of a single paramter over the current buffer.
1850 struct ParameterTracks
1851 {
1852     ParamID id;
1853     double* values; // values for each split sub-buffer, plus the final value. This points into a buffer shared across parameters.
1854 }