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