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