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