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