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