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