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