1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2015 - 2017 Auburn Sounds
6 
7 Portions copyright other contributors, see each source file for more information
8 
9 This software is provided 'as-is', without any express or implied warranty.  In no event will the authors be held liable for any damages arising from the use of this software.
10 
11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
12 
13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
15 1. This notice may not be removed or altered from any source distribution.
16 */
17 
18 /// Base client implementation. Every plugin format implementation hold a `Client` member.
19 module dplug.client.client;
20 
21 import core.atomic;
22 import core.stdc.string;
23 import core.stdc.stdio;
24 import core.stdc.stdlib: free;
25 
26 import dplug.core.nogc;
27 import dplug.core.math;
28 import dplug.core.vec;
29 import dplug.core.sync;
30 
31 import dplug.client.params;
32 import dplug.client.preset;
33 import dplug.client.midi;
34 import dplug.client.graphics;
35 import dplug.client.daw;
36 
37 
38 enum PluginFormat
39 {
40     vst2,
41     vst3,
42     aax,
43     auv2,
44     lv2
45 }
46 
47 
48 /// A plugin client can send commands to the host.
49 /// This interface is injected after the client creation though.
50 interface IHostCommand
51 {
52 nothrow @nogc:
53 
54     /// Notifies the host that editing of a parameter has begun from UI side.
55     void beginParamEdit(int paramIndex);
56 
57     /// Notifies the host that a parameter was edited from the UI side.
58     /// This enables the host to record automation.
59     /// It is illegal to call `paramAutomate` outside of a `beginParamEdit`/`endParamEdit` pair.
60     void paramAutomate(int paramIndex, float value);
61 
62     /// Notifies the host that editing of a parameter has finished from UI side.
63     void endParamEdit(int paramIndex);
64 
65     /// Requests to the host a resize of the plugin window's PARENT window, given logical pixels of plugin window.
66     ///
67     /// Note: UI widgets and plugin format clients have different coordinate systems.
68     ///
69     /// Params:
70     ///     width New width of the plugin, in logical pixels.
71     ///     height New height of the plugin, in logical pixels.
72     /// Returns: `true` if the host parent window has been resized.
73     bool requestResize(int widthLogicalPixels, int heightLogicalPixels);
74 
75     /// Report the identied host name (DAW).
76     /// MAYDO: not available for LV2.
77     DAW getDAW();
78 
79     /// Gets the plugin format used at runtime. Version identifier may not be enough in the future, in case of 
80     /// unity builds.
81     PluginFormat getPluginFormat();
82 }
83 
84 // Plugin version in major.minor.patch form.
85 struct PluginVersion
86 {
87 nothrow:
88 @nogc:
89     int major;
90     int minor;
91     int patch;
92 
93     int toVSTVersion() pure const
94     {
95         assert(major < 10 && minor < 10 && patch < 10);
96         return major * 1000 + minor * 100 + patch*10;
97     }
98 
99     int toAUVersion() pure const
100     {
101         assert(major < 256 && minor < 256 && patch < 256);
102         return (major << 16) | (minor << 8) | patch;
103     }
104 
105     int toAAXPackageVersion() pure const
106     {
107         // For AAX, considered binary-compatible unless major version change
108         return major;
109     }
110 
111     void toVST3VersionString(char* outBuffer, int bufLength) const
112     {
113         snprintf(outBuffer, bufLength, "%d.%d.%d", major, minor, patch);
114 
115         // DigitalMars's snprintf doesn't always add a terminal zero
116         if (bufLength > 0)
117             outBuffer[bufLength-1] = '\0';
118     }
119 }
120 
121 
122 // Statically known features of the plugin.
123 // There is some default for explanation purpose, but you really ought to override them all.
124 // Most of it is redundant with plugin.json, in the future the JSON will be parsed instead.
125 struct PluginInfo
126 {
127     string vendorName = "Witty Audio";
128 
129     /// A four char vendor "unique" ID
130     char[4] vendorUniqueID = "Wity";
131 
132     /// The vendor email adress for support. Can be null.
133     string vendorSupportEmail = null;
134 
135     /// Plugin name.
136     string pluginName = "Destructatorizer";
137 
138     /// Plugin web page. Can be null.
139     string pluginHomepage = null;
140 
141     /// Used for both VST and AU.
142     /// In AU it is namespaced by the manufacturer. In VST it
143     /// should be unique. While it seems no VST host use this
144     /// ID as a unique way to identify a plugin, common wisdom
145     /// is to try to get a sufficiently random one.
146     char[4] pluginUniqueID = "WiDi";
147 
148     // Plugin version information.
149     // It's important that the version you fill at runtime is identical to the
150     // one in `plugin.json` else you won't pass AU validation.
151     //
152     // Note: For AU, 0.x.y is supposed to mean "do not cache", however it is
153     //       unknown what it actually changes. AU caching hasn't caused any problem
154     //       and can probably be ignored.
155     PluginVersion publicVersion = PluginVersion(0, 0, 0);
156 
157     /// True if the plugin has a graphical UI. Easy way to disable it.
158     bool hasGUI = false;
159 
160     /// True if the plugin "is a synth". This has only a semantic effect.
161     bool isSynth = false;
162 
163     /// True if the plugin should receive MIDI events.
164     /// Warning: receiving MIDI forces you to call `getNextMidiMessages`
165     /// with the right number of `frames`, every buffer.
166     bool receivesMIDI = false;
167 
168     /// True if the plugin sends MIDI events.
169     bool sendsMIDI = false;
170 
171     /// Used for being at the right place in list of plug-ins.
172     PluginCategory category;
173 
174     /// Used as name of the bundle in VST.
175     string VSTBundleIdentifier;
176 
177     /// Used as name of the bundle in AU.
178     string AUBundleIdentifier;
179 
180     /// Used as name of the bundle in AAX.
181     string AAXBundleIdentifier;
182 }
183 
184 /// This allows to write things life tempo-synced LFO.
185 struct TimeInfo
186 {
187     /// BPM
188     double tempo = 120;
189 
190     /// Current time from the beginning of the song in samples.
191     /// This time can easily be negative, since eg. in REAPER
192     /// you can change song beginning with "Project start time" settings.
193     long timeInSamples = 0;
194 
195     /// Whether the host sequencer is currently playing
196     bool hostIsPlaying;
197 }
198 
199 /// Describe a combination of input channels count and output channels count
200 struct LegalIO
201 {
202     int numInputChannels;
203     int numOutputChannels;
204 }
205 
206 /// This is the interface used by the GUI, to reduce coupling and avoid exposing the whole of `Client` to it.
207 /// It should eventually allows to supersede/hide IHostCommand.
208 interface IClient
209 {
210 nothrow:
211 @nogc:
212     /// Requests a resize of the plugin window, notifying the host.
213     /// Returns: `true` if succeeded.
214     ///
215     /// Params:
216     ///     width New width of the plugin, in logical pixels.
217     ///     height New height of the plugin, in logical pixels.
218     bool requestResize(int widthLogicalPixels, int heightLogicalPixels);
219 
220     /// Report the identied host name (DAW).
221     DAW getDAW();
222 
223     /// Gets the plugin format used at runtime. Version identifier may not be enough in the future, in case of 
224     /// unity builds.
225     PluginFormat getPluginFormat();
226 }
227 
228 /// Plugin interface, from the client point of view.
229 /// This client has no knowledge of thread-safety, it must be handled externally.
230 /// User plugins derivate from this class.
231 /// Plugin formats wrappers owns one dplug.plugin.Client as a member.
232 ///
233 /// Note: this is an architecture failure since there are 3 users of that interface:
234 ///   1. the plugin "client" implementation (= product), 
235 ///   2. the format client
236 ///   3. the UI, directly
237 ///  Those should be splitted cleanly.
238 class Client : IClient
239 {
240 public:
241 nothrow:
242 @nogc:
243 
244     this()
245     {
246         _info = buildPluginInfo();
247 
248         // Create legal I/O combinations
249         _legalIOs = buildLegalIO();
250 
251         // Create parameters.
252         _params = buildParameters();
253 
254         // Check parameter consistency
255         // This avoid mistake when adding/reordering parameters in a plugin.
256         foreach(size_t i, Parameter param; _params)
257         {
258             // If you fail here, this means your buildParameter() override is incorrect.
259             // Check the values of the index you're giving.
260             // They should be 0, 1, 2, ..., N-1
261             // Maybe you have duplicated a line or misordered them.
262             assert(param.index() == i);
263 
264             // Sets owner reference.
265             param.setClientReference(this);
266         }
267 
268         // Create presets
269         _presetBank = mallocNew!PresetBank(this, buildPresets());
270 
271 
272         _maxFramesInProcess = maxFramesInProcess();
273 
274         _maxInputs = 0;
275         _maxOutputs = 0;
276         foreach(legalIO; _legalIOs)
277         {
278             if (_maxInputs < legalIO.numInputChannels)
279                 _maxInputs = legalIO.numInputChannels;
280             if (_maxOutputs < legalIO.numOutputChannels)
281                 _maxOutputs = legalIO.numOutputChannels;
282         }
283 
284         _inputMidiQueue = makeMidiQueue(); // PERF: only init those for plugins that need it?
285         _outputMidiQueue = makeMidiQueue();
286 
287         if (sendsMIDI)
288         {
289             _midiOutFromUIMutex = makeMutex();
290         }
291     }
292 
293     ~this()
294     {
295         // Destroy graphics
296         if (_graphics !is null)
297         {
298             // Acquire _graphicsIsAvailable forever
299             // so that it's the last time the audio uses it,
300             // and we can wait for its exit in _graphics destructor
301             while(!cas(&_graphicsIsAvailable, true, false))
302             {
303                 // MAYDO: relax CPU
304             }
305             _graphics.destroyFree();
306         }
307 
308         // Destroy presets
309         _presetBank.destroyFree();
310 
311         // Destroy parameters
312         foreach(p; _params)
313             p.destroyFree();
314         _params.freeSlice();
315         _legalIOs.freeSlice();
316     }
317 
318     final int maxInputs() pure const nothrow @nogc
319     {
320         return _maxInputs;
321     }
322 
323     final int maxOutputs() pure const nothrow @nogc
324     {
325         return _maxOutputs;
326     }
327 
328     /// Returns: Array of parameters.
329     final inout(Parameter[]) params() inout nothrow @nogc
330     {
331         return _params;
332     }
333 
334     /// Returns: Array of legal I/O combinations.
335     final LegalIO[] legalIOs() nothrow @nogc
336     {
337         return _legalIOs;
338     }
339 
340     /// Returns: true if the following I/O combination is a legal one.
341     ///          < 0 means "do not check"
342     final bool isLegalIO(int numInputChannels, int numOutputChannels) pure const nothrow @nogc
343     {
344         foreach(io; _legalIOs)
345             if  ( ( (numInputChannels < 0)
346                     ||
347                     (io.numInputChannels == numInputChannels) )
348                   &&
349                   ( (numOutputChannels < 0)
350                     ||
351                     (io.numOutputChannels == numOutputChannels) )
352                 )
353                 return true;
354 
355         return false;
356     }
357 
358     /// Returns: Array of presets.
359     final PresetBank presetBank() nothrow @nogc
360     {
361         return _presetBank;
362     }
363 
364     /// Returns: The parameter indexed by index.
365     final inout(Parameter) param(int index) inout nothrow @nogc
366     {
367         return _params.ptr[index];
368     }
369 
370     /// Returns: true if index is a valid parameter index.
371     final bool isValidParamIndex(int index) const nothrow @nogc
372     {
373         return index >= 0 && index < _params.length;
374     }
375 
376     /// Returns: true if index is a valid input index.
377     final bool isValidInputIndex(int index) nothrow @nogc
378     {
379         return index >= 0 && index < maxInputs();
380     }
381 
382     /// Returns: true if index is a valid output index.
383     final bool isValidOutputIndex(int index) nothrow @nogc
384     {
385         return index >= 0 && index < maxOutputs();
386     }
387 
388     /// Note: openGUI, getGUISize, getGraphics and closeGUI are guaranteed
389     /// synchronized by the client implementation
390     /// Only allowed for client implementation.
391     final void* openGUI(void* parentInfo, void* controlInfo, GraphicsBackend backend) nothrow @nogc
392     {
393         createGraphicsLazily();
394         assert(_hostCommand !is null);
395         return (cast(IGraphics)_graphics).openUI(parentInfo, controlInfo, this, backend);
396     }
397 
398     /// Only allowed for client implementation.
399     final bool getGUISize(int* widthLogicalPixels, int* heightLogicalPixels) nothrow @nogc
400     {
401         createGraphicsLazily();
402         auto graphics = (cast(IGraphics)_graphics);
403         if (graphics)
404         {
405             graphics.getGUISize(widthLogicalPixels, heightLogicalPixels);
406             return true;
407         }
408         else
409             return false;
410     }
411 
412     /// Close the plugin UI if one was opened.
413     /// Note: OBS Studio will happily call effEditClose without having called effEditOpen.
414     /// Only allowed for client implementation.
415     final void closeGUI() nothrow @nogc
416     {
417         auto graphics = (cast(IGraphics)_graphics);
418         if (graphics)
419         {
420             graphics.closeUI();
421         }
422     }
423 
424     /// This creates the GUIGraphics object lazily, and return it without synchronization.
425     /// Only allowed for client implementation.
426     final IGraphics getGraphics()
427     {
428         createGraphicsLazily();
429         return (cast(IGraphics)_graphics);
430     }
431 
432     // This should be called only by a client implementation.
433     void setParameterFromHost(int index, float value) nothrow @nogc
434     {
435         param(index).setFromHost(value);
436     }
437 
438     /// Override if you create a plugin with UI.
439     /// The returned IGraphics must be allocated with `mallocNew`.
440     /// `plugin.json` needs to have a "hasGUI" key equal to true, else this callback is never called.
441     IGraphics createGraphics() nothrow @nogc
442     {
443         return null;
444     }
445 
446     /// Intended from inside the audio thread, in `process`.
447     /// Enqueue one MIDI message on the output MIDI priority queue, so that it is
448     /// eventually sent.
449     /// Its offset is relative to the current buffer, and you can send messages arbitrarily 
450     /// in the future too.
451     void sendMIDIMessage(MidiMessage message) nothrow @nogc
452     {
453         _outputMidiQueue.enqueue(message);
454     }
455 
456     /// Send MIDI from inside the UI.
457     /// Intended to be called from inside an UI event callback.
458     ///
459     /// Enqueue several MIDI messages in a synchronized manner, so that they are sent all at once,
460     /// as early as possible as "live" MIDI messages.
461     /// No guarantee of any timing for these messages, for example this can be in response to 
462     /// a key press on a virtual keyboard.
463     /// The messages don't have to be ordered if they are spaced, but have to be if they 
464     /// have the same `offset`. 
465     ///
466     /// Note: It is guaranteed that all messages passed this way will keep their offset 
467     ///       relationship in MIDI output. (Typically such a messages would all have a zero
468     ///       timestamp).
469     ///       Though they are sent as soon as possible in a best effort manner, their relative 
470     ///       offset is preserved.
471     ///       Its offset is relative to the current buffer, and you can send messages arbitrarily 
472     ///       in the future too.
473     void sendMIDIMessagesFromUI(const(MidiMessage)[] messages) nothrow @nogc
474     {
475         _midiOutFromUIMutex.lock();
476         
477         foreach(msg; messages)
478             _outputMidiFromUI.pushBack(msg);
479         
480         _midiOutFromUIMutex.unlock();
481     }
482 
483     /// Getter for the IGraphics interface
484     /// This is intended ONLY for the audio thread inside processing and has acquire semantics.
485     /// Not reentrant! You can't call this twice without a graphicsRelease first.
486     /// THIS CAN RETURN NULL EVEN AFTER HAVING RETURNED NON-NULL AT ONE POINT.
487     /// Returns: null if feedback from audio thread is not welcome.
488     final IGraphics graphicsAcquire() nothrow @nogc
489     {
490         if (cas(&_graphicsIsAvailable, true, false)) // exclusive, since there is only one audio thread normally
491             return _graphics;
492         else
493             return null;
494     }
495 
496     /// Mirror function to release the IGraphics from the audio-thread.
497     /// Do not call if graphicsAcquire() returned `null`.
498     final void graphicsRelease() nothrow @nogc
499     {
500         // graphicsAcquire should have been called before
501         // MAYDO: which memory order here? Don't looks like we need a barrier.
502         atomicStore(_graphicsIsAvailable, true);
503     }
504 
505     // Getter for the IHostCommand interface
506     final IHostCommand hostCommand() nothrow @nogc
507     {
508         return _hostCommand;
509     }
510 
511     /// Override to clear state (eg: resize and clear delay lines) and allocate buffers.
512     /// Note: `reset` should not be called directly by plug-in format implementations. Use `resetFromHost` if you write a new client.
513     abstract void reset(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc;
514 
515     /// Override to set the plugin latency in samples.
516     /// Plugin latency can depend on `sampleRate` but no other value.
517     /// If you want your latency to depend on a `Parameter` your only choice is to
518     /// pessimize the needed latency and compensate in the process callback.
519     /// Returns: Plugin latency in samples.
520     /// Note: this can absolutely be called before `reset` was called, be prepared.
521     int latencySamples(double sampleRate) nothrow @nogc
522     {
523         return 0; // By default, no latency introduced by plugin
524     }
525 
526     /// Override to set the plugin tail length in seconds.
527     ///
528     /// This is the amount of time before silence is reached with a silent input, on the worst
529     /// possible settings.
530     ///
531     /// Returns: Plugin tail size in seconds.
532     ///     - Returning 0.0f means that as soon as your input is silent, the output will be silent. 
533     ///       It isn't a special value.
534     ///     - Returning `float.infinity` means that the host should not optimize calls to `processAudio`.
535     ///       If your plugin is a synth, or an effect generating sound, you MUST return `float.infinity`.
536     ///     - Otherwise, returning a particular tail size is the regular meaning.
537     ///
538     float tailSizeInSeconds() nothrow @nogc
539     {
540         // Default: always call `processAudio`. This is safest.
541         //
542         // It is recommended to setup this override at one point in developemnt, especially for an effect plugin.
543         // This allows VST3 and AU hosts to optimize things.
544         //
545         // For an effect 2 secs is a good starting point. Which should be safe for most effects plugins except delay or reverb
546         // Warning: plugins have often MUCH more tail size than expected!
547         // Don't reduce to a shorter time unless you know for sure what you are doing.
548         // Synths, and effects generating audio from MIDI should return `float.infinity`.
549 
550         return float.infinity;
551     }
552 
553     /// Override to declare the maximum number of samples to accept
554     /// If greater, the audio buffers will be splitted up.
555     /// This splitting have several benefits:
556     /// - help allocating temporary audio buffers on the stack
557     /// - keeps memory usage low and reuse it
558     /// - allow faster-than-buffer-size parameter changes (VST3)
559     /// Returns: Maximum number of samples
560     /// Warning: Some buffersize-related bugs might be hidden by having sub-buffers.
561     ///          If yoy are looking for a buffersize bug, maybe try to disable sub-buffers
562     ///          by returning the default 0.
563     int maxFramesInProcess() nothrow @nogc
564     {
565         return 0; // default returns 0 which means "do not split buffers"
566     }
567 
568     /// Process some audio.
569     /// Override to make some noise.
570     /// In processAudio you are always guaranteed to get valid pointers
571     /// to all the channels the plugin requested.
572     /// Unconnected input pins are zeroed.
573     /// This callback is the only place you may call `getNextMidiMessages()` (it is
574     /// even required for plugins receiving MIDI).
575     ///
576     /// Number of frames are guaranteed to be less or equal to what the last reset() call said.
577     /// Number of inputs and outputs are guaranteed to be exactly what the last reset() call said.
578     /// Warning: Do not modify the output pointers!
579     abstract void processAudio(const(float*)[] inputs,    // array of input channels
580                                float*[] outputs,           // array of output channels
581                                int frames,                // number of sample in each input & output channel
582                                TimeInfo timeInfo          // time information associated with this signal frame
583                                ) nothrow @nogc;
584 
585     /// Should only be called in `processAudio`.
586     /// This return a slice of MIDI messages corresponding to the next `frames` samples.
587     /// Useful if you don't want to process messages every samples, or every split buffer.
588     final const(MidiMessage)[] getNextMidiMessages(int frames) nothrow @nogc
589     {
590         return _inputMidiQueue.getNextMidiMessages(frames);
591     }
592 
593     /// Returns a new default preset.
594     final Preset makeDefaultPreset() nothrow @nogc
595     {
596         // MAYDO: use mallocSlice for perf
597         auto values = makeVec!float();
598         foreach(param; _params)
599             values.pushBack(param.getNormalizedDefault());
600 
601         // Perf: one could avoid malloc to copy those arrays again there
602         float[] valuesSlice = values.releaseData;
603         Preset result = mallocNew!Preset("Default", valuesSlice);
604         free(valuesSlice.ptr);
605         return result;
606     }
607 
608     // Getters for fields in _info
609 
610     final bool hasGUI() pure const nothrow @nogc
611     {
612         return _info.hasGUI;
613     }
614 
615     final bool isSynth() pure const nothrow @nogc
616     {
617         return _info.isSynth;
618     }
619 
620     final bool receivesMIDI() pure const nothrow @nogc
621     {
622         return _info.receivesMIDI;
623     }
624 
625     final bool sendsMIDI() pure const nothrow @nogc
626     {
627         return _info.sendsMIDI;
628     }
629 
630     final string vendorName() pure const nothrow @nogc
631     {
632         return _info.vendorName;
633     }
634 
635     final char[4] getVendorUniqueID() pure const nothrow @nogc
636     {
637         return _info.vendorUniqueID;
638     }
639 
640     final string getVendorSupportEmail() pure const nothrow @nogc
641     {
642         return _info.vendorSupportEmail;
643     }
644 
645     final string pluginName() pure const nothrow @nogc
646     {
647         return _info.pluginName;
648     }
649 
650     final string pluginHomepage() pure const nothrow @nogc
651     {
652         return _info.pluginHomepage;
653     }
654 
655     final PluginCategory pluginCategory() pure const nothrow @nogc
656     {
657         return _info.category;
658     }
659 
660     final string VSTBundleIdentifier() pure const nothrow @nogc
661     {
662         return _info.VSTBundleIdentifier;
663     }
664 
665     final string AUBundleIdentifier() pure const nothrow @nogc
666     {
667         return _info.AUBundleIdentifier;
668     }
669 
670     final string AAXBundleIdentifier() pure const nothrow @nogc
671     {
672         return _info.AAXBundleIdentifier;
673     }
674 
675     /// Returns: Plugin "unique" ID.
676     final char[4] getPluginUniqueID() pure const nothrow @nogc
677     {
678         return _info.pluginUniqueID;
679     }
680 
681     /// Returns: Plugin full name "$VENDOR $PRODUCT"
682     final void getPluginFullName(char* p, int bufLength) const nothrow @nogc
683     {
684         snprintf(p, bufLength, "%.*s %.*s",
685                  cast(int)(_info.vendorName.length), _info.vendorName.ptr,
686                  cast(int)(_info.pluginName.length), _info.pluginName.ptr);
687 
688         // DigitalMars's snprintf doesn't always add a terminal zero
689         if (bufLength > 0)
690         {
691             p[bufLength-1] = '\0';
692         }
693     }
694 
695     /// Returns: Plugin version in x.x.x.x decimal form.
696     final PluginVersion getPublicVersion() pure const nothrow @nogc
697     {
698         return _info.publicVersion;
699     }
700 
701     /// Boilerplate function to get the value of a `FloatParameter`, for use in `processAudio`.
702     final T readParam(T)(int paramIndex) nothrow @nogc
703         if (is(T == float))
704     {
705         auto p = param(paramIndex);
706         assert(cast(FloatParameter)p !is null); // check it's a FloatParameter
707         return unsafeObjectCast!FloatParameter(p).valueAtomic();
708     }
709 
710     /// Boilerplate function to get the value of an `IntParameter`, for use in `processAudio`.
711     final T readParam(T)(int paramIndex) nothrow @nogc
712         if (is(T == int) && !is(T == enum))
713     {
714         auto p = param(paramIndex);
715         assert(cast(IntegerParameter)p !is null); // check it's an IntParameter
716         return unsafeObjectCast!IntegerParameter(p).valueAtomic();
717     }
718 
719     /// Boilerplate function to get the value of an `EnumParameter`, for use in `processAudio`.
720     final T readParam(T)(int paramIndex) nothrow @nogc
721         if (is(T == enum))
722     {
723         auto p = param(paramIndex);
724         assert(cast(EnumParameter)p !is null); // check it's an EnumParameter
725         return cast(T)(unsafeObjectCast!EnumParameter(p).valueAtomic());
726     }
727 
728     /// Boilerplate function to get the value of a `BoolParameter`,for use in `processAudio`.
729     final T readParam(T)(int paramIndex) nothrow @nogc
730         if (is(T == bool))
731     {
732         auto p = param(paramIndex);
733         assert(cast(BoolParameter)p !is null); // check it's a BoolParameter
734         return unsafeObjectCast!BoolParameter(p).valueAtomic();
735     }
736 
737     /// For plugin format clients only.
738     final void setHostCommand(IHostCommand hostCommand) nothrow @nogc
739     {
740         _hostCommand = hostCommand;
741 
742         // In VST3, for accuracy of parameter automation we choose to split buffers in chunks of maximum 512.
743         // This avoids painful situations where parameters could higher precision.
744         if (hostCommand.getPluginFormat() == PluginFormat.vst3)
745         {
746             if (_maxFramesInProcess == 0 || _maxFramesInProcess > 512)
747                 _maxFramesInProcess = 512;
748         }
749     }
750 
751     /// For plugin format clients only.
752     /// Enqueues an incoming MIDI message.
753     void enqueueMIDIFromHost(MidiMessage message)
754     {
755         _inputMidiQueue.enqueue(message);
756     }
757 
758     /// For plugin format clients only.
759     /// This return a slice of MIDI messages to be sent for this (whole unsplit) buffer.
760     /// Internally, you need to either use split-buffering from this file, or if the format does
761     /// its own buffer split it needs to call `accumulateOutputMIDI` itself.
762     final const(MidiMessage)[] getAccumulatedOutputMidiMessages() nothrow @nogc
763     {
764         return _outputMidiMessages[];
765     }
766     /// For plugin format clients only.
767     /// Clear MIDI output buffer. Call it before `processAudioFromHost` or `accumulateOutputMIDI`.
768     /// What it also does it get all MIDI message from the UI, and add them to the priority queue, 
769     /// so that they may be accumulated like normal MIDI sent from the process callback.
770     final void clearAccumulatedOutputMidiMessages() nothrow @nogc
771     {
772         assert(sendsMIDI());
773 
774         _outputMidiMessages.clearContents();
775 
776         // Enqueue all messages from UI in the priority queue.
777         _midiOutFromUIMutex.lock();
778         foreach(msg; _outputMidiFromUI[])
779             _outputMidiQueue.enqueue(msg);
780         _outputMidiFromUI.clearContents();
781         _midiOutFromUIMutex.unlock();
782     }
783 
784     /// For plugin format clients only.
785     /// Calls processAudio repeatedly, splitting the buffers.
786     /// Splitting allow to decouple memory requirements from the actual host buffer size.
787     /// There is few performance penalty above 512 samples.
788     void processAudioFromHost(float*[] inputs,
789                               float*[] outputs,
790                               int frames,
791                               TimeInfo timeInfo,
792                               bool doNotSplit = false, // flag that exist in case the plugin client want to split itself
793                               ) nothrow @nogc
794     {
795         // In debug mode, fill all output audio buffers with `float.nan`.
796         // This avoids a plug-in forgetting to fill output buffers, which can happen if you
797         // implement silence detection badly.
798         // CAUTION: this assumes inputs and outputs buffers do not point into the same memory areas
799         debug
800         {
801             for (int k = 0; k < outputs.length; ++k)
802             {
803                 float* pOut = outputs[k];
804                 pOut[0..frames] = float.nan;
805             }
806         }
807 
808         if (_maxFramesInProcess == 0 || doNotSplit)
809         {
810             processAudio(inputs, outputs, frames, timeInfo);
811             if (sendsMIDI) accumulateOutputMIDI(frames);
812         }
813         else
814         {
815             // Slice audio in smaller parts
816             while (frames > 0)
817             {
818                 // Note: the last slice will be smaller than the others
819                 int sliceLength = frames;
820                 if (sliceLength > _maxFramesInProcess)
821                     sliceLength = _maxFramesInProcess;
822 
823                 processAudio(inputs, outputs, sliceLength, timeInfo);
824                 if (sendsMIDI) accumulateOutputMIDI(sliceLength);
825 
826                 // offset all input buffer pointers
827                 for (int i = 0; i < cast(int)inputs.length; ++i)
828                     inputs[i] = inputs[i] + sliceLength;
829 
830                 // offset all output buffer pointers
831                 for (int i = 0; i < cast(int)outputs.length; ++i)
832                     outputs[i] = outputs[i] + sliceLength;
833 
834                 frames -= sliceLength;
835 
836                 // timeInfo must be updated
837                 timeInfo.timeInSamples += sliceLength;
838             }
839             assert(frames == 0);
840         }
841     }
842 
843     /// For VST3 client only. Format clients that split the buffers themselves (for automation precision)
844     /// Need as well to accumulate MIDI output themselves.
845     /// See_also: `getAccumulatedOutputMidiMessages` for how to get those accumulated messages for the whole buffer.
846     final void accumulateOutputMIDI(int frames)
847     {
848         _outputMidiQueue.accumNextMidiMessages(_outputMidiMessages, frames);
849     }
850 
851     /// For plugin format clients only.
852     /// Calls `reset()`.
853     /// Must be called by the audio thread.
854     void resetFromHost(double sampleRate, int maxFrames, int numInputs, int numOutputs) nothrow @nogc
855     {
856         // Clear outstanding MIDI messages (now invalid)
857         _inputMidiQueue.initialize(); // MAYDO: should it push a MIDI message to mute all voices?
858 
859         _outputMidiQueue.initialize(); // TODO: that sounds fishy, what if we have send a note on but not the note off?
860 
861         // We potentially give to the client implementation a lower value
862         // for the maximum number of frames
863         if (_maxFramesInProcess != 0 && _maxFramesInProcess < maxFrames)
864             maxFrames = _maxFramesInProcess;
865 
866         // Calls the reset virtual call
867         reset(sampleRate, maxFrames, numInputs, numOutputs);
868     }
869 
870     /// For use by plugin format clients. This gives the buffer split size to use.
871     /// (0 == no split).
872     /// This is useful in the cast the format client wants to split buffers by itself.
873     final int getBufferSplitMaxFrames()
874     {
875         return _maxFramesInProcess;
876     }
877 
878     // <IClient>
879     override bool requestResize(int widthLogicalPixels, int heightLogicalPixels)
880     {
881         if (_hostCommand is null) 
882             return false;
883 
884         return _hostCommand.requestResize(widthLogicalPixels, heightLogicalPixels);
885     }
886 
887     override DAW getDAW()
888     {
889         assert(_hostCommand !is null);
890         return _hostCommand.getDAW();
891     }
892 
893     override PluginFormat getPluginFormat()
894     {
895         assert(_hostCommand !is null);
896         return _hostCommand.getPluginFormat();
897     }
898 
899     // </IClient>
900 
901 protected:
902 
903     /// Override this method to implement parameter creation.
904     /// This is an optional overload, default implementation declare no parameters.
905     /// The returned slice must be allocated with `malloc`/`mallocSlice` and contains
906     /// `Parameter` objects created with `mallocEmplace`.
907     Parameter[] buildParameters()
908     {
909         return [];
910     }
911 
912     /// Override this methods to load/fill presets.
913     /// This function must return a slice allocated with `malloc`,
914     /// that contains presets crteated with `mallocEmplace`.
915     Preset[] buildPresets() nothrow @nogc
916     {
917         auto presets = makeVec!Preset();
918         presets.pushBack( makeDefaultPreset() );
919         return presets.releaseData();
920     }
921 
922     /// Override this method to tell what plugin you are.
923     /// Mandatory override, fill the fields with care.
924     /// Note: this should not be called by a plugin client implementation directly.
925     ///       Access the content of PluginInfo through the various accessors.
926     abstract PluginInfo buildPluginInfo();
927 
928     /// Override this method to tell which I/O are legal.
929     /// The returned slice must be allocated with `malloc`/`mallocSlice`.
930     abstract LegalIO[] buildLegalIO();
931 
932     IGraphics _graphics;
933 
934     // Used as a flag that _graphics can be used (by audio thread or for destruction)
935     shared(bool) _graphicsIsAvailable = false;
936 
937     // Note: when implementing a new plug-in format, the format wrapper has to call
938     // `setHostCommand` and implement `IHostCommand`.
939     IHostCommand _hostCommand = null;
940 
941     PluginInfo _info;
942 
943 private:
944     Parameter[] _params;
945 
946     PresetBank _presetBank;
947 
948     LegalIO[] _legalIOs;
949 
950     int _maxInputs, _maxOutputs; // maximum number of input/outputs
951 
952     // Cache result of maxFramesInProcess(), maximum frame length
953     int _maxFramesInProcess;
954 
955     // Container for awaiting MIDI messages.
956     MidiQueue _inputMidiQueue;
957 
958     // Priority queue for sending MIDI messages.
959     MidiQueue _outputMidiQueue;
960 
961     // Protects MIDI out from UI.
962     UncheckedMutex _midiOutFromUIMutex;
963 
964     // Additional, unsorted messages to be sent, courtesy of the UI.
965     Vec!MidiMessage _outputMidiFromUI;
966 
967     // Accumulated output MIDI messages, for one unsplit buffer.
968     // Output MIDI messages, if any, are accumulated there.
969     Vec!MidiMessage _outputMidiMessages;
970 
971     final void createGraphicsLazily()
972     {
973         // First GUI opening create the graphics object
974         // no need to protect _graphics here since the audio thread
975         // does not write to it.
976         if ( (_graphics is null) && hasGUI())
977         {
978             // Why is the IGraphics created lazily? This allows to load a plugin very quickly,
979             // without opening its logical UI
980             IGraphics graphics = createGraphics();
981 
982             // Don't forget to override the createGraphics method!
983             assert(graphics !is null);
984 
985             _graphics = graphics;
986 
987             // Now that the UI is fully created, we enable the audio thread to use it
988             atomicStore(_graphicsIsAvailable, true);
989         }
990     }    
991 }
992 
993 /// Should be called in Client class during compile time
994 /// to parse a `PluginInfo` from a supplied json file.
995 PluginInfo parsePluginInfo(string json)
996 {
997     import std.json;
998     import std.string;
999     import std.conv;
1000 
1001     JSONValue j = parseJSON(json);
1002 
1003     static bool toBoolean(JSONValue value)
1004     {
1005         static if (__VERSION__ >= 2087)
1006         {
1007             if (value.type == JSONType.true_)
1008                 return true;
1009             if (value.type == JSONType.false_)
1010                 return false;
1011         }
1012         else
1013         {
1014             if (value.type == JSON_TYPE.TRUE)
1015                 return true;
1016             if (value.type == JSON_TYPE.FALSE)
1017                 return false;
1018         }
1019         throw new Exception(format("Expected a boolean, got %s instead", value));
1020     }
1021 
1022     // Check that a string is "x.y.z"
1023     // FUTURE: support larger integers than 0 to 9 in the string
1024     static PluginVersion parsePluginVersion(string value)
1025     {
1026         bool isDigit(char ch)
1027         {
1028             return ch >= '0' && ch <= '9';
1029         }
1030 
1031         if ( value.length != 5  ||
1032              !isDigit(value[0]) ||
1033              value[1] != '.'    ||
1034              !isDigit(value[2]) ||
1035              value[3] != '.'    ||
1036              !isDigit(value[4]))
1037         {
1038             throw new Exception("\"publicVersion\" should follow the form x.y.z (eg: \"1.0.0\")");
1039         }
1040 
1041         PluginVersion ver;
1042         ver.major = value[0] - '0';
1043         ver.minor = value[2] - '0';
1044         ver.patch = value[4] - '0';
1045         return ver;
1046     }
1047 
1048     PluginInfo info;
1049     info.vendorName = j["vendorName"].str;
1050     info.vendorUniqueID = j["vendorUniqueID"].str;
1051     info.pluginName = j["pluginName"].str;
1052     info.pluginUniqueID = j["pluginUniqueID"].str;
1053 
1054     if ("vendorSupportEmail" in j)
1055         info.vendorSupportEmail= j["vendorSupportEmail"].str;
1056 
1057     if ("pluginHomepage" in j)
1058         info.pluginHomepage = j["pluginHomepage"].str;
1059 
1060     if ("isSynth" in j)
1061         info.isSynth = toBoolean(j["isSynth"]);
1062     info.hasGUI = toBoolean(j["hasGUI"]);
1063     if ("receivesMIDI" in j)
1064         info.receivesMIDI = toBoolean(j["receivesMIDI"]);
1065     if ("sendsMIDI" in j)
1066         info.sendsMIDI = toBoolean(j["sendsMIDI"]);
1067 
1068     // Plugins that sends MIDI must also receives MIDI.
1069     if (info.sendsMIDI && !info.receivesMIDI)
1070         throw new Exception("A plugin that sends MIDI must also receives MIDI. Caution: a plugin that receives MIDI must call getNextMidiMessages() in the audio callback");
1071 
1072     info.publicVersion = parsePluginVersion(j["publicVersion"].str);
1073 
1074     string CFBundleIdentifierPrefix = j["CFBundleIdentifierPrefix"].str;
1075 
1076     string sanitizedName = sanitizeBundleString(info.pluginName);
1077     info.VSTBundleIdentifier = CFBundleIdentifierPrefix ~ ".vst." ~ sanitizedName;
1078     info.AUBundleIdentifier = CFBundleIdentifierPrefix ~ ".audiounit." ~ sanitizedName;
1079     info.AAXBundleIdentifier = CFBundleIdentifierPrefix ~ ".aax." ~ sanitizedName;
1080 
1081     PluginCategory category = parsePluginCategory(j["category"].str);
1082     if (category == PluginCategory.invalid)
1083         throw new Exception("Invalid \"category\" in plugin.json. Check out dplug.client.daw for valid values (eg: \"effectDynamics\").");
1084     info.category = category;
1085 
1086     // See Issue #581.
1087     // Check that we aren't leaking secrets in this build, through `import("plugin.json")`.
1088     void checkNotLeakingPassword(string key)
1089     {
1090         if (key in j)
1091         {
1092             string pwd = j[key].str;
1093             if (pwd == "!PROMPT")
1094                 return;
1095 
1096             if (pwd.length > 0 && pwd[0] == '$')
1097                 return; // using an envvar
1098 
1099             throw new Exception(
1100                         "\n*************************** WARNING ***************************\n\n"
1101                         ~ "  This build is using a plain text password in plugin.json\n"
1102                         ~ "  This will leak through `import(\"plugin.json\")`\n\n"
1103                         ~ "  Solutions:\n"
1104                         ~ "    1. Use environment variables, such as:\n"
1105                         ~ "           \"iLokPassword\": \"$ILOK_PASSWORD\"\n"
1106                         ~ "    2. Use the special value \"!PROMPT\", such as:\n"
1107                         ~ "           \"keyPassword-windows\": \"!PROMPT\"\n\n"
1108                         ~ "***************************************************************\n");
1109         }
1110     }
1111     checkNotLeakingPassword("keyPassword-windows");
1112     checkNotLeakingPassword("iLokPassword");
1113 
1114     void checkNotLeakingNoPrompt(string key)
1115     {
1116         if (key in j)
1117         {
1118             string pwd = j[key].str;
1119             if (pwd.length > 0 && pwd[0] == '$')
1120                 return; // using an envvar
1121 
1122             throw new Exception(
1123                         "\n*************************** WARNING ***************************\n\n"
1124                         ~ "  This build is using a plain text password in plugin.json\n"
1125                         ~ "  This will leak through `import(\"plugin.json\")`\n\n"
1126                         ~ "  Solution:\n"
1127                         ~ "       Use environment variables, such as:\n"
1128                         ~ "           \"appSpecificPassword-altool\": \"$APP_SPECIFIC_PASSWORD\"\n\n"
1129                         ~ "***************************************************************\n");
1130         }
1131     }
1132     checkNotLeakingNoPrompt("appSpecificPassword-altool");
1133     checkNotLeakingNoPrompt("appSpecificPassword-stapler");
1134 
1135     return info;
1136 }
1137 
1138 private string sanitizeBundleString(string s) pure
1139 {
1140     string r = "";
1141     foreach(dchar ch; s)
1142     {
1143         if (ch >= 'A' && ch <= 'Z')
1144             r ~= ch;
1145         else if (ch >= 'a' && ch <= 'z')
1146             r ~= ch;
1147         else if (ch == '.')
1148             r ~= ch;
1149         else
1150             r ~= "-";
1151     }
1152     return r;
1153 }