1 /*
2 Cockos WDL License
3 
4 Copyright (C) 2005 - 2015 Cockos Incorporated
5 Copyright (C) 2016 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    Audio Unit plug-in client.
19 */
20 module dplug.au.client;
21 
22 version(AU):
23 
24 import core.stdc.stdio;
25 import core.stdc.config;
26 import core.stdc.string;
27 
28 import std.string;
29 import std.conv;
30 
31 import derelict.carbon;
32 
33 import dplug.core.vec;
34 import dplug.core.nogc;
35 import dplug.core.lockedqueue;
36 import dplug.core.runtime;
37 import dplug.core.sync;
38 import dplug.core.fpcontrol;
39 import dplug.core.thread;
40 
41 import dplug.client.client;
42 import dplug.client.daw;
43 import dplug.client.midi;
44 import dplug.client.preset;
45 import dplug.client.params;
46 import dplug.client.graphics;
47 
48 import dplug.au.dfxutil;
49 import dplug.au.cocoaviewfactory;
50 import dplug.au.audiocomponentdispatch;
51 
52 
53 //debug = logDispatcher;
54 
55 // Difference with IPlug
56 // - no support for parameters group
57 // - no support for multi-output instruments
58 
59 // FUTURE: thread safety isn't very fine-grained, and there is 3 mutex lock in the audio thread
60 
61 
62 template AUEntryPoint(alias ClientClass)
63 {
64     // The entry point names must be kept in sync with names in the .rsrc
65 
66     const char[] AUEntryPoint =
67     "import derelict.carbon;" ~
68     "export extern(C) ComponentResult dplugAUEntryPoint(ComponentParameters* params, void* pPlug) nothrow  @nogc" ~
69     "{" ~
70         "return audioUnitEntryPoint!" ~ ClientClass.stringof ~ "(params, pPlug);" ~
71     "}" ~
72     "export extern(C) void* dplugAUComponentFactoryFunction(void* inDesc) nothrow  @nogc"  ~ // type-punned here to avoid the derelict.carbon import
73     "{" ~
74         "return audioUnitComponentFactory!" ~ ClientClass.stringof ~ "(inDesc);" ~
75     "}"
76     ;
77 }
78 
79 // LP64 => "long and pointers are 64-bit"
80 static if (size_t.sizeof == 8 && c_long.sizeof == 8)
81     private enum __LP64__ = 1;
82 else
83     private enum __LP64__ = 0;
84 
85 
86 
87 private T getCompParam(T, int Idx, int Num)(ComponentParameters* params) pure nothrow @nogc
88 {
89     c_long* p = params.params.ptr;
90 
91     static if (__LP64__)
92         return *cast(T*)(&p[Num - Idx]);
93     else
94         return *cast(T*)(&p[Idx]);
95 }
96 
97 package void acquireAUFunctions() nothrow @nogc
98 {
99     acquireCoreFoundationFunctions();
100     acquireCoreServicesFunctions();
101     acquireAudioUnitFunctions();
102     acquireAudioToolboxFunctions();
103 }
104 
105 package void releaseAUFunctions() nothrow @nogc
106 {
107     releaseCoreFoundationFunctions();
108     releaseCoreServicesFunctions();
109     releaseAudioUnitFunctions();
110     releaseAudioToolboxFunctions();
111 }
112 
113 ComponentResult audioUnitEntryPoint(alias ClientClass)(ComponentParameters* params, void* pPlug) nothrow @nogc
114 {
115     int select = params.what;
116 
117     // Special case for the audio case that doesn't need to initialize runtime
118     // and can't support attaching the thread (it's not interruptible)
119     bool isAudioThread = (select == kAudioUnitRenderSelect);
120     if (isAudioThread)
121     {
122         AUClient auClient = cast(AUClient)pPlug;
123         return auClient.dispatcher(select, params);
124     }
125 
126     ScopedForeignCallback!(false, true) scopedCallback;
127     scopedCallback.enter();
128 
129     if (select == kComponentOpenSelect)
130     {
131         acquireAUFunctions();
132 
133         // Create client and AUClient
134         ClientClass client = mallocNew!ClientClass();
135         ComponentInstance instance = params.getCompParam!(ComponentInstance, 0, 1);
136 
137         // Create with Component Manager
138         AUClient plugin = mallocNew!AUClient(client, instance, null);
139 
140         SetComponentInstanceStorage( instance, cast(Handle)(cast(void*)plugin) );
141         return noErr;
142     }
143 
144     AUClient auClient = cast(AUClient)pPlug;
145     return auClient.dispatcher(select, params);
146 }
147 
148 enum AUInputType
149 {
150     notConnected = 0,
151     directFastProc,
152     directNoFastProc,
153     renderCallback
154 }
155 
156 /// AU client wrapper
157 class AUClient : IHostCommand
158 {
159 public:
160 nothrow:
161 @nogc:
162 
163     // this allows to pass AUClient instance, whether we are using the Audio Component API or the Component Manager API
164     enum kAudioUnitProperty_DPLUG_AUCLIENT_INSTANCE = 0xDEADBEEF;
165 
166     this(Client client,
167         ComponentInstance componentInstance,
168         AudioComponentInstance audioComponentInstance)
169     {
170         _client = client;
171         _componentInstance = componentInstance; // null if Audio Component API
172         _audioComponentInstance = audioComponentInstance;
173 
174         int queueSize = 256;
175         _messageQueue = makeLockedQueue!AudioThreadMessage(queueSize);
176 
177         _maxInputs = _client.maxInputs();
178         _maxOutputs = _client.maxOutputs();
179 
180         _inputScratchBuffer = mallocSlice!(Vec!float)(_maxInputs);
181         _outputScratchBuffer = mallocSlice!(Vec!float)(_maxOutputs);
182 
183         for (int i = 0; i < _maxInputs; ++i)
184             _inputScratchBuffer[i] = makeVec!float(0, 64);
185 
186         for (int i = 0; i < _maxOutputs; ++i)
187             _outputScratchBuffer[i] = makeVec!float(0, 64);
188 
189         _inputPointers = mallocSlice!(float*)(_maxInputs);
190         _outputPointers = mallocSlice!(float*)(_maxOutputs);
191 
192         _inputPointersNoGap = mallocSlice!(float*)(_maxInputs);
193         _outputPointersNoGap = mallocSlice!(float*)(_maxOutputs);
194 
195         // Create input buses
196         int numInputBuses = (_maxInputs + 1) / 2;
197         _inBuses = mallocSlice!BusChannels(numInputBuses);
198         _inBusConnections = mallocSlice!InputBusConnection(numInputBuses);
199         foreach(i; 0..numInputBuses)
200         {
201             int channels = _maxInputs - i * 2;
202             if (channels > 2)
203                 channels = 2;
204             assert(channels == 1 || channels == 2);
205             _inBuses[i].connected = false;
206             _inBuses[i].numHostChannels = -1;
207             _inBuses[i].numPlugChannels = channels;
208             _inBuses[i].plugChannelStartIdx = i * 2;
209             snprintf(_inBuses[i].label.ptr, _inBuses[i].label.length, "input #%d", i);
210         }
211 
212         // Create output buses
213         // FUTURE: the current AU client largely support only one input bus and one output bus.
214 
215         int numOutputBuses = (_maxOutputs + 1) / 2;
216         _outBuses = mallocSlice!BusChannels(numOutputBuses);
217         foreach(i; 0..numOutputBuses)
218         {
219             int channels = _maxOutputs - i * 2;
220             if (channels > 2)
221                 channels = 2;
222             assert(channels == 1 || channels == 2);
223             _outBuses[i].connected = false;
224             _outBuses[i].numHostChannels = -1;
225             _outBuses[i].numPlugChannels = channels;
226             _outBuses[i].plugChannelStartIdx = i * 2;
227             snprintf(_outBuses[i].label.ptr, _outBuses[i].label.length, "output #%d", i);
228         }
229 
230         assessInputConnections();
231 
232         // Implements IHostCommand itself
233         client.setHostCommand(this);
234 
235         _globalMutex = makeMutex();
236         _renderNotifyMutex = makeMutex();
237 
238         _propertyListeners = makeVec!PropertyListener();
239         _renderNotify = makeVec!AURenderCallbackStruct();
240     }
241 
242     ~this()
243     {
244         _client.destroyFree();
245 
246         _messageQueue.destroy();
247 
248         for (int i = 0; i < _maxInputs; ++i)
249             _inputScratchBuffer[i].destroy();
250         _inputScratchBuffer.freeSlice();
251 
252         for (int i = 0; i < _maxOutputs; ++i)
253             _outputScratchBuffer[i].destroy();
254         _outputScratchBuffer.freeSlice();
255 
256         _inputPointers.freeSlice();
257         _outputPointers.freeSlice();
258         _inputPointersNoGap.freeSlice();
259         _outputPointersNoGap.freeSlice();
260 
261         _inBuses.freeSlice();
262         _inBusConnections.freeSlice();
263         _outBuses.freeSlice();
264     }
265 
266 private:
267     ComponentInstance _componentInstance; // null if Audio Component API
268     AudioComponentInstance _audioComponentInstance; // null if Component Manager PI
269 
270     void* instanceHandle()
271     {
272         if (_componentInstance)
273             return _componentInstance;
274         else
275             return _audioComponentInstance;
276     }
277 
278     Client _client;
279 
280     HostCallbackInfo _hostCallbacks;
281 
282     // Ugly protection for everything (for now)
283     UncheckedMutex _globalMutex;
284 
285     LockedQueue!AudioThreadMessage _messageQueue;
286 
287     int _maxInputs, _maxOutputs;
288     float _sampleRate = 44100.0f;
289     int _maxFrames = 1024;
290 
291     // From audio thread POV
292     bool _lastBypassed = false;
293     int _lastMaxFrames = 0;
294     float _lastSamplerate = 0;
295     int _lastUsedInputs = 0;
296     int _lastUsedOutputs = 0;
297 
298     double _lastRenderTimestamp = double.nan;
299 
300     // When true, buffers gets hard bypassed
301     bool _hardBypassed = false;
302 
303     bool _active = false;
304 
305     bool _isUIOpenedAlready = false;
306 
307     Vec!float[] _inputScratchBuffer;  // input buffer, one per possible input
308     Vec!float[] _outputScratchBuffer; // input buffer, one per output
309 
310     float*[] _inputPointers;  // where processAudio will take its audio input, one per possible input
311     float*[] _outputPointers; // where processAudio will output audio, one per possible output
312 
313     float*[] _inputPointersNoGap;  // same array, but flatten and modified in-place
314     float*[] _outputPointersNoGap; // same array, but flatten and modified in-place
315 
316 
317     // <MIDI out>
318     AUMIDIOutputCallbackStruct _midiOutCallback;
319     // </MIDI out>
320 
321 
322     //
323     // Property listeners
324     //
325     static struct PropertyListener
326     {
327         AudioUnitPropertyID mPropID;
328         AudioUnitPropertyListenerProc mListenerProc;
329         void* mProcArgs;
330     }
331     Vec!PropertyListener _propertyListeners;
332 
333     enum MaxIOChannels = 128;
334     static struct BufferList
335     {
336         int mNumberBuffers;
337         AudioBuffer[MaxIOChannels] mBuffers;
338     }
339 
340     // Every stereo pair of plugin input or output is a bus.
341     // Buses can have zero host channels if the host hasn't connected the bus at all,
342     // one host channel if the plugin supports mono and the host has supplied a mono stream,
343     // or two host channels if the host has supplied a stereo stream.
344     static struct BusChannels
345     {
346         bool connected;
347         int numHostChannels;
348         int numPlugChannels;
349         int plugChannelStartIdx;
350         char[16] label; // pretty name
351         AudioChannelLayoutTag[1] tagsBuffer;
352 
353         AudioChannelLayoutTag[] getSupportedChannelLayoutTags() return nothrow @nogc
354         {
355             // a bit rigid right now, could be useful to support mono systematically?
356             if (numPlugChannels == 1)
357             {
358                 tagsBuffer[0] = kAudioChannelLayoutTag_Mono;
359 
360             }
361             else if (numPlugChannels == 2)
362             {
363                 tagsBuffer[0] = kAudioChannelLayoutTag_Stereo;
364             }
365             else
366             {
367                 tagsBuffer[0] = kAudioChannelLayoutTag_Unknown | numPlugChannels;
368             }
369             return tagsBuffer[0..1];
370         }
371     }
372 
373     BusChannels[] _inBuses;
374     BusChannels[] _outBuses;
375 
376     BusChannels* getBus(AudioUnitScope scope_, AudioUnitElement busIdx) nothrow @nogc
377     {
378         if (scope_ == kAudioUnitScope_Input && busIdx < _inBuses.length)
379             return &_inBuses[busIdx];
380         else if (scope_ == kAudioUnitScope_Output && busIdx < _outBuses.length)
381             return &_outBuses[busIdx];
382         // Global bus is an alias for output bus zero.
383         if (scope_ == kAudioUnitScope_Global && _outBuses.length)
384             return &_outBuses[busIdx];
385         return null;
386     }
387 
388     static struct InputBusConnection
389     {
390         void* upstreamObj = null;
391         AudioUnitRenderProc upstreamRenderProc = null;
392 
393         AudioUnit upstreamUnit = null;
394         int upstreamBusIdx = 0;
395 
396         AURenderCallbackStruct upstreamRenderCallback = AURenderCallbackStruct(null, null);
397 
398         bool isConnected() pure const nothrow @nogc
399         {
400             return getInputType() != AUInputType.notConnected;
401         }
402 
403         AUInputType getInputType() pure const nothrow @nogc
404         {
405             // AU supports 3 ways to get input from the host (or whoever is upstream).
406             if (upstreamRenderProc != upstreamRenderProc && upstreamObj != null)
407             {
408                 // 1: direct input connection with fast render proc (and buffers) supplied by the upstream unit.
409                 return AUInputType.directFastProc;
410             }
411             else if (upstreamUnit != null)
412             {
413                 // 2: direct input connection with no render proc, buffers supplied by the upstream unit.
414                 return AUInputType.directNoFastProc;
415             }
416             else if (upstreamRenderCallback.inputProc)
417             {
418                 // 3: no direct connection, render callback, buffers supplied by us.
419                 return AUInputType.renderCallback;
420             }
421             else
422             {
423                 return AUInputType.notConnected;
424             }
425         }
426 
427         ComponentResult callUpstreamRender(AudioUnitRenderActionFlags* flags,
428                                            const(AudioTimeStamp)* pTimestamp,
429                                            uint nFrames,
430                                            AudioBufferList* pOutBufList,
431                                            int inputBusIdx) nothrow @nogc
432         {
433             switch (getInputType()) with (AUInputType)
434             {
435                 case directFastProc:
436                 {
437                     return upstreamRenderProc(upstreamObj, flags, pTimestamp, upstreamBusIdx, nFrames, pOutBufList);
438                 }
439 
440                 case directNoFastProc:
441                 {
442                     return AudioUnitRender(upstreamUnit, flags, pTimestamp, upstreamBusIdx, nFrames, pOutBufList);
443                 }
444 
445                 case renderCallback:
446                 {
447                     return callRenderCallback(upstreamRenderCallback, flags, pTimestamp, inputBusIdx, nFrames, pOutBufList);
448                 }
449                 default:
450                     return noErr;
451             }
452         }
453     }
454 
455     InputBusConnection[] _inBusConnections;
456 
457 
458     UncheckedMutex _renderNotifyMutex;
459     Vec!AURenderCallbackStruct _renderNotify;
460 
461 
462     // <scratch-buffers>
463 
464     /// Resize scratch buffers according to maximum block size.
465     void resizeScratchBuffers(int nFrames) nothrow @nogc
466     {
467         for (int i = 0; i < _maxInputs; ++i)
468             _inputScratchBuffer[i].resize(nFrames);
469 
470         for (int i = 0; i < _maxOutputs; ++i)
471             _outputScratchBuffer[i].resize(nFrames);
472 
473     }
474 
475     // </scratch-buffers>
476 
477     //
478     // DISPATCHER
479     //
480     final ComponentResult dispatcher(int select, ComponentParameters* params) nothrow
481     {
482         if (select == kComponentCloseSelect) // -2
483         {
484             destroyFree(cast(void*)this); // free all resources except the runtime
485             releaseAUFunctions();
486             return noErr;
487         }
488 
489         debug(logDispatcher) if (select != 14) debugLogf("select %d\n", select);
490 
491         switch(select)
492         {
493             case kComponentVersionSelect: // -4, S
494             {
495                 int ver = _client.getPublicVersion().toAUVersion;
496                 return ver;
497             }
498 
499             case kComponentCanDoSelect: // -3, S
500             {
501                 switch (params.params[0])
502                 {
503                     case kComponentOpenSelect:
504                     case kComponentCloseSelect:
505                     case kComponentVersionSelect:
506                     case kComponentCanDoSelect:
507                         return 1;
508 
509                     case kAudioUnitInitializeSelect:
510                     case kAudioUnitUninitializeSelect:
511 
512                     case kAudioUnitGetParameterSelect:
513                     case kAudioUnitSetParameterSelect:
514                     case kAudioUnitScheduleParametersSelect:
515 
516                     case kAudioUnitGetPropertySelect:
517                     case kAudioUnitSetPropertySelect:
518                     case kAudioUnitGetPropertyInfoSelect:
519 
520                     case kAudioUnitResetSelect:
521                     case kAudioUnitRenderSelect:
522 
523                     case kAudioUnitAddPropertyListenerSelect:
524                     case kAudioUnitRemovePropertyListenerSelect:
525                     case kAudioUnitRemovePropertyListenerWithUserDataSelect:
526 
527                     case kAudioUnitAddRenderNotifySelect:
528                     case kAudioUnitRemoveRenderNotifySelect:
529                         return 1;
530 
531                     case kAudioUnitProperty_MIDIOutputCallback:
532                     case kAudioUnitProperty_MIDIOutputCallbackInfo:
533                         return _client.sendsMIDI() ? 1 : 0; // ??? is this truly needed? strange
534 
535                     case kMusicDeviceMIDIEventSelect:
536                     case kMusicDeviceSysExSelect:
537                     case kMusicDeviceStartNoteSelect:
538                     case kMusicDeviceStopNoteSelect:
539                     case kMusicDevicePrepareInstrumentSelect:
540                     case kMusicDeviceReleaseInstrumentSelect:
541                         return _client.receivesMIDI() ? 1 : 0;
542 
543                     default:
544                         return 0;
545                 }
546             }
547 
548             case kAudioUnitInitializeSelect: // 1, S
549             {
550                 return DoInitialize();
551             }
552 
553             case kAudioUnitUninitializeSelect: // 2, S
554             {
555                 return DoUninitialize();
556             }
557 
558             case kAudioUnitGetPropertyInfoSelect: // 3
559             {
560                 AudioUnitPropertyID propID = params.getCompParam!(AudioUnitPropertyID, 4, 5);
561                 AudioUnitScope scope_ = params.getCompParam!(AudioUnitScope, 3, 5);
562                 AudioUnitElement element = params.getCompParam!(AudioUnitElement, 2, 5);
563                 UInt32* pDataSize = params.getCompParam!(UInt32*, 1, 5);
564                 Boolean* pWriteable = params.getCompParam!(Boolean*, 0, 5);
565                 return DoGetPropertyInfo(propID, scope_, element, pDataSize, pWriteable);
566             }
567 
568             case kAudioUnitGetPropertySelect: // 4
569             {
570                 AudioUnitPropertyID propID = params.getCompParam!(AudioUnitPropertyID, 4, 5);
571                 AudioUnitScope scope_ = params.getCompParam!(AudioUnitScope, 3, 5);
572                 AudioUnitElement element = params.getCompParam!(AudioUnitElement, 2, 5);
573                 void* pData = params.getCompParam!(void*, 1, 5);
574                 UInt32* pDataSize = params.getCompParam!(UInt32*, 0, 5);
575                 return DoGetProperty(propID, scope_, element, pData, pDataSize);
576             }
577 
578             case kAudioUnitSetPropertySelect: // 5
579             {
580                 AudioUnitPropertyID propID = params.getCompParam!(AudioUnitPropertyID, 4, 5);
581                 AudioUnitScope scope_ = params.getCompParam!(AudioUnitScope, 3, 5);
582                 AudioUnitElement element = params.getCompParam!(AudioUnitElement, 2, 5);
583                 const(void)* pData = params.getCompParam!(const(void)*, 1, 5);
584                 UInt32* pDataSize = params.getCompParam!(UInt32*, 0, 5);
585                 return DoSetProperty(propID, scope_, element, pData, pDataSize);
586             }
587 
588             case kAudioUnitGetParameterSelect: // 6
589             {
590                 AudioUnitParameterID paramID = params.getCompParam!(AudioUnitParameterID, 3, 4);
591                 AudioUnitScope scope_ = params.getCompParam!(AudioUnitScope, 2, 4);
592                 AudioUnitElement element = params.getCompParam!(AudioUnitElement, 1, 4);
593                 AudioUnitParameterValue* pValue = params.getCompParam!(AudioUnitParameterValue*, 0, 4);
594                 return DoGetParameter(paramID, scope_, element, pValue);
595             }
596 
597             case kAudioUnitSetParameterSelect: // 7
598             {
599                 AudioUnitParameterID paramID = params.getCompParam!(AudioUnitParameterID, 4, 5);
600                 AudioUnitScope scope_ = params.getCompParam!(AudioUnitScope, 3, 5);
601                 AudioUnitElement element = params.getCompParam!(AudioUnitElement, 2, 5);
602                 AudioUnitParameterValue value = params.getCompParam!(AudioUnitParameterValue, 1, 5);
603                 UInt32 offset = params.getCompParam!(UInt32, 0, 5);
604                 return DoSetParameter(paramID, scope_, element, value, offset);
605             }
606 
607             case kAudioUnitResetSelect: // 9
608             {
609                 AudioUnitScope scope_;
610                 AudioUnitElement elem;
611                 return DoReset(scope_, elem);
612             }
613 
614             case kAudioUnitAddPropertyListenerSelect: // 10
615             {
616                 AudioUnitPropertyID mPropID = params.getCompParam!(AudioUnitPropertyID, 2, 3);
617                 AudioUnitPropertyListenerProc mListenerProc = params.getCompParam!(AudioUnitPropertyListenerProc, 1, 3);
618                 void* mProcArgs = params.getCompParam!(void*, 0, 3);
619                 return DoAddPropertyListener(mPropID, mListenerProc, mProcArgs);
620 
621             }
622 
623             case kAudioUnitRemovePropertyListenerSelect: // 11
624             {
625                 AudioUnitPropertyID propID = params.getCompParam!(AudioUnitPropertyID, 1, 2);
626                 AudioUnitPropertyListenerProc proc = params.getCompParam!(AudioUnitPropertyListenerProc, 0, 2);
627                 return DoRemovePropertyListener(propID, proc);
628             }
629 
630             case kAudioUnitRenderSelect: // 14
631             {
632                 AudioUnitRenderActionFlags* pFlags = params.getCompParam!(AudioUnitRenderActionFlags*, 4, 5)();
633                 AudioTimeStamp* pTimestamp = params.getCompParam!(AudioTimeStamp*, 3, 5)();
634                 uint outputBusIdx = params.getCompParam!(uint, 2, 5)();
635                 uint nFrames = params.getCompParam!(uint, 1, 5)();
636                 AudioBufferList* pOutBufList = params.getCompParam!(AudioBufferList*, 0, 5)();
637                 return DoRender(pFlags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
638             }
639 
640             case kAudioUnitAddRenderNotifySelect: // 15
641             {
642                 AURenderCallback inputProc = params.getCompParam!(AURenderCallback, 1, 2);
643                 void* inputProcRefCon = params.getCompParam!(void*, 0, 2);
644                 return DoAddRenderNotify(inputProc, inputProcRefCon);
645             }
646 
647             case kAudioUnitRemoveRenderNotifySelect: // 16
648             {
649                 AURenderCallback inputProc = params.getCompParam!(AURenderCallback, 1, 2);
650                 void* inputProcRefCon = params.getCompParam!(void*, 0, 2);
651                 return DoRemoveRenderNotify(inputProc, inputProcRefCon);
652             }
653 
654             case kAudioUnitScheduleParametersSelect: // 17
655             {
656                 AudioUnitParameterEvent* pEvent = params.getCompParam!(AudioUnitParameterEvent*, 1, 2);
657                 uint nEvents = params.getCompParam!(uint, 0, 2);
658                 return DoScheduleParameters(pEvent, nEvents);
659             }
660 
661             case kAudioUnitRemovePropertyListenerWithUserDataSelect: // 18
662             {
663                 AudioUnitPropertyID propID = params.getCompParam!(AudioUnitPropertyID, 2, 3);
664                 AudioUnitPropertyListenerProc proc = params.getCompParam!(AudioUnitPropertyListenerProc, 1, 3);
665                 void* userData = params.getCompParam!(void*, 0, 3);
666                 return DoRemovePropertyListenerWithUserData(propID, proc, userData);
667             }
668 
669             case kMusicDeviceMIDIEventSelect: // 0x0101
670             {
671                 ubyte status = cast(ubyte)( params.getCompParam!(uint, 3, 4) );
672                 ubyte data1 = cast(ubyte)( params.getCompParam!(uint, 2, 4) );
673                 ubyte data2 = cast(ubyte)( params.getCompParam!(uint, 1, 4) );
674                 int offset = params.getCompParam!(uint, 0, 4);
675                 return DoMIDIEvent(status, data1, data2, offset);
676             }
677 
678             case kMusicDeviceSysExSelect: // 0x0102
679             {
680                 const UInt8* pInData = cast(UInt8*)( params.getCompParam!(uint, 1, 2) );
681                 uint offset = params.getCompParam!(uint, 0, 2);
682                 return DoSysEx(pInData, offset);
683             }
684 
685             case kMusicDevicePrepareInstrumentSelect: // 0x0103
686             {
687                 MusicDeviceInstrumentID inInstrument = params.getCompParam!(MusicDeviceInstrumentID, 0, 1);
688                 return DoPrepareInstrument(inInstrument);
689             }
690 
691             case kMusicDeviceReleaseInstrumentSelect: // 0x0104
692             {
693                 MusicDeviceInstrumentID inInstrument = params.getCompParam!(MusicDeviceInstrumentID, 0, 1);
694                 return DoReleaseInstrument(inInstrument);
695             }
696 
697             case kMusicDeviceStartNoteSelect: // 0x0105
698             {
699                 MusicDeviceInstrumentID inInstrument = params.getCompParam!(MusicDeviceInstrumentID, 4, 5);
700                 MusicDeviceGroupID inGroupID = params.getCompParam!(MusicDeviceGroupID, 3, 5);
701                 NoteInstanceID* pNoteID = params.getCompParam!(NoteInstanceID*, 2, 5);
702                 uint offset = params.getCompParam!(uint, 1, 5);
703                 MusicDeviceNoteParams* pNoteParams = params.getCompParam!(MusicDeviceNoteParams*, 0, 5);
704                 return DoStartNote(inInstrument, inGroupID, pNoteID, offset, pNoteParams);
705             }
706 
707             case kMusicDeviceStopNoteSelect: // 0x0106
708             {
709                 MusicDeviceGroupID inGroupID = params.getCompParam!(MusicDeviceGroupID, 2, 3);
710                 NoteInstanceID noteID = params.getCompParam!(NoteInstanceID, 1, 3);
711                 uint offset = params.getCompParam!(uint, 0, 3);
712                 return DoStopNote(inGroupID, noteID, offset);
713             }
714 
715             default:
716                 return badComponentSelector;
717         }
718     }
719 
720     // individual select actions, to be reused for the Audio Component API
721 
722 package:
723 
724     final OSStatus DoInitialize()
725     {
726         // Audio processing was switched on.
727         _globalMutex.lock();
728         scope(exit) _globalMutex.unlock();
729         _active = true;
730 
731         // We may end up with invalid I/O config at this point (Issue #385).
732         // Because validity check were made while _active was false.
733         // Check that the connected I/O is actually legal.
734         int nIn = numHostChannelsConnected(_inBuses);
735         int nOut = numHostChannelsConnected(_outBuses);
736         if (!_client.isLegalIO(nIn, nOut))
737             return kAudioUnitErr_FailedInitialization;
738 
739         return noErr;
740     }
741 
742     final OSStatus DoUninitialize()
743     {
744         _globalMutex.lock();
745         scope(exit) _globalMutex.unlock();
746         _active = false;
747         // Nothing to do here
748         return noErr;
749     }
750 
751     final OSStatus DoGetPropertyInfo(AudioUnitPropertyID prop,
752                                     AudioUnitScope scope_,
753                                     AudioUnitElement elem,
754                                     UInt32* pOutDataSize,
755                                     Boolean* pOutWritable)
756     {
757         UInt32 dataSize = 0;
758         if (!pOutDataSize)
759             pOutDataSize = &dataSize;
760 
761         Boolean writeable;
762         if (!pOutWritable)
763             pOutWritable = &writeable;
764 
765         *pOutWritable = false;
766         _globalMutex.lock();
767         scope(exit) _globalMutex.unlock();
768         return getProperty(prop, scope_, elem, pOutDataSize, pOutWritable, null);
769     }
770 
771     final OSStatus DoGetProperty(AudioUnitPropertyID inID,
772                                  AudioUnitScope inScope,
773                                  AudioUnitElement inElement,
774                                  void* pOutData,
775                                  UInt32* pIODataSize)
776     {
777         UInt32 dataSize = 0;
778         if (!pIODataSize)
779             pIODataSize = &dataSize;
780         Boolean writeable = false;
781         _globalMutex.lock();
782         scope(exit) _globalMutex.unlock();
783         return getProperty(inID, inScope, inElement, pIODataSize, &writeable, pOutData);
784     }
785 
786     final OSStatus DoSetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* pInData, UInt32* pInDataSize)
787     {
788         _globalMutex.lock();
789         scope(exit) _globalMutex.unlock();
790         return setProperty(inID, inScope, inElement, pInDataSize, pInData);
791     }
792 
793     final OSStatus DoAddPropertyListener(AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc, void* pUserData)
794     {
795         PropertyListener listener;
796         listener.mPropID = prop;
797         listener.mListenerProc = proc;
798         listener.mProcArgs = pUserData;
799 
800         _globalMutex.lock();
801         scope(exit) _globalMutex.unlock();
802         int n = cast(int)(_propertyListeners.length);
803         for (int i = 0; i < n; ++i)
804         {
805             PropertyListener pListener = _propertyListeners[i];
806             if (listener.mPropID == pListener.mPropID && listener.mListenerProc == pListener.mListenerProc)
807             {
808                 return noErr; // already in
809             }
810         }
811         _propertyListeners.pushBack(listener);
812         return noErr;
813     }
814 
815     final OSStatus DoRemovePropertyListener(AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc)
816     {
817         PropertyListener listener;
818         listener.mPropID = prop;
819         listener.mListenerProc = proc;
820         _globalMutex.lock();
821         scope(exit) _globalMutex.unlock();
822         int n = cast(int)(_propertyListeners.length);
823         for (int i = 0; i < n; ++i)
824         {
825             PropertyListener pListener = _propertyListeners[i];
826             if (listener.mPropID == pListener.mPropID
827                 && listener.mListenerProc == pListener.mListenerProc)
828             {
829                 _propertyListeners.removeAndReplaceByLastElement(i);
830                 break;
831             }
832         }
833         return noErr;
834     }
835 
836     final OSStatus DoRemovePropertyListenerWithUserData(AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc, void* pUserData)
837     {
838         PropertyListener listener;
839         listener.mPropID = prop;
840         listener.mListenerProc = proc;
841         listener.mProcArgs = pUserData;
842         _globalMutex.lock();
843         scope(exit) _globalMutex.unlock();
844         int n = cast(int)(_propertyListeners.length);
845         for (int i = 0; i < n; ++i)
846         {
847             PropertyListener pListener = _propertyListeners[i];
848             if (listener.mPropID == pListener.mPropID
849                 && listener.mListenerProc == pListener.mListenerProc
850                 && listener.mProcArgs == pListener.mProcArgs)
851             {
852                 _propertyListeners.removeAndReplaceByLastElement(i);
853                 break;
854             }
855         }
856         return noErr;
857     }
858 
859     final OSStatus DoAddRenderNotify(AURenderCallback proc, void* pUserData)
860     {
861         AURenderCallbackStruct acs;
862         acs.inputProc = proc;
863         acs.inputProcRefCon = pUserData;
864         _renderNotifyMutex.lock();
865         scope(exit) _renderNotifyMutex.unlock();
866         _renderNotify.pushBack(acs);
867         return noErr;
868     }
869 
870     final OSStatus DoRemoveRenderNotify(AURenderCallback proc, void* pUserData)
871     {
872         AURenderCallbackStruct acs;
873         acs.inputProc = proc;
874         acs.inputProcRefCon = pUserData;
875 
876         _renderNotifyMutex.lock();
877         scope(exit) _renderNotifyMutex.unlock();
878 
879         int iElem = _renderNotify.indexOf(acs);
880         if (iElem != -1)
881             _renderNotify.removeAndReplaceByLastElement(iElem);
882 
883         return noErr;
884     }
885 
886     final OSStatus DoGetParameter(AudioUnitParameterID param, AudioUnitScope scope_, AudioUnitElement elem, AudioUnitParameterValue *value)
887     {
888         if (!_client.isValidParamIndex(param))
889             return kAudioUnitErr_InvalidParameter;
890         *value = _client.param(param).getForHost();
891         return noErr;
892     }
893 
894     final OSStatus DoSetParameter(AudioUnitParameterID param, AudioUnitScope scope_, AudioUnitElement elem, AudioUnitParameterValue value, UInt32 bufferOffset)
895     {
896         // Note: buffer offset is ignored...
897         if (!_client.isValidParamIndex(param))
898             return kAudioUnitErr_InvalidParameter;
899         _client.setParameterFromHost(param, value);
900         return noErr;
901     }
902 
903     final OSStatus DoScheduleParameters(const AudioUnitParameterEvent *pEvent, UInt32 nEvents)
904     {
905         foreach(ref pE; pEvent[0..nEvents])
906         {
907             if (pE.eventType == kParameterEvent_Immediate)
908             {
909                 ComponentResult r = DoSetParameter(pE.parameter, pE.scope_, pE.element,
910                                                    pE.eventValues.immediate.value,
911                                                    pE.eventValues.immediate.bufferOffset);
912                 if (r != noErr)
913                     return r;
914             }
915         }
916         return noErr;
917     }
918 
919     final OSStatus DoRender(AudioUnitRenderActionFlags* pIOActionFlags,
920                             const AudioTimeStamp* pInTimeStamp,
921                             UInt32 inOutputBusNumber,
922                             UInt32 inNumberFrames,
923                             AudioBufferList* pIOData)
924     {
925         return render(pIOActionFlags, pInTimeStamp, inOutputBusNumber, inNumberFrames, pIOData);
926     }
927 
928     final OSStatus DoReset(AudioUnitScope scope_, AudioUnitElement elem)
929     {
930         _messageQueue.pushBack(makeResetStateMessage());
931         return noErr;
932     }
933 
934     final OSStatus DoMIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame)
935     {
936         if (!_client.receivesMIDI)
937             return kAudioUnitErr_InvalidProperty;
938         MidiMessage m =  MidiMessage(inOffsetSampleFrame, cast(ubyte)inStatus, cast(ubyte)inData1, cast(ubyte)inData2);
939         _messageQueue.pushBack(makeMidiThreadMessage(m));
940         return noErr;
941     }
942 
943     final OSStatus DoSysEx(const UInt8* pInData, UInt32 inLength)
944     {
945         // MAYDO Not supported yet
946         if (!_client.receivesMIDI)
947             return kAudioUnitErr_InvalidProperty;
948         return noErr;
949     }
950 
951     final OSStatus DoPrepareInstrument(MusicDeviceInstrumentID inInstrument)
952     {
953         if (!_client.receivesMIDI)
954             return kAudioUnitErr_InvalidProperty;
955         return noErr;
956     }
957 
958     final OSStatus DoReleaseInstrument(MusicDeviceInstrumentID inInstrument)
959     {
960         if (!_client.receivesMIDI)
961             return kAudioUnitErr_InvalidProperty;
962         return noErr;
963     }
964 
965     final OSStatus DoStartNote(MusicDeviceInstrumentID inInstrument,
966                                MusicDeviceGroupID inGroupID, NoteInstanceID *outNoteInstanceID,
967                                UInt32 inOffsetSampleFrame, const MusicDeviceNoteParams *inParams)
968     {
969         if (!_client.receivesMIDI)
970             return kAudioUnitErr_InvalidProperty;
971 
972         int noteNumber = cast(int) inParams.mPitch;
973         if (noteNumber < 0) noteNumber = 0;
974         if (noteNumber > 127) noteNumber = 127;
975 
976         int velocity = cast(int) inParams.mVelocity;
977         if (velocity < 0) velocity = 0;
978         if (velocity > 127) velocity = 127;
979 
980         // Note from IPlug: noteID is supposed to be some incremented unique ID,
981         // but we're just storing note number in it.
982         *outNoteInstanceID = noteNumber;
983 
984         int channel = 0; // always using channel 0
985         MidiMessage m = makeMidiMessageNoteOn(inOffsetSampleFrame, channel, noteNumber, velocity);
986         _messageQueue.pushBack(makeMidiThreadMessage(m));
987         return noErr;
988     }
989 
990     final OSStatus DoStopNote(MusicDeviceGroupID inGroupID, NoteInstanceID inNoteInstanceID, UInt32 inOffsetSampleFrame)
991     {
992         if (!_client.receivesMIDI)
993             return kAudioUnitErr_InvalidProperty;
994 
995         int channel = 0; // always using channel 0
996         MidiMessage m = makeMidiMessageNoteOff(inOffsetSampleFrame, channel, inNoteInstanceID);
997         _messageQueue.pushBack(makeMidiThreadMessage(m));
998         return noErr;
999     }
1000 
1001 private:
1002     //
1003     // </DISPATCHER>
1004     //
1005 
1006     //
1007     // GET PROPERTY
1008     //
1009     ComponentResult getProperty(AudioUnitPropertyID propID, AudioUnitScope scope_, AudioUnitElement element,
1010                                 UInt32* pDataSize, Boolean* pWriteable, void* pData) nothrow
1011     {
1012         debug(logDispatcher)
1013         {
1014             if (pData)
1015                 debugLogf("GET property %d\n", propID);
1016             else
1017                 debugLogf("GETINFO property %d\n", propID);
1018         }
1019 
1020         switch(propID)
1021         {
1022 
1023             case kAudioUnitProperty_ClassInfo: // 0
1024             {
1025                 *pDataSize = CFPropertyListRef.sizeof;
1026                 *pWriteable = true;
1027                 if (pData)
1028                 {
1029                     CFPropertyListRef* pList = cast(CFPropertyListRef*) pData;
1030                     return readState(pList);
1031                 }
1032                 return noErr;
1033             }
1034 
1035             case kAudioUnitProperty_MakeConnection: // 1
1036             {
1037                 if (!isInputOrGlobalScope(scope_))
1038                     return kAudioUnitErr_InvalidScope;
1039                 *pDataSize = cast(uint)AudioUnitConnection.sizeof;
1040                 *pWriteable = true;
1041                 return noErr;
1042             }
1043 
1044             case kAudioUnitProperty_SampleRate: // 2
1045             {
1046                 *pDataSize = 8;
1047                 *pWriteable = true;
1048                 if (pData)
1049                     *(cast(Float64*) pData) = _sampleRate;
1050                 return noErr;
1051             }
1052 
1053             case kAudioUnitProperty_ParameterList: // 3
1054             {
1055                 int numParams = cast(int)( _client.params().length );
1056                 int n = (scope_ == kAudioUnitScope_Global) ? numParams : 0;
1057                 *pDataSize = cast(uint)(n * AudioUnitParameterID.sizeof);
1058                 if (pData && n)
1059                 {
1060                     AudioUnitParameterID* pParamID = cast(AudioUnitParameterID*) pData;
1061                     for (int i = 0; i < n; ++i, ++pParamID)
1062                        *pParamID = cast(AudioUnitParameterID) i;
1063                 }
1064                 return noErr;
1065             }
1066 
1067             case kAudioUnitProperty_ParameterInfo: // 4
1068             {
1069                 if (!isGlobalScope(scope_))
1070                     return kAudioUnitErr_InvalidScope;
1071                 if (!_client.isValidParamIndex(element))
1072                     return kAudioUnitErr_InvalidElement;
1073 
1074                 *pDataSize = AudioUnitParameterInfo.sizeof;
1075                 if (pData)
1076                 {
1077                     AudioUnitParameterInfo* pInfo = cast(AudioUnitParameterInfo*)pData;
1078                     *pInfo = AudioUnitParameterInfo.init;
1079                     Parameter param = _client.param(element);
1080 
1081                     // every parameter in dplug:
1082                     //  - is readable
1083                     //  - is writeable (automatable)
1084                     //  - has a name, that must be CFRelease'd
1085                     pInfo.flags = kAudioUnitParameterFlag_CFNameRelease |
1086                                   kAudioUnitParameterFlag_HasCFNameString |
1087                                   kAudioUnitParameterFlag_IsReadable |
1088                                   kAudioUnitParameterFlag_IsWritable;
1089 
1090                     version(legacyAUHighResolutionParameters)
1091                     {}
1092                     else
1093                     {
1094                         pInfo.flags |= kAudioUnitParameterFlag_IsHighResolution;
1095                     }
1096 
1097 
1098                     if (!param.isAutomatable) {
1099                         // flag as non-automatable parameter
1100                         pInfo.flags |= kAudioUnitParameterFlag_NonRealTime;
1101                     }
1102 
1103                     pInfo.cfNameString = toCFString(param.name);
1104                     stringNCopy(pInfo.name.ptr, 52, param.name);
1105 
1106                     /*if (auto intParam = cast(IntegerParameter)param)
1107                     {
1108                         pInfo.unit = kAudioUnitParameterUnit_Indexed;
1109                         pInfo.minValue = intParam.minValue;
1110                         pInfo.maxValue = intParam.maxValue;
1111                         pInfo.defaultValue = intParam.defaultValue;
1112                     }
1113                     else if (auto boolParam = cast(BoolParameter)param)
1114                     {
1115                         pInfo.minValue = 0;
1116                         pInfo.maxValue = 1;
1117                         pInfo.defaultValue = boolParam.getNormalizedDefault();
1118                         pInfo.unit = kAudioUnitParameterUnit_Boolean;
1119                     }
1120                     else*/
1121                     {
1122                         // Generic label
1123                         assert(param.label !is null);
1124                         /*if (param.label != "")
1125                         {
1126                             pInfo.unitName = toCFString(param.label);
1127                             pInfo.unit = kAudioUnitParameterUnit_CustomUnit;
1128                         }
1129                         else
1130                         {
1131                             pInfo.unit = kAudioUnitParameterUnit_Generic;
1132                         }*/
1133 
1134                         // Should FloatParameter be mapped?
1135                         pInfo.unit = kAudioUnitParameterUnit_Generic;
1136                         pInfo.minValue = 0.0f;
1137                         pInfo.maxValue = 1.0f;
1138                         pInfo.defaultValue = param.getNormalizedDefault();
1139                     }
1140                     pInfo.clumpID = 0; // parameter groups not supported yet
1141                 }
1142                 return noErr;
1143             }
1144 
1145             case kAudioUnitProperty_FastDispatch: // 5
1146             {
1147                 if (isAudioComponentAPI)
1148                     return kAudioUnitErr_InvalidElement;
1149                 switch (element)
1150                 {
1151                     case kAudioUnitGetParameterSelect:
1152                         *pDataSize = AudioUnitGetParameterProc.sizeof;
1153                         if (pData)
1154                             *(cast(AudioUnitGetParameterProc*) pData) = &getParamProc;
1155                         return noErr;
1156 
1157                     case kAudioUnitSetParameterSelect:
1158                         *pDataSize = AudioUnitSetParameterProc.sizeof;
1159                         if (pData)
1160                             *(cast(AudioUnitSetParameterProc*) pData) = &setParamProc;
1161                         return noErr;
1162 
1163                     case kAudioUnitRenderSelect:
1164                         *pDataSize = AudioUnitRenderProc.sizeof;
1165                         if (pData)
1166                             *(cast(AudioUnitRenderProc*) pData) = &renderProc;
1167                         return noErr;
1168 
1169                     default:
1170                         return kAudioUnitErr_InvalidElement;
1171                 }
1172             }
1173 
1174             case kAudioUnitProperty_StreamFormat: // 8,
1175             {
1176                 BusChannels* pBus = getBus(scope_, element);
1177                 if (!pBus)
1178                     return kAudioUnitErr_InvalidElement;
1179 
1180                 *pDataSize = AudioStreamBasicDescription.sizeof;
1181                 *pWriteable = true;
1182                 if (pData)
1183                 {
1184                     int nChannels = pBus.numHostChannels;  // Report how many channels the host has connected.
1185                     if (nChannels < 0)    // Unless the host hasn't connected any yet, in which case report the default.
1186                         nChannels = pBus.numPlugChannels;
1187                     AudioStreamBasicDescription* pASBD = cast(AudioStreamBasicDescription*) pData;
1188 
1189                     pASBD.mSampleRate = _sampleRate;
1190                     pASBD.mFormatID = kAudioFormatLinearPCM;
1191                     pASBD.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
1192                     pASBD.mFramesPerPacket = 1;
1193                     pASBD.mChannelsPerFrame = nChannels;
1194                     pASBD.mBitsPerChannel = 8 * AudioSampleType.sizeof;
1195                     pASBD.mReserved = 0;
1196                     int bytesPerSample = cast(int)(AudioSampleType.sizeof);
1197                     pASBD.mBytesPerPacket = bytesPerSample;
1198                     pASBD.mBytesPerFrame = bytesPerSample;
1199                 }
1200                 return noErr;
1201             }
1202 
1203             case kAudioUnitProperty_ElementCount: // 11
1204             {
1205                 *pDataSize = uint.sizeof;
1206                 if (pData)
1207                 {
1208                     uint n = 0;
1209                     if (scope_ == kAudioUnitScope_Input)
1210                         n = cast(uint)_inBuses.length;
1211                     else if (scope_ == kAudioUnitScope_Output)
1212                         n = cast(uint)_outBuses.length;
1213                     else if (scope_ == kAudioUnitScope_Global)
1214                         n = 1;
1215                     *(cast(uint*) pData) = n;
1216                 }
1217                 return noErr;
1218             }
1219 
1220             case kAudioUnitProperty_Latency: // 12
1221             {
1222                 if (!isGlobalScope(scope_))
1223                     return kAudioUnitErr_InvalidScope;
1224                 *pDataSize = double.sizeof;
1225                 if (pData)
1226                 {
1227                     double latencySecs = cast(double)(_client.latencySamples(_sampleRate)) / _sampleRate;
1228                     *(cast(Float64*) pData) = latencySecs;
1229                 }
1230                 return noErr;
1231             }
1232 
1233             case kAudioUnitProperty_SupportedNumChannels: // 13
1234             {
1235                 if (!isGlobalScope(scope_))
1236                     return kAudioUnitErr_InvalidScope;
1237 
1238                 LegalIO[] legalIOs = _client.legalIOs();
1239                 *pDataSize = cast(uint)( legalIOs.length * AUChannelInfo.sizeof );
1240 
1241                 if (pData)
1242                 {
1243                     AUChannelInfo* pChInfo = cast(AUChannelInfo*) pData;
1244                     foreach(size_t i, ref legalIO; legalIOs)
1245                     {
1246                         pChInfo[i].inChannels = cast(short)legalIO.numInputChannels;
1247                         pChInfo[i].outChannels = cast(short)legalIO.numOutputChannels;
1248                     }
1249                 }
1250                 return noErr;
1251             }
1252 
1253             case kAudioUnitProperty_MaximumFramesPerSlice: // 14
1254             {
1255                 if (!isGlobalScope(scope_))
1256                     return kAudioUnitErr_InvalidScope;
1257                 *pDataSize = uint.sizeof;
1258                 *pWriteable = true;
1259                 if (pData)
1260                 {
1261                     *(cast(UInt32*) pData) = _maxFrames;
1262                 }
1263                 return noErr;
1264             }
1265 
1266             case kAudioUnitProperty_ParameterValueStrings: // 16
1267             {
1268                 if (!isGlobalScope(scope_))
1269                     return kAudioUnitErr_InvalidScope;
1270                 if (!_client.isValidParamIndex(element))
1271                     return kAudioUnitErr_InvalidElement;
1272 
1273                 if (auto intParam = cast(IntegerParameter)_client.param(element))
1274                 {
1275                     *pDataSize = CFArrayRef.sizeof;
1276                     if (pData)
1277                     {
1278                         int numValues = intParam.numValues();
1279                         CFMutableArrayRef nameArray = CFArrayCreateMutable(kCFAllocatorDefault, numValues, &kCFTypeArrayCallBacks);
1280 
1281                         if (auto enumParam = cast(EnumParameter)intParam)
1282                         {
1283                             for (int i = 0; i < numValues; ++i)
1284                                 CFArrayAppendValue(nameArray, toCFString(enumParam.getValueString(i)));
1285                         }
1286                         else
1287                         {
1288                             for (int i = 0; i < numValues; ++i)
1289                                 CFArrayAppendValue(nameArray, convertIntToCFString(intParam.minValue + i));
1290                         }
1291 
1292                         *(cast(CFArrayRef*) pData) = nameArray;
1293                     }
1294                     return noErr;
1295                 }
1296                 else
1297                 {
1298                     *pDataSize = 0;
1299                     return kAudioUnitErr_InvalidProperty;
1300                 }
1301             }
1302 
1303             case kAudioUnitProperty_AudioChannelLayout:
1304             {
1305                 return kAudioUnitErr_InvalidProperty; // MAYDO IPlug says "this seems wrong but works"
1306             }
1307 
1308             case kAudioUnitProperty_TailTime: // 20
1309             {
1310                 if (!isGlobalScope(scope_))
1311                     return kAudioUnitErr_InvalidScope;
1312 
1313                 double tailSize = _client.tailSizeInSeconds();
1314 
1315                 *pWriteable = false;
1316                 *pDataSize = Float64.sizeof;
1317 
1318                 if (pData)
1319                 {
1320                     // Note: it's unknown if every AU host will accept an infinite tail size, but I guess
1321                     // we'll know soon enough.
1322                     *(cast(Float64*) pData) = cast(double) tailSize;
1323                 }
1324                 return noErr;
1325             }
1326 
1327             case kAudioUnitProperty_BypassEffect: // 21
1328             {
1329                 if (!isGlobalScope(scope_))
1330                     return kAudioUnitErr_InvalidScope;
1331                 *pWriteable = true; // always implement a bypass option, whether hard or soft
1332                 *pDataSize = UInt32.sizeof;
1333                 if (pData)
1334                 {
1335                     bool bypassIsEnabled = (_hardBypassed ? 1 : 0);
1336                     *(cast(UInt32*) pData) = bypassIsEnabled;
1337                 }
1338                 return noErr;
1339             }
1340 
1341             case kAudioUnitProperty_LastRenderError:  // 22
1342             {
1343                 if(!isGlobalScope(scope_))
1344                     return kAudioUnitErr_InvalidScope;
1345                 *pDataSize = OSStatus.sizeof;
1346                 *pWriteable = false;
1347                 if (pData)
1348                     *(cast(OSStatus*) pData) = noErr;
1349                 return noErr;
1350             }
1351 
1352             case kAudioUnitProperty_SetRenderCallback: // 23
1353             {
1354                 // Not sure why it's not writing anything
1355                 if(!isInputOrGlobalScope(scope_))
1356                     return kAudioUnitErr_InvalidScope;
1357                 if (element >= _inBuses.length)
1358                     return kAudioUnitErr_InvalidElement;
1359                 *pDataSize = AURenderCallbackStruct.sizeof;
1360                 *pWriteable = true;
1361                 return noErr;
1362             }
1363 
1364             case kAudioUnitProperty_FactoryPresets: // 24
1365             {
1366                 *pDataSize = CFArrayRef.sizeof;
1367                 if (pData)
1368                 {
1369                     auto presetBank = _client.presetBank();
1370                     int numPresets = presetBank.numPresets();
1371 
1372                     auto callbacks = getCFAUPresetArrayCallBacks();
1373                     CFMutableArrayRef allPresets = CFArrayCreateMutable(kCFAllocatorDefault, numPresets, &callbacks);
1374 
1375                     if (allPresets == null)
1376                         return coreFoundationUnknownErr;
1377 
1378                     for (int presetIndex = 0; presetIndex < numPresets; ++presetIndex)
1379                     {
1380                         const(char)[] name = presetBank.preset(presetIndex).name;
1381                         CFStrLocal presetName = CFStrLocal.fromString(name);
1382 
1383                         CFAUPresetRef newPreset = CFAUPresetCreate(kCFAllocatorDefault, presetIndex, presetName);
1384                         if (newPreset != null)
1385                         {
1386                             CFArrayAppendValue(allPresets, newPreset);
1387                             CFAUPresetRelease(newPreset);
1388                         }
1389                     }
1390 
1391                     *(cast(CFMutableArrayRef*) pData) = allPresets;
1392                   }
1393                   return noErr;
1394             }
1395 
1396             case kAudioUnitProperty_HostCallbacks: // 27
1397             {
1398                 if(!isGlobalScope(scope_))
1399                     return kAudioUnitErr_InvalidScope;
1400                 *pDataSize = HostCallbackInfo.sizeof;
1401                 *pWriteable = true;
1402                 return noErr;
1403             }
1404 
1405             case kAudioUnitProperty_ElementName: // 30
1406             {
1407                 *pDataSize = cast(uint)(CFStringRef.sizeof);
1408                 *pWriteable = false;
1409                 if (!isInputOrOutputScope(scope_))
1410                     return kAudioUnitErr_InvalidScope;
1411                 BusChannels* pBus = getBus(scope_, element);
1412                 if (!pBus)
1413                     return kAudioUnitErr_InvalidElement;
1414 
1415                 if (pData)
1416                 {
1417                     *cast(CFStringRef *)pData = toCFString(pBus.label);
1418                 }
1419 
1420                 return noErr;
1421             }
1422 
1423             case kAudioUnitProperty_CocoaUI: // 31
1424             {
1425                 if ( _client.hasGUI() )
1426                 {
1427                     *pDataSize = AudioUnitCocoaViewInfo.sizeof;
1428                     if (pData)
1429                     {
1430                         const(char)[] factoryClassName = registerCocoaViewFactory(); // FUTURE: call unregisterCocoaViewFactory somewhere
1431                         CFBundleRef pBundle = CFBundleGetMainBundle();
1432                         CFURLRef url = CFBundleCopyBundleURL(pBundle);
1433                         AudioUnitCocoaViewInfo* pViewInfo = cast(AudioUnitCocoaViewInfo*) pData;
1434                         pViewInfo.mCocoaAUViewBundleLocation = url;
1435                         pViewInfo.mCocoaAUViewClass[0] = toCFString(factoryClassName);
1436                     }
1437                     return noErr;
1438                 }
1439                 else
1440                     return kAudioUnitErr_InvalidProperty;
1441             }
1442 
1443             case kAudioUnitProperty_SupportedChannelLayoutTags:
1444             {
1445                 if (isInputOrOutputScope(scope_))
1446                   return kAudioUnitErr_InvalidScope;
1447 
1448                 BusChannels* bus = getBus(scope_, element);
1449                 if (!bus)
1450                     return kAudioUnitErr_InvalidElement;
1451 
1452                 AudioChannelLayoutTag[] tags = bus.getSupportedChannelLayoutTags();
1453 
1454                 if (!pData) // GetPropertyInfo
1455                 {
1456                     *pDataSize = cast(int)(tags.length * AudioChannelLayoutTag.sizeof);
1457                     *pWriteable = true;
1458                 }
1459                 else
1460                 {
1461                     AudioChannelLayoutTag* ptags = cast(AudioChannelLayoutTag*)pData;
1462                     ptags[0..tags.length] = tags[];
1463                 }
1464                 return noErr;
1465             }
1466 
1467             case kAudioUnitProperty_ParameterIDName: // 34
1468             {
1469                 *pDataSize = AudioUnitParameterIDName.sizeof;
1470                 if (pData && scope_ == kAudioUnitScope_Global)
1471                 {
1472                     AudioUnitParameterIDName* pIDName = cast(AudioUnitParameterIDName*) pData;
1473                     Parameter parameter = _client.param(pIDName.inID);
1474 
1475                     size_t desiredLength = parameter.name.length;
1476                     if (pIDName.inDesiredLength != -1)
1477                     {
1478                         desiredLength = pIDName.inDesiredLength;
1479                         if (desiredLength > parameter.name.length)
1480                             desiredLength = parameter.name.length;
1481                     }
1482 
1483                     pIDName.outName = toCFString(parameter.name[0..desiredLength]);
1484                 }
1485                 return noErr;
1486             }
1487 
1488             case kAudioUnitProperty_ParameterClumpName: // 35
1489             {
1490                 *pDataSize = AudioUnitParameterNameInfo.sizeof;
1491                 if (pData && scope_ == kAudioUnitScope_Global)
1492                 {
1493                     AudioUnitParameterNameInfo* parameterNameInfo = cast(AudioUnitParameterNameInfo *) pData;
1494                     int clumpId = parameterNameInfo.inID;
1495                     if (clumpId < 1)
1496                         return kAudioUnitErr_PropertyNotInUse;
1497 
1498                     // Parameter groups not supported yet, always return the same string
1499                     parameterNameInfo.outName = toCFString("All params");
1500                 }
1501                 return noErr;
1502             }
1503 
1504             case kAudioUnitProperty_CurrentPreset: // 28
1505             case kAudioUnitProperty_PresentPreset: // 36
1506             {
1507                 *pDataSize = AUPreset.sizeof;
1508                 *pWriteable = true;
1509                 if (pData)
1510                 {
1511                     auto bank = _client.presetBank();
1512                     Preset preset = bank.currentPreset();
1513                     AUPreset* pAUPreset = cast(AUPreset*) pData;
1514                     pAUPreset.presetNumber = bank.currentPresetIndex();
1515                     pAUPreset.presetName = toCFString(preset.name);
1516                 }
1517                 return noErr;
1518             }
1519 
1520             case kAudioUnitProperty_ParameterStringFromValue: // 33
1521             {
1522                 *pDataSize = AudioUnitParameterStringFromValue.sizeof;
1523                 if (pData && scope_ == kAudioUnitScope_Global)
1524                 {
1525                     AudioUnitParameterStringFromValue* pSFV = cast(AudioUnitParameterStringFromValue*) pData;
1526                     Parameter parameter = _client.param(pSFV.inParamID);
1527 
1528                     char[128] buffer;
1529                     parameter.stringFromNormalizedValue(*pSFV.inValue, buffer.ptr, buffer.length);
1530                     size_t len = strlen(buffer.ptr);
1531                     pSFV.outString = toCFString(buffer[0..len]);
1532                 }
1533                 return noErr;
1534             }
1535 
1536             case kAudioUnitProperty_ParameterValueFromString: // 38
1537             {
1538                 *pDataSize = AudioUnitParameterValueFromString.sizeof;
1539                 if (pData)
1540                 {
1541                     AudioUnitParameterValueFromString* pVFS = cast(AudioUnitParameterValueFromString*) pData;
1542                     if (scope_ == kAudioUnitScope_Global)
1543                     {
1544                         Parameter parameter = _client.param(pVFS.inParamID);
1545                         string paramString = mallocStringFromCFString(pVFS.inString);
1546                         scope(exit) paramString.freeSlice();
1547                         double doubleValue;
1548                         if (parameter.normalizedValueFromString(paramString, doubleValue))
1549                             pVFS.outValue = doubleValue;
1550                         else
1551                             return kAudioUnitErr_InvalidProperty;
1552                     }
1553                 }
1554                 return noErr;
1555             }
1556 
1557             case kAudioUnitProperty_MIDIOutputCallbackInfo: // 47
1558                 if (!_client.sendsMIDI())
1559                     return kAudioUnitErr_InvalidProperty;
1560                 *pDataSize = CFArrayRef.sizeof;
1561                 *pWriteable = false;
1562                 if (pData)
1563                 {
1564                     CFStringRef[1] strs;
1565                     strs[0] = toCFString("MIDI Out");
1566                     CFArrayRef callbackArray = CFArrayCreate (null, cast(const(void)**)strs.ptr, 1, null);
1567                     CFRelease(strs[0]);
1568                     *cast(CFArrayRef*) pData = callbackArray;
1569                 }
1570                 return noErr;
1571 
1572             case kAudioUnitProperty_MIDIOutputCallback: // 48
1573                 if (!_client.sendsMIDI())
1574                     return kAudioUnitErr_InvalidProperty;
1575                 *pDataSize = AUMIDIOutputCallbackStruct.sizeof;
1576                 *pWriteable = true;
1577                 if (pData is null)
1578                     return noErr;
1579                 else
1580                     return kAudioUnitErr_InvalidProperty;
1581 
1582             case kMusicDeviceProperty_InstrumentCount:
1583             {
1584                 if (!isGlobalScope(scope_))
1585                     return kAudioUnitErr_InvalidScope;
1586 
1587                 if (_client.isSynth())
1588                 {
1589                     *pDataSize = UInt32.sizeof;
1590                     if (pData)
1591                         *(cast(UInt32*) pData) = 0; // mono-timbral
1592                     return noErr;
1593                 }
1594                 else
1595                     return kAudioUnitErr_InvalidProperty;
1596             }
1597 
1598             case kAudioUnitProperty_DPLUG_AUCLIENT_INSTANCE:
1599             {
1600                 *pWriteable = false;
1601                 *pDataSize = size_t.sizeof;
1602                 if (pData)
1603                     *(cast(void**) pData) = cast(void*)this;
1604                 return noErr;
1605             }
1606 
1607             default:
1608                 return kAudioUnitErr_InvalidProperty;
1609         }
1610     }
1611 
1612     final bool isAudioComponentAPI()
1613     {
1614         return _componentInstance is null;
1615     }
1616 
1617     final bool isComponentManagerAPI()
1618     {
1619         return _componentInstance !is null;
1620     }
1621 
1622     ComponentResult setProperty(AudioUnitPropertyID propID, AudioUnitScope scope_, AudioUnitElement element,
1623                                 UInt32* pDataSize, const(void)* pData) nothrow
1624     {
1625         // inform listeners
1626         foreach (ref listener; _propertyListeners)
1627             if (listener.mPropID == propID)
1628                 listener.mListenerProc(listener.mProcArgs, instanceHandle(), propID, scope_, 0); // always zero?
1629 
1630         debug(logDispatcher) debugLogf("SET property %d\n", propID);
1631 
1632         switch(propID)
1633         {
1634             case kAudioUnitProperty_ClassInfo:
1635                 return writeState(*(cast(CFPropertyListRef*) pData));
1636 
1637             case kAudioUnitProperty_MakeConnection: // 1
1638             {
1639                 if (!isInputOrGlobalScope(scope_))
1640                     return kAudioUnitErr_InvalidScope;
1641 
1642                 AudioUnitConnection* pAUC = cast(AudioUnitConnection*) pData;
1643                 if (pAUC.destInputNumber >= _inBusConnections.length)
1644                     return kAudioUnitErr_InvalidElement;
1645 
1646                 InputBusConnection* pInBusConn = &_inBusConnections[pAUC.destInputNumber];
1647                 *pInBusConn = InputBusConnection.init;
1648 
1649                 bool negotiatedOK = true;
1650                 if (pAUC.sourceAudioUnit)
1651                 {
1652                     // Open connection.
1653                     AudioStreamBasicDescription srcASBD;
1654                     uint size = cast(uint)(srcASBD.sizeof);
1655 
1656                     // Ask whoever is sending us audio what the format is.
1657                     negotiatedOK = (AudioUnitGetProperty(pAUC.sourceAudioUnit, kAudioUnitProperty_StreamFormat,
1658                                         kAudioUnitScope_Output, pAUC.sourceOutputNumber, &srcASBD, &size) == noErr);
1659 
1660                     negotiatedOK &= (setProperty(kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1661                                                  pAUC.destInputNumber, &size, &srcASBD) == noErr);
1662 
1663                     if (negotiatedOK)
1664                     {
1665                         pInBusConn.upstreamUnit = pAUC.sourceAudioUnit;
1666                         pInBusConn.upstreamBusIdx = pAUC.sourceOutputNumber;
1667 
1668                         // Will the upstream unit give us a fast render proc for input?
1669                         enum bool enableFastProc = true;
1670 
1671                         static if (enableFastProc)
1672                         {
1673                             AudioUnitRenderProc srcRenderProc;
1674                             size = AudioUnitRenderProc.sizeof;
1675                             if (AudioUnitGetProperty(pAUC.sourceAudioUnit, kAudioUnitProperty_FastDispatch, kAudioUnitScope_Global, kAudioUnitRenderSelect,
1676                                                    &srcRenderProc, &size) == noErr)
1677                             {
1678                                 // Yes, we got a fast render proc, and we also need to store the pointer to the upstream audio unit object.
1679                                 pInBusConn.upstreamRenderProc = srcRenderProc;
1680                                 pInBusConn.upstreamObj = GetComponentInstanceStorage(pAUC.sourceAudioUnit);
1681                             }
1682                             // Else no fast render proc, so leave the input bus connection struct's upstream render proc and upstream object empty,
1683                             // and we will need to make a component call through the component manager to get input data.
1684                         }
1685                     }
1686                     // Else this is a call to close the connection, which we effectively did by clearing the InputBusConnection struct,
1687                     // which counts as a successful negotiation.
1688                 }
1689                 assessInputConnections();
1690                 return negotiatedOK ? noErr : kAudioUnitErr_InvalidProperty;
1691             }
1692 
1693             case kAudioUnitProperty_SampleRate: // 2
1694             {
1695                 _sampleRate = *(cast(Float64*)pData);
1696                 _messageQueue.pushBack(makeResetStateMessage());
1697 
1698 
1699                 // Warn about change of latency.
1700                 // Most hosts listen this property.
1701                 foreach (ref listener; _propertyListeners)
1702                 {
1703                     if (listener.mPropID == kAudioUnitProperty_Latency)
1704                         listener.mListenerProc(listener.mProcArgs, instanceHandle(), kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0);
1705                 }
1706 
1707                 return noErr;
1708             }
1709 
1710             case kAudioUnitProperty_StreamFormat: // 8
1711             {
1712                 AudioStreamBasicDescription* pASBD = cast(AudioStreamBasicDescription*) pData;
1713                 int nHostChannels = pASBD.mChannelsPerFrame;
1714                 BusChannels* pBus = getBus(scope_, element);
1715                 if (!pBus)
1716                     return kAudioUnitErr_InvalidElement;
1717 
1718                 pBus.numHostChannels = 0;
1719                 // The connection is OK if the plugin expects the same number of channels as the host is attempting to connect,
1720                 // or if the plugin supports mono channels (meaning it's flexible about how many inputs to expect)
1721                 // and the plugin supports at least as many channels as the host is attempting to connect.
1722                 bool moreThanOneChannel = (nHostChannels > 0);
1723                 bool isLegalIO = checkLegalIO(scope_, element, nHostChannels) == noErr;
1724                 bool compatibleFormat = (pASBD.mFormatID == kAudioFormatLinearPCM) && (pASBD.mFormatFlags & kAudioFormatFlagsNativeFloatPacked);
1725                 bool connectionOK = moreThanOneChannel && isLegalIO && compatibleFormat;
1726 
1727                 // Interleaved not supported here
1728 
1729                 if (connectionOK)
1730                 {
1731                     pBus.numHostChannels = nHostChannels;
1732 
1733                     // Eventually change sample rate
1734                     if (pASBD.mSampleRate > 0.0)
1735                     {
1736                         _sampleRate = pASBD.mSampleRate;
1737                         _messageQueue.pushBack(makeResetStateMessage());
1738                     }
1739                 }
1740                 return (connectionOK ? noErr : kAudioUnitErr_InvalidProperty);
1741             }
1742 
1743             case kAudioUnitProperty_MaximumFramesPerSlice: // 14
1744             {
1745                 _maxFrames = *(cast(uint*)pData);
1746                 _messageQueue.pushBack(makeResetStateMessage());
1747                 return noErr;
1748             }
1749 
1750             case kAudioUnitProperty_BypassEffect: // 21
1751             {
1752                 // using hard bypass
1753                 _hardBypassed = (*(cast(UInt32*) pData) != 0);
1754                 _messageQueue.pushBack(makeResetStateMessage());
1755                 return noErr;
1756             }
1757 
1758             case kAudioUnitProperty_SetRenderCallback: // 23
1759             {
1760                 if (!isInputScope(scope_))
1761                     return kAudioUnitErr_InvalidScope;
1762 
1763                 if (element >= _inBusConnections.length)
1764                     return kAudioUnitErr_InvalidElement;
1765 
1766                 InputBusConnection* pInBusConn = &_inBusConnections[element];
1767                 *pInBusConn = InputBusConnection.init;
1768                 AURenderCallbackStruct* pCS = cast(AURenderCallbackStruct*) pData;
1769                 if (pCS.inputProc != null)
1770                     pInBusConn.upstreamRenderCallback = *pCS;
1771                 assessInputConnections();
1772                 return noErr;
1773             }
1774 
1775             case kAudioUnitProperty_HostCallbacks: // 27
1776             {
1777                 if (!isGlobalScope(scope_))
1778                     return kAudioUnitScope_Global;
1779                 _hostCallbacks = *(cast(HostCallbackInfo*)pData);
1780                 return noErr;
1781             }
1782 
1783             case kAudioUnitProperty_CurrentPreset: // 28
1784             case kAudioUnitProperty_PresentPreset: // 36
1785             {
1786                 AUPreset* auPreset = cast(AUPreset*)pData;
1787                 int presetIndex = auPreset.presetNumber;
1788 
1789                 PresetBank bank = _client.presetBank();
1790                 if (bank.isValidPresetIndex(presetIndex))
1791                 {
1792                     bank.loadPresetFromHost(presetIndex);
1793                     return noErr;
1794                 }
1795                 else
1796                 {
1797                     // Do not create a preset _inside_ our presetbank.
1798                     // Probably very few hosts rely on that, and DigitalPerformer
1799                     // doesn't need it, it created issue #606 when previously creating
1800                     // a preset at the end of the bank and then loading it.
1801                     // Will we need to return preset -1 on GET?
1802                     // Anyway, this was tested on DP11, Logic, REAPER.
1803                     return noErr;
1804                 }
1805             }
1806 
1807             case kAudioUnitProperty_OfflineRender:
1808                 return noErr; // 37
1809 
1810             case kAudioUnitProperty_AUHostIdentifier:            // 46,
1811             {
1812                 AUHostIdentifier* pHostID = cast(AUHostIdentifier*) pData;
1813                 string dawName = mallocStringFromCFString(pHostID.hostName);
1814                 _daw = identifyDAW(dawName.ptr); // dawName is guaranteed zero-terminated
1815                 dawName.freeSlice();
1816                 return noErr;
1817             }
1818 
1819             case kAudioUnitProperty_MIDIOutputCallback: // 48
1820             {
1821                 AUMIDIOutputCallbackStruct* cbs = (cast(AUMIDIOutputCallbackStruct*) pData);
1822                 _midiOutCallback = *cbs;
1823                 return noErr;
1824             }
1825 
1826             default:
1827                 return kAudioUnitErr_InvalidProperty; // NO-OP, or unsupported
1828         }
1829     }
1830 
1831     // From connection information, transmit to client
1832     void assessInputConnections() nothrow
1833     {
1834         foreach (i; 0.._inBuses.length )
1835         {
1836             BusChannels* pInBus = &_inBuses[i];
1837             InputBusConnection* pInBusConn = &_inBusConnections[i];
1838 
1839             pInBus.connected = pInBusConn.isConnected();
1840 
1841             int startChannelIdx = pInBus.plugChannelStartIdx;
1842             if (pInBus.connected)
1843             {
1844                 // There's an input connection, so we need to tell the plug to expect however many channels
1845                 // are in the negotiated host stream format.
1846                 if (pInBus.numHostChannels < 0)
1847                 {
1848                     // The host set up a connection without specifying how many channels in the stream.
1849                     // Assume the host will send all the channels the plugin asks for, and hope for the best.
1850                     pInBus.numHostChannels = pInBus.numPlugChannels;
1851                 }
1852             }
1853         }
1854 
1855         _messageQueue.pushBack(makeResetStateMessage());
1856     }
1857 
1858     ComponentResult checkLegalIO(AudioUnitScope scope_, int busIdx, int nChannels) nothrow
1859     {
1860         import core.stdc.stdio;
1861 
1862         if (scope_ == kAudioUnitScope_Input)
1863         {
1864             int nIn = numHostChannelsConnected(_inBuses, busIdx);
1865             if (nIn < 0) nIn = 0;
1866             int nOut = _active ? numHostChannelsConnected(_outBuses) : -1;
1867             ComponentResult res = _client.isLegalIO(nIn + nChannels, nOut) ? noErr : kAudioUnitErr_InvalidScope;
1868             return res;
1869         }
1870         else if (scope_ == kAudioUnitScope_Output)
1871         {
1872             int nIn = _active ? numHostChannelsConnected(_inBuses) : -1;
1873             int nOut = numHostChannelsConnected(_outBuses, busIdx);
1874             if (nOut < 0) nOut = 0;
1875 
1876             // Fix for Issue #727 https://github.com/AuburnSounds/Dplug/issues/727
1877             // 1 channel means "flexible" apparently, auvaltool with -oop will demand 1-2 in a transient manner while testing
1878             // 
1879             // FUTURE: the AUv2 client is a real mess, this should be rewrittent with proper bus management in client one day.
1880             bool legal = _client.isLegalIO(nIn, nOut  + nChannels);
1881             if (!legal && nIn == 1)
1882             {
1883                 // Another chance to deem it legal.
1884                 legal = _client.isLegalIO(nOut + nChannels, nOut + nChannels);
1885             }
1886             return legal ? noErr : kAudioUnitErr_InvalidScope;
1887         }
1888         else
1889             return kAudioUnitErr_InvalidScope;
1890     }
1891 
1892     static int numHostChannelsConnected(BusChannels[] pBuses, int excludeIdx = -1) pure nothrow @nogc
1893     {
1894         bool init = false;
1895         int nCh = 0;
1896         int n = cast(int)pBuses.length;
1897 
1898         for (int i = 0; i < n; ++i)
1899         {
1900             if (i != excludeIdx) // -1 => no bus excluded
1901             {
1902                 int nHostChannels = pBuses[i].numHostChannels;
1903                 if (nHostChannels >= 0)
1904                 {
1905                     nCh += nHostChannels;
1906                     init = true;
1907                 }
1908             }
1909         }
1910 
1911         if (init)
1912             return nCh;
1913         else
1914             return -1;
1915     }
1916 
1917     final AudioThreadMessage makeResetStateMessage() pure const nothrow @nogc
1918     {
1919         return AudioThreadMessage(AudioThreadMessage.Type.resetState, _maxFrames, _sampleRate, _hardBypassed);
1920     }
1921 
1922     // Note: this logic is duplicated in several places in dplug-build
1923     final int componentType()
1924     {
1925         if (_client.isSynth)
1926             return CCONST('a', 'u', 'm', 'u');
1927         else if (_client.receivesMIDI)
1928             return CCONST('a', 'u', 'm', 'f');
1929         else
1930             return CCONST('a', 'u', 'f', 'x');
1931     }
1932 
1933     final int componentSubType()
1934     {
1935         char[4] pid = _client.getPluginUniqueID();
1936         return CCONST(pid[0], pid[1], pid[2], pid[3]);
1937     }
1938 
1939     final int componentManufacturer()
1940     {
1941         char[4] vid = _client.getVendorUniqueID();
1942         return CCONST(vid[0], vid[1], vid[2], vid[3]);
1943     }
1944 
1945     // Serialize state
1946     final ComponentResult readState(CFPropertyListRef* ppPropList) nothrow
1947     {
1948         int cType = componentType(),
1949             cSubType = componentSubType,
1950             cManufacturer = componentManufacturer();
1951 
1952         CFMutableDictionaryRef pDict = CFDictionaryCreateMutable(null, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1953         int version_ = _client.getPublicVersion().toAUVersion;
1954         putNumberInDict(pDict, kAUPresetVersionKey, &version_, kCFNumberSInt32Type);
1955 
1956         putNumberInDict(pDict, kAUPresetTypeKey, &cType, kCFNumberSInt32Type);
1957         putNumberInDict(pDict, kAUPresetSubtypeKey, &cSubType, kCFNumberSInt32Type);
1958         putNumberInDict(pDict, kAUPresetManufacturerKey, &cManufacturer, kCFNumberSInt32Type);
1959         auto presetBank = _client.presetBank();
1960         putStrInDict(pDict, kAUPresetNameKey, presetBank.currentPreset().name);
1961         ubyte[] state = presetBank.getStateChunkFromCurrentState(); // LEAK???
1962         putDataInDict(pDict, kAUPresetDataKey, state);
1963         *ppPropList = pDict;
1964         return noErr;
1965     }
1966 
1967     final ComponentResult writeState(CFPropertyListRef ppPropList) nothrow @nogc
1968     {
1969         int cType = componentType(),
1970         cSubType = componentSubType,
1971         cManufacturer = componentManufacturer();
1972 
1973         int version_, type, subtype, mfr;
1974         string presetName;
1975 
1976         CFMutableDictionaryRef pDict = cast(CFMutableDictionaryRef)ppPropList;
1977 
1978         if (!getNumberFromDict(pDict, kAUPresetVersionKey, &version_, kCFNumberSInt32Type) ||
1979             !getNumberFromDict(pDict, kAUPresetTypeKey, &type, kCFNumberSInt32Type) ||
1980             !getNumberFromDict(pDict, kAUPresetSubtypeKey, &subtype, kCFNumberSInt32Type) ||
1981             !getNumberFromDict(pDict, kAUPresetManufacturerKey, &mfr, kCFNumberSInt32Type) ||
1982             !getStrFromDict(pDict, kAUPresetNameKey, presetName) ||
1983               type != cType ||
1984               subtype != cSubType ||
1985               mfr != cManufacturer)
1986         {
1987             return kAudioUnitErr_InvalidPropertyValue;
1988         }
1989 
1990         scope(exit) presetName.freeSlice();
1991 
1992         ubyte[] chunk;
1993         if (!getDataFromDict(pDict, kAUPresetDataKey, chunk))
1994         {
1995             return kAudioUnitErr_InvalidPropertyValue;
1996         }
1997 
1998         scope(exit) chunk.freeSlice();
1999 
2000         bool err;
2001         auto presetBank = _client.presetBank();
2002         presetBank.loadStateChunk(chunk, &err);
2003 
2004         if (err)
2005         {
2006             return kAudioUnitErr_InvalidPropertyValue;
2007         }
2008         return noErr;
2009     }
2010 
2011 
2012     //
2013     // Render procedure
2014     //
2015 
2016     final ComponentResult render(AudioUnitRenderActionFlags* pFlags,
2017                                  const(AudioTimeStamp)* pTimestamp,
2018                                  uint outputBusIdx,
2019                                  uint nFrames,
2020                                  AudioBufferList* pOutBufList) nothrow @nogc
2021     {
2022         // GarageBand will happily send NULL pFlags, do not use it
2023 
2024         // Non-existing bus
2025         if (outputBusIdx > _outBuses.length)
2026             return kAudioUnitErr_InvalidElement;
2027 
2028         // Invalid timestamp
2029         if (!(pTimestamp.mFlags & kAudioTimeStampSampleTimeValid))
2030             return kAudioUnitErr_InvalidPropertyValue;
2031 
2032         // process messages to get newer number of input, samplerate or frame number
2033         void processMessages(ref float newSamplerate, ref int newMaxFrames, ref bool newBypassed) nothrow @nogc
2034         {
2035             // Only the last reset state message is meaningful, so we unwrap them
2036 
2037             AudioThreadMessage msg = void;
2038 
2039             while(_messageQueue.tryPopFront(msg))
2040             {
2041                 final switch(msg.type) with (AudioThreadMessage.Type)
2042                 {
2043                     case resetState:
2044                         // Note: number of input/ouputs is discarded from the message
2045                         newMaxFrames = msg.maxFrames;
2046                         newSamplerate = msg.samplerate;
2047                         newBypassed = msg.bypassed;
2048                         break;
2049 
2050                     case midi:
2051                         _client.enqueueMIDIFromHost(msg.midiMessage);
2052 
2053                 }
2054             }
2055         }
2056         float newSamplerate = _lastSamplerate;
2057         int newMaxFrames = _lastMaxFrames;
2058         processMessages(newSamplerate, newMaxFrames, _lastBypassed);
2059 
2060         // Must fail when given too much frames
2061         if (nFrames > newMaxFrames)
2062             return kAudioUnitErr_TooManyFramesToProcess;
2063 
2064         // We'll need scratch buffers to render upstream
2065         if (newMaxFrames != _lastMaxFrames)
2066             resizeScratchBuffers(newMaxFrames);
2067 
2068 
2069         {
2070             _renderNotifyMutex.lock();
2071             scope(exit) _renderNotifyMutex.unlock();
2072 
2073             // pre-render
2074             if (_renderNotify.length)
2075             {
2076                 foreach(ref renderCallbackStruct; _renderNotify)
2077                 {
2078                     AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PreRender;
2079                     callRenderCallback(renderCallbackStruct, &flags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2080                 }
2081             }
2082         }
2083 
2084         double renderTimestamp = pTimestamp.mSampleTime;
2085 
2086         bool isNewTimestamp = (renderTimestamp != _lastRenderTimestamp);
2087 
2088         // On a novel timestamp, we render upstream (pull) and process audio.
2089         // Else, just copy the results.
2090         // We always provide buffers to upstream unit
2091         int lastConnectedOutputBus = -1;
2092         {
2093             // Lock input and output buses
2094             _globalMutex.lock();
2095             scope(exit) _globalMutex.unlock();
2096 
2097             if (isNewTimestamp)
2098             {
2099                 BufferList bufList;
2100                 AudioBufferList* pInBufList = cast(AudioBufferList*) &bufList;
2101 
2102                 // Clear inputPointers and fill it:
2103                 //  - with null for unconnected channels
2104                 //  - with a pointer to scratch for connected channels
2105                 _inputPointers[] = null;
2106 
2107                 // call render for each upstream units
2108                 foreach(size_t inputBusIdx, ref pInBus; _inBuses)
2109                 {
2110                     InputBusConnection* pInBusConn = &_inBusConnections[inputBusIdx];
2111 
2112                     if (pInBus.connected)
2113                     {
2114                         pInBufList.mNumberBuffers = pInBus.numHostChannels;
2115 
2116                         for (int b = 0; b < pInBufList.mNumberBuffers; ++b)
2117                         {
2118                             AudioBuffer* pBuffer = &(pInBufList.mBuffers.ptr[b]);
2119                             int whichScratch = pInBus.plugChannelStartIdx + b;
2120                             float* buffer = _inputScratchBuffer[whichScratch].ptr;
2121                             pBuffer.mData = buffer;
2122                             pBuffer.mNumberChannels = 1;
2123                             pBuffer.mDataByteSize = cast(uint)(nFrames * AudioSampleType.sizeof);
2124                         }
2125                         AudioUnitRenderActionFlags flags = 0;
2126                         ComponentResult r = pInBusConn.callUpstreamRender(&flags, pTimestamp, nFrames, pInBufList, cast(int)inputBusIdx);
2127                         if (r != noErr)
2128                             return r;   // Something went wrong upstream.
2129 
2130                         // Get back input data pointer, that may have been modified by upstream
2131                         for (int b = 0; b < pInBufList.mNumberBuffers; ++b)
2132                         {
2133                             AudioBuffer* pBuffer = &(pInBufList.mBuffers.ptr[b]);
2134                             int whichScratch = pInBus.plugChannelStartIdx + b;
2135                             _inputPointers[whichScratch] = cast(float*)(pBuffer.mData);
2136                         }
2137                     }
2138                 }
2139 
2140                 _lastRenderTimestamp = renderTimestamp;
2141             }
2142             BusChannels* pOutBus = &_outBuses[outputBusIdx];
2143 
2144             // if this bus is not connected OR the number of buffers that the host has given are not equal to the number the bus expects
2145             // then consider it connected
2146             if (!(pOutBus.connected) || pOutBus.numHostChannels != pOutBufList.mNumberBuffers)
2147             {
2148                 pOutBus.connected = true;
2149             }
2150 
2151             foreach(outBus; _outBuses)
2152             {
2153                 if(!outBus.connected)
2154                     break;
2155                 else
2156                     lastConnectedOutputBus++;
2157             }
2158 
2159             // assign _outputPointers
2160             for (int i = 0; i < pOutBufList.mNumberBuffers; ++i)
2161             {
2162                 int chIdx = pOutBus.plugChannelStartIdx + i;
2163 
2164                 AudioSampleType* pData = cast(AudioSampleType*)( pOutBufList.mBuffers.ptr[i].mData );
2165                 if (pData == null)
2166                 {
2167                     // No output buffers given, we use scratch buffers instead
2168                     pData = _outputScratchBuffer[chIdx].ptr;
2169                     pOutBufList.mBuffers.ptr[i].mData = pData;
2170                 }
2171 
2172                 _outputPointers[chIdx] = pData;
2173             }
2174 
2175             // Second the _outputPointers array is cleared for above > mNumberBuffersthis, to avoid errors when going from 2-2 to 1-1
2176             for (int i = pOutBufList.mNumberBuffers; i < pOutBus.numPlugChannels; ++i)
2177             {
2178                 int chIdx = pOutBus.plugChannelStartIdx + i;
2179                 _outputPointers[chIdx] = null;
2180             }
2181         }
2182 
2183 
2184         if (outputBusIdx == lastConnectedOutputBus)
2185         {
2186             // Here we can finally know the real number of input and outputs connected, but not before.
2187             // We also flatten the pointer arrays
2188             int newUsedInputs = 0;
2189             foreach(inputPointer; _inputPointers[])
2190                 if (inputPointer != null)
2191                     _inputPointersNoGap[newUsedInputs++] = inputPointer;
2192 
2193             int newUsedOutputs = 0;
2194             foreach(outputPointer; _outputPointers[])
2195                 if (outputPointer != null)
2196                     _outputPointersNoGap[newUsedOutputs++] = outputPointer;
2197 
2198             // Call client.reset if we do need to call it, and only once.
2199             bool needReset = (newMaxFrames != _lastMaxFrames || newUsedInputs != _lastUsedInputs ||
2200                               newUsedOutputs != _lastUsedOutputs || newSamplerate != _lastSamplerate);
2201 
2202 
2203             // in case upstream has changed flags
2204             FPControl fpControl;
2205             fpControl.initialize();
2206 
2207             if (needReset)
2208             {
2209                 _client.resetFromHost(newSamplerate, newMaxFrames, newUsedInputs, newUsedOutputs);
2210                 _lastMaxFrames = newMaxFrames;
2211                 _lastSamplerate = newSamplerate;
2212                 _lastUsedInputs = newUsedInputs;
2213                 _lastUsedOutputs = newUsedOutputs;
2214             }
2215 
2216             if (_lastBypassed)
2217             {
2218                 // MAYDO: should delay by latency when bypassed
2219                 int minIO = newUsedInputs;
2220                 if (minIO > newUsedOutputs)
2221                     minIO = newUsedOutputs;
2222 
2223                 for (int i = 0; i < minIO; ++i)
2224                     _outputPointersNoGap[i][0..nFrames] = _inputPointersNoGap[i][0..nFrames];
2225 
2226                 for (int i = minIO; i < newUsedOutputs; ++i)
2227                     _outputPointersNoGap[i][0..nFrames] = 0;
2228             }
2229             else
2230             {
2231                 TimeInfo timeInfo = getTimeInfo();
2232 
2233 
2234                 // clear MIDI out buffers
2235                 if (_client.sendsMIDI)
2236                     _client.clearAccumulatedOutputMidiMessages();
2237 
2238                 // Welcome to Issue #890 workaround.
2239                 // Here is our last chance to avoid have same pointer for input and output (Studio One 7).
2240                 // If we detect the same buffer here, copy to scratch input buffer and point there.
2241                 {
2242                     int minIO = newUsedInputs;
2243                     if (minIO > newUsedOutputs) minIO = newUsedOutputs;
2244                     for (int b = 0; b < minIO; ++b)
2245                     {
2246                         if (_inputPointersNoGap[b] == _outputPointersNoGap[b])
2247                         {
2248                             // Assuming here that the scratch buffer couldn't be in input AND output, though
2249                             // I'm not 100% certain to be fair.
2250                             float* buffer = _inputScratchBuffer[b].ptr;
2251                             buffer[0..nFrames] =  _inputPointersNoGap[b][0..nFrames]; // copy
2252                             _inputPointersNoGap[b] = buffer;
2253                         }
2254                     }
2255                 }
2256 
2257                 _client.processAudioFromHost(_inputPointersNoGap[0..newUsedInputs],
2258                                              _outputPointersNoGap[0..newUsedOutputs],
2259                                              nFrames,
2260                                              timeInfo);
2261 
2262                 if (_client.sendsMIDI() && _midiOutCallback.midiOutputCallback !is null)
2263                 {
2264                     const(MidiMessage)[] outMsgs = _client.getAccumulatedOutputMidiMessages();
2265 
2266                     foreach(msg; outMsgs)
2267                     {
2268                         MIDIPacketList packets;
2269                         packets.packet[0].length = cast(ushort) msg.toBytes(cast(ubyte*) packets.packet[0].data.ptr, 256);
2270                         if (packets.packet[0].length == 0)
2271                         {
2272                             // nothing written, ignore this message
2273                             continue;
2274                         }
2275                         packets.packet[0].timeStamp = msg.offset;
2276                         packets.numPackets = 1;
2277                         _midiOutCallback.midiOutputCallback(_midiOutCallback.userData,
2278                                                             pTimestamp,
2279                                                             0,
2280                                                             &packets);
2281                     }
2282                 }
2283             }
2284         }
2285 
2286         // post-render
2287         if (_renderNotify.length)
2288         {
2289             _renderNotifyMutex.lock();
2290             scope(exit) _renderNotifyMutex.unlock();
2291 
2292             foreach(ref renderCallbackStruct; _renderNotify)
2293             {
2294                 AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PostRender;
2295                 callRenderCallback(renderCallbackStruct, &flags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2296             }
2297         }
2298 
2299         return noErr;
2300     }
2301 
2302     // IHostCommand
2303     public
2304     {
2305         final void sendAUEvent(AudioUnitEventType type, int paramIndex) nothrow @nogc
2306         {
2307             AudioUnitEvent auEvent;
2308             auEvent.mEventType = type;
2309             auEvent.mArgument.mParameter.mAudioUnit = instanceHandle();
2310             auEvent.mArgument.mParameter.mParameterID = paramIndex;
2311             auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
2312             auEvent.mArgument.mParameter.mElement = 0;
2313             AUEventListenerNotify(null, null, &auEvent);
2314         }
2315 
2316         override void beginParamEdit(int paramIndex)
2317         {
2318             sendAUEvent(kAudioUnitEvent_BeginParameterChangeGesture, paramIndex);
2319         }
2320 
2321         override void paramAutomate(int paramIndex, float value)
2322         {
2323             sendAUEvent(kAudioUnitEvent_ParameterValueChange, paramIndex);
2324         }
2325 
2326         override void endParamEdit(int paramIndex)
2327         {
2328             sendAUEvent(kAudioUnitEvent_EndParameterChangeGesture, paramIndex);
2329         }
2330 
2331         override bool requestResize(int width, int height)
2332         {
2333             // On macOS 10.14
2334             // * Logic: window plugin resizing seems enough
2335             // * Live 10: window plugin resizing seems enough
2336             // * REAPER: problems with host parent window
2337             return true;
2338         }
2339 
2340         override bool notifyResized()
2341         {
2342             return false;
2343         }
2344 
2345         DAW _daw = DAW.Unknown;
2346 
2347         override DAW getDAW()
2348         {
2349             return _daw;
2350         }
2351 
2352         override PluginFormat getPluginFormat()
2353         {
2354             return PluginFormat.auv2;
2355         }
2356     }
2357 
2358     // Host callbacks
2359     final TimeInfo getTimeInfo() nothrow @nogc
2360     {
2361         TimeInfo result;
2362 
2363         auto hostCallbacks = _hostCallbacks;
2364 
2365         if (hostCallbacks.transportStateProc)
2366         {
2367             double samplePos = 0.0, loopStartBeat, loopEndBeat;
2368             Boolean playing, changed, looping;
2369             hostCallbacks.transportStateProc(hostCallbacks.hostUserData, &playing, &changed, &samplePos,
2370                                              &looping, &loopStartBeat, &loopEndBeat);
2371             result.timeInSamples = cast(long)(samplePos + 0.5);
2372             result.hostIsPlaying = (playing  != 0);
2373         }
2374 
2375         if (hostCallbacks.beatAndTempoProc)
2376         {
2377             double currentBeat = 0.0, tempo = 0.0;
2378             hostCallbacks.beatAndTempoProc(hostCallbacks.hostUserData, &currentBeat, &tempo);
2379             if (tempo > 0.0)
2380                 result.tempo = tempo;
2381         }
2382         return result;
2383     }
2384 
2385     package void* openGUIAndReturnCocoaView()
2386     {
2387         if (!_client.hasGUI())
2388             return null;
2389 
2390         if (_isUIOpenedAlready)
2391             _client.closeGUI();
2392 
2393         _isUIOpenedAlready = true;
2394 
2395         return _client.openGUI(null, null, GraphicsBackend.cocoa);
2396     }
2397 }
2398 
2399 
2400 private:
2401 
2402 // Helpers for scope
2403 static bool isGlobalScope(AudioUnitScope scope_) pure nothrow @nogc
2404 {
2405     return (scope_ == kAudioUnitScope_Global);
2406 }
2407 
2408 static bool isInputScope(AudioUnitScope scope_) pure nothrow @nogc
2409 {
2410     return (scope_ == kAudioUnitScope_Input);
2411 }
2412 
2413 static bool isOutputScope(AudioUnitScope scope_) pure nothrow @nogc
2414 {
2415     return (scope_ == kAudioUnitScope_Output);
2416 }
2417 
2418 static bool isInputOrGlobalScope(AudioUnitScope scope_) pure nothrow @nogc
2419 {
2420     return (scope_ == kAudioUnitScope_Input || scope_ == kAudioUnitScope_Global);
2421 }
2422 
2423 static bool isInputOrOutputScope(AudioUnitScope scope_) pure nothrow @nogc
2424 {
2425     return (scope_ == kAudioUnitScope_Input || scope_ == kAudioUnitScope_Output);
2426 }
2427 
2428 /// Calls a render callback
2429 static ComponentResult callRenderCallback(ref AURenderCallbackStruct pCB, AudioUnitRenderActionFlags* pFlags, const(AudioTimeStamp)* pTimestamp,
2430                        UInt32 inputBusIdx, UInt32 nFrames, AudioBufferList* pOutBufList) nothrow @nogc
2431 {
2432     return pCB.inputProc(pCB.inputProcRefCon, pFlags, pTimestamp, inputBusIdx, nFrames, pOutBufList);
2433 }
2434 
2435 
2436 extern(C) ComponentResult getParamProc(void* pPlug,
2437                              AudioUnitParameterID paramID,
2438                              AudioUnitScope scope_,
2439                              AudioUnitElement element,
2440                              AudioUnitParameterValue* pValue) nothrow @nogc
2441 {
2442     AUClient _this = cast(AUClient)pPlug;
2443     return _this.DoGetParameter(paramID, scope_, element, pValue);
2444 }
2445 
2446 extern(C) ComponentResult setParamProc(void* pPlug,
2447                              AudioUnitParameterID paramID,
2448                              AudioUnitScope scope_,
2449                              AudioUnitElement element,
2450                              AudioUnitParameterValue value,
2451                              UInt32 offsetFrames) nothrow @nogc
2452 {
2453     AUClient _this = cast(AUClient)pPlug;
2454     return _this.DoSetParameter(paramID, scope_, element, value, offsetFrames);
2455 }
2456 
2457 extern(C) ComponentResult renderProc(void* pPlug,
2458                                      AudioUnitRenderActionFlags* pFlags,
2459                                      const(AudioTimeStamp)* pTimestamp,
2460                                      uint outputBusIdx,
2461                                      uint nFrames,
2462                                      AudioBufferList* pOutBufList) nothrow @nogc
2463 {
2464     ScopedForeignCallback!(false, true) scopedCallback;
2465     scopedCallback.enter();
2466 
2467     AUClient _this = cast(AUClient)pPlug;
2468     return _this.render(pFlags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2469 }
2470 
2471 
2472 //
2473 // MessageQueue
2474 //
2475 
2476 
2477 /// A message for the audio thread.
2478 /// Intended to be passed from a non critical thread to the audio thread.
2479 struct AudioThreadMessage
2480 {
2481     enum Type
2482     {
2483         resetState, // reset plugin state, set samplerate and buffer size (samplerate = fParam, buffersize in frames = iParam)
2484         midi
2485     }
2486 
2487     this(Type type_, int maxFrames_, float samplerate_, bool bypassed_) pure const nothrow @nogc
2488     {
2489         type = type_;
2490         maxFrames = maxFrames_;
2491         samplerate = samplerate_;
2492         bypassed = bypassed_;
2493     }
2494 
2495     Type type;
2496     int maxFrames;
2497     float samplerate;
2498     bool bypassed;
2499     MidiMessage midiMessage;
2500 }
2501 
2502 AudioThreadMessage makeMidiThreadMessage(MidiMessage midiMessage) pure nothrow @nogc
2503 {
2504     AudioThreadMessage msg;
2505     msg.type = AudioThreadMessage.Type.midi;
2506     msg.midiMessage = midiMessage;
2507     return msg;
2508 }
2509 
2510 /** Four Character Constant (for AEffect->uniqueID) */
2511 private int CCONST(int a, int b, int c, int d) pure nothrow @nogc
2512 {
2513     return (a << 24) | (b << 16) | (c << 8) | (d << 0);
2514 }