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                 return noErr;
1698             }
1699 
1700             case kAudioUnitProperty_StreamFormat: // 8
1701             {
1702                 AudioStreamBasicDescription* pASBD = cast(AudioStreamBasicDescription*) pData;
1703                 int nHostChannels = pASBD.mChannelsPerFrame;
1704                 BusChannels* pBus = getBus(scope_, element);
1705                 if (!pBus)
1706                     return kAudioUnitErr_InvalidElement;
1707 
1708                 pBus.numHostChannels = 0;
1709                 // The connection is OK if the plugin expects the same number of channels as the host is attempting to connect,
1710                 // or if the plugin supports mono channels (meaning it's flexible about how many inputs to expect)
1711                 // and the plugin supports at least as many channels as the host is attempting to connect.
1712                 bool moreThanOneChannel = (nHostChannels > 0);
1713                 bool isLegalIO = checkLegalIO(scope_, element, nHostChannels) == noErr;
1714                 bool compatibleFormat = (pASBD.mFormatID == kAudioFormatLinearPCM) && (pASBD.mFormatFlags & kAudioFormatFlagsNativeFloatPacked);
1715                 bool connectionOK = moreThanOneChannel && isLegalIO && compatibleFormat;
1716 
1717                 // Interleaved not supported here
1718 
1719                 if (connectionOK)
1720                 {
1721                     pBus.numHostChannels = nHostChannels;
1722 
1723                     // Eventually change sample rate
1724                     if (pASBD.mSampleRate > 0.0)
1725                     {
1726                         _sampleRate = pASBD.mSampleRate;
1727                         _messageQueue.pushBack(makeResetStateMessage());
1728                     }
1729                 }
1730                 return (connectionOK ? noErr : kAudioUnitErr_InvalidProperty);
1731             }
1732 
1733             case kAudioUnitProperty_MaximumFramesPerSlice: // 14
1734             {
1735                 _maxFrames = *(cast(uint*)pData);
1736                 _messageQueue.pushBack(makeResetStateMessage());
1737                 return noErr;
1738             }
1739 
1740             case kAudioUnitProperty_BypassEffect: // 21
1741             {
1742                 // using hard bypass
1743                 _hardBypassed = (*(cast(UInt32*) pData) != 0);
1744                 _messageQueue.pushBack(makeResetStateMessage());
1745                 return noErr;
1746             }
1747 
1748             case kAudioUnitProperty_SetRenderCallback: // 23
1749             {
1750                 if (!isInputScope(scope_))
1751                     return kAudioUnitErr_InvalidScope;
1752 
1753                 if (element >= _inBusConnections.length)
1754                     return kAudioUnitErr_InvalidElement;
1755 
1756                 InputBusConnection* pInBusConn = &_inBusConnections[element];
1757                 *pInBusConn = InputBusConnection.init;
1758                 AURenderCallbackStruct* pCS = cast(AURenderCallbackStruct*) pData;
1759                 if (pCS.inputProc != null)
1760                     pInBusConn.upstreamRenderCallback = *pCS;
1761                 assessInputConnections();
1762                 return noErr;
1763             }
1764 
1765             case kAudioUnitProperty_HostCallbacks: // 27
1766             {
1767                 if (!isGlobalScope(scope_))
1768                     return kAudioUnitScope_Global;
1769                 _hostCallbacks = *(cast(HostCallbackInfo*)pData);
1770                 return noErr;
1771             }
1772 
1773             case kAudioUnitProperty_CurrentPreset: // 28
1774             case kAudioUnitProperty_PresentPreset: // 36
1775             {
1776                 AUPreset* auPreset = cast(AUPreset*)pData;
1777                 int presetIndex = auPreset.presetNumber;
1778 
1779 
1780                 PresetBank bank = _client.presetBank();
1781                 if (bank.isValidPresetIndex(presetIndex))
1782                 {
1783                     bank.loadPresetFromHost(presetIndex);
1784                 }
1785                 else if (auPreset.presetName != null)
1786                 {
1787                     string presetName = mallocStringFromCFString(auPreset.presetName);
1788                     scope(exit) presetName.freeSlice();
1789                     bank.addNewDefaultPresetFromHost(presetName);
1790                 }
1791                 return noErr;
1792             }
1793 
1794             case kAudioUnitProperty_OfflineRender:
1795                 return noErr; // 37
1796 
1797             case kAudioUnitProperty_AUHostIdentifier:            // 46,
1798             {
1799                 AUHostIdentifier* pHostID = cast(AUHostIdentifier*) pData;
1800                 string dawName = mallocStringFromCFString(pHostID.hostName);
1801                 _daw = identifyDAW(dawName.ptr); // dawName is guaranteed zero-terminated
1802                 dawName.freeSlice();
1803                 return noErr;
1804             }
1805 
1806             case kAudioUnitProperty_MIDIOutputCallback: // 48
1807             {
1808                 AUMIDIOutputCallbackStruct* cbs = (cast(AUMIDIOutputCallbackStruct*) pData);
1809                 _midiOutCallback = *cbs;
1810                 return noErr;
1811             }
1812 
1813             default:
1814                 return kAudioUnitErr_InvalidProperty; // NO-OP, or unsupported
1815         }
1816     }
1817 
1818     // From connection information, transmit to client
1819     void assessInputConnections() nothrow
1820     {
1821         foreach (i; 0.._inBuses.length )
1822         {
1823             BusChannels* pInBus = &_inBuses[i];
1824             InputBusConnection* pInBusConn = &_inBusConnections[i];
1825 
1826             pInBus.connected = pInBusConn.isConnected();
1827 
1828             int startChannelIdx = pInBus.plugChannelStartIdx;
1829             if (pInBus.connected)
1830             {
1831                 // There's an input connection, so we need to tell the plug to expect however many channels
1832                 // are in the negotiated host stream format.
1833                 if (pInBus.numHostChannels < 0)
1834                 {
1835                     // The host set up a connection without specifying how many channels in the stream.
1836                     // Assume the host will send all the channels the plugin asks for, and hope for the best.
1837                     pInBus.numHostChannels = pInBus.numPlugChannels;
1838                 }
1839             }
1840         }
1841 
1842         _messageQueue.pushBack(makeResetStateMessage());
1843     }
1844 
1845     ComponentResult checkLegalIO(AudioUnitScope scope_, int busIdx, int nChannels) nothrow
1846     {
1847         import core.stdc.stdio;
1848 
1849         if (scope_ == kAudioUnitScope_Input)
1850         {
1851             int nIn = numHostChannelsConnected(_inBuses, busIdx);
1852             if (nIn < 0) nIn = 0;
1853             int nOut = _active ? numHostChannelsConnected(_outBuses) : -1;
1854             ComponentResult res = _client.isLegalIO(nIn + nChannels, nOut) ? noErr : kAudioUnitErr_InvalidScope;
1855             return res;
1856         }
1857         else if (scope_ == kAudioUnitScope_Output)
1858         {
1859             int nIn = _active ? numHostChannelsConnected(_inBuses) : -1;
1860             int nOut = numHostChannelsConnected(_outBuses, busIdx);
1861             if (nOut < 0) nOut = 0;
1862 
1863             // Fix for Issue #727 https://github.com/AuburnSounds/Dplug/issues/727
1864             // 1 channel means "flexible" apparently, auvaltool with -oop will demand 1-2 in a transient manner while testing
1865             // 
1866             // FUTURE: the AUv2 client is a real mess, this should be rewrittent with proper bus management in client one day.
1867             bool legal = _client.isLegalIO(nIn, nOut  + nChannels);
1868             if (!legal && nIn == 1)
1869             {
1870                 // Another chance to deem it legal.
1871                 legal = _client.isLegalIO(nOut + nChannels, nOut + nChannels);
1872             }
1873             return legal ? noErr : kAudioUnitErr_InvalidScope;
1874         }
1875         else
1876             return kAudioUnitErr_InvalidScope;
1877     }
1878 
1879     static int numHostChannelsConnected(BusChannels[] pBuses, int excludeIdx = -1) pure nothrow @nogc
1880     {
1881         bool init = false;
1882         int nCh = 0;
1883         int n = cast(int)pBuses.length;
1884 
1885         for (int i = 0; i < n; ++i)
1886         {
1887             if (i != excludeIdx) // -1 => no bus excluded
1888             {
1889                 int nHostChannels = pBuses[i].numHostChannels;
1890                 if (nHostChannels >= 0)
1891                 {
1892                     nCh += nHostChannels;
1893                     init = true;
1894                 }
1895             }
1896         }
1897 
1898         if (init)
1899             return nCh;
1900         else
1901             return -1;
1902     }
1903 
1904     final AudioThreadMessage makeResetStateMessage() pure const nothrow @nogc
1905     {
1906         return AudioThreadMessage(AudioThreadMessage.Type.resetState, _maxFrames, _sampleRate, _hardBypassed);
1907     }
1908 
1909     // Note: this logic is duplicated in several places in dplug-build
1910     final int componentType()
1911     {
1912         if (_client.isSynth)
1913             return CCONST('a', 'u', 'm', 'u');
1914         else if (_client.receivesMIDI)
1915             return CCONST('a', 'u', 'm', 'f');
1916         else
1917             return CCONST('a', 'u', 'f', 'x');
1918     }
1919 
1920     final int componentSubType()
1921     {
1922         char[4] pid = _client.getPluginUniqueID();
1923         return CCONST(pid[0], pid[1], pid[2], pid[3]);
1924     }
1925 
1926     final int componentManufacturer()
1927     {
1928         char[4] vid = _client.getVendorUniqueID();
1929         return CCONST(vid[0], vid[1], vid[2], vid[3]);
1930     }
1931 
1932     // Serialize state
1933     final ComponentResult readState(CFPropertyListRef* ppPropList) nothrow
1934     {
1935         int cType = componentType(),
1936             cSubType = componentSubType,
1937             cManufacturer = componentManufacturer();
1938 
1939         CFMutableDictionaryRef pDict = CFDictionaryCreateMutable(null, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1940         int version_ = _client.getPublicVersion().toAUVersion;
1941         putNumberInDict(pDict, kAUPresetVersionKey, &version_, kCFNumberSInt32Type);
1942 
1943         putNumberInDict(pDict, kAUPresetTypeKey, &cType, kCFNumberSInt32Type);
1944         putNumberInDict(pDict, kAUPresetSubtypeKey, &cSubType, kCFNumberSInt32Type);
1945         putNumberInDict(pDict, kAUPresetManufacturerKey, &cManufacturer, kCFNumberSInt32Type);
1946         auto presetBank = _client.presetBank();
1947         putStrInDict(pDict, kAUPresetNameKey, presetBank.currentPreset().name);
1948         ubyte[] state = presetBank.getStateChunkFromCurrentState();
1949         putDataInDict(pDict, kAUPresetDataKey, state);
1950         *ppPropList = pDict;
1951         return noErr;
1952     }
1953 
1954     final ComponentResult writeState(CFPropertyListRef ppPropList) nothrow @nogc
1955     {
1956         int cType = componentType(),
1957         cSubType = componentSubType,
1958         cManufacturer = componentManufacturer();
1959 
1960         int version_, type, subtype, mfr;
1961         string presetName;
1962 
1963         CFMutableDictionaryRef pDict = cast(CFMutableDictionaryRef)ppPropList;
1964 
1965         if (!getNumberFromDict(pDict, kAUPresetVersionKey, &version_, kCFNumberSInt32Type) ||
1966             !getNumberFromDict(pDict, kAUPresetTypeKey, &type, kCFNumberSInt32Type) ||
1967             !getNumberFromDict(pDict, kAUPresetSubtypeKey, &subtype, kCFNumberSInt32Type) ||
1968             !getNumberFromDict(pDict, kAUPresetManufacturerKey, &mfr, kCFNumberSInt32Type) ||
1969             !getStrFromDict(pDict, kAUPresetNameKey, presetName) ||
1970               type != cType ||
1971               subtype != cSubType ||
1972               mfr != cManufacturer)
1973         {
1974             return kAudioUnitErr_InvalidPropertyValue;
1975         }
1976 
1977         scope(exit) presetName.freeSlice();
1978 
1979         ubyte[] chunk;
1980         if (!getDataFromDict(pDict, kAUPresetDataKey, chunk))
1981         {
1982             return kAudioUnitErr_InvalidPropertyValue;
1983         }
1984 
1985         scope(exit) chunk.freeSlice();
1986 
1987         try
1988         {
1989             auto presetBank = _client.presetBank();
1990             presetBank.loadStateChunk(chunk);
1991         }
1992         catch(Exception e)
1993         {
1994             e.destroyFree();
1995             return kAudioUnitErr_InvalidPropertyValue;
1996         }
1997         return noErr;
1998     }
1999 
2000 
2001     //
2002     // Render procedure
2003     //
2004 
2005     final ComponentResult render(AudioUnitRenderActionFlags* pFlags,
2006                                  const(AudioTimeStamp)* pTimestamp,
2007                                  uint outputBusIdx,
2008                                  uint nFrames,
2009                                  AudioBufferList* pOutBufList) nothrow @nogc
2010     {
2011         // GarageBand will happily send NULL pFlags, do not use it
2012 
2013         // Non-existing bus
2014         if (outputBusIdx > _outBuses.length)
2015             return kAudioUnitErr_InvalidElement;
2016 
2017         // Invalid timestamp
2018         if (!(pTimestamp.mFlags & kAudioTimeStampSampleTimeValid))
2019             return kAudioUnitErr_InvalidPropertyValue;
2020 
2021         // process messages to get newer number of input, samplerate or frame number
2022         void processMessages(ref float newSamplerate, ref int newMaxFrames, ref bool newBypassed) nothrow @nogc
2023         {
2024             // Only the last reset state message is meaningful, so we unwrap them
2025 
2026             AudioThreadMessage msg = void;
2027 
2028             while(_messageQueue.tryPopFront(msg))
2029             {
2030                 final switch(msg.type) with (AudioThreadMessage.Type)
2031                 {
2032                     case resetState:
2033                         // Note: number of input/ouputs is discarded from the message
2034                         newMaxFrames = msg.maxFrames;
2035                         newSamplerate = msg.samplerate;
2036                         newBypassed = msg.bypassed;
2037                         break;
2038 
2039                     case midi:
2040                         _client.enqueueMIDIFromHost(msg.midiMessage);
2041 
2042                 }
2043             }
2044         }
2045         float newSamplerate = _lastSamplerate;
2046         int newMaxFrames = _lastMaxFrames;
2047         processMessages(newSamplerate, newMaxFrames, _lastBypassed);
2048 
2049         // Must fail when given too much frames
2050         if (nFrames > newMaxFrames)
2051             return kAudioUnitErr_TooManyFramesToProcess;
2052 
2053         // We'll need scratch buffers to render upstream
2054         if (newMaxFrames != _lastMaxFrames)
2055             resizeScratchBuffers(newMaxFrames);
2056 
2057 
2058         {
2059             _renderNotifyMutex.lock();
2060             scope(exit) _renderNotifyMutex.unlock();
2061 
2062             // pre-render
2063             if (_renderNotify.length)
2064             {
2065                 foreach(ref renderCallbackStruct; _renderNotify)
2066                 {
2067                     AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PreRender;
2068                     callRenderCallback(renderCallbackStruct, &flags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2069                 }
2070             }
2071         }
2072 
2073         double renderTimestamp = pTimestamp.mSampleTime;
2074 
2075         bool isNewTimestamp = (renderTimestamp != _lastRenderTimestamp);
2076 
2077         // On a novel timestamp, we render upstream (pull) and process audio.
2078         // Else, just copy the results.
2079         // We always provide buffers to upstream unit
2080         int lastConnectedOutputBus = -1;
2081         {
2082             // Lock input and output buses
2083             _globalMutex.lock();
2084             scope(exit) _globalMutex.unlock();
2085 
2086             if (isNewTimestamp)
2087             {
2088                 BufferList bufList;
2089                 AudioBufferList* pInBufList = cast(AudioBufferList*) &bufList;
2090 
2091                 // Clear inputPointers and fill it:
2092                 //  - with null for unconnected channels
2093                 //  - with a pointer to scratch for connected channels
2094                 _inputPointers[] = null;
2095 
2096                 // call render for each upstream units
2097                 foreach(size_t inputBusIdx, ref pInBus; _inBuses)
2098                 {
2099                     InputBusConnection* pInBusConn = &_inBusConnections[inputBusIdx];
2100 
2101                     if (pInBus.connected)
2102                     {
2103                         pInBufList.mNumberBuffers = pInBus.numHostChannels;
2104 
2105                         for (int b = 0; b < pInBufList.mNumberBuffers; ++b)
2106                         {
2107                             AudioBuffer* pBuffer = &(pInBufList.mBuffers.ptr[b]);
2108                             int whichScratch = pInBus.plugChannelStartIdx + b;
2109                             float* buffer = _inputScratchBuffer[whichScratch].ptr;
2110                             pBuffer.mData = buffer;
2111                             pBuffer.mNumberChannels = 1;
2112                             pBuffer.mDataByteSize = cast(uint)(nFrames * AudioSampleType.sizeof);
2113                         }
2114                         AudioUnitRenderActionFlags flags = 0;
2115                         ComponentResult r = pInBusConn.callUpstreamRender(&flags, pTimestamp, nFrames, pInBufList, cast(int)inputBusIdx);
2116                         if (r != noErr)
2117                             return r;   // Something went wrong upstream.
2118 
2119                         // Get back input data pointer, that may have been modified by upstream
2120                         for (int b = 0; b < pInBufList.mNumberBuffers; ++b)
2121                         {
2122                             AudioBuffer* pBuffer = &(pInBufList.mBuffers.ptr[b]);
2123                             int whichScratch = pInBus.plugChannelStartIdx + b;
2124                             _inputPointers[whichScratch] = cast(float*)(pBuffer.mData);
2125                         }
2126                     }
2127                 }
2128 
2129                 _lastRenderTimestamp = renderTimestamp;
2130             }
2131             BusChannels* pOutBus = &_outBuses[outputBusIdx];
2132 
2133             // 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
2134             // then consider it connected
2135             if (!(pOutBus.connected) || pOutBus.numHostChannels != pOutBufList.mNumberBuffers)
2136             {
2137                 pOutBus.connected = true;
2138             }
2139 
2140             foreach(outBus; _outBuses)
2141             {
2142                 if(!outBus.connected)
2143                     break;
2144                 else
2145                     lastConnectedOutputBus++;
2146             }
2147 
2148             // assign _outputPointers
2149             for (int i = 0; i < pOutBufList.mNumberBuffers; ++i)
2150             {
2151                 int chIdx = pOutBus.plugChannelStartIdx + i;
2152 
2153                 AudioSampleType* pData = cast(AudioSampleType*)( pOutBufList.mBuffers.ptr[i].mData );
2154                 if (pData == null)
2155                 {
2156                     // No output buffers given, we use scratch buffers instead
2157                     pData = _outputScratchBuffer[chIdx].ptr;
2158                     pOutBufList.mBuffers.ptr[i].mData = pData;
2159                 }
2160 
2161                 _outputPointers[chIdx] = pData;
2162             }
2163 
2164             // Second the _outputPointers array is cleared for above > mNumberBuffersthis, to avoid errors when going from 2-2 to 1-1
2165             for (int i = pOutBufList.mNumberBuffers; i < pOutBus.numPlugChannels; ++i)
2166             {
2167                 int chIdx = pOutBus.plugChannelStartIdx + i;
2168                 _outputPointers[chIdx] = null;
2169             }
2170         }
2171 
2172 
2173         if (outputBusIdx == lastConnectedOutputBus)
2174         {
2175             // Here we can finally know the real number of input and outputs connected, but not before.
2176             // We also flatten the pointer arrays
2177             int newUsedInputs = 0;
2178             foreach(inputPointer; _inputPointers[])
2179                 if (inputPointer != null)
2180                     _inputPointersNoGap[newUsedInputs++] = inputPointer;
2181 
2182             int newUsedOutputs = 0;
2183             foreach(outputPointer; _outputPointers[])
2184                 if (outputPointer != null)
2185                     _outputPointersNoGap[newUsedOutputs++] = outputPointer;
2186 
2187             // Call client.reset if we do need to call it, and only once.
2188             bool needReset = (newMaxFrames != _lastMaxFrames || newUsedInputs != _lastUsedInputs ||
2189                               newUsedOutputs != _lastUsedOutputs || newSamplerate != _lastSamplerate);
2190 
2191 
2192             // in case upstream has changed flags
2193             FPControl fpControl;
2194             fpControl.initialize();
2195 
2196             if (needReset)
2197             {
2198                 _client.resetFromHost(newSamplerate, newMaxFrames, newUsedInputs, newUsedOutputs);
2199                 _lastMaxFrames = newMaxFrames;
2200                 _lastSamplerate = newSamplerate;
2201                 _lastUsedInputs = newUsedInputs;
2202                 _lastUsedOutputs = newUsedOutputs;
2203             }
2204 
2205             if (_lastBypassed)
2206             {
2207                 // MAYDO: should delay by latency when bypassed
2208                 int minIO = newUsedInputs;
2209                 if (minIO > newUsedOutputs)
2210                     minIO = newUsedOutputs;
2211 
2212                 for (int i = 0; i < minIO; ++i)
2213                     _outputPointersNoGap[i][0..nFrames] = _inputPointersNoGap[i][0..nFrames];
2214 
2215                 for (int i = minIO; i < newUsedOutputs; ++i)
2216                     _outputPointersNoGap[i][0..nFrames] = 0;
2217             }
2218             else
2219             {
2220                 TimeInfo timeInfo = getTimeInfo();
2221 
2222 
2223                 // clear MIDI out buffers
2224                 if (_client.sendsMIDI)
2225                     _client.clearAccumulatedOutputMidiMessages();
2226 
2227                 _client.processAudioFromHost(_inputPointersNoGap[0..newUsedInputs],
2228                                              _outputPointersNoGap[0..newUsedOutputs],
2229                                              nFrames,
2230                                              timeInfo);
2231 
2232                 if (_client.sendsMIDI() && _midiOutCallback.midiOutputCallback !is null)
2233                 {
2234                     const(MidiMessage)[] outMsgs = _client.getAccumulatedOutputMidiMessages();
2235 
2236                     foreach(msg; outMsgs)
2237                     {
2238                         MIDIPacketList packets;
2239                         packets.packet[0].length = cast(ushort) msg.toBytes(cast(ubyte*) packets.packet[0].data.ptr, 256);
2240                         packets.packet[0].timeStamp = msg.offset;
2241                         packets.numPackets = 1;
2242                         _midiOutCallback.midiOutputCallback(_midiOutCallback.userData,
2243                                                             pTimestamp,
2244                                                             0,
2245                                                             &packets);
2246                     }
2247                 }
2248             }
2249         }
2250 
2251         // post-render
2252         if (_renderNotify.length)
2253         {
2254             _renderNotifyMutex.lock();
2255             scope(exit) _renderNotifyMutex.unlock();
2256 
2257             foreach(ref renderCallbackStruct; _renderNotify)
2258             {
2259                 AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PostRender;
2260                 callRenderCallback(renderCallbackStruct, &flags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2261             }
2262         }
2263 
2264         return noErr;
2265     }
2266 
2267     // IHostCommand
2268     public
2269     {
2270         final void sendAUEvent(AudioUnitEventType type, int paramIndex) nothrow @nogc
2271         {
2272             AudioUnitEvent auEvent;
2273             auEvent.mEventType = type;
2274             auEvent.mArgument.mParameter.mAudioUnit = instanceHandle();
2275             auEvent.mArgument.mParameter.mParameterID = paramIndex;
2276             auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
2277             auEvent.mArgument.mParameter.mElement = 0;
2278             AUEventListenerNotify(null, null, &auEvent);
2279         }
2280 
2281         override void beginParamEdit(int paramIndex)
2282         {
2283             sendAUEvent(kAudioUnitEvent_BeginParameterChangeGesture, paramIndex);
2284         }
2285 
2286         override void paramAutomate(int paramIndex, float value)
2287         {
2288             sendAUEvent(kAudioUnitEvent_ParameterValueChange, paramIndex);
2289         }
2290 
2291         override void endParamEdit(int paramIndex)
2292         {
2293             sendAUEvent(kAudioUnitEvent_EndParameterChangeGesture, paramIndex);
2294         }
2295 
2296         override bool requestResize(int width, int height)
2297         {
2298             // On macOS 10.14
2299             // * Logic: window plugin resizing seems enough
2300             // * Live 10: window plugin resizing seems enough
2301             // * REAPER: problems with host parent window
2302             return true;
2303         }
2304 
2305         DAW _daw = DAW.Unknown;
2306 
2307         override DAW getDAW()
2308         {
2309             return _daw;
2310         }
2311 
2312         override PluginFormat getPluginFormat()
2313         {
2314             return PluginFormat.auv2;
2315         }
2316     }
2317 
2318     // Host callbacks
2319     final TimeInfo getTimeInfo() nothrow @nogc
2320     {
2321         TimeInfo result;
2322 
2323         auto hostCallbacks = _hostCallbacks;
2324 
2325         if (hostCallbacks.transportStateProc)
2326         {
2327             double samplePos = 0.0, loopStartBeat, loopEndBeat;
2328             Boolean playing, changed, looping;
2329             hostCallbacks.transportStateProc(hostCallbacks.hostUserData, &playing, &changed, &samplePos,
2330                                              &looping, &loopStartBeat, &loopEndBeat);
2331             result.timeInSamples = cast(long)(samplePos + 0.5);
2332             result.hostIsPlaying = (playing  != 0);
2333         }
2334 
2335         if (hostCallbacks.beatAndTempoProc)
2336         {
2337             double currentBeat = 0.0, tempo = 0.0;
2338             hostCallbacks.beatAndTempoProc(hostCallbacks.hostUserData, &currentBeat, &tempo);
2339             if (tempo > 0.0)
2340                 result.tempo = tempo;
2341         }
2342         return result;
2343     }
2344 
2345     package void* openGUIAndReturnCocoaView()
2346     {
2347         if (!_client.hasGUI())
2348             return null;
2349 
2350         if (_isUIOpenedAlready)
2351             _client.closeGUI();
2352 
2353         _isUIOpenedAlready = true;
2354 
2355         return _client.openGUI(null, null, GraphicsBackend.cocoa);
2356     }
2357 }
2358 
2359 
2360 private:
2361 
2362 // Helpers for scope
2363 static bool isGlobalScope(AudioUnitScope scope_) pure nothrow @nogc
2364 {
2365     return (scope_ == kAudioUnitScope_Global);
2366 }
2367 
2368 static bool isInputScope(AudioUnitScope scope_) pure nothrow @nogc
2369 {
2370     return (scope_ == kAudioUnitScope_Input);
2371 }
2372 
2373 static bool isOutputScope(AudioUnitScope scope_) pure nothrow @nogc
2374 {
2375     return (scope_ == kAudioUnitScope_Output);
2376 }
2377 
2378 static bool isInputOrGlobalScope(AudioUnitScope scope_) pure nothrow @nogc
2379 {
2380     return (scope_ == kAudioUnitScope_Input || scope_ == kAudioUnitScope_Global);
2381 }
2382 
2383 static bool isInputOrOutputScope(AudioUnitScope scope_) pure nothrow @nogc
2384 {
2385     return (scope_ == kAudioUnitScope_Input || scope_ == kAudioUnitScope_Output);
2386 }
2387 
2388 /// Calls a render callback
2389 static ComponentResult callRenderCallback(ref AURenderCallbackStruct pCB, AudioUnitRenderActionFlags* pFlags, const(AudioTimeStamp)* pTimestamp,
2390                        UInt32 inputBusIdx, UInt32 nFrames, AudioBufferList* pOutBufList) nothrow @nogc
2391 {
2392     return pCB.inputProc(pCB.inputProcRefCon, pFlags, pTimestamp, inputBusIdx, nFrames, pOutBufList);
2393 }
2394 
2395 
2396 extern(C) ComponentResult getParamProc(void* pPlug,
2397                              AudioUnitParameterID paramID,
2398                              AudioUnitScope scope_,
2399                              AudioUnitElement element,
2400                              AudioUnitParameterValue* pValue) nothrow @nogc
2401 {
2402     AUClient _this = cast(AUClient)pPlug;
2403     return _this.DoGetParameter(paramID, scope_, element, pValue);
2404 }
2405 
2406 extern(C) ComponentResult setParamProc(void* pPlug,
2407                              AudioUnitParameterID paramID,
2408                              AudioUnitScope scope_,
2409                              AudioUnitElement element,
2410                              AudioUnitParameterValue value,
2411                              UInt32 offsetFrames) nothrow @nogc
2412 {
2413     AUClient _this = cast(AUClient)pPlug;
2414     return _this.DoSetParameter(paramID, scope_, element, value, offsetFrames);
2415 }
2416 
2417 extern(C) ComponentResult renderProc(void* pPlug,
2418                                      AudioUnitRenderActionFlags* pFlags,
2419                                      const(AudioTimeStamp)* pTimestamp,
2420                                      uint outputBusIdx,
2421                                      uint nFrames,
2422                                      AudioBufferList* pOutBufList) nothrow @nogc
2423 {
2424     ScopedForeignCallback!(false, true) scopedCallback;
2425     scopedCallback.enter();
2426 
2427     AUClient _this = cast(AUClient)pPlug;
2428     return _this.render(pFlags, pTimestamp, outputBusIdx, nFrames, pOutBufList);
2429 }
2430 
2431 
2432 //
2433 // MessageQueue
2434 //
2435 
2436 
2437 /// A message for the audio thread.
2438 /// Intended to be passed from a non critical thread to the audio thread.
2439 struct AudioThreadMessage
2440 {
2441     enum Type
2442     {
2443         resetState, // reset plugin state, set samplerate and buffer size (samplerate = fParam, buffersize in frames = iParam)
2444         midi
2445     }
2446 
2447     this(Type type_, int maxFrames_, float samplerate_, bool bypassed_) pure const nothrow @nogc
2448     {
2449         type = type_;
2450         maxFrames = maxFrames_;
2451         samplerate = samplerate_;
2452         bypassed = bypassed_;
2453     }
2454 
2455     Type type;
2456     int maxFrames;
2457     float samplerate;
2458     bool bypassed;
2459     MidiMessage midiMessage;
2460 }
2461 
2462 AudioThreadMessage makeMidiThreadMessage(MidiMessage midiMessage) pure nothrow @nogc
2463 {
2464     AudioThreadMessage msg;
2465     msg.type = AudioThreadMessage.Type.midi;
2466     msg.midiMessage = midiMessage;
2467     return msg;
2468 }
2469 
2470 /** Four Character Constant (for AEffect->uniqueID) */
2471 private int CCONST(int a, int b, int c, int d) pure nothrow @nogc
2472 {
2473     return (a << 24) | (b << 16) | (c << 8) | (d << 0);
2474 }