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