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