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