Repository: apple/AudioUnitSDK Branch: main Commit: bd98b31feff5 Files: 50 Total size: 352.1 KB Directory structure: gitextract_ldpfski0/ ├── .github/ │ └── pull_request_template.md ├── .gitignore ├── AudioUnitSDK.xcodeproj/ │ ├── project.pbxproj │ └── xcshareddata/ │ └── xcschemes/ │ ├── AudioUnitSDK.xcscheme │ ├── EmptyPlugIns.xcscheme │ └── Tests.xcscheme ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── _clang-format ├── _clang-tidy ├── demos/ │ └── EmptyPlugIns/ │ ├── EmptyPlugIns.cpp │ └── Info.plist ├── hooks/ │ ├── install.sh │ └── pre-commit ├── include/ │ └── AudioUnitSDK/ │ ├── AUBase.h │ ├── AUBuffer.h │ ├── AUConfig.h │ ├── AUEffectBase.h │ ├── AUInputElement.h │ ├── AUMIDIBase.h │ ├── AUMIDIEffectBase.h │ ├── AUMIDIUtility.h │ ├── AUOutputElement.h │ ├── AUPlugInDispatch.h │ ├── AUScopeElement.h │ ├── AUSilentTimeout.h │ ├── AUThreadSafeList.h │ ├── AUUtility.h │ ├── AudioUnitSDK.h │ ├── ComponentBase.h │ └── MusicDeviceBase.h ├── readme.md ├── src/ │ └── AudioUnitSDK/ │ ├── AUBase.cpp │ ├── AUBuffer.cpp │ ├── AUBufferAllocator.cpp │ ├── AUEffectBase.cpp │ ├── AUInputElement.cpp │ ├── AUMIDIBase.cpp │ ├── AUMIDIEffectBase.cpp │ ├── AUOutputElement.cpp │ ├── AUPlugInDispatch.cpp │ ├── AUScopeElement.cpp │ ├── ComponentBase.cpp │ └── MusicDeviceBase.cpp ├── tests/ │ ├── AUThreadSafeListTests.mm │ ├── Info.plist │ └── Tests.mm └── tools/ ├── FindUB.sh └── build.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/pull_request_template.md ================================================ NOTE: - While you can open new pull requests WE ADVISE AGAINST IT and as such our response may be limited. This project doesn't accept pull requests. Opening an issue is the recommended method to report a bug or provide feedback. You can also fork the repo if you wish to customize it. If you want to open a pull request anyway, please review [CONTRIBUTING.md](../CONTRIBUTING.md) first and indicate your agreement to the terms outlined in CONTRIBUTING.md by checking the box below. We appreciate your interest in the project! Do not erase the below when submitting your pull request: ######### - [ ] I understand that response time may be limited because the project doesn't accept pull requests. - [ ] I agree to the terms outlined in CONTRIBUTING.md ================================================ FILE: .gitignore ================================================ .DS_Store .cache/ xcuserdata/ ================================================ FILE: AudioUnitSDK.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 394A97042576BF1700897571 /* AUMIDIUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 394A97032576BF1700897571 /* AUMIDIUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 64339772294B5DDC00DCD59F /* AUThreadSafeListTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64339771294B5DDC00DCD59F /* AUThreadSafeListTests.mm */; }; 643D7987292BF34C00910294 /* AUThreadSafeList.h in Headers */ = {isa = PBXBuildFile; fileRef = 643D7986292BF34C00910294 /* AUThreadSafeList.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100832E24DF0EB6003E57AE /* AUInputElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC75924D9181600725ABE /* AUInputElement.cpp */; }; 9100832F24DF0EE7003E57AE /* AUOutputElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC75824D9181600725ABE /* AUOutputElement.cpp */; }; 9100833024DF0F2C003E57AE /* AUBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC76224D9181600725ABE /* AUBase.cpp */; }; 9100833D24DF1CCC003E57AE /* EmptyPlugIns.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9100833C24DF1CCC003E57AE /* EmptyPlugIns.cpp */; }; 9100834124DF1EEB003E57AE /* libAudioUnitSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 910C29CE24D910D300B9116B /* libAudioUnitSDK.a */; }; 9100834324DF1EF5003E57AE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9100834224DF1EF5003E57AE /* AudioToolbox.framework */; }; 9100835124DF3D54003E57AE /* AUEffectBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9100834F24DF3245003E57AE /* AUEffectBase.cpp */; }; 9100835424DF4125003E57AE /* AUMIDIBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9100834E24DF3245003E57AE /* AUMIDIBase.cpp */; }; 9100835524DF421A003E57AE /* MusicDeviceBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9100834624DF3245003E57AE /* MusicDeviceBase.cpp */; }; 9100835724DF9BAC003E57AE /* AUMIDIEffectBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9100834C24DF3245003E57AE /* AUMIDIEffectBase.cpp */; }; 9100835824E05892003E57AE /* AUScopeElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC75C24D9181600725ABE /* AUScopeElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835924E05892003E57AE /* AUSilentTimeout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100835224DF3DAC003E57AE /* AUSilentTimeout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835A24E05892003E57AE /* AUInputElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC76024D9181600725ABE /* AUInputElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835B24E05892003E57AE /* AUMIDIEffectBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100834524DF3245003E57AE /* AUMIDIEffectBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835C24E05892003E57AE /* AUPlugInDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC75F24D9181600725ABE /* AUPlugInDispatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835D24E05892003E57AE /* AUMIDIBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100834424DF3245003E57AE /* AUMIDIBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835E24E05892003E57AE /* AUOutputElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC75B24D9181600725ABE /* AUOutputElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100835F24E05892003E57AE /* AUEffectBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100834924DF3245003E57AE /* AUEffectBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100836024E05892003E57AE /* MusicDeviceBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100834B24DF3245003E57AE /* MusicDeviceBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100836124E05892003E57AE /* AUUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9100832D24DF0C5B003E57AE /* AUUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9100836224E05892003E57AE /* AUBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC76124D9181600725ABE /* AUBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 910C29D824D9115100B9116B /* ComponentBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 910C29D624D9115100B9116B /* ComponentBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 910C29D924D9115100B9116B /* ComponentBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 910C29D724D9115100B9116B /* ComponentBase.cpp */; }; 914EC77424D91FFA00725ABE /* AUPlugInDispatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC77324D91FFA00725ABE /* AUPlugInDispatch.cpp */; }; 914EC77824D920CC00725ABE /* AUBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC77624D920CC00725ABE /* AUBuffer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 914EC77B24D9225800725ABE /* AudioUnitSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 914EC77A24D9225800725ABE /* AudioUnitSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 914EC77D24D9D91A00725ABE /* AUBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC77524D920CC00725ABE /* AUBuffer.cpp */; }; 914EC77E24D9DB3800725ABE /* AUScopeElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 914EC75D24D9181600725ABE /* AUScopeElement.cpp */; }; 915DA08024E32B37007C6B53 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 915DA07F24E32B37007C6B53 /* CoreFoundation.framework */; }; 91850A902CFA37FC005F9E2F /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9100834224DF1EF5003E57AE /* AudioToolbox.framework */; }; 919B0CC62555C72000C59BDC /* AUBufferAllocator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 919B0CC42555C72000C59BDC /* AUBufferAllocator.cpp */; }; 91E93AC324E8962D00BF7289 /* Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 91E93AC224E8962D00BF7289 /* Tests.mm */; }; 91E93AC524E8962D00BF7289 /* libAudioUnitSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 910C29CE24D910D300B9116B /* libAudioUnitSDK.a */; }; B49E353A29E8039C0093D6B7 /* AUConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = B49E353929E8039C0093D6B7 /* AUConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 9100833E24DF1DEF003E57AE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 910C29C624D910D300B9116B /* Project object */; proxyType = 1; remoteGlobalIDString = 910C29CD24D910D300B9116B; remoteInfo = AudioUnitSDK; }; 91E93AC624E8962D00BF7289 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 910C29C624D910D300B9116B /* Project object */; proxyType = 1; remoteGlobalIDString = 910C29CD24D910D300B9116B; remoteInfo = AudioUnitSDK; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 394A97032576BF1700897571 /* AUMIDIUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUMIDIUtility.h; sourceTree = ""; }; 64339771294B5DDC00DCD59F /* AUThreadSafeListTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AUThreadSafeListTests.mm; sourceTree = ""; }; 643D7986292BF34C00910294 /* AUThreadSafeList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUThreadSafeList.h; sourceTree = ""; }; 9100832D24DF0C5B003E57AE /* AUUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUUtility.h; sourceTree = ""; }; 9100833524DF1C82003E57AE /* EmptyPlugIns.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EmptyPlugIns.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 9100833724DF1C82003E57AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9100833C24DF1CCC003E57AE /* EmptyPlugIns.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EmptyPlugIns.cpp; sourceTree = ""; }; 9100834224DF1EF5003E57AE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9100834424DF3245003E57AE /* AUMIDIBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUMIDIBase.h; sourceTree = ""; }; 9100834524DF3245003E57AE /* AUMIDIEffectBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUMIDIEffectBase.h; sourceTree = ""; }; 9100834624DF3245003E57AE /* MusicDeviceBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MusicDeviceBase.cpp; sourceTree = ""; }; 9100834924DF3245003E57AE /* AUEffectBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUEffectBase.h; sourceTree = ""; }; 9100834B24DF3245003E57AE /* MusicDeviceBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MusicDeviceBase.h; sourceTree = ""; }; 9100834C24DF3245003E57AE /* AUMIDIEffectBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AUMIDIEffectBase.cpp; sourceTree = ""; }; 9100834E24DF3245003E57AE /* AUMIDIBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AUMIDIBase.cpp; sourceTree = ""; }; 9100834F24DF3245003E57AE /* AUEffectBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AUEffectBase.cpp; sourceTree = ""; }; 9100835224DF3DAC003E57AE /* AUSilentTimeout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUSilentTimeout.h; sourceTree = ""; }; 9100837424E06BA7003E57AE /* build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build.sh; sourceTree = ""; }; 910C29CE24D910D300B9116B /* libAudioUnitSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAudioUnitSDK.a; sourceTree = BUILT_PRODUCTS_DIR; }; 910C29D624D9115100B9116B /* ComponentBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComponentBase.h; sourceTree = ""; }; 910C29D724D9115100B9116B /* ComponentBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComponentBase.cpp; sourceTree = ""; }; 914EC75824D9181600725ABE /* AUOutputElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUOutputElement.cpp; sourceTree = ""; }; 914EC75924D9181600725ABE /* AUInputElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUInputElement.cpp; sourceTree = ""; }; 914EC75B24D9181600725ABE /* AUOutputElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUOutputElement.h; sourceTree = ""; }; 914EC75C24D9181600725ABE /* AUScopeElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUScopeElement.h; sourceTree = ""; }; 914EC75D24D9181600725ABE /* AUScopeElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUScopeElement.cpp; sourceTree = ""; }; 914EC75F24D9181600725ABE /* AUPlugInDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUPlugInDispatch.h; sourceTree = ""; }; 914EC76024D9181600725ABE /* AUInputElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUInputElement.h; sourceTree = ""; }; 914EC76124D9181600725ABE /* AUBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUBase.h; sourceTree = ""; }; 914EC76224D9181600725ABE /* AUBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUBase.cpp; sourceTree = ""; }; 914EC77324D91FFA00725ABE /* AUPlugInDispatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUPlugInDispatch.cpp; sourceTree = ""; }; 914EC77524D920CC00725ABE /* AUBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AUBuffer.cpp; sourceTree = ""; }; 914EC77624D920CC00725ABE /* AUBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUBuffer.h; sourceTree = ""; }; 914EC77A24D9225800725ABE /* AudioUnitSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioUnitSDK.h; sourceTree = ""; }; 915DA07F24E32B37007C6B53 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 919B0CC42555C72000C59BDC /* AUBufferAllocator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AUBufferAllocator.cpp; sourceTree = ""; }; 91E93AC024E8962D00BF7289 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 91E93AC224E8962D00BF7289 /* Tests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Tests.mm; sourceTree = ""; }; 91E93AC424E8962D00BF7289 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B49E353929E8039C0093D6B7 /* AUConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AUConfig.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9100833224DF1C82003E57AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9100834324DF1EF5003E57AE /* AudioToolbox.framework in Frameworks */, 915DA08024E32B37007C6B53 /* CoreFoundation.framework in Frameworks */, 9100834124DF1EEB003E57AE /* libAudioUnitSDK.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 910C29CC24D910D300B9116B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 91E93ABD24E8962D00BF7289 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 91850A902CFA37FC005F9E2F /* AudioToolbox.framework in Frameworks */, 91E93AC524E8962D00BF7289 /* libAudioUnitSDK.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9100833624DF1C82003E57AE /* EmptyPlugIns */ = { isa = PBXGroup; children = ( 9100833724DF1C82003E57AE /* Info.plist */, 9100833C24DF1CCC003E57AE /* EmptyPlugIns.cpp */, ); path = EmptyPlugIns; sourceTree = ""; }; 9100834024DF1EEB003E57AE /* Frameworks */ = { isa = PBXGroup; children = ( 9100834224DF1EF5003E57AE /* AudioToolbox.framework */, 915DA07F24E32B37007C6B53 /* CoreFoundation.framework */, ); name = Frameworks; sourceTree = ""; }; 9100837324E06B8F003E57AE /* tools */ = { isa = PBXGroup; children = ( 9100837424E06BA7003E57AE /* build.sh */, ); path = tools; sourceTree = ""; }; 910C29C524D910D300B9116B = { isa = PBXGroup; children = ( B487F106284500520074F0B2 /* include */, B487F108284500630074F0B2 /* src */, 9100837324E06B8F003E57AE /* tools */, B487F109284501700074F0B2 /* demos */, 91E93AC124E8962D00BF7289 /* tests */, 910C29CF24D910D300B9116B /* Products */, 9100834024DF1EEB003E57AE /* Frameworks */, ); sourceTree = ""; }; 910C29CF24D910D300B9116B /* Products */ = { isa = PBXGroup; children = ( 910C29CE24D910D300B9116B /* libAudioUnitSDK.a */, 9100833524DF1C82003E57AE /* EmptyPlugIns.bundle */, 91E93AC024E8962D00BF7289 /* Tests.xctest */, ); name = Products; sourceTree = ""; }; 91E93AC124E8962D00BF7289 /* tests */ = { isa = PBXGroup; children = ( 64339771294B5DDC00DCD59F /* AUThreadSafeListTests.mm */, 91E93AC224E8962D00BF7289 /* Tests.mm */, 91E93AC424E8962D00BF7289 /* Info.plist */, ); path = tests; sourceTree = ""; }; B487F106284500520074F0B2 /* include */ = { isa = PBXGroup; children = ( B4888687282AC1D800521D1A /* AudioUnitSDK */, ); path = include; sourceTree = ""; }; B487F108284500630074F0B2 /* src */ = { isa = PBXGroup; children = ( B48885E4282A6D6D00521D1A /* AudioUnitSDK */, ); path = src; sourceTree = ""; }; B487F109284501700074F0B2 /* demos */ = { isa = PBXGroup; children = ( 9100833624DF1C82003E57AE /* EmptyPlugIns */, ); path = demos; sourceTree = ""; }; B48885E4282A6D6D00521D1A /* AudioUnitSDK */ = { isa = PBXGroup; children = ( 914EC76224D9181600725ABE /* AUBase.cpp */, 914EC77524D920CC00725ABE /* AUBuffer.cpp */, 919B0CC42555C72000C59BDC /* AUBufferAllocator.cpp */, 9100834F24DF3245003E57AE /* AUEffectBase.cpp */, 914EC75924D9181600725ABE /* AUInputElement.cpp */, 9100834E24DF3245003E57AE /* AUMIDIBase.cpp */, 9100834C24DF3245003E57AE /* AUMIDIEffectBase.cpp */, 914EC75824D9181600725ABE /* AUOutputElement.cpp */, 914EC77324D91FFA00725ABE /* AUPlugInDispatch.cpp */, 914EC75D24D9181600725ABE /* AUScopeElement.cpp */, 910C29D724D9115100B9116B /* ComponentBase.cpp */, 9100834624DF3245003E57AE /* MusicDeviceBase.cpp */, ); path = AudioUnitSDK; sourceTree = ""; }; B4888687282AC1D800521D1A /* AudioUnitSDK */ = { isa = PBXGroup; children = ( B49E353929E8039C0093D6B7 /* AUConfig.h */, 914EC76124D9181600725ABE /* AUBase.h */, 914EC77624D920CC00725ABE /* AUBuffer.h */, 914EC77A24D9225800725ABE /* AudioUnitSDK.h */, 9100834924DF3245003E57AE /* AUEffectBase.h */, 914EC76024D9181600725ABE /* AUInputElement.h */, 9100834424DF3245003E57AE /* AUMIDIBase.h */, 9100834524DF3245003E57AE /* AUMIDIEffectBase.h */, 394A97032576BF1700897571 /* AUMIDIUtility.h */, 914EC75B24D9181600725ABE /* AUOutputElement.h */, 914EC75F24D9181600725ABE /* AUPlugInDispatch.h */, 914EC75C24D9181600725ABE /* AUScopeElement.h */, 9100835224DF3DAC003E57AE /* AUSilentTimeout.h */, 643D7986292BF34C00910294 /* AUThreadSafeList.h */, 9100832D24DF0C5B003E57AE /* AUUtility.h */, 910C29D624D9115100B9116B /* ComponentBase.h */, 9100834B24DF3245003E57AE /* MusicDeviceBase.h */, ); path = AudioUnitSDK; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 910C29CA24D910D300B9116B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B49E353A29E8039C0093D6B7 /* AUConfig.h in Headers */, 9100836224E05892003E57AE /* AUBase.h in Headers */, 914EC77824D920CC00725ABE /* AUBuffer.h in Headers */, 914EC77B24D9225800725ABE /* AudioUnitSDK.h in Headers */, 9100835F24E05892003E57AE /* AUEffectBase.h in Headers */, 9100835A24E05892003E57AE /* AUInputElement.h in Headers */, 9100835D24E05892003E57AE /* AUMIDIBase.h in Headers */, 9100835B24E05892003E57AE /* AUMIDIEffectBase.h in Headers */, 9100835E24E05892003E57AE /* AUOutputElement.h in Headers */, 9100835C24E05892003E57AE /* AUPlugInDispatch.h in Headers */, 394A97042576BF1700897571 /* AUMIDIUtility.h in Headers */, 9100835824E05892003E57AE /* AUScopeElement.h in Headers */, 9100835924E05892003E57AE /* AUSilentTimeout.h in Headers */, 643D7987292BF34C00910294 /* AUThreadSafeList.h in Headers */, 9100836124E05892003E57AE /* AUUtility.h in Headers */, 910C29D824D9115100B9116B /* ComponentBase.h in Headers */, 9100836024E05892003E57AE /* MusicDeviceBase.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 9100833424DF1C82003E57AE /* EmptyPlugIns */ = { isa = PBXNativeTarget; buildConfigurationList = 9100833824DF1C82003E57AE /* Build configuration list for PBXNativeTarget "EmptyPlugIns" */; buildPhases = ( 9100833124DF1C82003E57AE /* Sources */, 9100833224DF1C82003E57AE /* Frameworks */, 9100833324DF1C82003E57AE /* Resources */, ); buildRules = ( ); dependencies = ( 9100833F24DF1DEF003E57AE /* PBXTargetDependency */, ); name = EmptyPlugIns; productName = EmptyPlugIns; productReference = 9100833524DF1C82003E57AE /* EmptyPlugIns.bundle */; productType = "com.apple.product-type.bundle"; }; 910C29CD24D910D300B9116B /* AudioUnitSDK */ = { isa = PBXNativeTarget; buildConfigurationList = 910C29D224D910D300B9116B /* Build configuration list for PBXNativeTarget "AudioUnitSDK" */; buildPhases = ( 9100837924E1A54B003E57AE /* Install clang-format hook */, 910C29CA24D910D300B9116B /* Headers */, 910C29CB24D910D300B9116B /* Sources */, 910C29CC24D910D300B9116B /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = AudioUnitSDK; productName = AudioUnitSDK; productReference = 910C29CE24D910D300B9116B /* libAudioUnitSDK.a */; productType = "com.apple.product-type.library.static"; }; 91E93ABF24E8962D00BF7289 /* Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 91E93ACA24E8962D00BF7289 /* Build configuration list for PBXNativeTarget "Tests" */; buildPhases = ( 91E93ABC24E8962D00BF7289 /* Sources */, 91E93ABD24E8962D00BF7289 /* Frameworks */, 91E93ABE24E8962D00BF7289 /* Resources */, ); buildRules = ( ); dependencies = ( 91E93AC724E8962D00BF7289 /* PBXTargetDependency */, ); name = Tests; productName = Tests; productReference = 91E93AC024E8962D00BF7289 /* Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 910C29C624D910D300B9116B /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1200; TargetAttributes = { 9100833424DF1C82003E57AE = { CreatedOnToolsVersion = 12.0; }; 910C29CD24D910D300B9116B = { CreatedOnToolsVersion = 12.0; }; 91E93ABF24E8962D00BF7289 = { CreatedOnToolsVersion = 12.0; }; }; }; buildConfigurationList = 910C29C924D910D300B9116B /* Build configuration list for PBXProject "AudioUnitSDK" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 910C29C524D910D300B9116B; productRefGroup = 910C29CF24D910D300B9116B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 910C29CD24D910D300B9116B /* AudioUnitSDK */, 9100833424DF1C82003E57AE /* EmptyPlugIns */, 91E93ABF24E8962D00BF7289 /* Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 9100833324DF1C82003E57AE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 91E93ABE24E8962D00BF7289 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 9100837924E1A54B003E57AE /* Install clang-format hook */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "$(SRCROOT)/hooks/install.sh", ); name = "Install clang-format hook"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if [ $CONFIGURATION == Debug ] ; then\n\t\"$SRCROOT/hooks/install.sh\"\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9100833124DF1C82003E57AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9100833D24DF1CCC003E57AE /* EmptyPlugIns.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 910C29CB24D910D300B9116B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9100833024DF0F2C003E57AE /* AUBase.cpp in Sources */, 914EC77D24D9D91A00725ABE /* AUBuffer.cpp in Sources */, 9100835124DF3D54003E57AE /* AUEffectBase.cpp in Sources */, 9100832E24DF0EB6003E57AE /* AUInputElement.cpp in Sources */, 9100835424DF4125003E57AE /* AUMIDIBase.cpp in Sources */, 9100835724DF9BAC003E57AE /* AUMIDIEffectBase.cpp in Sources */, 9100832F24DF0EE7003E57AE /* AUOutputElement.cpp in Sources */, 919B0CC62555C72000C59BDC /* AUBufferAllocator.cpp in Sources */, 914EC77424D91FFA00725ABE /* AUPlugInDispatch.cpp in Sources */, 914EC77E24D9DB3800725ABE /* AUScopeElement.cpp in Sources */, 910C29D924D9115100B9116B /* ComponentBase.cpp in Sources */, 9100835524DF421A003E57AE /* MusicDeviceBase.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 91E93ABC24E8962D00BF7289 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64339772294B5DDC00DCD59F /* AUThreadSafeListTests.mm in Sources */, 91E93AC324E8962D00BF7289 /* Tests.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 9100833F24DF1DEF003E57AE /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 910C29CD24D910D300B9116B /* AudioUnitSDK */; targetProxy = 9100833E24DF1DEF003E57AE /* PBXContainerItemProxy */; }; 91E93AC724E8962D00BF7289 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 910C29CD24D910D300B9116B /* AudioUnitSDK */; targetProxy = 91E93AC624E8962D00BF7289 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 9100833924DF1C82003E57AE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = include; INFOPLIST_FILE = demos/EmptyPlugIns/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; PRODUCT_BUNDLE_IDENTIFIER = com.apple.audio.EmptyPlugIns; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; USE_HEADERMAP = NO; WRAPPER_EXTENSION = bundle; }; name = Debug; }; 9100833A24DF1C82003E57AE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = include; INFOPLIST_FILE = demos/EmptyPlugIns/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; PRODUCT_BUNDLE_IDENTIFIER = com.apple.audio.EmptyPlugIns; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; USE_HEADERMAP = NO; WRAPPER_EXTENSION = bundle; }; name = Release; }; 910C29D024D910D300B9116B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "c++23"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_FLOAT_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_PEDANTIC = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = NO; MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; SDKROOT = macosx; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; SUPPORTS_MACCATALYST = YES; WARNING_CFLAGS = ( "-Wall", "-Wextra", "-Wimport-preprocessor-directive-pedantic", "-Wunreachable-code-break", "-Wunreachable-code-return", "-Wvla", "-Wno-gnu-statement-expression-from-macro-expansion", ); }; name = Debug; }; 910C29D124D910D300B9116B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "c++23"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_FLOAT_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_PEDANTIC = YES; GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; SUPPORTS_MACCATALYST = YES; WARNING_CFLAGS = ( "-Wall", "-Wextra", "-Wimport-preprocessor-directive-pedantic", "-Wunreachable-code-break", "-Wunreachable-code-return", "-Wvla", "-Wno-gnu-statement-expression-from-macro-expansion", ); }; name = Release; }; 910C29D324D910D300B9116B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; EXECUTABLE_PREFIX = lib; GCC_SYMBOLS_PRIVATE_EXTERN = YES; HEADER_SEARCH_PATHS = include; OTHER_CFLAGS = "-fvisibility=hidden"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/AudioUnitSDK; SUPPORTS_MACCATALYST = YES; USE_HEADERMAP = NO; }; name = Debug; }; 910C29D424D910D300B9116B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; EXECUTABLE_PREFIX = lib; GCC_SYMBOLS_PRIVATE_EXTERN = YES; HEADER_SEARCH_PATHS = include; OTHER_CFLAGS = "-fvisibility=hidden"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/AudioUnitSDK; SUPPORTS_MACCATALYST = YES; USE_HEADERMAP = NO; }; name = Release; }; 91E93AC824E8962D00BF7289 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; HEADER_SEARCH_PATHS = include; INFOPLIST_FILE = tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.audio.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; USE_HEADERMAP = NO; WARNING_CFLAGS = ( "$(inherited)", "-Wno-gnu-statement-expression", ); }; name = Debug; }; 91E93AC924E8962D00BF7289 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; HEADER_SEARCH_PATHS = include; INFOPLIST_FILE = tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.apple.audio.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; USE_HEADERMAP = NO; WARNING_CFLAGS = ( "$(inherited)", "-Wno-gnu-statement-expression", ); }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9100833824DF1C82003E57AE /* Build configuration list for PBXNativeTarget "EmptyPlugIns" */ = { isa = XCConfigurationList; buildConfigurations = ( 9100833924DF1C82003E57AE /* Debug */, 9100833A24DF1C82003E57AE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 910C29C924D910D300B9116B /* Build configuration list for PBXProject "AudioUnitSDK" */ = { isa = XCConfigurationList; buildConfigurations = ( 910C29D024D910D300B9116B /* Debug */, 910C29D124D910D300B9116B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 910C29D224D910D300B9116B /* Build configuration list for PBXNativeTarget "AudioUnitSDK" */ = { isa = XCConfigurationList; buildConfigurations = ( 910C29D324D910D300B9116B /* Debug */, 910C29D424D910D300B9116B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 91E93ACA24E8962D00BF7289 /* Build configuration list for PBXNativeTarget "Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 91E93AC824E8962D00BF7289 /* Debug */, 91E93AC924E8962D00BF7289 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 910C29C624D910D300B9116B /* Project object */; } ================================================ FILE: AudioUnitSDK.xcodeproj/xcshareddata/xcschemes/AudioUnitSDK.xcscheme ================================================ ================================================ FILE: AudioUnitSDK.xcodeproj/xcshareddata/xcschemes/EmptyPlugIns.xcscheme ================================================ ================================================ FILE: AudioUnitSDK.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme ================================================ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the open source team at [opensource-conduct@group.apple.com](mailto:opensource-conduct@group.apple.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guide Thank you for your interest in contributing to the AudioUnit SDK! While this is an open-source project in spirit, it is intended to be updated infrequently with new version releases of the SDK. While you can open new pull requests WE ADVISE AGAINST IT and as such our response may be limited. Alternatively, you can open an issue which we will endeavour to address in the next official release. ## Submitting a Pull Request By submitting a pull request, you represent that you have the right to license your contribution to Apple and the community, and agree by submitting the patch that your contributions are licensed under the [LICENSE](LICENSE.md). *"I agree that all information entered is original and owned by me, and I hereby provide an irrevocable, royalty-free license to Apple to use, modify, copy, publish, prepare derivate works of, distribute (including under the Apache License Version 2.0), such information and all intellectual property therein in whole or part, in perpetuity and worldwide, without any attribution."* ## Code of Conduct We ask that all community members read and observe our [Code of Conduct](CODE_OF_CONDUCT.md). ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: _clang-format ================================================ --- # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BreakBeforeBinaryOperators: None BreakBeforeBraces: WebKit BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerBinding: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IndentCaseLabels: false IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: true MaxEmptyLinesToKeep: 2 NamespaceIndentation: None ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left QualifierAlignment: Left SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 StatementMacros: ['AUSDK_Catch'] TabWidth: 4 UseTab: ForContinuationAndIndentation ... ================================================ FILE: _clang-tidy ================================================ --- Checks: ' *, clang-diagnostic-*, clang-analyzer-*, -clang-diagnostic-pragma-once-outside-header, -llvm-header-guard, -fuchsia-default-arguments-calls, -fuchsia-default-arguments-declarations, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, -fuchsia-trailing-return, -hicpp-uppercase-literal-suffix, -google-default-arguments, -google-objc*, -google-readability-todo, -google-runtime-references, -modernize-use-trailing-return-type, -objc-property-declaration, -readability-implicit-bool-cast, -readability-uppercase-literal-suffix ' WarningsAsErrors: '' HeaderFilterRegex: '' FormatStyle: file CheckOptions: - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays value: '1' - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic value: '1' ... ================================================ FILE: demos/EmptyPlugIns/EmptyPlugIns.cpp ================================================ /*! @file EmptyPlugIns.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include #include // ------------------------------------------------------------------------------------------------- class AUBase_Derived : public ausdk::AUBase { using Base = ausdk::AUBase; public: explicit AUBase_Derived(AudioComponentInstance ci) : Base{ ci, 1, 1 } {} bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override { return true; } bool CanScheduleParameters() const noexcept AUSDK_RTSAFE override { return false; } }; AUSDK_COMPONENT_ENTRY(ausdk::AUBaseFactory, AUBase_Derived) // ------------------------------------------------------------------------------------------------- class AUEffectBase_Derived : public ausdk::AUEffectBase { using Base = ausdk::AUEffectBase; public: explicit AUEffectBase_Derived(AudioComponentInstance ci) : Base{ ci, true } {} }; AUSDK_COMPONENT_ENTRY(ausdk::AUBaseFactory, AUEffectBase_Derived) // ------------------------------------------------------------------------------------------------- class MusicDeviceBase_Derived : public ausdk::MusicDeviceBase { using Base = ausdk::MusicDeviceBase; public: explicit MusicDeviceBase_Derived(AudioComponentInstance ci) : Base{ ci, 0, 1 } {} bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override { return true; } bool CanScheduleParameters() const noexcept AUSDK_RTSAFE override { return false; } }; AUSDK_COMPONENT_ENTRY(ausdk::AUMusicDeviceFactory, MusicDeviceBase_Derived) ================================================ FILE: demos/EmptyPlugIns/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 NSPrincipalClass ================================================ FILE: hooks/install.sh ================================================ #! /bin/sh git rev-parse 2> /dev/null if [ $? != 0 ]; then echo "not currently working in a git repository, therefore aborting `basename $0`" exit 0 fi set -e SOURCE_DIR="`git rev-parse --show-toplevel`/hooks" INSTALL_DIR=$(git rev-parse --git-path hooks) FILENAME="pre-commit" mkdir -p "${INSTALL_DIR}" cp -f "${SOURCE_DIR}"/${FILENAME} "${INSTALL_DIR}"/${FILENAME} chmod +x "${INSTALL_DIR}"/${FILENAME} ================================================ FILE: hooks/pre-commit ================================================ #! /bin/zsh # This pre-commit hook uses clang-format to format the staged changes. if ! git rev-parse --verify HEAD >/dev/null 2>&1 then echo "not currently working in a git repository or unable to verify HEAD, therefore skipping `basename $0`" exit 0 fi set -e REPO_ROOT=$(git rev-parse --show-toplevel) DIFF_ARGS="--name-only --diff-filter=ACMRT" git diff-index ${=DIFF_ARGS} --cached HEAD | grep -E "\.(h|hpp|c|cpp|m|mm)$" | while read -r FILE do FILE_PATH="${REPO_ROOT}"/"${FILE}" if [[ `git diff ${=DIFF_ARGS} "${FILE_PATH}"` == $FILE ]] then echo -e "\xF0\x9F\x94\xA5 \xE2\x98\xA0 \xF0\x9F\x94\xA5 WARNING: creating a stash for file due to unstaged changes: ${FILE}" # format before stashing to avoid a merge conflict when applying the stash after commit xcrun clang-format -i "${FILE_PATH}" git stash push -k -q -m "pre-clang-format: ${FILE}" -- "${FILE_PATH}" fi xcrun clang-format -i "${FILE_PATH}" git add "${FILE_PATH}" done ================================================ FILE: include/AudioUnitSDK/AUBase.h ================================================ /*! @file AudioUnitSDK/AUBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUBase_h #define AudioUnitSDK_AUBase_h // module // clang-format off #include // must come first // clang-format on #include #include #include #include #include #include #include #include // OS #include #include #if AUSDK_HAVE_MUSIC_DEVICE #include #endif #if AUSDK_HAVE_MIDI2 #include #endif // std #include #include #include #include #include #include // ________________________________________________________________________ namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS /*! @class AUBase @brief Abstract base class for an Audio Unit implementation. */ class AUBase : public ComponentBase { public: constexpr static double kAUDefaultSampleRate = 44100.0; #if !TARGET_OS_WIN32 constexpr static UInt32 kAUDefaultMaxFramesPerSlice = 1156; // this allows enough default frames for a 512 dest 44K and SRC from 96K // add a padding of 4 frames for any vector rounding #else constexpr static UInt32 kAUDefaultMaxFramesPerSlice = 2048; #endif AUBase(AudioComponentInstance inInstance, UInt32 numInputElements, UInt32 numOutputElements, UInt32 numGroupElements = 0); ~AUBase() override; AUBase(const AUBase&) = delete; AUBase(AUBase&&) = delete; AUBase& operator=(const AUBase&) = delete; AUBase& operator=(AUBase&&) = delete; /// Called immediately after construction, when virtual methods work. Or, a subclass may call /// this in order to have access to elements in its constructor. void CreateElements(); virtual void CreateExtendedElements() {} #pragma mark - #pragma mark AU dispatch // ________________________________________________________________________ // Virtual methods (mostly) directly corresponding to the entry points. Many of these // have useful implementations here and will not need overriding. /// Implements the entry point and ensures that Initialize is called exactly once from an /// uninitialized state. OSStatus DoInitialize(); // Overrides to this method can assume that they will only be called exactly once // when transitioning from an uninitialized state. virtual OSStatus Initialize(); [[nodiscard]] bool IsInitialized() const noexcept { return mInitialized; } [[nodiscard]] bool HasBegunInitializing() const noexcept { return mHasBegunInitializing; } /// Implements the entry point and ensures that Cleanup is called exactly once from an /// initialized state. void DoCleanup(); // Overrides to this method can assume that they will only be called exactly once // when transitioning from an initialized state to an uninitialized state. virtual void Cleanup(); /// Implements the entry point and ensures that critical audio Reset work always occurs OSStatus DoReset(AudioUnitScope inScope, AudioUnitElement inElement); /// Not realtime-safe by contract, but if a derived class wishes to implement a safe Reset, /// it can call BaseReset(). virtual OSStatus Reset(AudioUnitScope inScope, AudioUnitElement inElement); OSStatus BaseReset(AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/) AUSDK_RTSAFE { return noErr; } // Note about GetPropertyInfo, GetProperty, SetProperty: // Certain properties are trapped out in these dispatch functions and handled with different // virtual methods. (To discourage hacks and keep vtable size down, these are non-virtual) OSStatus DispatchGetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable); OSStatus DispatchGetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData); OSStatus DispatchSetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); OSStatus DispatchRemovePropertyValue( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement); virtual OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable); virtual OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData); virtual OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); virtual OSStatus RemovePropertyValue( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement); virtual OSStatus AddPropertyListener( AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcRefCon); virtual OSStatus RemovePropertyListener(AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcRefCon, bool refConSpecified); virtual OSStatus SetRenderNotification(AURenderCallback inProc, void* inRefCon); virtual OSStatus RemoveRenderNotification(AURenderCallback inProc, void* inRefCon); virtual OSStatus GetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, AudioUnitParameterValue& outValue) AUSDK_RTSAFE; virtual OSStatus SetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, AudioUnitParameterValue inValue, UInt32 inBufferOffsetInFrames) AUSDK_RTSAFE; // N.B. For internal use; asserts that the ParameterID is valid. AudioUnitParameterValue GetParameterRT(AudioUnitParameterID paramID) AUSDK_RTSAFE { return Globals()->GetParameterRT(paramID); } void SetParameterRT(AudioUnitParameterID paramID, AudioUnitParameterValue inValue) AUSDK_RTSAFE { Globals()->SetParameterRT(paramID, inValue); } [[nodiscard]] virtual bool CanScheduleParameters() const AUSDK_RTSAFE = 0; virtual OSStatus ScheduleParameter( const AudioUnitParameterEvent* inParameterEvent, UInt32 inNumEvents) AUSDK_RTSAFE; // The non-virtual DoRender/etc. methods are noexcept because they catch any exceptions. OSStatus DoRender(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inBusNumber, UInt32 inFramesToProcess, AudioBufferList& ioData) noexcept AUSDK_RTSAFE; OSStatus DoProcess(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inFramesToProcess, AudioBufferList& ioData) noexcept AUSDK_RTSAFE; OSStatus DoProcessMultiple(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inFramesToProcess, UInt32 inNumberInputBufferLists, const AudioBufferList** inInputBufferLists, UInt32 inNumberOutputBufferLists, AudioBufferList** ioOutputBufferLists) noexcept AUSDK_RTSAFE; virtual OSStatus ProcessBufferLists(AudioUnitRenderActionFlags& /*ioActionFlags*/, const AudioBufferList& /*inBuffer*/, AudioBufferList& /*outBuffer*/, UInt32 /*inFramesToProcess*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } virtual OSStatus ProcessMultipleBufferLists(AudioUnitRenderActionFlags& /*ioActionFlags*/, UInt32 /*inFramesToProcess*/, UInt32 /*inNumberInputBufferLists*/, const AudioBufferList** /*inInputBufferLists*/, UInt32 /*inNumberOutputBufferLists*/, AudioBufferList** /*ioOutputBufferLists*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } virtual OSStatus ComplexRender(AudioUnitRenderActionFlags& /*ioActionFlags*/, const AudioTimeStamp& /*inTimeStamp*/, UInt32 /*inOutputBusNumber*/, UInt32 /*inNumberOfPackets*/, UInt32* /*outNumberOfPackets*/, AudioStreamPacketDescription* /*outPacketDescriptions*/, AudioBufferList& /*ioData*/, void* /*outMetadata*/, UInt32* /*outMetadataByteSize*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } // Override this method if your AU processes multiple output busses completely independently -- // you'll want to just call Render without the NeedsToRender check. // Otherwise, override Render(). // // N.B. Implementations of this method can assume that the output's buffer list has already been // prepared and access it with GetOutput(inBusNumber)->GetBufferList() instead of // GetOutput(inBusNumber)->PrepareBuffer(nFrames) -- if PrepareBuffer is called, a // copy may occur after rendering. virtual OSStatus RenderBus(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 /*inBusNumber*/, UInt32 inNumberFrames) AUSDK_RTSAFE { if (NeedsToRender(inTimeStamp)) { return Render(ioActionFlags, inTimeStamp, inNumberFrames); } return noErr; // was presumably already rendered via another bus } // N.B. For a unit with only one output bus, it can assume in its implementation of this // method that the output's buffer list has already been prepared and access it with // GetOutput(0)->GetBufferList() instead of GetOutput(0)->PrepareBuffer(nFrames) // -- if PrepareBuffer is called, a copy may occur after rendering. virtual OSStatus Render(AudioUnitRenderActionFlags& /*ioActionFlags*/, const AudioTimeStamp& /*inTimeStamp*/, UInt32 /*inNumberFrames*/) AUSDK_RTSAFE { return noErr; } #pragma mark - #pragma mark Property Dispatch // ________________________________________________________________________ // These are called from DispatchGetProperty/DispatchGetPropertyInfo/DispatchSetProperty virtual bool BusCountWritable(AudioUnitScope /*inScope*/) { return false; } virtual OSStatus SetBusCount(AudioUnitScope inScope, UInt32 inCount); virtual OSStatus SetConnection(const AudioUnitConnection& inConnection); virtual OSStatus SetInputCallback( UInt32 inPropertyID, AudioUnitElement inElement, AURenderCallback inProc, void* inRefCon); virtual OSStatus GetParameterList( AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters); // outParameterList may be a null pointer virtual OSStatus GetParameterInfo(AudioUnitScope inScope, AudioUnitParameterID inParameterID, AudioUnitParameterInfo& outParameterInfo); virtual OSStatus GetParameterHistoryInfo(AudioUnitScope inScope, AudioUnitParameterID inParameterID, Float32& outUpdatesPerSecond, Float32& outHistoryDurationInSeconds); virtual OSStatus SaveState(CFPropertyListRef* outData); virtual void SaveExtendedScopes(CFMutableDataRef /*outData*/) {} virtual OSStatus RestoreState(CFPropertyListRef plist); virtual OSStatus GetParameterValueStrings( AudioUnitScope inScope, AudioUnitParameterID inParameterID, CFArrayRef* outStrings); virtual OSStatus CopyClumpName(AudioUnitScope inScope, UInt32 inClumpID, UInt32 inDesiredNameLength, CFStringRef* outClumpName); virtual OSStatus GetPresets(CFArrayRef* outData) const; /// Set the default preset for the unit. The number of the preset must be >= 0 and the name /// should be valid, or the preset will be rejected. bool SetAFactoryPresetAsCurrent(const AUPreset& inPreset); // Called when the host sets a new, valid preset. // If this is a valid preset, then the subclass sets its state to that preset // and returns noErr. // If not a valid preset, return an error, and the pre-existing preset is restored. virtual OSStatus NewFactoryPresetSet(const AUPreset& inNewFactoryPreset); virtual OSStatus NewCustomPresetSet(const AUPreset& inNewCustomPreset); #if AUSDK_HAVE_UI virtual CFURLRef CopyIconLocation(); #endif // default is no latency, and unimplemented tail time virtual Float64 GetLatency() AUSDK_RTSAFE { return 0.0; } virtual Float64 GetTailTime() AUSDK_RTSAFE { return 0.0; } virtual bool SupportsTail() AUSDK_RTSAFE { return false; } // Stream formats: scope will always be input or output bool IsStreamFormatWritable(AudioUnitScope scope, AudioUnitElement element); virtual bool StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) = 0; // pass in a pointer to get the struct, and num channel infos // you can pass in NULL to just get the number // a return value of 0 (the default in AUBase) means the property is not supported... virtual UInt32 SupportedNumChannels(const AUChannelInfo** outInfo); /// Will only be called after StreamFormatWritable has succeeded. Default implementation /// requires non-interleaved native-endian 32-bit float, any sample rate, any number of /// channels; override when other formats are supported. A subclass's override can choose to /// always return true and trap invalid formats in ChangeStreamFormat. virtual bool ValidFormat(AudioUnitScope inScope, AudioUnitElement inElement, const AudioStreamBasicDescription& inNewFormat); virtual AudioStreamBasicDescription GetStreamFormat( AudioUnitScope inScope, AudioUnitElement inElement) AUSDK_RTSAFE_LOOSE; // Will only be called after StreamFormatWritable // and ValidFormat have succeeded. virtual OSStatus ChangeStreamFormat(AudioUnitScope inScope, AudioUnitElement inElement, const AudioStreamBasicDescription& inPrevFormat, const AudioStreamBasicDescription& inNewFormat); // ________________________________________________________________________ // Methods useful for subclasses AUScope& GetScope(AudioUnitScope inScope) { if (inScope >= kNumScopes) { AUScope* const scope = GetScopeExtended(inScope); ThrowQuietIf(scope == nullptr, kAudioUnitErr_InvalidScope); return *scope; } return mScopes[inScope]; // NOLINT } virtual AUScope* GetScopeExtended(AudioUnitScope /*inScope*/) AUSDK_RTSAFE { return nullptr; } AUScope& GlobalScope() { return mScopes[kAudioUnitScope_Global]; } AUScope& Inputs() { return mScopes[kAudioUnitScope_Input]; } AUScope& Outputs() { return mScopes[kAudioUnitScope_Output]; } AUScope& Groups() { return mScopes[kAudioUnitScope_Group]; } AUElement* Globals() AUSDK_RTSAFE { auto maybeElem = mScopes[kAudioUnitScope_Global].GetElementOrError(0); AUSDK_Assert(maybeElem); return maybeElem.get(); } void SetNumberOfElements(AudioUnitScope inScope, UInt32 numElements); virtual std::unique_ptr CreateElement( AudioUnitScope scope, AudioUnitElement element); AUElement* GetElement(AudioUnitScope inScope, AudioUnitElement inElement) { return GetScope(inScope).GetElement(inElement); } AUSDK_DEPRECATED("Use IOElement()") AUIOElement* GetIOElement(AudioUnitScope inScope, AudioUnitElement inElement) { return &IOElement(inScope, inElement); } AUIOElement& IOElement(AudioUnitScope inScope, AudioUnitElement inElement) { return *GetScope(inScope).GetIOElement(inElement); } AUSDK_DEPRECATED("Use Element()") AUElement* SafeGetElement(AudioUnitScope inScope, AudioUnitElement inElement) { return &Element(inScope, inElement); } AUElement& Element(AudioUnitScope inScope, AudioUnitElement inElement) { return *GetScope(inScope).SafeGetElement(inElement); } AUSDK_DEPRECATED("Use Input()") AUInputElement* GetInput(AudioUnitElement inElement) { return &Input(inElement); } AUInputElement& Input(AudioUnitElement inElement) { return static_cast(*Inputs().SafeGetElement(inElement)); // NOLINT downcast } AUSDK_DEPRECATED("Use Output()") AUOutputElement* GetOutput(AudioUnitElement inElement) { return &Output(inElement); } AUOutputElement& Output(AudioUnitElement inElement) { return static_cast( // NOLINT downcast *Outputs().SafeGetElement(inElement)); } AUSDK_DEPRECATED("Use Group()") AUElement* GetGroup(AudioUnitElement inElement) { return &Group(inElement); } AUElement& Group(AudioUnitElement inElement) { return *Groups().SafeGetElement(inElement); } ExpectedPtr GetScopeOrError(AudioUnitScope inScope) AUSDK_RTSAFE { if (inScope >= kNumScopes) { if (auto* scope = GetScopeExtended(inScope)) { return *scope; } return Unexpected(kAudioUnitErr_InvalidScope); } return mScopes[inScope]; // NOLINT } ExpectedPtr GetIOElementOrError( AudioUnitScope inScope, AudioUnitElement inElement) AUSDK_RTSAFE { AUScope& scope = AUSDK_UnwrapOrReturnUnexpected(GetScopeOrError(inScope)); return scope.GetIOElementOrError(inElement); } ExpectedPtr GetElementOrError( AudioUnitScope inScope, AudioUnitElement inElement) AUSDK_RTSAFE { AUScope& scope = AUSDK_UnwrapOrReturnUnexpected(GetScopeOrError(inScope)); return scope.GetElementOrError(inElement); } template ExpectedPtr GetInputOrError(AudioUnitElement inElement) AUSDK_RTSAFE { return Inputs().GetElementOrError(inElement); } template ExpectedPtr GetOutputOrError(AudioUnitElement inElement) AUSDK_RTSAFE { return Outputs().GetElementOrError(inElement); } // Since almost every AudioUnit has at least one output element, this method is useful for // obtaining output 0 without requiring error checking/handling. template E& GetOutput0() AUSDK_RTSAFE { auto maybeElem = Outputs().GetElementOrError(0); AUSDK_Assert(maybeElem); return *maybeElem; } // Most AudioUnits have at least one input element, so this method is useful for // obtaining input 0 without requiring error checking/handling. template E& GetInput0() AUSDK_RTSAFE { auto maybeElem = Inputs().GetElementOrError(0); AUSDK_Assert(maybeElem); return *maybeElem; } ExpectedPtr GetGroupOrError(AudioUnitElement inElement) AUSDK_RTSAFE { return Groups().GetElementOrError(inElement); } OSStatus PullInput(UInt32 inBusNumber, AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames) AUSDK_RTSAFE { AUInputElement& input = AUSDK_UnwrapOrReturnError(GetInputOrError(inBusNumber)); return input.PullInput(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames); } [[nodiscard]] UInt32 GetMaxFramesPerSlice() const noexcept { return mMaxFramesPerSlice; } [[nodiscard]] bool UsesFixedBlockSize() const noexcept { return mUsesFixedBlockSize; } void SetUsesFixedBlockSize(bool inUsesFixedBlockSize) noexcept { mUsesFixedBlockSize = inUsesFixedBlockSize; } [[nodiscard]] virtual bool InRenderThread() const AUSDK_RTSAFE { // Marked unsafe because the library function isn't annotated. return AUSDK_RT_UNSAFE(std::this_thread::get_id()) == mRenderThreadID; } /// Says whether an input is connected or has a callback. /// Not realtime-safe; use bus->IsActive(). bool HasInput(AudioUnitElement inElement) { auto* const in = static_cast(Inputs().GetElement(inElement)); // NOLINT downcast return in != nullptr && in->IsActive(); } virtual void PropertyChanged( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement); // These calls can be used to call a Host's Callbacks. The method returns -1 if the host // hasn't supplied the callback. Any other result is returned by the host. // As in the API contract, for a parameter's value, you specify a pointer // to that data type. Specify NULL for a parameter that you are not interested // as this can save work in the host. OSStatus CallHostBeatAndTempo(Float64* outCurrentBeat, Float64* outCurrentTempo) const { return (mHostCallbackInfo.beatAndTempoProc != nullptr ? (*mHostCallbackInfo.beatAndTempoProc)( mHostCallbackInfo.hostUserData, outCurrentBeat, outCurrentTempo) : -1); } OSStatus CallHostMusicalTimeLocation(UInt32* outDeltaSampleOffsetToNextBeat, Float32* outTimeSig_Numerator, UInt32* outTimeSig_Denominator, Float64* outCurrentMeasureDownBeat) const { return (mHostCallbackInfo.musicalTimeLocationProc != nullptr ? (*mHostCallbackInfo.musicalTimeLocationProc)(mHostCallbackInfo.hostUserData, outDeltaSampleOffsetToNextBeat, outTimeSig_Numerator, outTimeSig_Denominator, outCurrentMeasureDownBeat) : -1); } OSStatus CallHostTransportState(Boolean* outIsPlaying, Boolean* outTransportStateChanged, Float64* outCurrentSampleInTimeLine, Boolean* outIsCycling, Float64* outCycleStartBeat, Float64* outCycleEndBeat) const { return (mHostCallbackInfo.transportStateProc != nullptr ? (*mHostCallbackInfo.transportStateProc)(mHostCallbackInfo.hostUserData, outIsPlaying, outTransportStateChanged, outCurrentSampleInTimeLine, outIsCycling, outCycleStartBeat, outCycleEndBeat) : -1); } [[nodiscard]] const char* GetLoggingString() const noexcept; AUMutex* GetMutex() noexcept { return mAUMutex; } // The caller of SetMutex is responsible for the managing the lifetime of the // mutex object and, if deleted before the AUBase instance, is responsible // for calling SetMutex(nullptr) void SetMutex(AUMutex* mutex) noexcept { mAUMutex = mutex; } #pragma mark - #pragma mark AU Output Base Dispatch // ________________________________________________________________________ // ________________________________________________________________________ // ________________________________________________________________________ // output unit methods virtual OSStatus Start() { return kAudio_UnimplementedError; } virtual OSStatus Stop() { return kAudio_UnimplementedError; } #pragma mark - #pragma mark AU Music Base Dispatch // ________________________________________________________________________ // ________________________________________________________________________ // ________________________________________________________________________ // music device/music effect methods virtual OSStatus MIDIEvent(UInt32 /*inStatus*/, UInt32 /*inData1*/, UInt32 /*inData2*/, UInt32 /*inOffsetSampleFrame*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } virtual OSStatus SysEx(const UInt8* /*inData*/, UInt32 /*inLength*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } #if AUSDK_HAVE_MIDI2 virtual OSStatus MIDIEventList( UInt32 /*inOffsetSampleFrame*/, const MIDIEventList* /*eventList*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } #endif #if AUSDK_HAVE_MUSIC_DEVICE virtual OSStatus StartNote(MusicDeviceInstrumentID /*inInstrument*/, MusicDeviceGroupID /*inGroupID*/, NoteInstanceID* /*outNoteInstanceID*/, UInt32 /*inOffsetSampleFrame*/, const MusicDeviceNoteParams& /*inParams*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } virtual OSStatus StopNote(MusicDeviceGroupID /*inGroupID*/, NoteInstanceID /*inNoteInstanceID*/, UInt32 /*inOffsetSampleFrame*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } /// Obsolete static OSStatus PrepareInstrument(MusicDeviceInstrumentID /*inInstrument*/) { return kAudio_UnimplementedError; } /// Obsolete static OSStatus ReleaseInstrument(MusicDeviceInstrumentID /*inInstrument*/) { return kAudio_UnimplementedError; } #endif // AUSDK_HAVE_MUSIC_DEVICE // ________________________________________________________________________ // ________________________________________________________________________ // ________________________________________________________________________ protected: #pragma mark - #pragma mark Implementation methods void PostConstructorInternal() final; void PreDestructorInternal() final; /// needs to be called when mMaxFramesPerSlice changes virtual void ReallocateBuffers(); virtual void DeallocateIOBuffers(); static void FillInParameterName( AudioUnitParameterInfo& ioInfo, CFStringRef inName, bool inShouldRelease) { ioInfo.cfNameString = inName; ioInfo.flags |= kAudioUnitParameterFlag_HasCFNameString; if (inShouldRelease) { ioInfo.flags |= kAudioUnitParameterFlag_CFNameRelease; } CFStringGetCString( inName, std::data(ioInfo.name), std::ssize(ioInfo.name), kCFStringEncodingUTF8); } static void HasClump(AudioUnitParameterInfo& ioInfo, UInt32 inClumpID) noexcept { ioInfo.clumpID = inClumpID; ioInfo.flags |= kAudioUnitParameterFlag_HasClump; } virtual void SetMaxFramesPerSlice(UInt32 nFrames); [[nodiscard]] virtual OSStatus CanSetMaxFrames() const; [[nodiscard]] bool WantsRenderThreadID() const noexcept { return mWantsRenderThreadID; } void SetWantsRenderThreadID(bool inFlag); OSStatus SetRenderError(OSStatus inErr) AUSDK_RTSAFE { if (inErr != noErr && mLastRenderError == 0) { mLastRenderError = inErr; AUSDK_RT_UNSAFE_BEGIN("FIXME: PropertyChanged is unsafe") PropertyChanged(kAudioUnitProperty_LastRenderError, kAudioUnitScope_Global, 0); AUSDK_RT_UNSAFE_END } return inErr; } struct PropertyListener { AudioUnitPropertyID propertyID{ 0 }; AudioUnitPropertyListenerProc listenerProc{ nullptr }; void* listenerRefCon{ nullptr }; }; using PropertyListeners = std::vector; [[nodiscard]] const PropertyListeners& GetPropertyListeners() const noexcept { return mPropertyListeners; } HostCallbackInfo& GetHostCallbackInfo() noexcept { return mHostCallbackInfo; } private: // shared between Render and RenderSlice, inlined to minimize function call overhead OSStatus DoRenderBus(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inBusNumber, AUOutputElement& theOutput, UInt32 inNumberFrames, AudioBufferList& ioData) AUSDK_RTSAFE { ExpectedPtr maybeBuf; if (ioData.mBuffers[0].mData == nullptr || (theOutput.WillAllocateBuffer() && Outputs().GetNumberOfElements() > 1)) { // will render into cache buffer maybeBuf = theOutput.PrepareBufferOrError(inNumberFrames); } else { // will render into caller's buffer maybeBuf = theOutput.SetBufferListOrError(ioData); } if (!maybeBuf) { return maybeBuf.error(); } AUSDK_Require_noerr(RenderBus(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames)); Expected copyResult; if (ioData.mBuffers[0].mData == nullptr) { copyResult = theOutput.CopyBufferListToOrError(ioData); } else { copyResult = theOutput.CopyBufferContentsToOrError(ioData); theOutput.InvalidateBufferList(); } if (!copyResult) { return copyResult.error(); } return noErr; } bool HasIcon(); [[nodiscard]] std::string CreateLoggingString() const; protected: //. Returns size. outLayoutPtr may be null if querying only for size. virtual UInt32 GetAudioChannelLayout(AudioUnitScope scope, AudioUnitElement element, AudioChannelLayout* outLayoutPtr, bool& outWritable); /// Layout is non-null. virtual OSStatus SetAudioChannelLayout( AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout); virtual OSStatus RemoveAudioChannelLayout(AudioUnitScope scope, AudioUnitElement element); virtual std::vector GetChannelLayoutTags( AudioUnitScope scope, AudioUnitElement element); bool NeedsToRender(const AudioTimeStamp& inTimeStamp) { const bool needsToRender = (inTimeStamp.mSampleTime != mCurrentRenderTime.mSampleTime); if (needsToRender) { // only copy this if we need to render mCurrentRenderTime = inTimeStamp; } return needsToRender; } // Scheduled parameter implementation: using ParameterEventList = std::vector; // Usually, you won't override this method. You only need to call this if your DSP code // is prepared to handle scheduled immediate and ramped parameter changes. // Before calling this method, it is assumed you have already called PullInput() on the input // busses for which the DSP code depends. ProcessForScheduledParams() will call (potentially // repeatedly) virtual method ProcessScheduledSlice() to perform the actual DSP for a given // sub-division of the buffer. The job of ProcessForScheduledParams() is to sub-divide the // buffer into smaller pieces according to the scheduled times found in the ParameterEventList // (usually coming directly from a previous call to ScheduleParameter() ), setting the // appropriate immediate or ramped parameter values for the corresponding scopes and elements, // then calling ProcessScheduledSlice() to do the actual DSP for each of these divisions. virtual OSStatus ProcessForScheduledParams( ParameterEventList& inParamList, UInt32 inFramesToProcess, void* inUserData) AUSDK_RTSAFE; // This method is called (potentially repeatedly) by ProcessForScheduledParams() // in order to perform the actual DSP required for this portion of the entire buffer // being processed. The entire buffer can be divided up into smaller "slices" // according to the timestamps on the scheduled parameters... // // sub-classes wishing to handle scheduled parameter changes should override this method // in order to do the appropriate DSP. AUEffectBase already overrides this for standard // effect AudioUnits. virtual OSStatus ProcessScheduledSlice(void* /*inUserData*/, UInt32 /*inStartFrameInBuffer*/, UInt32 /*inSliceFramesToProcess*/, UInt32 /*inTotalBufferFrames*/) AUSDK_RTSAFE { // default implementation does nothing. return noErr; } [[nodiscard]] const AudioTimeStamp& CurrentRenderTime() const noexcept { return mCurrentRenderTime; } void ResetRenderTime(); // ________________________________________________________________________ // Private data members to discourage hacking in subclasses private: struct RenderCallback { RenderCallback() : RenderCallback(nullptr, nullptr) {} RenderCallback(AURenderCallback proc, void* ref) : mRenderNotify(proc), mRenderNotifyRefCon(ref) { } AURenderCallback mRenderNotify{ nullptr }; void* mRenderNotifyRefCon{ nullptr }; bool operator==(const RenderCallback& other) const { return this->mRenderNotify == other.mRenderNotify && this->mRenderNotifyRefCon == other.mRenderNotifyRefCon; } }; protected: static constexpr AudioUnitScope kNumScopes = 4; ParameterEventList& GetParamEventList() noexcept { return mParamEventList; } void SetBuffersAllocated(bool b) noexcept { mBuffersAllocated = b; } [[nodiscard]] CFStringRef GetContextName() const noexcept { return *mContextName; } void SetContextName(CFStringRef str) noexcept { mContextName = str; } [[nodiscard]] CFStringRef GetNickName() const noexcept { return *mNickName; } private: bool mElementsCreated{ false }; bool mInitialized{ false }; bool mHasBegunInitializing{ false }; const UInt32 mInitNumInputEls; const UInt32 mInitNumOutputEls; const UInt32 mInitNumGroupEls; std::array mScopes; AUThreadSafeList mRenderCallbacks; bool mRenderCallbacksTouched{ false }; std::thread::id mRenderThreadID{}; bool mWantsRenderThreadID{ false }; AudioTimeStamp mCurrentRenderTime{}; UInt32 mMaxFramesPerSlice{ 0 }; OSStatus mLastRenderError{ noErr }; #ifndef AUSDK_NO_LOGGING const double mHostTimeFrequency{ HostTime::Frequency() }; // cache because there is calculation cost uint64_t mLastTimeMessagePrinted{ 0 }; #endif AUPreset mCurrentPreset{ -1, nullptr }; bool mUsesFixedBlockSize{ false }; ParameterEventList mParamEventList; PropertyListeners mPropertyListeners; bool mBuffersAllocated{ false }; const std::string mLogString; Owned mNickName; /*! @var mAUMutex If non-null, guards all non-realtime entry points into the AudioUnit. Most AudioUnits do not need to use this. It's useful for the case of an AU which must synchronize an external source of callbacks against entry from the host. */ AUMutex* mAUMutex{ nullptr }; HostCallbackInfo mHostCallbackInfo{}; Owned mContextName; }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUBase_h ================================================ FILE: include/AudioUnitSDK/AUBuffer.h ================================================ /*! @file AudioUnitSDK/AUBuffer.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUBuffer_h #define AudioUnitSDK_AUBuffer_h // clang-format off #include // must come first // clang-format on #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS /// struct created/destroyed by allocator. Do not attempt to manually create/destroy. struct AllocatedBuffer { const UInt32 mMaximumNumberBuffers; const UInt32 mMaximumBytesPerBuffer; const UInt32 mReservedA[2]{}; // NOLINT C-style array const UInt32 mHeaderSize; const UInt32 mBufferDataSize; const UInt32 mReservedB[2]{}; // NOLINT C-style array void* const mBufferData; void* const mReservedC{}; AudioBufferList mAudioBufferList{}; // opaque variable-length data may follow the AudioBufferList AllocatedBuffer(UInt32 maxBuffers, UInt32 maxBytesPerBuffer, UInt32 headerSize, UInt32 bufferDataSize, void* bufferData) : mMaximumNumberBuffers{ maxBuffers }, mMaximumBytesPerBuffer{ maxBytesPerBuffer }, mHeaderSize{ headerSize }, mBufferDataSize{ bufferDataSize }, mBufferData{ bufferData } { } AudioBufferList& Prepare(UInt32 channelsPerBuffer, UInt32 bytesPerBuffer); ExpectedPtr PrepareOrError( UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) AUSDK_RTSAFE; AudioBufferList& PrepareNull(UInt32 channelsPerBuffer, UInt32 bytesPerBuffer); ExpectedPtr PrepareNullOrError( UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) AUSDK_RTSAFE; }; /*! @class BufferAllocator @brief Class which allocates memory for internal audio buffers. To customize, create a subclass and replace the BufferAllocator::instance() implementation. */ class BufferAllocator { public: /// Obtain the global instance, creating it if necessary. static BufferAllocator& instance(); BufferAllocator() = default; virtual ~BufferAllocator() = default; // Rule of 5 BufferAllocator(const BufferAllocator&) = delete; BufferAllocator(BufferAllocator&&) = delete; BufferAllocator& operator=(const BufferAllocator&) = delete; BufferAllocator& operator=(BufferAllocator&&) = delete; // N.B. Must return zeroed memory aligned to at least 16 bytes. virtual AllocatedBuffer* Allocate( UInt32 numberBuffers, UInt32 maxBytesPerBuffer, UInt32 reservedFlags); virtual void Deallocate(AllocatedBuffer* allocatedBuffer); }; /*! @class AUBufferList @brief Manages an `AudioBufferList` backed by allocated memory buffers. */ class AUBufferList { enum class EPtrState { Invalid, ToMyMemory, ToExternalMemory }; public: AUBufferList() = default; ~AUBufferList() { Deallocate(); } AUBufferList(const AUBufferList&) = delete; AUBufferList(AUBufferList&&) = delete; AUBufferList& operator=(const AUBufferList&) = delete; AUBufferList& operator=(AUBufferList&&) = delete; AudioBufferList& PrepareBuffer(const AudioStreamBasicDescription& format, UInt32 nFrames) { const auto maybeABL = PrepareBufferOrError(format, nFrames); ThrowExceptionIfUnexpected(maybeABL); return *maybeABL; } ExpectedPtr PrepareBufferOrError( const AudioStreamBasicDescription& format, UInt32 nFrames) AUSDK_RTSAFE; AudioBufferList& PrepareNullBuffer(const AudioStreamBasicDescription& format, UInt32 nFrames) { const auto maybeABL = PrepareNullBufferOrError(format, nFrames); ThrowExceptionIfUnexpected(maybeABL); return *maybeABL; } ExpectedPtr PrepareNullBufferOrError( const AudioStreamBasicDescription& format, UInt32 nFrames) AUSDK_RTSAFE; AudioBufferList& SetBufferList(const AudioBufferList& abl) { const auto maybeABL = SetBufferListOrError(abl); ThrowExceptionIfUnexpected(maybeABL); return *maybeABL; } ExpectedPtr SetBufferListOrError(const AudioBufferList& abl) AUSDK_RTSAFE { if (mAllocatedStreams < abl.mNumberBuffers) { return Unexpected(-1); } mPtrState = EPtrState::ToExternalMemory; auto& myabl = mBuffers->mAudioBufferList; memcpy(&myabl, &abl, static_cast( reinterpret_cast(&abl.mBuffers[abl.mNumberBuffers]) - // NOLINT reinterpret_cast(&abl))); // NOLINT return myabl; } void SetBuffer(UInt32 index, const AudioBuffer& ab) { const auto res = SetBufferOrError(index, ab); ThrowExceptionIfUnexpected(res); } Expected SetBufferOrError(UInt32 index, const AudioBuffer& ab) AUSDK_RTSAFE { auto& myabl = mBuffers->mAudioBufferList; if (mPtrState == EPtrState::Invalid || index >= myabl.mNumberBuffers) { return Unexpected(-1); } mPtrState = EPtrState::ToExternalMemory; myabl.mBuffers[index] = ab; // NOLINT return {}; } void InvalidateBufferList() noexcept { mPtrState = EPtrState::Invalid; } [[nodiscard]] AudioBufferList& GetBufferList() const { const auto maybeABL = GetBufferListOrError(); ThrowExceptionIfUnexpected(maybeABL); return *maybeABL; } [[nodiscard]] ExpectedPtr GetBufferListOrError() const AUSDK_RTSAFE { if (mPtrState == EPtrState::Invalid) { return Unexpected(-1); } return mBuffers->mAudioBufferList; } void CopyBufferListTo(AudioBufferList& abl) const { const auto res = CopyBufferListToOrError(abl); ThrowExceptionIfUnexpected(res); } Expected CopyBufferListToOrError(AudioBufferList& abl) const AUSDK_RTSAFE { if (mPtrState == EPtrState::Invalid) { return Unexpected(-1); } memcpy(&abl, &mBuffers->mAudioBufferList, static_cast( reinterpret_cast(&abl.mBuffers[abl.mNumberBuffers]) - // NOLINT reinterpret_cast(&abl))); // NOLINT return {}; } void CopyBufferContentsTo(AudioBufferList& destabl) const { const auto res = CopyBufferContentsToOrError(destabl); ThrowExceptionIfUnexpected(res); } Expected CopyBufferContentsToOrError(AudioBufferList& destabl) const AUSDK_RTSAFE { if (mPtrState == EPtrState::Invalid) { return Unexpected(-1); } const auto& srcabl = mBuffers->mAudioBufferList; const AudioBuffer* srcbuf = srcabl.mBuffers; // NOLINT AudioBuffer* destbuf = destabl.mBuffers; // NOLINT for (UInt32 i = 0; i < destabl.mNumberBuffers; ++i, ++srcbuf, ++destbuf) { // NOLINT if (i >= srcabl.mNumberBuffers) { // duplicate last source to additional outputs [4341137] --srcbuf; // NOLINT } const auto srcByteSize = srcbuf->mDataByteSize; const void* srcData = srcbuf->mData; void* dstData = destbuf->mData; if (srcByteSize > 0 && (srcData == nullptr || dstData == nullptr)) { return Unexpected(-1); } if (dstData != srcData) { memmove(dstData, srcData, srcByteSize); } destbuf->mDataByteSize = srcByteSize; } return {}; } void Allocate(const AudioStreamBasicDescription& format, UInt32 nFrames); void Deallocate(); // AudioBufferList utilities static void ZeroBuffer(AudioBufferList& abl) { AudioBuffer* buf = abl.mBuffers; // NOLINT for (UInt32 i = 0; i < abl.mNumberBuffers; ++i, ++buf) { // NOLINT memset(buf->mData, 0, buf->mDataByteSize); } } [[nodiscard]] UInt32 GetAllocatedFrames() const noexcept { return mAllocatedFrames; } private: EPtrState mPtrState{ EPtrState::Invalid }; AllocatedBuffer* mBuffers = nullptr; // only valid between Allocate and Deallocate UInt32 mAllocatedStreams{ 0 }; UInt32 mAllocatedFrames{ 0 }; }; } // namespace ausdk AUSDK_END_NO_RT_WARNINGS #endif // AudioUnitSDK_AUBuffer_h ================================================ FILE: include/AudioUnitSDK/AUConfig.h ================================================ /*! @file AudioUnitSDK/AUConfig.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUConfig_h #define AudioUnitSDK_AUConfig_h #include #if defined(__has_include) && __has_include() #include #endif #if defined(__has_include) && __has_include() #include #else enum { noErr = 0 }; #endif #if defined(__has_include) && __has_include() #include #else #include #endif // This optional prefix header allows external customization of the build. #if defined(__has_include) && __has_include("AudioUnitSDKPrefix.h") #include "AudioUnitSDKPrefix.h" #endif // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark Version #define AUSDK_VERSION_MAJOR 1 #define AUSDK_VERSION_MINOR 4 #define AUSDK_VERSION_PATCH 0 // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark Deprecations #ifdef AUSDK_NO_DEPRECATIONS #define AUSDK_DEPRECATED(msg) #else #define AUSDK_DEPRECATED(msg) [[deprecated(msg)]] // NOLINT macro #endif // AUSDK_NO_DEPRECATIONS // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark UI #if !defined(AUSDK_HAVE_UI) #define AUSDK_HAVE_UI 1 #endif // !defined(AUSDK_HAVE_UI) // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark Mach #if !defined(AUSDK_HAVE_MACH_TIME) #if defined(__has_include) && __has_include() #define AUSDK_HAVE_MACH_TIME 1 #else #define AUSDK_HAVE_MACH_TIME 0 #endif #endif // !defined(AUSDK_HAVE_MACH_TIME) // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark MIDI #if !defined(AUSDK_HAVE_MIDI) #if defined(__has_include) && __has_include() #define AUSDK_HAVE_MIDI 1 #else #define AUSDK_HAVE_MIDI 0 #endif #endif // !defined(AUSDK_HAVE_MIDI) #if !defined(AUSDK_HAVE_MIDI2) #if defined(__MAC_12_0) || defined(__IPHONE_15_0) #define AUSDK_HAVE_MIDI2 (AUSDK_HAVE_MIDI) #else #define AUSDK_HAVE_MIDI2 0 #endif #endif // !defined(AUSDK_HAVE_MIDI2) // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark MusicDevice #if !defined(AUSDK_HAVE_MUSIC_DEVICE) #if defined(__has_include) && __has_include() #define AUSDK_HAVE_MUSIC_DEVICE 1 #else #define AUSDK_HAVE_MUSIC_DEVICE 0 #endif #endif // !defined(AUSDK_HAVE_MUSIC_DEVICE) // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark AudioOutputUnit #if !defined(AUSDK_HAVE_IO_UNITS) #if defined(__has_include) && __has_include() #define AUSDK_HAVE_IO_UNITS 1 #else #define AUSDK_HAVE_IO_UNITS 0 #endif #endif // !defined(AUSDK_HAVE_MUSIC_DEVICE) #endif /* AUConfig_h */ ================================================ FILE: include/AudioUnitSDK/AUEffectBase.h ================================================ /*! @file AudioUnitSDK/AUEffectBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUEffectBase_h #define AudioUnitSDK_AUEffectBase_h // clang-format off #include // must come first // clang-format on #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS class AUKernelBase; /*! @class AUEffectBase @brief Base class for an effect with one input stream, one output stream, and any number of channels. */ class AUEffectBase : public AUBase { public: explicit AUEffectBase(AudioComponentInstance audioUnit, bool inProcessesInPlace = true); AUEffectBase(const AUEffectBase&) = delete; AUEffectBase(AUEffectBase&&) = delete; AUEffectBase& operator=(const AUEffectBase&) = delete; AUEffectBase& operator=(AUEffectBase&&) = delete; ~AUEffectBase() override = default; OSStatus Initialize() override; void Cleanup() override; OSStatus Reset(AudioUnitScope inScope, AudioUnitElement inElement) override; OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) override; OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) override; OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) override; bool StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) override; OSStatus ChangeStreamFormat(AudioUnitScope inScope, AudioUnitElement inElement, const AudioStreamBasicDescription& inPrevFormat, const AudioStreamBasicDescription& inNewFormat) override; OSStatus Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 nFrames) AUSDK_RTSAFE override; // If a kernel implements ResetRT, this can be used to reset with a realtime-safety assertion. template OSStatus ResetRT(AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/) AUSDK_RTSAFE { for (auto& kernel : mKernelList) { if (kernel) { static_cast(kernel.get())->ResetRT(); } } // Note that AUBase::Reset() is a no-op but not declared safe, so we bypass it. return noErr; } // our virtual methods // If your unit processes N to N channels, and there are no interactions between channels, // it can override NewKernel to create a mono processing object per channel. Otherwise, // don't override NewKernel, and instead, override ProcessBufferLists. virtual std::unique_ptr NewKernel() { return {}; } OSStatus ProcessBufferLists(AudioUnitRenderActionFlags& ioActionFlags, const AudioBufferList& inBuffer, AudioBufferList& outBuffer, UInt32 inFramesToProcess) AUSDK_RTSAFE override; // convenience format accessors (use output 0's format) Float64 GetSampleRate() AUSDK_RTSAFE { return GetOutput0().GetStreamFormat().mSampleRate; } UInt32 GetNumberOfChannels() AUSDK_RTSAFE { return GetOutput0().GetStreamFormat().mChannelsPerFrame; } // convenience wrappers for accessing parameters in the global scope // TODO: duplication with AUBase? using AUBase::SetParameter; void SetParameter(AudioUnitParameterID paramID, AudioUnitParameterValue value) AUSDK_RTSAFE { Globals()->SetParameterRT(paramID, value); } using AUBase::GetParameter; AudioUnitParameterValue GetParameter(AudioUnitParameterID paramID) AUSDK_RTSAFE { return Globals()->GetParameterRT(paramID); } [[nodiscard]] bool CanScheduleParameters() const AUSDK_RTSAFE override { return true; } // This is used for the property value - to reflect to the UI if an effect is bypassed [[nodiscard]] bool IsBypassEffect() const noexcept { return mBypassEffect; } virtual void SetBypassEffect(bool inFlag) { mBypassEffect = inFlag; } void SetParamHasSampleRateDependency(bool inFlag) noexcept { mParamSRDep = inFlag; } [[nodiscard]] bool GetParamHasSampleRateDependency() const noexcept { return mParamSRDep; } /// Context, passed as `void* userData`, for `ProcessScheduledSlice()`. struct ScheduledProcessParams { AudioUnitRenderActionFlags* actionFlags = nullptr; AudioBufferList* inputBufferList = nullptr; AudioBufferList* outputBufferList = nullptr; }; OSStatus ProcessScheduledSlice(void* inUserData, UInt32 inStartFrameInBuffer, UInt32 inSliceFramesToProcess, UInt32 inTotalBufferFrames) AUSDK_RTSAFE override; [[nodiscard]] bool ProcessesInPlace() const noexcept { return mProcessesInPlace; } void SetProcessesInPlace(bool inProcessesInPlace) noexcept { mProcessesInPlace = inProcessesInPlace; } using KernelList = std::vector>; protected: void MaintainKernels(); // This is used in the render call to see if an effect is bypassed // It can return a different status than IsBypassEffect (though it MUST take that into account) virtual bool ShouldBypassEffect() AUSDK_RTSAFE { return IsBypassEffect(); } [[nodiscard]] AUKernelBase* GetKernel(UInt32 index) const { return (index < mKernelList.size()) ? mKernelList[index].get() : nullptr; } [[nodiscard]] const KernelList& GetKernelList() const noexcept { return mKernelList; } bool IsInputSilent(AudioUnitRenderActionFlags inActionFlags, UInt32 inFramesToProcess) { bool inputSilent = (inActionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0; // take latency and tail time into account when propagating the silent bit const auto silentTimeoutFrames = static_cast(GetSampleRate() * (GetLatency() + GetTailTime())); mSilentTimeout.Process(inFramesToProcess, silentTimeoutFrames, inputSilent); return inputSilent; } #if TARGET_OS_IPHONE void SetOnlyOneKernel(bool inUseOnlyOneKernel) noexcept { mOnlyOneKernel = inUseOnlyOneKernel; } // set in ctor of subclass that wants it. #endif private: KernelList mKernelList; bool mBypassEffect{ false }; bool mParamSRDep{ false }; bool mProcessesInPlace; AUSilentTimeout mSilentTimeout; AUOutputElement* mMainOutput{ nullptr }; AUInputElement* mMainInput{ nullptr }; #if TARGET_OS_IPHONE bool mOnlyOneKernel; #endif UInt32 mBytesPerFrame = 0; }; /*! @class AUKernelBase @brief Base class for a signal-processing "kernel", an object that performs DSP on one channel of an audio stream. */ class AUKernelBase { public: explicit AUKernelBase(AUEffectBase& inAudioUnit) : mAudioUnit(inAudioUnit) {} AUSDK_DEPRECATED("Construct with a reference") explicit AUKernelBase(AUEffectBase* inAudioUnit) : mAudioUnit(*inAudioUnit) {} AUKernelBase(const AUKernelBase&) = delete; AUKernelBase(AUKernelBase&&) = delete; AUKernelBase& operator=(const AUKernelBase&) = delete; AUKernelBase& operator=(AUKernelBase&&) = delete; virtual ~AUKernelBase() = default; virtual void Reset() {} virtual void Process(const Float32* /*inSourceP*/, Float32* /*inDestP*/, UInt32 /*inFramesToProcess*/, bool& /*ioSilence*/) AUSDK_RTSAFE = 0; Float64 GetSampleRate() AUSDK_RTSAFE { return mAudioUnit.GetSampleRate(); } AudioUnitParameterValue GetParameter(AudioUnitParameterID paramID) AUSDK_RTSAFE { return mAudioUnit.GetParameter(paramID); } void SetChannelNum(UInt32 inChan) noexcept { mChannelNum = inChan; } [[nodiscard]] UInt32 GetChannelNum() const noexcept { return mChannelNum; } protected: AUEffectBase& mAudioUnit; // NOLINT protected UInt32 mChannelNum = 0; // NOLINT protected }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUEffectBase_h ================================================ FILE: include/AudioUnitSDK/AUInputElement.h ================================================ /*! @file AudioUnitSDK/AUInputElement.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUInputElement_h #define AudioUnitSDK_AUInputElement_h // clang-format off #include // must come first // clang-format on #include #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS /*! @class AUInputElement @brief Implements an audio unit input element, managing the source of input from a callback or connection. */ class AUInputElement : public AUIOElement { public: using AUIOElement::AUIOElement; // AUElement override OSStatus SetStreamFormat(const AudioStreamBasicDescription& fmt) override; [[nodiscard]] bool NeedsBufferSpace() const override { return IsCallback(); } void SetConnection(const AudioUnitConnection& conn); void SetInputCallback(AURenderCallback proc, void* refCon); [[nodiscard]] bool IsActive() const noexcept { return mInputType != EInputType::NoInput; } [[nodiscard]] bool IsCallback() const noexcept { return mInputType == EInputType::FromCallback; } [[nodiscard]] bool HasConnection() const noexcept { return mInputType == EInputType::FromConnection; } OSStatus PullInput(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, AudioUnitElement inElement, UInt32 nFrames) AUSDK_RTSAFE; OSStatus PullInputWithBufferList(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, AudioUnitElement inElement, UInt32 nFrames, AudioBufferList& inBufferList) AUSDK_RTSAFE; protected: void Disconnect(); private: enum class EInputType { NoInput, FromConnection, FromCallback }; EInputType mInputType{ EInputType::NoInput }; // if from callback: AURenderCallback mInputProc{ nullptr }; void* mInputProcRefCon{ nullptr }; // if from connection: AudioUnitConnection mConnection{}; }; inline OSStatus AUInputElement::PullInputWithBufferList(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, AudioUnitElement inElement, UInt32 nFrames, AudioBufferList& inBufferList) AUSDK_RTSAFE { OSStatus theResult = noErr; if (HasConnection()) { // only support connections for V2 audio units theResult = AudioUnitRender(mConnection.sourceAudioUnit, &ioActionFlags, &inTimeStamp, mConnection.sourceOutputNumber, nFrames, &inBufferList); } else { // kFromCallback: theResult = (mInputProc)(mInputProcRefCon, &ioActionFlags, &inTimeStamp, inElement, nFrames, &inBufferList); } // defense: the upstream could have disconnected. // it's a horrible thing to do, but may happen! AUSDK_Require(mInputType != EInputType::NoInput, kAudioUnitErr_NoConnection); #if !TARGET_OS_IPHONE || DEBUG if (theResult == noErr) { // if there's already an error, there's no point (and maybe some harm) // in validating. AUSDK_Require( !(ABL::IsBogusAudioBufferList(inBufferList) & 1), kAudioUnitErr_InvalidPropertyValue); } #endif return theResult; } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUInputElement_h ================================================ FILE: include/AudioUnitSDK/AUMIDIBase.h ================================================ /*! @file AudioUnitSDK/AUMIDIBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUMIDIBase_h #define AudioUnitSDK_AUMIDIBase_h // clang-format off #include // must come first // clang-format on #include #include #ifndef AUSDK_HAVE_XML_NAMES #define AUSDK_HAVE_XML_NAMES TARGET_OS_OSX // NOLINT(cppcoreguidelines-macro-usage) #endif #ifndef AUSDK_HAVE_MIDI_MAPPING #define AUSDK_HAVE_MIDI_MAPPING TARGET_OS_OSX // NOLINT(cppcoreguidelines-macro-usage) #endif namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS #if AUSDK_HAVE_MIDI_MAPPING /// Abstract interface for parameter MIDI mapping class AUMIDIMapper { public: AUMIDIMapper() = default; virtual ~AUMIDIMapper() = default; AUMIDIMapper(const AUMIDIMapper&) = delete; AUMIDIMapper(AUMIDIMapper&&) = delete; AUMIDIMapper& operator=(const AUMIDIMapper&) = delete; AUMIDIMapper& operator=(AUMIDIMapper&&) = delete; [[nodiscard]] virtual UInt32 GetNumberMaps() const = 0; virtual void GetMaps(AUParameterMIDIMapping* outMapping) = 0; virtual void GetHotParameterMap(AUParameterMIDIMapping& outMapping) = 0; virtual void AddParameterMapping( const AUParameterMIDIMapping* maps, UInt32 count, AUBase& auBase) = 0; virtual void RemoveParameterMapping( const AUParameterMIDIMapping* maps, UInt32 count, bool& outDidChange) = 0; virtual void SetHotMapping(const AUParameterMIDIMapping& mapping) = 0; virtual void ReplaceAllMaps( const AUParameterMIDIMapping* maps, UInt32 count, AUBase& auBase) = 0; virtual bool HandleHotMapping( UInt8 status, UInt8 channel, UInt8 data1, AUBase& auBase) AUSDK_RTSAFE = 0; virtual bool FindParameterMapEventMatch(UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame, AUBase& auBase) AUSDK_RTSAFE = 0; }; #endif // ________________________________________________________________________ // AUMIDIBase // /*! @class AUMIDIBase @brief Auxiliary class supporting MIDI events. */ class AUMIDIBase { public: explicit AUMIDIBase(AUBase& inBase) : mAUBaseInstance(inBase) {} virtual ~AUMIDIBase() = default; AUMIDIBase(const AUMIDIBase&) = delete; AUMIDIBase(AUMIDIBase&&) = delete; AUMIDIBase& operator=(const AUMIDIBase&) = delete; AUMIDIBase& operator=(AUMIDIBase&&) = delete; virtual OSStatus MIDIEvent( UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) AUSDK_RTSAFE { const auto strippedStatus = static_cast(inStatus & 0xf0U); // NOLINT const auto channel = static_cast(inStatus & 0x0fU); // NOLINT return HandleMIDIEvent(strippedStatus, channel, inData1, inData2, inOffsetSampleFrame); } #if AUSDK_HAVE_MIDI2 virtual OSStatus MIDIEventList( UInt32 /*inOffsetSampleFrame*/, const MIDIEventList* /*eventList*/) AUSDK_RTSAFE { return kAudio_UnimplementedError; } #endif virtual OSStatus SysEx(const UInt8* inData, UInt32 inLength) AUSDK_RTSAFE; virtual OSStatus DelegateGetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable); virtual OSStatus DelegateGetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData); virtual OSStatus DelegateSetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); protected: // MIDI dispatch virtual OSStatus HandleMIDIEvent(UInt8 inStatus, UInt8 inChannel, UInt8 inData1, UInt8 inData2, UInt32 inStartFrame) AUSDK_RTSAFE; virtual OSStatus HandleNonNoteEvent( UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame) AUSDK_RTSAFE; // Old name AUSDK_DEPRECATED("HandleMIDIEvent") OSStatus HandleMidiEvent(UInt8 inStatus, UInt8 inChannel, UInt8 inData1, UInt8 inData2, UInt32 inStartFrame) AUSDK_RTSAFE { return HandleMIDIEvent(inStatus, inChannel, inData1, inData2, inStartFrame); } #if AUSDK_HAVE_XML_NAMES virtual OSStatus GetXMLNames(CFURLRef* /*outNameDocument*/) { return kAudioUnitErr_InvalidProperty; } // if not overridden, it's unsupported #endif // channel messages virtual OSStatus HandleNoteOn(UInt8 /*inChannel*/, UInt8 /*inNoteNumber*/, UInt8 /*inVelocity*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleNoteOff(UInt8 /*inChannel*/, UInt8 /*inNoteNumber*/, UInt8 /*inVelocity*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleControlChange(UInt8 /*inChannel*/, UInt8 /*inController*/, UInt8 /*inValue*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandlePitchWheel(UInt8 /*inChannel*/, UInt8 /*inPitch1*/, UInt8 /*inPitch2*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleChannelPressure( UInt8 /*inChannel*/, UInt8 /*inValue*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleProgramChange(UInt8 /*inChannel*/, UInt8 /*inValue*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandlePolyPressure(UInt8 /*inChannel*/, UInt8 /*inKey*/, UInt8 /*inValue*/, UInt32 /*inStartFrame*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleResetAllControllers(UInt8 /*inChannel*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleAllNotesOff(UInt8 /*inChannel*/) AUSDK_RTSAFE { return noErr; } virtual OSStatus HandleAllSoundOff(UInt8 /*inChannel*/) AUSDK_RTSAFE { return noErr; } // System messages virtual OSStatus HandleSysEx(const UInt8* /*inData*/, UInt32 /*inLength*/) AUSDK_RTSAFE { return noErr; } #if AUSDK_HAVE_MIDI_MAPPING void SetMIDIMapper(const std::shared_ptr& mapper) { mMIDIMapper = mapper; } #endif private: AUBase& mAUBaseInstance; #if AUSDK_HAVE_MIDI_MAPPING std::shared_ptr mMIDIMapper; #endif }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUMIDIBase_h ================================================ FILE: include/AudioUnitSDK/AUMIDIEffectBase.h ================================================ /*! @file AudioUnitSDK/AUMIDIEffectBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUMIDIEffectBase_h #define AudioUnitSDK_AUMIDIEffectBase_h // clang-format off #include // must come first // clang-format on #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS /*! @class AUMIDIEffectBase @brief Subclass of AUEffectBase and AUMIDIBase, providing an abstract base class for music effects. */ class AUMIDIEffectBase : public AUEffectBase, public AUMIDIBase { public: explicit AUMIDIEffectBase(AudioComponentInstance inInstance, bool inProcessesInPlace = false); OSStatus MIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) AUSDK_RTSAFE override { return AUMIDIBase::MIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); } OSStatus SysEx(const UInt8* inData, UInt32 inLength) AUSDK_RTSAFE override { return AUMIDIBase::SysEx(inData, inLength); } OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) override; OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) override; OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) override; }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUMIDIEffectBase_h ================================================ FILE: include/AudioUnitSDK/AUMIDIUtility.h ================================================ /*! @file AudioUnitSDK/AUMIDIUtility.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUMIDIUtility_h #define AudioUnitSDK_AUMIDIUtility_h #pragma message("This header is deprecated and will be removed in a future release.") // clang-format off #include // must come first // clang-format on #endif // AudioUnitSDK_AUMIDIUtility_h ================================================ FILE: include/AudioUnitSDK/AUOutputElement.h ================================================ /*! @file AudioUnitSDK/AUOutputElement.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUOutputElement_h #define AudioUnitSDK_AUOutputElement_h // clang-format off #include // must come first // clang-format on #include #include #include namespace ausdk { class AUBase; /*! @class AUOutputElement @brief Implements an audio unit output element. */ class AUOutputElement : public AUIOElement { public: explicit AUOutputElement(AUBase& audioUnit); AUOutputElement(AUBase& audioUnit, const AudioStreamBasicDescription& format); AUSDK_DEPRECATED("Construct with a reference") explicit AUOutputElement(AUBase* audioUnit) : AUOutputElement(*audioUnit) {} // AUElement override OSStatus SetStreamFormat(const AudioStreamBasicDescription& desc) override; [[nodiscard]] bool NeedsBufferSpace() const override { return true; } }; } // namespace ausdk #endif // AudioUnitSDK_AUOutputElement_h ================================================ FILE: include/AudioUnitSDK/AUPlugInDispatch.h ================================================ /*! @file AudioUnitSDK/AUPlugInDispatch.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUPlugInDispatch_h #define AudioUnitSDK_AUPlugInDispatch_h // clang-format off #include // must come first // clang-format on #include namespace ausdk { /// Method lookup for a basic AUBase subclass. struct AUBaseLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for a basic AUBase subclass. template class AUBaseFactory : public APFactory {}; /// Method lookup for a AUBase subclass which implements I/O methods (Start, Stop). struct AUOutputLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements I/O methods (Start, Stop). template class AUOutputBaseFactory : public APFactory {}; /// Method lookup for an AUBase subclass which implements I/O methods (Start, Stop) and /// ComplexRender. struct AUComplexOutputLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements I/O methods (Start, Stop) and ComplexRender. template class AUOutputComplexBaseFactory : public APFactory {}; /// Method lookup for an AUBase subclass which implements Process. struct AUBaseProcessLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements Process. template class AUBaseProcessFactory : public APFactory {}; /// Method lookup for an AUBase subclass which implements ProcessMultiple. struct AUBaseProcessMultipleLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements ProcessMultiple. template class AUBaseProcessMultipleFactory : public APFactory {}; /// Method lookup for an AUBase subclass which implements Process and ProcessMultiple. struct AUBaseProcessAndMultipleLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements Process and ProcessMultiple. template class AUBaseProcessAndMultipleFactory : public APFactory {}; #if AUSDK_HAVE_MIDI /// Method lookup for an AUBase subclass which implements MusicDevice methods (MIDIEvent and SysEx). struct AUMIDILookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements MusicDevice methods (MIDIEvent and SysEx). template class AUMIDIEffectFactory : public APFactory {}; /// Method lookup for an AUBase subclass which implements Process and MusicDevice methods (MIDIEvent /// and SysEx). struct AUMIDIProcessLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements Process and MusicDevice methods (MIDIEvent /// and SysEx). template class AUMIDIProcessFactory : public APFactory {}; #endif // AUSDK_HAVE_MIDI #if AUSDK_HAVE_MUSIC_DEVICE /// Method lookup for an AUBase subclass which implements the full set of MusicDevice methods /// (MIDIEvent, SysEx, StartNote, StopNote). struct AUMusicLookup { static AudioComponentMethod Lookup(SInt16 selector); }; /// Factory for an AUBase subclass which implements the full set of MusicDevice methods /// (MIDIEvent, SysEx, StartNote, StopNote). template class AUMusicDeviceFactory : public APFactory {}; #endif // AUSDK_HAVE_MUSIC_DEVICE } // namespace ausdk #endif // AudioUnitSDK_AUPlugInDispatch_h ================================================ FILE: include/AudioUnitSDK/AUScopeElement.h ================================================ /*! @file AudioUnitSDK/AUScopeElement.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUScopeElement_h #define AudioUnitSDK_AUScopeElement_h // module // clang-format off #include // must come first // clang-format on #include #include #include // OS #include // std #include #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS class AUBase; /// Wrap an atomic in a copy-constructible/assignable object. This allows storing atomic values in a /// vector (not directly possible since atomics are not copy-constructible/assignable). template class AtomicValue { public: AtomicValue() = default; explicit AtomicValue(T val) : mValue{ val } {} ~AtomicValue() = default; AtomicValue(const AtomicValue& other) : mValue{ other.mValue.load() } {} AtomicValue(AtomicValue&& other) noexcept : mValue{ other.mValue.load() } {} AtomicValue& operator=(const AtomicValue& other) { if (&other != this) { mValue.store(other.mValue.load()); } return *this; } AtomicValue& operator=(AtomicValue&& other) noexcept { mValue.store(other.mValue.load()); return *this; } T load(std::memory_order m = std::memory_order_seq_cst) const { return mValue.load(m); } void store(T v, std::memory_order m = std::memory_order_seq_cst) { mValue.store(v, m); } operator T() const { return load(); } // NOLINT implicit conversions OK AtomicValue& operator=(T value) { store(value); return *this; } private: std::atomic mValue{}; static_assert(decltype(mValue)::is_always_lock_free); }; /// A bare-bones reinvention of boost::flat_map, just enough to hold parameters in sorted vectors. template class flat_map { using KVPair = std::pair; using Impl = std::vector>; static bool keyless(const KVPair& item, Key k) AUSDK_RTSAFE { return k > item.first; } Impl mImpl; public: using iterator = typename Impl::iterator; using const_iterator = typename Impl::const_iterator; [[nodiscard]] bool empty() const noexcept { return mImpl.empty(); } [[nodiscard]] size_t size() const noexcept { return mImpl.size(); } [[nodiscard]] const_iterator begin() const noexcept { return mImpl.begin(); } [[nodiscard]] const_iterator end() const noexcept { return mImpl.end(); } iterator begin() noexcept { return mImpl.begin(); } iterator end() noexcept { return mImpl.end(); } const_iterator cbegin() noexcept { return mImpl.cbegin(); } const_iterator cend() noexcept { return mImpl.cend(); } [[nodiscard]] const_iterator lower_bound(Key k) const { return std::lower_bound(mImpl.cbegin(), mImpl.cend(), k, RTSafeFP{ keyless }); } iterator lower_bound(Key k) { return std::lower_bound(mImpl.begin(), mImpl.end(), k, RTSafeFP{ keyless }); } [[nodiscard]] const_iterator find(Key k) const { auto iter = lower_bound(k); if (iter != mImpl.end()) { if (iter->first != k) { iter = mImpl.end(); } } return iter; } iterator find(Key k) { auto iter = lower_bound(k); if (iter != mImpl.end()) { if (iter->first != k) { iter = mImpl.end(); } } return iter; } class ItemProxy { public: ItemProxy(flat_map& map, Key k) : mMap{ map }, mKey{ k } {} operator Value() const // NOLINT implicit conversion is OK { const auto iter = mMap.find(mKey); if (iter == mMap.end()) { throw std::runtime_error("Invalid map key"); } return iter->second; } ItemProxy& operator=(const Value& v) { const auto iter = mMap.lower_bound(mKey); if (iter != mMap.end() && iter->first == mKey) { iter->second = v; } else { mMap.mImpl.insert(iter, { mKey, v }); } return *this; } private: flat_map& mMap; const Key mKey; }; ItemProxy operator[](Key k) { return ItemProxy{ *this, k }; } }; // ____________________________________________________________________________ // class AUIOElement; /// An organizational unit for parameters, with a name. class AUElement { public: explicit AUElement(AUBase& audioUnit) : mAudioUnit(audioUnit), mUseIndexedParameters(false) {} AUSDK_DEPRECATED("Construct with a reference") explicit AUElement(AUBase* audioUnit) : AUElement(*audioUnit) {} AUElement(const AUElement&) = delete; AUElement(AUElement&&) = delete; AUElement& operator=(const AUElement&) = delete; AUElement& operator=(AUElement&&) = delete; virtual ~AUElement() = default; virtual UInt32 GetNumberOfParameters() { return mUseIndexedParameters ? static_cast(mIndexedParameters.size()) : static_cast(mParameters.size()); } virtual void GetParameterList(AudioUnitParameterID* outList); [[nodiscard]] bool HasParameterID(AudioUnitParameterID paramID) const AUSDK_RTSAFE; // Use this from the control (non-realtime) context. Throws if the parameter doesn't exist. [[nodiscard]] AudioUnitParameterValue GetParameter(AudioUnitParameterID paramID) const { const auto res = GetParameterOrError(paramID); ThrowExceptionIfUnexpected(res); return *res; } // Use this from the render (realtime) context, when you are sure the parameter should exist. [[nodiscard]] AudioUnitParameterValue GetParameterRT( AudioUnitParameterID paramID) const AUSDK_RTSAFE { const auto res = GetParameterOrError(paramID); AUSDK_Assert(res); return *res; } // Primitive, returns an error if the parameter does not exist. [[nodiscard]] Expected GetParameterOrError( AudioUnitParameterID paramID) const AUSDK_RTSAFE; // Use this from the control (non-realtime) context. Throws if the parameter doesn't exist. // Only set okWhenInitialized to true when you know the outside world cannot access this // element. Otherwise the parameter map could get corrupted. void SetParameter( AudioUnitParameterID paramID, AudioUnitParameterValue value, bool okWhenInitialized = false) { const auto res = SetParameterOrError(paramID, value, okWhenInitialized); ThrowExceptionIfUnexpected(res); } // Use this from the render (realtime) context, when you are sure the parameter should exist. void SetParameterRT(AudioUnitParameterID paramID, AudioUnitParameterValue value, bool okWhenInitialized = false) AUSDK_RTSAFE { const auto res = SetParameterOrError(paramID, value, okWhenInitialized); AUSDK_Assert(res); } // Primitive, returns an error if the parameter does not exist. Expected SetParameterOrError(AudioUnitParameterID paramID, AudioUnitParameterValue value, bool okWhenInitialized = false) AUSDK_RTSAFE; // Only set okWhenInitialized to true when you know the outside world cannot access this // element. Otherwise the parameter map could get corrupted. N.B. This only handles // immediate parameters. Override to implement ramping. Called from // AUBase::ProcessForScheduledParams. [[nodiscard]] virtual OSStatus SetScheduledEvent(AudioUnitParameterID paramID, const AudioUnitParameterEvent& inEvent, UInt32 inSliceOffsetInBuffer, UInt32 inSliceDurationFrames, bool okWhenInitialized = false) AUSDK_RTSAFE; [[nodiscard]] AUBase& GetAudioUnit() const noexcept { return mAudioUnit; } void SaveState(AudioUnitScope scope, CFMutableDataRef data); const UInt8* RestoreState(const UInt8* state); [[nodiscard]] Owned GetName() const noexcept { return mElementName; } void SetName(CFStringRef inName) noexcept { mElementName = inName; } [[nodiscard]] bool HasName() const noexcept { return *mElementName != nullptr; } virtual void UseIndexedParameters(UInt32 inNumberOfParameters); virtual AUIOElement* AsIOElement() AUSDK_RTSAFE { return nullptr; } private: using ParameterValue = AtomicValue; AUBase& mAudioUnit; flat_map mParameters; bool mUseIndexedParameters; std::vector mIndexedParameters; Owned mElementName; }; // ____________________________________________________________________________ // /// A subclass of AUElement which represents an input or output bus, and has an associated /// audio format and buffers. class AUIOElement : public AUElement { public: explicit AUIOElement(AUBase& audioUnit); AUIOElement(AUBase& audioUnit, const AudioStreamBasicDescription& format) : AUIOElement{ audioUnit } { mStreamFormat = format; } AUSDK_DEPRECATED("Construct with a reference") explicit AUIOElement(AUBase* audioUnit) : AUIOElement(*audioUnit) {} [[nodiscard]] const AudioStreamBasicDescription& GetStreamFormat() const noexcept { return mStreamFormat; } virtual OSStatus SetStreamFormat(const AudioStreamBasicDescription& format); virtual void AllocateBuffer(UInt32 inFramesToAllocate = 0); void DeallocateBuffer(); /// Determines (via subclass override) whether the element's buffer list needs to be allocated. [[nodiscard]] virtual bool NeedsBufferSpace() const = 0; void SetWillAllocateBuffer(bool inFlag) noexcept { mWillAllocate = inFlag; } [[nodiscard]] bool WillAllocateBuffer() const noexcept { return mWillAllocate; } AudioBufferList& PrepareBuffer(UInt32 nFrames) { if (mWillAllocate) { return mIOBuffer.PrepareBuffer(mStreamFormat, nFrames); } Throw(kAudioUnitErr_InvalidPropertyValue); } ExpectedPtr PrepareBufferOrError(UInt32 nFrames) AUSDK_RTSAFE { if (mWillAllocate) { return mIOBuffer.PrepareBufferOrError(mStreamFormat, nFrames); } return Unexpected(kAudioUnitErr_InvalidPropertyValue); } AudioBufferList& PrepareNullBuffer(UInt32 nFrames) { return mIOBuffer.PrepareNullBuffer(mStreamFormat, nFrames); } ExpectedPtr PrepareNullBufferOrError(UInt32 nFrames) AUSDK_RTSAFE { return mIOBuffer.PrepareNullBufferOrError(mStreamFormat, nFrames); } AudioBufferList& SetBufferList(const AudioBufferList& abl) { return mIOBuffer.SetBufferList(abl); } ExpectedPtr SetBufferListOrError(const AudioBufferList& abl) AUSDK_RTSAFE { return mIOBuffer.SetBufferListOrError(abl); } void SetBuffer(UInt32 index, AudioBuffer& ab) { mIOBuffer.SetBuffer(index, ab); } Expected SetBufferOrError(UInt32 index, AudioBuffer& ab) AUSDK_RTSAFE { return mIOBuffer.SetBufferOrError(index, ab); } void InvalidateBufferList() { mIOBuffer.InvalidateBufferList(); } [[nodiscard]] AudioBufferList& GetBufferList() const { return mIOBuffer.GetBufferList(); } ExpectedPtr GetBufferListOrError() const AUSDK_RTSAFE { return mIOBuffer.GetBufferListOrError(); } [[nodiscard]] float* GetFloat32ChannelData(UInt32 ch) const { const auto& abl = GetBufferList(); if (IsInterleaved()) { return static_cast(abl.mBuffers[0].mData) + ch; // NOLINT } return static_cast(abl.mBuffers[ch].mData); // NOLINT } // N.B. Returns null pointers on failure. [[nodiscard]] float* GetFloat32ChannelDataRT(UInt32 ch) const AUSDK_RTSAFE { const auto abl = GetBufferListOrError(); if (!abl) [[unlikely]] { return nullptr; } if (IsInterleaved()) { return static_cast(abl->mBuffers[0].mData) + ch; // NOLINT } return static_cast(abl->mBuffers[ch].mData); // NOLINT } void CopyBufferListTo(AudioBufferList& abl) const { mIOBuffer.CopyBufferListTo(abl); } void CopyBufferContentsTo(AudioBufferList& abl) const { mIOBuffer.CopyBufferContentsTo(abl); } Expected CopyBufferListToOrError(AudioBufferList& abl) const AUSDK_RTSAFE { return mIOBuffer.CopyBufferListToOrError(abl); } Expected CopyBufferContentsToOrError(AudioBufferList& abl) const AUSDK_RTSAFE { return mIOBuffer.CopyBufferContentsToOrError(abl); } [[nodiscard]] bool IsInterleaved() const noexcept { return ASBD::IsInterleaved(mStreamFormat); } [[nodiscard]] UInt32 NumberChannels() const noexcept { return mStreamFormat.mChannelsPerFrame; } [[nodiscard]] UInt32 NumberInterleavedChannels() const noexcept { return ASBD::NumberInterleavedChannels(mStreamFormat); } virtual std::vector GetChannelLayoutTags(); [[nodiscard]] const AUChannelLayout& ChannelLayout() const { return mChannelLayout; } // Old layout methods virtual OSStatus SetAudioChannelLayout(const AudioChannelLayout& inLayout); virtual UInt32 GetAudioChannelLayout(AudioChannelLayout* outLayoutPtr, bool& outWritable); virtual OSStatus RemoveAudioChannelLayout(); /*! @fn AsIOElement*/ AUIOElement* AsIOElement() AUSDK_RTSAFE override { return this; } protected: AUBufferList& IOBuffer() noexcept { return mIOBuffer; } void ForceSetAudioChannelLayout(const AudioChannelLayout& inLayout) { mChannelLayout = inLayout; } private: AudioStreamBasicDescription mStreamFormat{}; AUChannelLayout mChannelLayout{}; AUBufferList mIOBuffer; // for input: input proc buffer, only allocated when needed // for output: output cache, usually allocated early on bool mWillAllocate{ false }; }; // ____________________________________________________________________________ // /*! @class AUScopeDelegate @brief Provides a way to customize a scope, thereby obtaining virtual scopes. Can be used to implement scopes with variable numbers of elements. */ class AUScopeDelegate { public: AUScopeDelegate() = default; virtual ~AUScopeDelegate() = default; AUScopeDelegate(const AUScopeDelegate&) = delete; AUScopeDelegate(AUScopeDelegate&&) = delete; AUScopeDelegate& operator=(const AUScopeDelegate&) = delete; AUScopeDelegate& operator=(AUScopeDelegate&&) = delete; void Initialize(AUBase* creator, AudioUnitScope scope, UInt32 numElements) { mCreator = creator; mScope = scope; SetNumberOfElements(numElements); } virtual void SetNumberOfElements(UInt32 numElements) = 0; virtual UInt32 GetNumberOfElements() AUSDK_RTSAFE = 0; virtual AUElement* GetElement(UInt32 elementIndex) AUSDK_RTSAFE = 0; [[nodiscard]] AUBase* GetCreator() const noexcept { return mCreator; } [[nodiscard]] AudioUnitScope GetScope() const noexcept { return mScope; } private: AUBase* mCreator{ nullptr }; AudioUnitScope mScope{ 0 }; }; // ____________________________________________________________________________ // /*! @class AUScope @brief Organizes one or more elements into an addressable group (e.g. global, input, output). */ class AUScope { public: AUScope() = default; ~AUScope() = default; AUScope(const AUScope&) = delete; AUScope(AUScope&&) = delete; AUScope& operator=(const AUScope&) = delete; AUScope& operator=(AUScope&&) = delete; void Initialize(AUBase* creator, AudioUnitScope scope, UInt32 numElements) { mCreator = creator; mScope = scope; if (mDelegate != nullptr) { return mDelegate->Initialize(creator, scope, numElements); } SetNumberOfElements(numElements); } void SetNumberOfElements(UInt32 numElements); [[nodiscard]] UInt32 GetNumberOfElements() const { if (mDelegate != nullptr) { return mDelegate->GetNumberOfElements(); } return static_cast(mElements.size()); } [[nodiscard]] AUElement* GetElement(UInt32 elementIndex) const AUSDK_RTSAFE { if (mDelegate != nullptr) { return mDelegate->GetElement(elementIndex); } return elementIndex < mElements.size() ? mElements[elementIndex].get() : nullptr; } [[nodiscard]] AUElement* SafeGetElement(UInt32 elementIndex) const { AUElement* const element = GetElement(elementIndex); ausdk::ThrowExceptionIf(element == nullptr, kAudioUnitErr_InvalidElement); return element; } [[nodiscard]] AUIOElement* GetIOElement(UInt32 elementIndex) const { AUElement* const element = GetElement(elementIndex); AUIOElement* const ioel = element != nullptr ? element->AsIOElement() : nullptr; ausdk::ThrowExceptionIf(ioel == nullptr, kAudioUnitErr_InvalidElement); return ioel; } template [[nodiscard]] ExpectedPtr GetElementOrError(UInt32 elementIndex) const AUSDK_RTSAFE { if (mDelegate != nullptr) { if (auto* elem = mDelegate->GetElement(elementIndex)) { return *static_cast(elem); } } else if (elementIndex < mElements.size()) { if (auto* elem = mElements[elementIndex].get()) { return *static_cast(elem); } } return Unexpected(kAudioUnitErr_InvalidElement); } [[nodiscard]] ExpectedPtr GetIOElementOrError(UInt32 elementIndex) const { auto& element = AUSDK_UnwrapOrReturnUnexpected(GetElementOrError(elementIndex)); if (AUIOElement* const ioel = element.AsIOElement()) { return *ioel; } return Unexpected(kAudioUnitErr_InvalidElement); } [[nodiscard]] bool HasElementWithName() const; void AddElementNamesToDict(CFMutableDictionaryRef inNameDict) const; [[nodiscard]] std::vector RestoreElementNames( CFDictionaryRef inNameDict) const; [[nodiscard]] AudioUnitScope GetScope() const noexcept { return mScope; } void SetDelegate(AUScopeDelegate* inDelegate) noexcept { mDelegate = inDelegate; } void SaveState(CFMutableDataRef data) const; const UInt8* RestoreState(const UInt8* state) const; private: using ElementVector = std::vector>; AUBase* mCreator{ nullptr }; AudioUnitScope mScope{ 0 }; ElementVector mElements; AUScopeDelegate* mDelegate{ nullptr }; }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUScopeElement_h ================================================ FILE: include/AudioUnitSDK/AUSilentTimeout.h ================================================ /*! @file AudioUnitSDK/AUSilentTimeout.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUSilentTimeout_h #define AudioUnitSDK_AUSilentTimeout_h // clang-format off #include // must come first // clang-format on #include // for UInt32 #include namespace ausdk { /*! @class AUSilentTimeout @brief Utility to assist in propagating a silence flag from signal-processing input to output, factoring in a processing delay. */ class AUSilentTimeout { public: AUSilentTimeout() = default; void Process(UInt32 inFramesToProcess, UInt32 inTimeoutLimit, bool& ioSilence) { if (ioSilence) { if (mResetTimer) { mTimeoutCounter = inTimeoutLimit; mResetTimer = false; } if (mTimeoutCounter > 0) { mTimeoutCounter -= std::min(inFramesToProcess, mTimeoutCounter); ioSilence = false; } } else { // signal to reset the next time we receive silence mResetTimer = true; } } void Reset() { mResetTimer = true; } private: UInt32 mTimeoutCounter{ 0 }; bool mResetTimer{ false }; }; } // namespace ausdk #endif // AudioUnitSDK_AUSilentTimeout_h ================================================ FILE: include/AudioUnitSDK/AUThreadSafeList.h ================================================ /*! @file AudioUnitSDK/AUThreadSafeList.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUThreadSafeList_h #define AudioUnitSDK_AUThreadSafeList_h // module // clang-format off #include // must come first // clang-format on #include // std #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS /*! @class AUAtomicStack @brief Linked list LIFO or FIFO (popAllReversed) stack, elements are pushed and popped atomically. */ #ifdef __cpp_lib_concepts template #else template , bool> = true> #endif class AUAtomicStack { public: AUAtomicStack() = default; // Non-atomic routines, for use when initializing/deinitializing, operate NON-atomically void PushNonAtomic(T* item) noexcept { item->Next() = mHead; mHead = item; } T* PopNonAtomic() noexcept { T* result = mHead; if (result) mHead = result->Next(); return result; } // Atomic routines void PushAtomic(T* item) noexcept { T* head_{}; do { head_ = mHead; item->Next() = head_; } while (!CompareAndSwap(head_, item)); } // Pushes entire linked list headed by item void PushMultipleAtomic(T* item) noexcept { T *head_{}, *p = item, *tail{}; // Find the last one -- when done, it will be linked to head do { tail = p; p = p->Next(); } while (p); do { head_ = mHead; tail->Next() = head_; } while (!CompareAndSwap(head_, item)); } // This may only be used when only one thread may potentially pop from the stack. // if multiple threads may pop, this suffers from the ABA problem. T* PopAtomicSingleReader() noexcept { T* result{}; do { if ((result = mHead) == nullptr) break; } while (!CompareAndSwap(result, result->Next())); return result; } // This is inefficient for large linked lists. // prefer PopAll() to a series of calls to PopAtomic. // PushMultipleAtomic has to traverse the entire list. T* PopAtomic() noexcept { T* result = PopAll(); if (result) { T* next = result->Next(); if (next) // push all the remaining items back onto the stack PushMultipleAtomic(next); } return result; } T* PopAll() noexcept { T* result{}; do { if ((result = mHead) == nullptr) break; } while (!CompareAndSwap(result, nullptr)); return result; } T* PopAllReversed() noexcept { AUAtomicStack reversed; T* p = PopAll(); while (p != nullptr) { T* next = p->Next(); reversed.PushNonAtomic(p); p = next; } return reversed.mHead; } bool CompareAndSwap(T* oldvalue, T* newvalue) noexcept { return std::atomic_compare_exchange_strong_explicit( &mHead, &oldvalue, newvalue, std::memory_order_seq_cst, std::memory_order_relaxed); } [[nodiscard]] bool empty() const noexcept { return mHead == nullptr; } T* head() const noexcept { return mHead; } void SetHead(T* newHead) { this->mHead.store(newHead); } protected: std::atomic mHead{ nullptr }; static_assert(decltype(mHead)::is_always_lock_free); }; // ------------------------------------------------------------------------------------------------- /*! @class AUThreadSafeList @brief A thread-safe linked list. */ template class AUThreadSafeList { public: enum class EventType { Unknown, Add, Remove, Clear }; class Node { public: Node* mNext{ nullptr }; EventType mEventType{ EventType::Unknown }; T mObject{}; Node*& Next() { return mNext; } }; using NodeStack = AUAtomicStack; class iterator { public: iterator() {} iterator(Node* n) : mNode(n) {} bool operator==(const iterator& other) const { return this->mNode == other.mNode; } bool operator!=(const iterator& other) const { return this->mNode != other.mNode; } T& operator*() const { return mNode->mObject; } iterator& operator++() { mNode = mNode->Next(); return *this; } // preincrement iterator operator++(int) { iterator tmp = *this; mNode = mNode->next(); return tmp; } // postincrement using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = T; using reference = T&; using pointer = T*; private: Node* mNode{ nullptr }; }; AUThreadSafeList() = default; ~AUThreadSafeList() { FreeAll(mActiveList); FreeAll(mPendingList); FreeAll(mFreeList); } AUThreadSafeList(const AUThreadSafeList&) = delete; AUThreadSafeList(AUThreadSafeList&&) = delete; AUThreadSafeList& operator=(const AUThreadSafeList&) = delete; AUThreadSafeList& operator=(AUThreadSafeList&&) = delete; // These may be called on any thread void Add(const T& obj) { Node* node = AllocNode(); node->mEventType = EventType::Add; node->mObject = obj; mPendingList.PushAtomic(node); } // can be called on any thread void Remove(const T& obj) { Node* node = AllocNode(); node->mEventType = EventType::Remove; node->mObject = obj; mPendingList.PushAtomic(node); } void Clear() // can be called on any thread { Node* node = AllocNode(); node->mEventType = EventType::Clear; mPendingList.PushAtomic(node); } // These must be called from only one thread void Update() AUSDK_RTSAFE { NodeStack reversed; Node* event{}; bool workDone = false; // reverse the events so they are in order event = mPendingList.PopAll(); while (event != nullptr) { Node* next = event->mNext; reversed.PushNonAtomic(event); event = next; workDone = true; } if (workDone) { // now process them while ((event = reversed.PopNonAtomic()) != nullptr) { switch (event->mEventType) { case EventType::Add: { Node* endNode{}; bool needToInsert = true; for (Node* node = mActiveList.head(); node != nullptr; node = node->mNext) { if (node->mObject == event->mObject) { FreeNode(event); needToInsert = false; break; } endNode = node; } if (needToInsert) { // link the new event in at the end of the active list if (!endNode) { mActiveList.PushNonAtomic(event); } else { endNode->Next() = event; event->mNext = nullptr; } } } break; case EventType::Remove: { // find matching node in the active list, remove it Node* previousNode{}; for (Node* node = mActiveList.head(); node != nullptr; node = node->mNext) { if (node->mObject == event->mObject) { if (!previousNode) { mActiveList.SetHead(node->mNext); } else { previousNode->Next() = node->mNext; // remove from linked list } FreeNode(node); break; } previousNode = node; } // dispose the request node FreeNode(event); } break; case EventType::Clear: { Node* next{}; for (Node* node = mActiveList.head(); node != nullptr;) { next = node->mNext; FreeNode(node); node = next; } FreeNode(event); if (mActiveList.head()) { mActiveList.SetHead(nullptr); } } break; default: AUSDK_LogError_RT("Unknown AUThreadSafeList event type"); break; } } } } iterator begin() const noexcept { return iterator(mActiveList.head()); } iterator end() const noexcept { return iterator(nullptr); } private: Node* AllocNode() { Node* node = mFreeList.PopAtomic(); if (node == nullptr) node = new Node(); return node; } void FreeNode(Node* node) { mFreeList.PushAtomic(node); } static void FreeAll(AUAtomicStack& stack) { Node* node{}; while ((node = stack.PopNonAtomic()) != nullptr) { delete node; } } NodeStack mActiveList; // what's actually in the container - only accessed on one thread NodeStack mPendingList; // add or remove requests - threadsafe NodeStack mFreeList; // free nodes for reuse - threadsafe }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_AUThreadSafeList_h ================================================ FILE: include/AudioUnitSDK/AUUtility.h ================================================ /*! @file AudioUnitSDK/AUUtility.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_AUUtility_h #define AudioUnitSDK_AUUtility_h // clang-format off #include // must come first // clang-format on // OS #include #if AUSDK_HAVE_MACH_TIME #include #endif // std #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark Error-handling macros #ifndef AUSDK_LOG_OBJECT #define AUSDK_LOG_OBJECT OS_LOG_DEFAULT // NOLINT macro #endif // AUSDK_LOG_OBJECT #ifdef AUSDK_NO_LOGGING #define AUSDK_LogError(...) /* NOLINT macro */ #else #include #include #define AUSDK_LogError(...) /* NOLINT macro */ \ if (__builtin_available(macOS 10.11, *)) { \ os_log_error(AUSDK_LOG_OBJECT, __VA_ARGS__); \ } else { \ syslog(LOG_ERR, __VA_ARGS__); \ } #endif // logging from realtime thread #define AUSDK_LogError_RT(...) AUSDK_RT_UNSAFE(AUSDK_LogError(__VA_ARGS__)) /* NOLINT macro */ #define AUSDK_Catch(result) /* NOLINT(cppcoreguidelines-macro-usage) */ \ catch (const ausdk::AUException& exc) { (result) = exc.mError; } \ catch (const std::bad_alloc&) { (result) = kAudio_MemFullError; } \ catch (const OSStatus& catch_err) { (result) = catch_err; } \ catch (const std::system_error& exc) { (result) = exc.code().value(); } \ catch (...) { (result) = -1; } #define AUSDK_Require(expr, error) /* NOLINT(cppcoreguidelines-macro-usage) */ \ do { \ if (!(expr)) { \ return error; \ } \ } while (0) /* NOLINT */ // Check an expected; if it holds an error, return that error. #define AUSDK_RequireExpected(exp) /* NOLINT(cppcoreguidelines-macro-usage) */ \ AUSDK_Require(exp, exp.error()) // Evaluate an expression resulting in an Expected. If the Expected contains an error, return // the error. Otherwise, unwrap the Expected. #define AUSDK_UnwrapOrReturnError(_expression) \ *({ \ const auto e = (_expression); \ if (!e) [[unlikely]] \ return e.error(); \ e; \ }) // TODO: deprecate this macro because it swallows the error #define AUSDK_UnwrapOrReturnVoid(_expression) \ *({ \ const auto e = (_expression); \ if (!e) [[unlikely]] \ return; \ e; \ }) #define AUSDK_CheckReturnError(_expression) \ ({ \ const auto e = (_expression); \ if (!e) [[unlikely]] \ return e.error(); \ }) // Evaluate an expression resulting in an Expected. If the Expected contains an error, return // the error as an Unexpected. Otherwise, unwrap the Expected. #define AUSDK_UnwrapOrReturnUnexpected(_expression) \ *({ \ const auto e = (_expression); \ if (!e) [[unlikely]] \ return ausdk::Unexpected{ e.error() }; \ e; \ }) #define AUSDK_Require_noerr(_expression) /* NOLINT(cppcoreguidelines-macro-usage) */ \ do { \ const auto status_tmp_macro_detail_ = (_expression); \ if (status_tmp_macro_detail_ != noErr) [[unlikely]] { \ return status_tmp_macro_detail_; \ } \ } while (0) #define AUSDK_Assert(_expression) \ ({ \ const auto e = (_expression); \ if (!e) [[unlikely]] \ std::abort(); \ }) // clang-format off // The "loose" realtime-safety contract: exceptions are allowed. #ifndef AUSDK_LOOSE_RT_SAFETY #define AUSDK_LOOSE_RT_SAFETY 1 #endif // AUSDK adopts `noexcept` independently of `[[clang::nonblocking]]`; suppress diagnostics. #define AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \ _Pragma("clang diagnostic ignored \"-Wperf-constraint-implies-noexcept\"") #define AUSDK_END_NO_RT_NOEXCEPT_WARNINGS \ _Pragma("clang diagnostic pop") // ASAN inserts blocking function calls in otherwise nonblocking functions. #if defined(__has_feature) && __has_feature(address_sanitizer) #define AUSDK_BEGIN_NO_RT_WARNINGS \ AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS \ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") #else #define AUSDK_BEGIN_NO_RT_WARNINGS \ AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS #endif #define AUSDK_END_NO_RT_WARNINGS \ AUSDK_END_NO_RT_NOEXCEPT_WARNINGS /*! @macro AUSDK_RTSAFE_TYPE @brief A function type which is guaranteed / required to be realtime safe. */ #if defined(__has_attribute) && __has_attribute(nonblocking) # ifdef __cplusplus # define AUSDK_RTSAFE_TYPE [[clang::nonblocking]] # else # define AUSDK_RTSAFE_TYPE __attribute__((nonblocking)) # endif #else # define AUSDK_RTSAFE_TYPE #endif #ifndef AUSDK_RTSAFE_SECTION # define AUSDK_RTSAFE_SECTION #endif /*! @macro AUSDK_RTSAFE @brief Declares a function as `nonblocking`, with a section attribute. Example placement: `void func(int param) AUSDK_RTSAFE;` */ #define AUSDK_RTSAFE AUSDK_RTSAFE_TYPE AUSDK_RTSAFE_SECTION #if AUSDK_LOOSE_RT_SAFETY #define AUSDK_RTSAFE_LOOSE AUSDK_RTSAFE #else #define AUSDK_RTSAFE_LOOSE #endif /*! @macro AUSDK_RTSAFE_LAMBDA @brief Declares a lambda as `nonblocking`, with a section attribute. Example placement: `auto lambda = [captures](int param) AUSDK_RTSAFE_LAMBDA -> int { ... };` Note: It's weird and annoying that this has to be different from `AUSDK_RTSAFE`. */ #define AUSDK_RTSAFE_LAMBDA AUSDK_RTSAFE_SECTION AUSDK_RTSAFE_TYPE /*! @macro AUSDK_RT_UNSAFE_BEGIN @brief Begins a region of code as exempt from nonblocking checks. N.B. Every use of this is a maintenance and safety liability; use as a last resort. */ #define AUSDK_RT_UNSAFE_BEGIN(reason) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") /*! @macro AUSDK_RT_UNSAFE_END @brief Ends a region of code which is exempt from nonblocking checks. */ #define AUSDK_RT_UNSAFE_END \ _Pragma("clang diagnostic pop") /*! @macro AUSDK_RT_UNSAFE_END @brief Disables nonblocking checks during the evaluation of the expression. N.B. Every use of this is a maintenance and safety liability; use as a last resort. */ #define AUSDK_RT_UNSAFE(...) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") \ __VA_ARGS__ \ _Pragma("clang diagnostic pop") // clang-format on #pragma mark - // ------------------------------------------------------------------------------------------------- namespace ausdk { // ------------------------------------------------------------------------------------------------- /// A wrapper to preserve the nonblocking attribute on a function pointer. Copied from the /// nonblocking proposal. template class RTSafeFP; template class RTSafeFP { public: using impl_t = R (*)(Args...) AUSDK_RTSAFE_TYPE; private: impl_t mImpl; public: RTSafeFP(impl_t f) : mImpl{ f } {} R operator()(Args... args) const { return mImpl(std::forward(args)...); } }; // deduction guide (copied from std::function) template RTSafeFP(R (*)(ArgTypes...)) -> RTSafeFP; // ------------------------------------------------------------------------------------------------- /// A subclass of std::runtime_error that holds an OSStatus error. class AUException : public std::runtime_error { public: explicit AUException(OSStatus err) : std::runtime_error{ std::string("OSStatus ") + std::to_string(err) }, mError{ err } { } const OSStatus mError; }; AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS [[noreturn]] inline void Throw(OSStatus err) AUSDK_RTSAFE_LOOSE { AUSDK_RT_UNSAFE_BEGIN("Can only log and throw under loose contract") AUSDK_LogError("throwing %d", static_cast(err)); throw AUException{ err }; AUSDK_RT_UNSAFE_END } AUSDK_END_NO_RT_NOEXCEPT_WARNINGS inline void ThrowExceptionIf(bool condition, OSStatus err) { if (condition) [[unlikely]] { Throw(err); } } AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS [[noreturn]] inline void ThrowQuiet(OSStatus err) AUSDK_RTSAFE_LOOSE { AUSDK_RT_UNSAFE_BEGIN("Can only throw under loose realtime contract") throw AUException{ err }; AUSDK_RT_UNSAFE_END } AUSDK_END_NO_RT_NOEXCEPT_WARNINGS inline void ThrowQuietIf(bool condition, OSStatus err) { if (condition) [[unlikely]] { ThrowQuiet(err); } } // ------------------------------------------------------------------------------------------------- /// Wrap a std::recursive_mutex in a C++ Mutex (named requirement). Methods are virtual to support /// customization. class AUMutex { public: AUMutex() = default; virtual ~AUMutex() = default; AUMutex(const AUMutex&) = delete; AUMutex(AUMutex&&) = delete; AUMutex& operator=(const AUMutex&) = delete; AUMutex& operator=(AUMutex&&) = delete; virtual void lock() { mImpl.lock(); } virtual void unlock() noexcept { mImpl.unlock(); } virtual bool try_lock() noexcept { return mImpl.try_lock(); } private: std::recursive_mutex mImpl; }; // ------------------------------------------------------------------------------------------------- /// Implement optional locking at AudioUnit non-realtime entry points (required only for a small /// number of plug-ins which must synchronize against external entry points). class AUEntryGuard { public: explicit AUEntryGuard(AUMutex* maybeMutex) : mMutex{ maybeMutex } { if (mMutex != nullptr) { mMutex->lock(); } } ~AUEntryGuard() noexcept { if (mMutex != nullptr) { mMutex->unlock(); } } AUEntryGuard(const AUEntryGuard&) = delete; AUEntryGuard(AUEntryGuard&&) = delete; AUEntryGuard& operator=(const AUEntryGuard&) = delete; AUEntryGuard& operator=(AUEntryGuard&&) = delete; private: AUMutex* mMutex; }; // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark ASBD /// Utility functions relating to AudioStreamBasicDescription. namespace ASBD { constexpr bool IsInterleaved(const AudioStreamBasicDescription& format) noexcept { return (format.mFormatFlags & kLinearPCMFormatFlagIsNonInterleaved) == 0u; } constexpr UInt32 NumberInterleavedChannels(const AudioStreamBasicDescription& format) noexcept { return IsInterleaved(format) ? format.mChannelsPerFrame : 1; } constexpr UInt32 NumberChannelStreams(const AudioStreamBasicDescription& format) noexcept { return IsInterleaved(format) ? 1 : format.mChannelsPerFrame; } constexpr bool IsCommonFloat32(const AudioStreamBasicDescription& format) noexcept { return ( format.mFormatID == kAudioFormatLinearPCM && format.mFramesPerPacket == 1 && format.mBytesPerPacket == format.mBytesPerFrame // so far, it's a valid PCM format && (format.mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0 && (format.mChannelsPerFrame == 1 || (format.mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) && ((format.mFormatFlags & kAudioFormatFlagIsBigEndian) == kAudioFormatFlagsNativeEndian) && format.mBitsPerChannel == 32 // NOLINT && format.mBytesPerFrame == NumberInterleavedChannels(format) * sizeof(float)); } constexpr AudioStreamBasicDescription CreateCommonFloat32( Float64 sampleRate, UInt32 numChannels, bool interleaved = false) noexcept { constexpr auto sampleSize = sizeof(Float32); AudioStreamBasicDescription asbd{}; asbd.mFormatID = kAudioFormatLinearPCM; asbd.mFormatFlags = kAudioFormatFlagIsFloat | static_cast(kAudioFormatFlagsNativeEndian) | kAudioFormatFlagIsPacked; asbd.mBitsPerChannel = 8 * sampleSize; // NOLINT magic number asbd.mChannelsPerFrame = numChannels; asbd.mFramesPerPacket = 1; asbd.mSampleRate = sampleRate; if (interleaved) { asbd.mBytesPerPacket = asbd.mBytesPerFrame = numChannels * sampleSize; } else { asbd.mBytesPerPacket = asbd.mBytesPerFrame = sampleSize; asbd.mFormatFlags |= kAudioFormatFlagIsNonInterleaved; } return asbd; } constexpr bool MinimalSafetyCheck(const AudioStreamBasicDescription& x) noexcept { // This function returns false if there are sufficiently unreasonable values in any field. // It is very conservative so even some very unlikely values will pass. // This is just meant to catch the case where the data from a file is corrupted. return (x.mSampleRate >= 0.) && (x.mSampleRate < 3e6) // NOLINT SACD sample rate is 2.8224 MHz && (x.mBytesPerPacket < 1000000) // NOLINT && (x.mFramesPerPacket < 1000000) // NOLINT && (x.mBytesPerFrame < 1000000) // NOLINT && (x.mChannelsPerFrame > 0) && (x.mChannelsPerFrame <= 1024) // NOLINT && (x.mBitsPerChannel <= 1024) // NOLINT && (x.mFormatID != 0) && !(x.mFormatID == kAudioFormatLinearPCM && (x.mFramesPerPacket != 1 || x.mBytesPerPacket != x.mBytesPerFrame)); } inline bool IsEqual( const AudioStreamBasicDescription& lhs, const AudioStreamBasicDescription& rhs) noexcept { return memcmp(&lhs, &rhs, sizeof(AudioStreamBasicDescription)) == 0; } } // namespace ASBD // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark ACL /// Utility functions relating to AudioChannelLayout. namespace ACL { constexpr bool operator==(const AudioChannelLayout& lhs, const AudioChannelLayout& rhs) noexcept { if (lhs.mChannelLayoutTag != rhs.mChannelLayoutTag) { return false; } if (lhs.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { return lhs.mChannelBitmap == rhs.mChannelBitmap; } if (lhs.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { if (lhs.mNumberChannelDescriptions != rhs.mNumberChannelDescriptions) { return false; } for (UInt32 i = 0; i < lhs.mNumberChannelDescriptions; ++i) { const auto& lhdesc = lhs.mChannelDescriptions[i]; // NOLINT array subscript const auto& rhdesc = rhs.mChannelDescriptions[i]; // NOLINT array subscript if (lhdesc.mChannelLabel != rhdesc.mChannelLabel) { return false; } if (lhdesc.mChannelLabel == kAudioChannelLabel_UseCoordinates) { if (memcmp(&lhdesc, &rhdesc, sizeof(AudioChannelDescription)) != 0) { return false; } } } } return true; } } // namespace ACL // ------------------------------------------------------------------------------------------------- /// Utility wrapper for the variably-sized AudioChannelLayout struct. class AUChannelLayout { public: AUChannelLayout() : AUChannelLayout(0, kAudioChannelLayoutTag_UseChannelDescriptions, 0) {} /// Can construct from a layout tag. explicit AUChannelLayout(AudioChannelLayoutTag inTag) : AUChannelLayout(0, inTag, 0) {} AUChannelLayout(uint32_t inNumberChannelDescriptions, AudioChannelLayoutTag inChannelLayoutTag, AudioChannelBitmap inChannelBitMap) : mStorage(ObjectCountForByteSize(DataByteSize(inNumberChannelDescriptions))) { auto& acl = mStorage.front(); acl.mChannelLayoutTag = inChannelLayoutTag; acl.mChannelBitmap = inChannelBitMap; acl.mNumberChannelDescriptions = inNumberChannelDescriptions; } /// Implicit conversion from AudioChannelLayout& is allowed. AUChannelLayout(const AudioChannelLayout& acl) // NOLINT : mStorage(ObjectCountForByteSize(DataByteSize(acl.mNumberChannelDescriptions))) { std::memcpy(mStorage.data(), &acl, DataByteSize(acl.mNumberChannelDescriptions)); } ~AUChannelLayout() noexcept = default; AUChannelLayout(AUChannelLayout&&) noexcept = default; AUChannelLayout& operator=(AUChannelLayout&&) noexcept = default; // copy overloads required because AudioChannelLayout deletes them AUChannelLayout(const AUChannelLayout& other) { CopyStorage(other); } AUChannelLayout& operator=(const AUChannelLayout& other) { CopyStorage(other); return *this; } bool operator==(const AUChannelLayout& other) const noexcept { return ACL::operator==(Layout(), other.Layout()); } bool operator!=(const AUChannelLayout& other) const noexcept { return !(*this == other); } [[nodiscard]] bool IsValid() const noexcept { return NumberChannels() > 0; } [[nodiscard]] const AudioChannelLayout& Layout() const noexcept { return mStorage.front(); } [[nodiscard]] const AudioChannelLayout* LayoutPtr() const noexcept { return mStorage.data(); } /// After default construction, this method will return /// kAudioChannelLayoutTag_UseChannelDescriptions with 0 channel descriptions. [[nodiscard]] AudioChannelLayoutTag Tag() const noexcept { return Layout().mChannelLayoutTag; } [[nodiscard]] uint32_t NumberChannels() const noexcept { return NumberChannels(Layout()); } [[nodiscard]] uint32_t Size() const noexcept { return static_cast(DataByteSize(Layout().mNumberChannelDescriptions)); } static uint32_t NumberChannels(const AudioChannelLayout& inLayout) noexcept { if (inLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { return inLayout.mNumberChannelDescriptions; } if (inLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { constexpr size_t bitWidth = sizeof(inLayout.mChannelBitmap) * CHAR_BIT; return static_cast(std::bitset(inLayout.mChannelBitmap).count()); } return AudioChannelLayoutTag_GetNumberOfChannels(inLayout.mChannelLayoutTag); } static constexpr size_t DataByteSize(uint32_t inNumberChannelDescriptions) noexcept { constexpr size_t headerSize = offsetof(AudioChannelLayout, mChannelDescriptions); return headerSize + (inNumberChannelDescriptions * sizeof(AudioChannelDescription)); } private: // number of contiguous ACL objects required to accommodate a specific storage size static constexpr size_t ObjectCountForByteSize(size_t inDataByteSize) noexcept { const size_t padding = ((inDataByteSize % sizeof(AudioChannelLayout)) > 0) ? 1 : 0; return (inDataByteSize / sizeof(AudioChannelLayout)) + padding; } void CopyStorage(const AUChannelLayout& inSource) { mStorage = decltype(mStorage)(inSource.mStorage.size()); std::memcpy( mStorage.data(), inSource.mStorage.data(), std::span(inSource.mStorage).size_bytes()); } // the data representation is not literally an array of these entire objects, however using it // as the underlying storage data type ensures object lifetime and alignment of the data pointer std::vector mStorage; }; // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark AudioBufferList /// Utility functions relating to AudioBufferList. namespace ABL { // if the return result is odd, there was a null buffer. constexpr uint32_t IsBogusAudioBufferList(const AudioBufferList& abl) { const AudioBuffer *buf = abl.mBuffers, *const bufEnd = buf + abl.mNumberBuffers; uint32_t sum = 0; // defeat attempts by the compiler to optimize away the code that touches the buffers uint32_t anyNull = 0; for (; buf < bufEnd; ++buf) { const uint32_t* const p = static_cast(buf->mData); if (p == nullptr) { anyNull = 1; continue; } const auto dataSize = buf->mDataByteSize; if (dataSize >= sizeof(*p)) { const size_t frameCount = dataSize / sizeof(*p); sum += p[0]; sum += p[frameCount - 1]; } } return anyNull | (sum & ~1u); } } // namespace ABL // ------------------------------------------------------------------------------------------------- #pragma mark - #pragma mark HostTime #if AUSDK_HAVE_MACH_TIME AUSDK_BEGIN_NO_RT_WARNINGS /// Utility functions relating to Mach absolute time. namespace HostTime { /// Returns the current host time inline uint64_t Current() AUSDK_RTSAFE { return AUSDK_RT_UNSAFE(mach_absolute_time()); } /// Returns the frequency of the host timebase, in ticks per second. inline double Frequency() { struct mach_timebase_info timeBaseInfo {}; // NOLINT mach_timebase_info(&timeBaseInfo); // the frequency of that clock is: (sToNanosDenominator / sToNanosNumerator) * 10^9 return static_cast(timeBaseInfo.denom) / static_cast(timeBaseInfo.numer) * 1.0e9; // NOLINT } } // namespace HostTime AUSDK_END_NO_RT_WARNINGS #endif // AUSDK_HAVE_MACH_TIME // ------------------------------------------------------------------------------------------------- #pragma mark - /// Basic RAII wrapper for CoreFoundation types template requires std::is_pointer_v class Owned { explicit Owned(T obj, bool fromget) noexcept : mImpl{ obj } { if (fromget) { retainRef(); } } public: static Owned from_get(T obj) noexcept { return Owned{ obj, true }; } static Owned from_create(T obj) noexcept { return Owned{ obj, false }; } static Owned from_copy(T obj) noexcept { return Owned{ obj, false }; } Owned() noexcept = default; ~Owned() noexcept { releaseRef(); } Owned(const Owned& other) noexcept : mImpl{ other.mImpl } { retainRef(); } Owned(Owned&& other) noexcept : mImpl{ std::exchange(other.mImpl, nullptr) } {} Owned& operator=(const Owned& other) noexcept { if (this != &other) { releaseRef(); mImpl = other.mImpl; retainRef(); } return *this; } Owned& operator=(Owned&& other) noexcept { std::swap(mImpl, other.mImpl); return *this; } T operator*() const noexcept { return get(); } T get() const noexcept { return mImpl; } /// As with `unique_ptr::release()`, releases ownership of the reference to the caller (not /// to be confused with decrementing the reference count as with `CFRelease()`). T release() noexcept { return std::exchange(mImpl, nullptr); } /// This is a from_get operation. Owned& operator=(T cfobj) noexcept { if (mImpl != cfobj) { releaseRef(); mImpl = cfobj; retainRef(); } return *this; } private: void retainRef() noexcept { if (mImpl != nullptr) { CFRetain(mImpl); } } void releaseRef() noexcept { if (mImpl != nullptr) { CFRelease(mImpl); } } T mImpl{ nullptr }; }; // ------------------------------------------------------------------------------------------------- #pragma mark - template concept TriviallyCopySerializable = std::is_trivially_copyable_v && std::is_standard_layout_v; // copy object data to arbitrary memory address that may not share object alignment void Serialize(const TriviallyCopySerializable auto& inValue, void* outData) noexcept { std::memcpy(outData, std::addressof(inValue), sizeof(inValue)); } template void Serialize(std::span inValues, void* outData) noexcept { std::memcpy(outData, inValues.data(), inValues.size_bytes()); } // ensure object lifetime and alignment of object data from opaque pointer template requires std::is_nothrow_default_constructible_v && (!std::is_const_v) T Deserialize(const void* inData) noexcept { T result{}; std::memcpy(std::addressof(result), inData, sizeof(result)); return result; } template requires std::is_default_constructible_v && (!std::is_const_v) std::vector DeserializeArray(const void* inData, size_t inSizeBytes) { std::vector result(inSizeBytes / sizeof(T)); std::memcpy(result.data(), inData, std::span(result).size_bytes()); return result; } inline UInt32 DeserializeBigUInt32AndAdvance(const UInt8*& ioData) noexcept { const auto value = Deserialize(ioData); ioData += sizeof(value); // NOLINT return CFSwapInt32BigToHost(value); } inline std::string MakeStringFrom4CC(uint32_t in4CC) { in4CC = CFSwapInt32HostToBig(in4CC); constexpr auto safeIsPrint = [](char character) { return (character >= ' ') && (character <= '~'); }; char* const string = reinterpret_cast(&in4CC); // NOLINT for (size_t i = 0; i < sizeof(in4CC); ++i) { if (!safeIsPrint(string[i])) { // NOLINT string[i] = '.'; // NOLINT } } return std::string{ string, sizeof(in4CC) }; } using Unexpected = std::unexpected; template using Expected = std::expected; // expected> is unweildy. // Instead, extend expected (with a guarantee that the pointer is non-null). template class [[nodiscard]] ExpectedPtr : public std::expected { public: using Base = std::expected; // Caution: default-constructed to null ptr. ExpectedPtr() = default; // Construct with a reference, to enforce non-null guarantee (unless default-constructed). ExpectedPtr(T& ref) : Base{ &ref } {} // NOLINT implicit OK ExpectedPtr(Unexpected&& u) : Base{ u } {} // NOLINT implicit OK // unchecked (parallel to expected::operator*); use operator bool to verify T* operator->() const { return Base::operator*(); } T* get() const { return Base::operator*(); } T& operator*() const { return *get(); } }; template inline void ThrowExceptionIfUnexpected(const Expected& e) { if (!e) [[unlikely]] { Throw(e.error()); } } } // namespace ausdk #endif // AudioUnitSDK_AUUtility_h ================================================ FILE: include/AudioUnitSDK/AudioUnitSDK.h ================================================ /*! @file AudioUnitSDK/AudioUnitSDK.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_h #define AudioUnitSDK_h // clang-format off #include // must come first // clang-format on #include #include #include #include #if AUSDK_HAVE_MIDI #include #include #endif // AUSDK_HAVE_MIDI #include #include #include #include #include #include #if AUSDK_HAVE_MUSIC_DEVICE #include #endif // AUSDK_HAVE_MUSIC_DEVICE #endif /* AudioUnitSDK_h */ ================================================ FILE: include/AudioUnitSDK/ComponentBase.h ================================================ /*! @file AudioUnitSDK/ComponentBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_ComponentBase_h #define AudioUnitSDK_ComponentBase_h // module // clang-format off #include // must come first // clang-format on #include // OS #include // std #include #include #include namespace ausdk { /*! @class ComponentBase @brief Base class for implementing an `AudioComponentInstance`. */ class ComponentBase { public: /// Construct given an AudioComponentInstance, typically from APFactory::Constuct. explicit ComponentBase(AudioComponentInstance inInstance); virtual ~ComponentBase() = default; ComponentBase(const ComponentBase&) = delete; ComponentBase(ComponentBase&&) = delete; ComponentBase& operator=(const ComponentBase&) = delete; ComponentBase& operator=(ComponentBase&&) = delete; /// Called from dispatchers after constructing an instance. void DoPostConstructor(); /// Called from dispatchers before destroying an instance. void DoPreDestructor(); /// Obtain the wrapped `AudioComponentInstance` (underlying type of `AudioUnit`, `AudioCodec`, /// and others). [[nodiscard]] AudioComponentInstance GetComponentInstance() const noexcept { return mComponentInstance; } /// Return the instance's `AudioComponentDescription`. [[nodiscard]] AudioComponentDescription GetComponentDescription() const; /// Component dispatch method. static OSStatus AP_Open(void* self, AudioComponentInstance compInstance); /// Component dispatch method. static OSStatus AP_Close(void* self); /// A mutex which is held during `Open`, since some AU's and the Component Manager itself /// are not thread-safe against globals. static std::recursive_mutex& InitializationMutex(); protected: // subclasses are free to to override these methods to add functionality virtual void PostConstructor() {} virtual void PreDestructor() {} // these methods, however, are reserved for override only within this SDK virtual void PostConstructorInternal() {} virtual void PreDestructorInternal() {} private: AudioComponentInstance mComponentInstance; }; /*! @class AudioComponentPlugInInstance @brief Object which implements an AudioComponentPlugInInterface for the framework, and which holds the C++ implementation object. */ struct AudioComponentPlugInInstance { // The AudioComponentPlugInInterface must remain first AudioComponentPlugInInterface mPlugInInterface{}; void* (*mConstruct)(void* memory, AudioComponentInstance ci){ nullptr }; void (*mDestruct)(void* memory){ nullptr }; std::array mPad{}; // pad to a 16-byte boundary (in either 32 or 64 bit mode) // the ACI implementation object is constructed into this memory // (this member is just a placeholder. it is aligned to a 16-byte boundary) UInt32 mInstanceStorage{ 0 }; }; /*! @class APFactory @tparam APMethodLookup A class (e.g. AUBaseLookup) which provides a method selector lookup function. @tparam Implementor The class which implements the full plug-in (AudioUnit) interface. @brief Provides an AudioComponentFactoryFunction and a convenience wrapper for AudioComponentRegister. */ template class APFactory { public: static void* Construct(void* memory, AudioComponentInstance compInstance) { return new (memory) Implementor(compInstance); // NOLINT manual memory management } static void Destruct(void* memory) { static_cast(memory)->~Implementor(); } // This is the AudioComponentFactoryFunction. It returns an AudioComponentPlugInInstance. // The actual implementation object is not created until Open(). static AudioComponentPlugInInterface* Factory(const AudioComponentDescription* /* inDesc */) { auto* const acpi = // NOLINT owning memory static_cast(malloc( // NOLINT manual memory management offsetof(AudioComponentPlugInInstance, mInstanceStorage) + sizeof(Implementor))); acpi->mPlugInInterface.Open = ComponentBase::AP_Open; acpi->mPlugInInterface.Close = ComponentBase::AP_Close; acpi->mPlugInInterface.Lookup = APMethodLookup::Lookup; acpi->mPlugInInterface.reserved = nullptr; acpi->mConstruct = Construct; acpi->mDestruct = Destruct; acpi->mPad.fill(nullptr); return &acpi->mPlugInInterface; } // This is for runtime registration (not for plug-ins loaded from bundles). static AudioComponent Register( UInt32 type, UInt32 subtype, UInt32 manuf, CFStringRef name, UInt32 vers, UInt32 flags = 0) { const AudioComponentDescription desc = { type, subtype, manuf, flags, 0 }; return AudioComponentRegister(&desc, name, vers, Factory); } }; #ifndef AUSDK_EXPORT #if __GNUC__ #define AUSDK_EXPORT __attribute__((visibility("default"))) // NOLINT #else #warning export? #endif #endif /// Macro to generate the factory function for the specified Audio Component. Factory is an /// APFactory such as AUBaseFactory. Class is the name of the final ComponentBase class which /// implements instances of the class. #define AUSDK_COMPONENT_ENTRY(FactoryType, Class) /* NOLINT macro */ \ AUSDK_EXPORT \ extern "C" void* Class##Factory(const AudioComponentDescription* inDesc); \ extern "C" void* Class##Factory(const AudioComponentDescription* inDesc) \ { \ return FactoryType::Factory(inDesc); /* NOLINT parens */ \ } } // namespace ausdk #endif // AudioUnitSDK_ComponentBase_h ================================================ FILE: include/AudioUnitSDK/MusicDeviceBase.h ================================================ /*! @file AudioUnitSDK/MusicDeviceBase.h @copyright © 2000-2025 Apple Inc. All rights reserved. */ #ifndef AudioUnitSDK_MusicDeviceBase_h #define AudioUnitSDK_MusicDeviceBase_h // clang-format off #include // must come first // clang-format on #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS // ________________________________________________________________________ // MusicDeviceBase // /*! @class MusicDeviceBase @brief Deriving from AUBase and AUMIDIBase, an abstract base class for Music Device subclasses. */ class MusicDeviceBase : public AUBase, public AUMIDIBase { public: MusicDeviceBase(AudioComponentInstance inInstance, UInt32 numInputs, UInt32 numOutputs, UInt32 numGroups = 0); OSStatus MIDIEvent( UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) override { return AUMIDIBase::MIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); } OSStatus SysEx(const UInt8* inData, UInt32 inLength) override { return AUMIDIBase::SysEx(inData, inLength); } #if AUSDK_HAVE_MIDI2 OSStatus MIDIEventList( UInt32 inOffsetSampleFrame, const struct MIDIEventList* eventList) override { return AUMIDIBase::MIDIEventList(inOffsetSampleFrame, eventList); } #endif OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) override; OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) override; OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) override; OSStatus HandleNoteOn( UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity, UInt32 inStartFrame) override; OSStatus HandleNoteOff( UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity, UInt32 inStartFrame) override; virtual OSStatus GetInstrumentCount(UInt32& outInstCount) const; }; AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AudioUnitSDK_MusicDeviceBase_h ================================================ FILE: readme.md ================================================ # AudioUnitSDK ## Overview The AudioUnitSDK contains a set of base classes as well as utility sources required for Audio Unit development. These utility classes extend or wrap Core Audio API’s providing developers with the essential scaffolding to create audio effects, instruments, and generators on Apple platforms. They provide an easier to implement C++ class model wrapping the C framework APIs. ## Installing dependencies 1. Install [Xcode][Xcode] [Xcode]: https://developer.apple.com/xcode/resources/ ## Building the project 1. Open AudioUnitSDK.xcodeproj 2. Build the AudioUnitSDK target 3. Add the `include` folder to your projects Header Search Paths 4. Link libAudioUnitSDK.a to your project Alternatively, you can add the AudioUnitSDK source directly to your project and build as part of your target. ## Supported Deployment Targets macOS 11.0 / iOS 14.0 or later. ## Changelog ### Version 1.4.0 ##### Additions - Adopted `[[clang::nonblocking]]` attribute throughout the SDK using `AUSDK_RTSAFE_TYPE` and `AUSDK_RTSAFE_SECTION` macros with support for "loose" real-time safety contract. ##### Changes - C++ language version to `C++23` for `std::expected`. - Improved exception safety throughout render paths and buffer allocations. - Minor bug fixes. ##### Security - Fixed out-of-bounds read in AUMIDIBase deserialization. ### Version 1.3.0 - Minor updates and bug fixes. ### Version 1.2.0 ##### Additions - New header `AUConfig.h` for improved project organization. ##### Changes - C++ language version to `C++20` for modern language features. ### Version 1.1.0 ##### Changes - The `Source` folder was split in two folders: `include` for public headers, and `src` for private source files. Users building the AudioUnitSDK sources from within their Xcode project should update the source file locations and change the include path to `path/to/AudioUnitSDK/include`. Include directives should be prefixed with AudioUnitSDK (i.e. `#include "AudioUnitSDK/AUBase.h"` instead of `#include "AUBase.h"`). ### Version 1.0.0 - Initial upload. Copyright (C) 2023 Apple Inc. All rights reserved. ================================================ FILE: src/AudioUnitSDK/AUBase.cpp ================================================ /*! @file AudioUnitSDK/AUBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ // clang-format off #include // must come first // clang-format on #include #include #include #include #include #include #include #include #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS #if TARGET_OS_MAC && (TARGET_CPU_X86 || TARGET_CPU_X86_64) class DenormalDisabler { public: DenormalDisabler() noexcept : mSavedMXCSR(GetCSR()) { SetCSR(mSavedMXCSR | 0x8040); } DenormalDisabler(const DenormalDisabler&) = delete; DenormalDisabler(DenormalDisabler&&) = delete; DenormalDisabler& operator=(const DenormalDisabler&) = delete; DenormalDisabler& operator=(DenormalDisabler&&) = delete; ~DenormalDisabler() noexcept { SetCSR(mSavedMXCSR); } private: #if 0 // not sure if this is right: // #if __has_include() static unsigned GetCSR() noexcept { return _mm_getcsr(); } static void SetCSR(unsigned x) noexcept { _mm_setcsr(x); } #else // our compiler does ALL floating point with SSE static unsigned GetCSR() noexcept { unsigned result{}; asm volatile("stmxcsr %0" : "=m"(*&result)); // NOLINT asm return result; } static void SetCSR(unsigned a) noexcept { unsigned temp = a; asm volatile("ldmxcsr %0" : : "m"(*&temp)); // NOLINT asm } #endif const unsigned mSavedMXCSR; }; #else // while denormals can be flushed to zero on ARM processors, there is no performance benefit class DenormalDisabler { public: DenormalDisabler() = default; }; #endif static CFStringRef GetPresetDefaultName() noexcept { static const auto name = CFSTR("Untitled"); return name; } static constexpr auto kNoLastRenderedSampleTime = std::numeric_limits::lowest(); //_____________________________________________________________________________ // AUBase::AUBase(AudioComponentInstance inInstance, UInt32 numInputElements, UInt32 numOutputElements, UInt32 numGroupElements) : ComponentBase(inInstance), mInitNumInputEls(numInputElements), mInitNumOutputEls(numOutputElements), mInitNumGroupEls(numGroupElements), mLogString(CreateLoggingString()) { ResetRenderTime(); GlobalScope().Initialize(this, kAudioUnitScope_Global, 1); mCurrentPreset.presetNumber = -1; CFRetain(mCurrentPreset.presetName = GetPresetDefaultName()); } //_____________________________________________________________________________ // AUBase::~AUBase() { if (mCurrentPreset.presetName != nullptr) { CFRelease(mCurrentPreset.presetName); } } //_____________________________________________________________________________ // void AUBase::PostConstructorInternal() { // invoked after construction because they are virtual methods and/or call virtual methods if (mMaxFramesPerSlice == 0) { SetMaxFramesPerSlice(kAUDefaultMaxFramesPerSlice); } CreateElements(); } //_____________________________________________________________________________ // void AUBase::PreDestructorInternal() { // this is called from the ComponentBase dispatcher, which doesn't know anything about our // (optional) lock const AUEntryGuard guard(mAUMutex); DoCleanup(); } //_____________________________________________________________________________ // void AUBase::CreateElements() { if (!mElementsCreated) { Inputs().Initialize(this, kAudioUnitScope_Input, mInitNumInputEls); Outputs().Initialize(this, kAudioUnitScope_Output, mInitNumOutputEls); Groups().Initialize(this, kAudioUnitScope_Group, mInitNumGroupEls); CreateExtendedElements(); mElementsCreated = true; } } //_____________________________________________________________________________ // void AUBase::SetMaxFramesPerSlice(UInt32 nFrames) { if (nFrames == mMaxFramesPerSlice) { return; } mMaxFramesPerSlice = nFrames; if (mBuffersAllocated) { ReallocateBuffers(); } PropertyChanged(kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0); } //_____________________________________________________________________________ // OSStatus AUBase::CanSetMaxFrames() const { return IsInitialized() ? kAudioUnitErr_Initialized : OSStatus(noErr); } //_____________________________________________________________________________ // void AUBase::ReallocateBuffers() { CreateElements(); const UInt32 nOutputs = Outputs().GetNumberOfElements(); for (UInt32 i = 0; i < nOutputs; ++i) { Output(i).AllocateBuffer(); // does no work if already allocated } const UInt32 nInputs = Inputs().GetNumberOfElements(); for (UInt32 i = 0; i < nInputs; ++i) { Input(i).AllocateBuffer(); // does no work if already allocated } mBuffersAllocated = true; } //_____________________________________________________________________________ // void AUBase::DeallocateIOBuffers() { if (!mBuffersAllocated) { return; } const UInt32 nOutputs = Outputs().GetNumberOfElements(); for (UInt32 i = 0; i < nOutputs; ++i) { Output(i).DeallocateBuffer(); } const UInt32 nInputs = Inputs().GetNumberOfElements(); for (UInt32 i = 0; i < nInputs; ++i) { Input(i).DeallocateBuffer(); } mBuffersAllocated = false; } //_____________________________________________________________________________ // OSStatus AUBase::DoInitialize() { if (!mInitialized) { AUSDK_Require_noerr(Initialize()); if (CanScheduleParameters()) { mParamEventList.reserve(24); // NOLINT magic # } mHasBegunInitializing = true; ReallocateBuffers(); // calls CreateElements() mInitialized = true; // signal that it's okay to render std::atomic_thread_fence(std::memory_order_seq_cst); } return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::Initialize() { return noErr; } //_____________________________________________________________________________ // void AUBase::DoCleanup() { if (mInitialized) { Cleanup(); } DeallocateIOBuffers(); ResetRenderTime(); mInitialized = false; mHasBegunInitializing = false; } //_____________________________________________________________________________ // void AUBase::Cleanup() {} //_____________________________________________________________________________ // OSStatus AUBase::DoReset(AudioUnitScope inScope, AudioUnitElement inElement) { ResetRenderTime(); return Reset(inScope, inElement); } //_____________________________________________________________________________ // OSStatus AUBase::Reset(AudioUnitScope inScope, AudioUnitElement inElement) { return BaseReset(inScope, inElement); } //_____________________________________________________________________________ // OSStatus AUBase::DispatchGetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) { OSStatus result = noErr; bool validateElement = true; switch (inID) { case kAudioUnitProperty_MakeConnection: AUSDK_Require(inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(AudioUnitConnection); outWritable = true; break; case kAudioUnitProperty_SetRenderCallback: AUSDK_Require(inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(AURenderCallbackStruct); outWritable = true; break; case kAudioUnitProperty_StreamFormat: outDataSize = sizeof(AudioStreamBasicDescription); outWritable = IsStreamFormatWritable(inScope, inElement); break; case kAudioUnitProperty_SampleRate: outDataSize = sizeof(Float64); outWritable = IsStreamFormatWritable(inScope, inElement); break; case kAudioUnitProperty_ClassInfo: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(CFPropertyListRef); outWritable = true; break; case kAudioUnitProperty_FactoryPresets: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require_noerr(GetPresets(nullptr)); outDataSize = sizeof(CFArrayRef); outWritable = false; break; case kAudioUnitProperty_PresentPreset: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(AUPreset); outWritable = true; break; case kAudioUnitProperty_ElementName: outDataSize = sizeof(CFStringRef); outWritable = true; break; case kAudioUnitProperty_ParameterList: { UInt32 nParams = 0; AUSDK_Require_noerr(GetParameterList(inScope, nullptr, nParams)); outDataSize = sizeof(AudioUnitParameterID) * nParams; outWritable = false; validateElement = false; break; } case kAudioUnitProperty_ParameterInfo: outDataSize = sizeof(AudioUnitParameterInfo); outWritable = false; validateElement = false; break; case kAudioUnitProperty_ParameterHistoryInfo: outDataSize = sizeof(AudioUnitParameterHistoryInfo); outWritable = false; validateElement = false; break; case kAudioUnitProperty_ElementCount: outDataSize = sizeof(UInt32); outWritable = BusCountWritable(inScope); validateElement = false; break; case kAudioUnitProperty_Latency: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(Float64); outWritable = false; break; case kAudioUnitProperty_TailTime: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(SupportsTail(), kAudioUnitErr_InvalidProperty); outDataSize = sizeof(Float64); outWritable = false; break; case kAudioUnitProperty_MaximumFramesPerSlice: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(UInt32); outWritable = true; break; case kAudioUnitProperty_LastRenderError: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(OSStatus); outWritable = false; break; case kAudioUnitProperty_SupportedNumChannels: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); const UInt32 num = SupportedNumChannels(nullptr); AUSDK_Require(num != 0u, kAudioUnitErr_InvalidProperty); outDataSize = sizeof(AUChannelInfo) * num; outWritable = false; break; } case kAudioUnitProperty_SupportedChannelLayoutTags: { const auto tags = GetChannelLayoutTags(inScope, inElement); AUSDK_Require(!tags.empty(), kAudioUnitErr_InvalidProperty); outDataSize = static_cast(std::span(tags).size_bytes()); outWritable = false; validateElement = false; // already done it break; } case kAudioUnitProperty_AudioChannelLayout: { outWritable = false; outDataSize = GetAudioChannelLayout(inScope, inElement, nullptr, outWritable); if (outDataSize != 0u) { result = noErr; } else { const auto tags = GetChannelLayoutTags(inScope, inElement); return tags.empty() ? kAudioUnitErr_InvalidProperty : kAudioUnitErr_InvalidPropertyValue; } validateElement = false; // already done it break; } case kAudioUnitProperty_ShouldAllocateBuffer: AUSDK_Require((inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output), kAudioUnitErr_InvalidScope); outWritable = true; outDataSize = sizeof(UInt32); break; case kAudioUnitProperty_ParameterValueStrings: AUSDK_Require_noerr(GetParameterValueStrings(inScope, inElement, nullptr)); outDataSize = sizeof(CFArrayRef); outWritable = false; validateElement = false; break; case kAudioUnitProperty_HostCallbacks: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(mHostCallbackInfo); outWritable = true; break; case kAudioUnitProperty_ContextName: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(CFStringRef); outWritable = true; break; #if AUSDK_HAVE_UI && !TARGET_OS_IPHONE case kAudioUnitProperty_IconLocation: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(HasIcon(), kAudioUnitErr_InvalidProperty); outWritable = false; outDataSize = sizeof(CFURLRef); break; #endif case kAudioUnitProperty_ParameterClumpName: outDataSize = sizeof(AudioUnitParameterNameInfo); outWritable = false; break; case kAudioUnitProperty_LastRenderSampleTime: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(Float64); outWritable = false; break; case kAudioUnitProperty_NickName: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(CFStringRef); outWritable = true; break; default: result = GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); validateElement = false; break; } if ((result == noErr) && validateElement) { AUSDK_Require(GetElement(inScope, inElement) != nullptr, kAudioUnitErr_InvalidElement); } return result; } //_____________________________________________________________________________ // // NOLINTNEXTLINE(misc-no-recursion) with SaveState OSStatus AUBase::DispatchGetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { // NOTE: We're currently only called from AUBase::ComponentEntryDispatch, which // calls DispatchGetPropertyInfo first, which performs validation of the scope/element, // and ensures that the outData buffer is non-null and large enough. OSStatus result = noErr; switch (inID) { case kAudioUnitProperty_StreamFormat: Serialize(GetStreamFormat(inScope, inElement), outData); break; case kAudioUnitProperty_SampleRate: Serialize(GetStreamFormat(inScope, inElement).mSampleRate, outData); break; case kAudioUnitProperty_ParameterList: { UInt32 parameterCount = 0; result = GetParameterList(inScope, nullptr, parameterCount); if (result == noErr) { std::vector parameterIDs(parameterCount); result = GetParameterList(inScope, parameterIDs.data(), parameterCount); if (result == noErr) { Serialize(std::span(parameterIDs), outData); } } break; } case kAudioUnitProperty_ParameterInfo: { AudioUnitParameterInfo parameterInfo{}; result = GetParameterInfo(inScope, inElement, parameterInfo); Serialize(parameterInfo, outData); break; } case kAudioUnitProperty_ParameterHistoryInfo: { AudioUnitParameterHistoryInfo info{}; result = GetParameterHistoryInfo( inScope, inElement, info.updatesPerSecond, info.historyDurationInSeconds); Serialize(info, outData); break; } case kAudioUnitProperty_ClassInfo: { CFPropertyListRef plist = nullptr; result = SaveState(&plist); Serialize(plist, outData); break; } case kAudioUnitProperty_FactoryPresets: { CFArrayRef array = nullptr; result = GetPresets(&array); Serialize(array, outData); break; } case kAudioUnitProperty_PresentPreset: { Serialize(mCurrentPreset, outData); // retain current string (as client owns a reference to it and will release it) if ((inID == kAudioUnitProperty_PresentPreset) && (mCurrentPreset.presetName != nullptr)) { CFRetain(mCurrentPreset.presetName); } result = noErr; break; } case kAudioUnitProperty_ElementName: { const AUElement* const element = GetElement(inScope, inElement); const CFStringRef name = *element->GetName(); AUSDK_Require(name != nullptr, kAudioUnitErr_PropertyNotInUse); CFRetain(name); // must return a +1 reference Serialize(name, outData); break; } case kAudioUnitProperty_ElementCount: Serialize(GetScope(inScope).GetNumberOfElements(), outData); break; case kAudioUnitProperty_Latency: Serialize(GetLatency(), outData); break; case kAudioUnitProperty_TailTime: AUSDK_Require(SupportsTail(), kAudioUnitErr_InvalidProperty); Serialize(GetTailTime(), outData); break; case kAudioUnitProperty_MaximumFramesPerSlice: Serialize(mMaxFramesPerSlice, outData); break; case kAudioUnitProperty_LastRenderError: Serialize(mLastRenderError, outData); mLastRenderError = 0; break; case kAudioUnitProperty_SupportedNumChannels: { const AUChannelInfo* infos = nullptr; const auto count = SupportedNumChannels(&infos); if ((count > 0) && (infos != nullptr)) { Serialize(std::span(infos, count), outData); } break; } case kAudioUnitProperty_SupportedChannelLayoutTags: { const auto tags = GetChannelLayoutTags(inScope, inElement); AUSDK_Require(!tags.empty(), kAudioUnitErr_InvalidProperty); Serialize(std::span(tags), outData); break; } case kAudioUnitProperty_AudioChannelLayout: { auto* const acl = static_cast(outData); bool writable = false; const auto dataSize = GetAudioChannelLayout(inScope, inElement, acl, writable); AUSDK_Require(dataSize != 0, kAudioUnitErr_InvalidProperty); break; } case kAudioUnitProperty_ShouldAllocateBuffer: { const auto& element = IOElement(inScope, inElement); Serialize(static_cast(element.WillAllocateBuffer()), outData); break; } case kAudioUnitProperty_ParameterValueStrings: { CFArrayRef array = nullptr; result = GetParameterValueStrings(inScope, inElement, &array); Serialize(array, outData); break; } case kAudioUnitProperty_HostCallbacks: Serialize(mHostCallbackInfo, outData); break; case kAudioUnitProperty_ContextName: { const auto* const name = *mContextName; Serialize(name, outData); if (name) { CFRetain(name); // must return a +1 reference result = noErr; } else { result = kAudioUnitErr_PropertyNotInUse; } break; } #if AUSDK_HAVE_UI && !TARGET_OS_IPHONE case kAudioUnitProperty_IconLocation: { const auto iconLocation = CopyIconLocation(); AUSDK_Require(iconLocation != nullptr, kAudioUnitErr_InvalidProperty); Serialize(iconLocation, outData); break; } #endif case kAudioUnitProperty_ParameterClumpName: { auto clumpInfo = Deserialize(outData); AUSDK_Require(clumpInfo.inID != kAudioUnitClumpID_System, kAudioUnitErr_InvalidPropertyValue); // this ID value is reserved result = CopyClumpName(inScope, clumpInfo.inID, static_cast(std::max(clumpInfo.inDesiredLength, SInt32(0))), &clumpInfo.outName); Serialize(clumpInfo, outData); // this is provided for compatbility with existing implementations that don't know // about this new mechanism if (result == kAudioUnitErr_InvalidProperty) { result = GetProperty(inID, inScope, inElement, outData); } break; } case kAudioUnitProperty_LastRenderSampleTime: Serialize(mCurrentRenderTime.mSampleTime, outData); break; case kAudioUnitProperty_NickName: { const auto* const name = *mNickName; Serialize(name, outData); // Ownership follows Core Foundation's 'Copy Rule' if (name) { CFRetain(name); } break; } default: result = GetProperty(inID, inScope, inElement, outData); break; } return result; } //_____________________________________________________________________________ // // Note: We can be sure inData is non-null; otherwise RemoveProperty would have been called. // NOLINTNEXTLINE(misc-no-recursion) with RestoreState OSStatus AUBase::DispatchSetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { OSStatus result = noErr; switch (inID) { case kAudioUnitProperty_MakeConnection: { AUSDK_Require( inDataSize >= sizeof(AudioUnitConnection), kAudioUnitErr_InvalidPropertyValue); const auto connection = Deserialize(inData); result = SetConnection(connection); break; } case kAudioUnitProperty_SetRenderCallback: { AUSDK_Require( inDataSize >= sizeof(AURenderCallbackStruct), kAudioUnitErr_InvalidPropertyValue); const auto callback = Deserialize(inData); result = SetInputCallback(kAudioUnitProperty_SetRenderCallback, inElement, callback.inputProc, callback.inputProcRefCon); break; } case kAudioUnitProperty_ElementCount: AUSDK_Require(inDataSize == sizeof(UInt32), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(BusCountWritable(inScope), kAudioUnitErr_PropertyNotWritable); result = SetBusCount(inScope, Deserialize(inData)); if (result == noErr) { PropertyChanged(inID, inScope, inElement); } break; case kAudioUnitProperty_MaximumFramesPerSlice: AUSDK_Require(inDataSize == sizeof(UInt32), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require_noerr(CanSetMaxFrames()); SetMaxFramesPerSlice(Deserialize(inData)); break; case kAudioUnitProperty_StreamFormat: { constexpr static UInt32 kMinimumValidASBDSize = 36; AUSDK_Require(inDataSize >= kMinimumValidASBDSize, kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(GetElement(inScope, inElement) != nullptr, kAudioUnitErr_InvalidElement); AudioStreamBasicDescription newDesc{}; // now we're going to be ultra conservative! because of discrepancies between // sizes of this struct based on aligment padding inconsistencies memcpy(&newDesc, inData, kMinimumValidASBDSize); AUSDK_Require(ASBD::MinimalSafetyCheck(newDesc), kAudioUnitErr_FormatNotSupported); AUSDK_Require(ValidFormat(inScope, inElement, newDesc), kAudioUnitErr_FormatNotSupported); const auto curDesc = GetStreamFormat(inScope, inElement); if (!ASBD::IsEqual(curDesc, newDesc)) { AUSDK_Require( IsStreamFormatWritable(inScope, inElement), kAudioUnitErr_PropertyNotWritable); result = ChangeStreamFormat(inScope, inElement, curDesc, newDesc); } break; } case kAudioUnitProperty_SampleRate: { AUSDK_Require(inDataSize == sizeof(Float64), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(GetElement(inScope, inElement) != nullptr, kAudioUnitErr_InvalidElement); const auto curDesc = GetStreamFormat(inScope, inElement); AudioStreamBasicDescription newDesc = curDesc; newDesc.mSampleRate = Deserialize(inData); AUSDK_Require(ValidFormat(inScope, inElement, newDesc), kAudioUnitErr_FormatNotSupported); if (!ASBD::IsEqual(curDesc, newDesc)) { AUSDK_Require( IsStreamFormatWritable(inScope, inElement), kAudioUnitErr_PropertyNotWritable); result = ChangeStreamFormat(inScope, inElement, curDesc, newDesc); } break; } case kAudioUnitProperty_AudioChannelLayout: { // Check the variable-size AudioChannelLayout object size: // - Is the memory area big enough so that AudioChannelLayout::mNumberChannelDescriptions // can be read? AUSDK_Require( inDataSize >= AUChannelLayout::DataByteSize(0), kAudioUnitErr_InvalidPropertyValue); // - Is the whole size consistent? using NumberChannelDescriptionsT = decltype(AudioChannelLayout::mNumberChannelDescriptions); constexpr auto numberChannelDescriptionsOffset = offsetof(AudioChannelLayout, mNumberChannelDescriptions); const auto numberChannelDescriptions = Deserialize( static_cast(inData) + numberChannelDescriptionsOffset); AUSDK_Require(inDataSize >= AUChannelLayout::DataByteSize(numberChannelDescriptions), kAudioUnitErr_InvalidPropertyValue); // copy incoming data to storage aligned for the object type const size_t padding = ((inDataSize % sizeof(AudioChannelLayout)) > 0) ? 1 : 0; std::vector layout((inDataSize / sizeof(AudioChannelLayout)) + padding); std::memcpy(layout.data(), inData, inDataSize); result = SetAudioChannelLayout(inScope, inElement, layout.data()); if (result == noErr) { PropertyChanged(inID, inScope, inElement); } break; } case kAudioUnitProperty_ClassInfo: AUSDK_Require(inDataSize == sizeof(CFPropertyListRef), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); result = RestoreState(Deserialize(inData)); break; case kAudioUnitProperty_PresentPreset: { AUSDK_Require(inDataSize == sizeof(AUPreset), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); const auto newPreset = Deserialize(inData); if (newPreset.presetNumber >= 0) { result = NewFactoryPresetSet(newPreset); // NewFactoryPresetSet SHOULD call SetAFactoryPreset if the preset is valid // from its own list of preset number->name if (result == noErr) { PropertyChanged(inID, inScope, inElement); } } else if (newPreset.presetName != nullptr) { result = NewCustomPresetSet(newPreset); if (result == noErr) { PropertyChanged(inID, inScope, inElement); } } else { result = kAudioUnitErr_InvalidPropertyValue; } break; } case kAudioUnitProperty_ElementName: { AUSDK_Require(GetElement(inScope, inElement) != nullptr, kAudioUnitErr_InvalidElement); AUSDK_Require(inDataSize == sizeof(CFStringRef), kAudioUnitErr_InvalidPropertyValue); const auto element = GetScope(inScope).GetElement(inElement); element->SetName(Deserialize(inData)); PropertyChanged(inID, inScope, inElement); break; } case kAudioUnitProperty_ShouldAllocateBuffer: { AUSDK_Require((inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output), kAudioUnitErr_InvalidScope); AUSDK_Require(GetElement(inScope, inElement) != nullptr, kAudioUnitErr_InvalidElement); AUSDK_Require(inDataSize == sizeof(UInt32), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(!IsInitialized(), kAudioUnitErr_Initialized); auto& element = IOElement(inScope, inElement); element.SetWillAllocateBuffer(Deserialize(inData) != 0); break; } case kAudioUnitProperty_HostCallbacks: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); const auto availSize = std::min(static_cast(inDataSize), sizeof(HostCallbackInfo)); const bool hasChanged = memcmp(&mHostCallbackInfo, inData, availSize) != 0; mHostCallbackInfo = {}; memcpy(&mHostCallbackInfo, inData, availSize); if (hasChanged) { PropertyChanged(inID, inScope, inElement); } break; } case kAudioUnitProperty_ContextName: AUSDK_Require(inDataSize == sizeof(CFStringRef), kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); mContextName = Deserialize(inData); PropertyChanged(inID, inScope, inElement); break; case kAudioUnitProperty_NickName: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inDataSize == sizeof(CFStringRef), kAudioUnitErr_InvalidPropertyValue); mNickName = Deserialize(inData); PropertyChanged(inID, inScope, inElement); break; default: result = SetProperty(inID, inScope, inElement, inData, inDataSize); if (result == noErr) { PropertyChanged(inID, inScope, inElement); } break; } return result; } //_____________________________________________________________________________ // OSStatus AUBase::DispatchRemovePropertyValue( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement) { OSStatus result = noErr; switch (inID) { case kAudioUnitProperty_AudioChannelLayout: { result = RemoveAudioChannelLayout(inScope, inElement); if (result == noErr) { PropertyChanged(inID, inScope, inElement); } break; } case kAudioUnitProperty_HostCallbacks: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); constexpr decltype(mHostCallbackInfo) zeroInfo{}; if (memcmp(&mHostCallbackInfo, &zeroInfo, sizeof(mHostCallbackInfo)) != 0) { mHostCallbackInfo = {}; PropertyChanged(inID, inScope, inElement); } break; } case kAudioUnitProperty_ContextName: mContextName = nullptr; result = noErr; break; case kAudioUnitProperty_NickName: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); mNickName = nullptr; PropertyChanged(inID, inScope, inElement); break; } default: result = RemovePropertyValue(inID, inScope, inElement); break; } return result; } //_____________________________________________________________________________ // OSStatus AUBase::GetPropertyInfo(AudioUnitPropertyID /*inID*/, AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/, UInt32& /*outDataSize*/, bool& /*outWritable*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // OSStatus AUBase::GetProperty(AudioUnitPropertyID /*inID*/, AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/, void* /*outData*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // OSStatus AUBase::SetProperty(AudioUnitPropertyID /*inID*/, AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/, const void* /*inData*/, UInt32 /*inDataSize*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // OSStatus AUBase::RemovePropertyValue( AudioUnitPropertyID /*inID*/, AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/) { return kAudioUnitErr_InvalidPropertyValue; } //_____________________________________________________________________________ // OSStatus AUBase::AddPropertyListener( AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcRefCon) { const PropertyListener pl{ .propertyID = inID, .listenerProc = inProc, .listenerRefCon = inProcRefCon }; if (mPropertyListeners.empty()) { mPropertyListeners.reserve(32); // NOLINT magic# } mPropertyListeners.push_back(pl); return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::RemovePropertyListener(AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcRefCon, bool refConSpecified) { std::erase_if(mPropertyListeners, [&](auto& item) { return item.propertyID == inID && item.listenerProc == inProc && (!refConSpecified || item.listenerRefCon == inProcRefCon); }); return noErr; } //_____________________________________________________________________________ // void AUBase::PropertyChanged( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement) { for (const auto& pl : mPropertyListeners) { if (pl.propertyID == inID) { (pl.listenerProc)(pl.listenerRefCon, GetComponentInstance(), inID, inScope, inElement); } } } //_____________________________________________________________________________ // OSStatus AUBase::SetRenderNotification(AURenderCallback inProc, void* inRefCon) { if (inProc == nullptr) { return kAudio_ParamError; } mRenderCallbacksTouched = true; mRenderCallbacks.Add(RenderCallback(inProc, inRefCon)); // this will do nothing if it's already in the list return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::RemoveRenderNotification(AURenderCallback inProc, void* inRefCon) { mRenderCallbacks.Remove(RenderCallback(inProc, inRefCon)); return noErr; // error? } //_____________________________________________________________________________ // OSStatus AUBase::GetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, AudioUnitParameterValue& outValue) AUSDK_RTSAFE { AUElement& elem = AUSDK_UnwrapOrReturnError(GetElementOrError(inScope, inElement)); outValue = AUSDK_UnwrapOrReturnError(elem.GetParameterOrError(inID)); return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::SetParameter(AudioUnitParameterID inID, AudioUnitScope inScope, AudioUnitElement inElement, AudioUnitParameterValue inValue, UInt32 /*inBufferOffsetInFrames*/) AUSDK_RTSAFE { AUElement& elem = AUSDK_UnwrapOrReturnError(GetElementOrError(inScope, inElement)); AUSDK_CheckReturnError(elem.SetParameterOrError(inID, inValue)); return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::ScheduleParameter( const AudioUnitParameterEvent* inParameterEvent, UInt32 inNumEvents) AUSDK_RTSAFE { const bool canScheduleParameters = CanScheduleParameters(); for (UInt32 i = 0; i < inNumEvents; ++i) { const auto& pe = inParameterEvent[i]; // NOLINT subscript if (pe.eventType == kParameterEvent_Immediate) { SetParameter(pe.parameter, pe.scope, pe.element, pe.eventValues.immediate.value, // NOLINT union pe.eventValues.immediate.bufferOffset); // NOLINT union } if (canScheduleParameters) { // TODO: Need a realtime-safe parameter schedule AUSDK_RT_UNSAFE(mParamEventList.push_back(pe)); } } return noErr; } // ____________________________________________________________________________ // constexpr bool ParameterEventListSortPredicate( const AudioUnitParameterEvent& ev1, const AudioUnitParameterEvent& ev2) AUSDK_RTSAFE { constexpr auto bufferOffset = [](const AudioUnitParameterEvent& event) { // ramp.startBufferOffset is signed return (event.eventType == kParameterEvent_Immediate) ? static_cast(event.eventValues.immediate.bufferOffset) // NOLINT union : event.eventValues.ramp.startBufferOffset; // NOLINT union }; return bufferOffset(ev1) < bufferOffset(ev2); } // ____________________________________________________________________________ // OSStatus AUBase::ProcessForScheduledParams( ParameterEventList& inParamList, UInt32 inFramesToProcess, void* inUserData) AUSDK_RTSAFE { OSStatus result = noErr; UInt32 framesRemaining = inFramesToProcess; UInt32 currentStartFrame = 0; // start of the whole buffer // sort the ParameterEventList by startBufferOffset std::ranges::sort(inParamList, RTSafeFP{ ParameterEventListSortPredicate }); while (framesRemaining > 0) { // first of all, go through the ramped automation events and find out where the next // division of our whole buffer will be UInt32 currentEndFrame = inFramesToProcess; // start out assuming we'll process all the way // to the end of the buffer // find the next break point for (const auto& event : inParamList) { SInt32 offset = event.eventType == kParameterEvent_Immediate ? static_cast(event.eventValues.immediate.bufferOffset) // NOLINT : event.eventValues.ramp.startBufferOffset; if (offset > static_cast(currentStartFrame) && offset < static_cast(currentEndFrame)) { currentEndFrame = static_cast(offset); break; } // consider ramp end to be a possible choice (there may be gaps in the supplied ramp // events) if (event.eventType == kParameterEvent_Ramped) { offset = event.eventValues.ramp.startBufferOffset + static_cast(event.eventValues.ramp.durationInFrames); // NOLINT if (offset > static_cast(currentStartFrame) && offset < static_cast(currentEndFrame)) { currentEndFrame = static_cast(offset); } } } const UInt32 framesThisTime = currentEndFrame - currentStartFrame; // next, setup the parameter maps to be current for the ramp parameters active during // this time segment... for (const auto& event : inParamList) { bool eventFallsInSlice = false; if (event.eventType == kParameterEvent_Ramped) { const auto& ramp = event.eventValues.ramp; eventFallsInSlice = ramp.startBufferOffset < static_cast(currentEndFrame) && (ramp.startBufferOffset + static_cast(ramp.durationInFrames)) > static_cast(currentStartFrame); } else { /* kParameterEvent_Immediate */ // actually, for the same parameter, there may be future immediate events which // override this one, but it's OK since the event list is sorted in time order, // we're guaranteed to end up with the current one eventFallsInSlice = event.eventValues.immediate.bufferOffset <= currentStartFrame; } if (eventFallsInSlice) { const auto element = GetElementOrError(event.scope, event.element); if (element) { std::ignore = element->SetScheduledEvent(event.parameter, event, currentStartFrame, currentEndFrame - currentStartFrame); } } } // Finally, actually do the processing for this slice..... result = ProcessScheduledSlice(inUserData, currentStartFrame, framesThisTime, inFramesToProcess); if (result != noErr) { break; } framesRemaining -= std::min(framesThisTime, framesRemaining); currentStartFrame = currentEndFrame; // now start from where we left off last time } return result; } //_____________________________________________________________________________ // void AUBase::ResetRenderTime() { mCurrentRenderTime = {}; mCurrentRenderTime.mSampleTime = kNoLastRenderedSampleTime; } //_____________________________________________________________________________ // void AUBase::SetWantsRenderThreadID(bool inFlag) { if (inFlag == mWantsRenderThreadID) { return; } mWantsRenderThreadID = inFlag; if (!mWantsRenderThreadID) { mRenderThreadID = {}; }; } //_____________________________________________________________________________ // OSStatus AUBase::DoRender(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inBusNumber, UInt32 inFramesToProcess, AudioBufferList& ioData) noexcept AUSDK_RTSAFE { const auto postRender = [this, &ioActionFlags, &inTimeStamp, inBusNumber, inFramesToProcess, &ioData](OSStatus error) AUSDK_RTSAFE_LAMBDA { SetRenderError(error); if (mRenderCallbacksTouched) { AudioUnitRenderActionFlags flags = ioActionFlags | kAudioUnitRenderAction_PostRender; if (error != noErr) { flags |= kAudioUnitRenderAction_PostRenderError; } for (const RenderCallback& rc : mRenderCallbacks) { rc.mRenderNotify(rc.mRenderNotifyRefCon, &flags, &inTimeStamp, inBusNumber, inFramesToProcess, &ioData); } } // The vector is being emptied because these events should only apply to this Render cycle, // so anything left over is from a preceding cycle and should be dumped. // New scheduled parameters must be scheduled from the next pre-render callback. if (!mParamEventList.empty()) { mParamEventList.clear(); } }; const auto errorExit = [this, postRender](OSStatus error) AUSDK_RTSAFE_LAMBDA { AUSDK_LogError_RT(" from %s, render err: %d", GetLoggingString(), static_cast(error)); SetRenderError(error); postRender(error); return error; }; OSStatus theError = noErr; [[maybe_unused]] const DenormalDisabler denormalDisabler; #if AUSDK_LOOSE_RT_SAFETY try { #endif // AUSDK_LOOSE_RT_SAFETY AUSDK_Require(IsInitialized(), errorExit(kAudioUnitErr_Uninitialized)); if (inFramesToProcess > mMaxFramesPerSlice) { #ifndef AUSDK_NO_LOGGING const UInt64 now = HostTime::Current(); if (static_cast(now - mLastTimeMessagePrinted) > mHostTimeFrequency) { // not more than once per second. mLastTimeMessagePrinted = now; AUSDK_LogError_RT("kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=%u, " "mMaxFramesPerSlice=%u", static_cast(inFramesToProcess), static_cast(mMaxFramesPerSlice)); } #endif return errorExit(kAudioUnitErr_TooManyFramesToProcess); } AUSDK_Require(!UsesFixedBlockSize() || inFramesToProcess == GetMaxFramesPerSlice(), errorExit(kAudio_ParamError)); AUOutputElement& output = AUSDK_UnwrapOrReturnError(GetOutputOrError(inBusNumber)); if (ASBD::NumberChannelStreams(output.GetStreamFormat()) != ioData.mNumberBuffers) { AUSDK_LogError_RT( "ioData.mNumberBuffers=%u, " "ASBD::NumberChannelStreams(output.GetStreamFormat())=%u; kAudio_ParamError", static_cast(ioData.mNumberBuffers), static_cast(ASBD::NumberChannelStreams(output.GetStreamFormat()))); return errorExit(kAudio_ParamError); } const unsigned expectedBufferByteSize = inFramesToProcess * output.GetStreamFormat().mBytesPerFrame; for (unsigned ibuf = 0; ibuf < ioData.mNumberBuffers; ++ibuf) { AudioBuffer& buf = ioData.mBuffers[ibuf]; // NOLINT if (buf.mData != nullptr) { // only care about the size if the buffer is non-null if (buf.mDataByteSize < expectedBufferByteSize) { // if the buffer is too small, we cannot render safely. kAudio_ParamError. AUSDK_LogError_RT("%u frames, %u bytes/frame, expected %u-byte buffer; " "ioData.mBuffers[%u].mDataByteSize=%u; kAudio_ParamError", static_cast(inFramesToProcess), static_cast(output.GetStreamFormat().mBytesPerFrame), expectedBufferByteSize, ibuf, static_cast(buf.mDataByteSize)); return errorExit(kAudio_ParamError); } // Some clients incorrectly pass bigger buffers than expectedBufferByteSize. // We will generally set the buffer size at the end of rendering, before we return. // However we should ensure that no one, DURING rendering, READS a // potentially incorrect size. This can lead to doing too much work, or // reading past the end of an input buffer into unmapped memory. buf.mDataByteSize = expectedBufferByteSize; } } if (WantsRenderThreadID()) { mRenderThreadID = AUSDK_RT_UNSAFE(std::this_thread::get_id()); } if (mRenderCallbacksTouched) { mRenderCallbacks.Update(); AudioUnitRenderActionFlags flags = ioActionFlags | kAudioUnitRenderAction_PreRender; for (const RenderCallback& rc : mRenderCallbacks) { rc.mRenderNotify(rc.mRenderNotifyRefCon, &flags, &inTimeStamp, inBusNumber, inFramesToProcess, &ioData); } } theError = DoRenderBus(ioActionFlags, inTimeStamp, inBusNumber, output, inFramesToProcess, ioData); postRender(theError); #if AUSDK_LOOSE_RT_SAFETY AUSDK_RT_UNSAFE_BEGIN("exception-catching under loose safety model") } catch (const ausdk::AUException& err) { return errorExit(err.mError); } catch (const OSStatus& err) { return errorExit(err); } catch (...) { return errorExit(-1); } AUSDK_RT_UNSAFE_END #endif // AUSDK_LOOSE_RT_SAFETY return theError; } inline bool CheckRenderArgs(AudioUnitRenderActionFlags flags) { return (flags & kAudioUnitRenderAction_DoNotCheckRenderArgs) == 0u; } //_____________________________________________________________________________ // OSStatus AUBase::DoProcess(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inFramesToProcess, AudioBufferList& ioData) noexcept AUSDK_RTSAFE { const auto errorExit = [this](OSStatus error) AUSDK_RTSAFE_LAMBDA { AUSDK_LogError_RT( " from %s, process err: %d", GetLoggingString(), static_cast(error)); SetRenderError(error); return error; }; OSStatus theError = noErr; [[maybe_unused]] const DenormalDisabler denormalDisabler; #if AUSDK_LOOSE_RT_SAFETY try { #endif // AUSDK_LOOSE_RT_SAFETY if (CheckRenderArgs(ioActionFlags)) { AUSDK_Require(IsInitialized(), errorExit(kAudioUnitErr_Uninitialized)); AUSDK_Require(inFramesToProcess <= mMaxFramesPerSlice, errorExit(kAudioUnitErr_TooManyFramesToProcess)); AUSDK_Require(!UsesFixedBlockSize() || inFramesToProcess == GetMaxFramesPerSlice(), errorExit(kAudio_ParamError)); AUInputElement& input = AUSDK_UnwrapOrReturnError(GetInputOrError(0)); if (ASBD::NumberChannelStreams(input.GetStreamFormat()) != ioData.mNumberBuffers) { AUSDK_LogError_RT( "ioData.mNumberBuffers=%u, " "ASBD::NumberChannelStreams(input.GetStreamFormat())=%u; kAudio_ParamError", static_cast(ioData.mNumberBuffers), static_cast(ASBD::NumberChannelStreams(input.GetStreamFormat()))); return errorExit(kAudio_ParamError); } const unsigned expectedBufferByteSize = inFramesToProcess * input.GetStreamFormat().mBytesPerFrame; for (unsigned ibuf = 0; ibuf < ioData.mNumberBuffers; ++ibuf) { AudioBuffer& buf = ioData.mBuffers[ibuf]; // NOLINT if (buf.mData != nullptr) { // only care about the size if the buffer is non-null if (buf.mDataByteSize < expectedBufferByteSize) { // if the buffer is too small, we cannot render safely. kAudio_ParamError. AUSDK_LogError_RT("%u frames, %u bytes/frame, expected %u-byte buffer; " "ioData.mBuffers[%u].mDataByteSize=%u; kAudio_ParamError", static_cast(inFramesToProcess), static_cast(input.GetStreamFormat().mBytesPerFrame), expectedBufferByteSize, ibuf, static_cast(buf.mDataByteSize)); return errorExit(kAudio_ParamError); } // Some clients incorrectly pass bigger buffers than expectedBufferByteSize. // We will generally set the buffer size at the end of rendering, before we // return. However we should ensure that no one, DURING rendering, READS a // potentially incorrect size. This can lead to doing too much work, or // reading past the end of an input buffer into unmapped memory. buf.mDataByteSize = expectedBufferByteSize; } } } if (WantsRenderThreadID()) { mRenderThreadID = AUSDK_RT_UNSAFE(std::this_thread::get_id()); } if (NeedsToRender(inTimeStamp)) { theError = ProcessBufferLists(ioActionFlags, ioData, ioData, inFramesToProcess); } else { theError = noErr; } #if AUSDK_LOOSE_RT_SAFETY AUSDK_RT_UNSAFE_BEGIN("exception-catching under loose safety model") } catch (const ausdk::AUException& err) { return errorExit(err.mError); } catch (const OSStatus& err) { return errorExit(err); } catch (...) { return errorExit(-1); } AUSDK_RT_UNSAFE_END #endif // AUSDK_LOOSE_RT_SAFETY return theError; } OSStatus AUBase::DoProcessMultiple(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inFramesToProcess, UInt32 inNumberInputBufferLists, const AudioBufferList** inInputBufferLists, UInt32 inNumberOutputBufferLists, AudioBufferList** ioOutputBufferLists) noexcept AUSDK_RTSAFE { const auto errorExit = [this](OSStatus error) AUSDK_RTSAFE_LAMBDA { AUSDK_LogError_RT( " from %s, processmultiple err: %d", GetLoggingString(), static_cast(error)); SetRenderError(error); return error; }; OSStatus theError = noErr; [[maybe_unused]] const DenormalDisabler denormalDisabler; #if AUSDK_LOOSE_RT_SAFETY try { #endif // AUSDK_LOOSE_RT_SAFETY if (CheckRenderArgs(ioActionFlags)) { AUSDK_Require(IsInitialized(), errorExit(kAudioUnitErr_Uninitialized)); AUSDK_Require(inFramesToProcess <= mMaxFramesPerSlice, errorExit(kAudioUnitErr_TooManyFramesToProcess)); AUSDK_Require(!UsesFixedBlockSize() || inFramesToProcess == GetMaxFramesPerSlice(), errorExit(kAudio_ParamError)); for (unsigned ibl = 0; ibl < inNumberInputBufferLists; ++ibl) { if (inInputBufferLists[ibl] != nullptr) { // NOLINT AUInputElement& input = AUSDK_UnwrapOrReturnError(GetInputOrError(ibl)); const unsigned expectedBufferByteSize = inFramesToProcess * input.GetStreamFormat().mBytesPerFrame; if (ASBD::NumberChannelStreams(input.GetStreamFormat()) != inInputBufferLists[ibl]->mNumberBuffers) { // NOLINT AUSDK_LogError_RT("inInputBufferLists[%u]->mNumberBuffers=%u, " "ASBD::NumberChannelStreams(input.GetStreamFormat())=%u; " "kAudio_ParamError", ibl, static_cast(inInputBufferLists[ibl]->mNumberBuffers), static_cast( ASBD::NumberChannelStreams(input.GetStreamFormat()))); return errorExit(kAudio_ParamError); } for (unsigned ibuf = 0; ibuf < inInputBufferLists[ibl]->mNumberBuffers; // NOLINT ++ibuf) { const AudioBuffer& buf = inInputBufferLists[ibl]->mBuffers[ibuf]; // NOLINT if (buf.mData != nullptr) { if (buf.mDataByteSize < expectedBufferByteSize) { // the buffer is too small AUSDK_LogError_RT( "%u frames, %u bytes/frame, expected %u-byte buffer; " "inInputBufferLists[%u].mBuffers[%u].mDataByteSize=%u; " "kAudio_ParamError", static_cast(inFramesToProcess), static_cast(input.GetStreamFormat().mBytesPerFrame), expectedBufferByteSize, ibl, ibuf, static_cast(buf.mDataByteSize)); return errorExit(kAudio_ParamError); } } else { // the buffer must exist return errorExit(kAudio_ParamError); } } } else { // skip NULL input audio buffer list } } for (unsigned obl = 0; obl < inNumberOutputBufferLists; ++obl) { if (ioOutputBufferLists[obl] != nullptr) { // NOLINT AUOutputElement& output = AUSDK_UnwrapOrReturnError(GetOutputOrError(obl)); const unsigned expectedBufferByteSize = inFramesToProcess * output.GetStreamFormat().mBytesPerFrame; if (ASBD::NumberChannelStreams(output.GetStreamFormat()) != ioOutputBufferLists[obl]->mNumberBuffers) { // NOLINT AUSDK_LogError_RT( "ioOutputBufferLists[%u]->mNumberBuffers=%u, " "ASBD::NumberChannelStreams(output.GetStreamFormat())=%u; " "kAudio_ParamError", obl, static_cast(ioOutputBufferLists[obl]->mNumberBuffers), static_cast( ASBD::NumberChannelStreams(output.GetStreamFormat()))); return errorExit(kAudio_ParamError); } for (unsigned obuf = 0; obuf < ioOutputBufferLists[obl]->mNumberBuffers; // NOLINT ++obuf) { AudioBuffer& buf = ioOutputBufferLists[obl]->mBuffers[obuf]; // NOLINT if (buf.mData != nullptr) { // only care about the size if the buffer is non-null if (buf.mDataByteSize < expectedBufferByteSize) { // if the buffer is too small, we cannot render safely. // kAudio_ParamError. AUSDK_LogError_RT( "%u frames, %u bytes/frame, expected %u-byte buffer; " "ioOutputBufferLists[%u]->mBuffers[%u].mDataByteSize=%u; " "kAudio_ParamError", static_cast(inFramesToProcess), static_cast(output.GetStreamFormat().mBytesPerFrame), expectedBufferByteSize, obl, obuf, static_cast(buf.mDataByteSize)); return errorExit(kAudio_ParamError); } // Some clients incorrectly pass bigger buffers than // expectedBufferByteSize. We will generally set the buffer size at the // end of rendering, before we return. However we should ensure that no // one, DURING rendering, READS a potentially incorrect size. This can // lead to doing too much work, or reading past the end of an input // buffer into unmapped memory. buf.mDataByteSize = expectedBufferByteSize; } } } else { // skip NULL output audio buffer list } } } if (WantsRenderThreadID()) { mRenderThreadID = AUSDK_RT_UNSAFE(std::this_thread::get_id()); } if (NeedsToRender(inTimeStamp)) { theError = ProcessMultipleBufferLists(ioActionFlags, inFramesToProcess, inNumberInputBufferLists, inInputBufferLists, inNumberOutputBufferLists, ioOutputBufferLists); } else { theError = noErr; } #if AUSDK_LOOSE_RT_SAFETY AUSDK_RT_UNSAFE_BEGIN("exception-catching under loose safety model") } catch (const ausdk::AUException& err) { return errorExit(err.mError); } catch (const OSStatus& err) { return errorExit(err); } catch (...) { return errorExit(-1); } AUSDK_RT_UNSAFE_END #endif // AUSDK_LOOSE_RT_SAFETY return theError; } //_____________________________________________________________________________ // OSStatus AUBase::SetInputCallback( UInt32 inPropertyID, AudioUnitElement inElement, AURenderCallback inProc, void* inRefCon) { auto& input = Input(inElement); // may throw input.SetInputCallback(inProc, inRefCon); PropertyChanged(inPropertyID, kAudioUnitScope_Input, inElement); return noErr; } //_____________________________________________________________________________ // // NOLINTNEXTLINE(misc-no-recursion) with DispatchSetProperty OSStatus AUBase::SetConnection(const AudioUnitConnection& inConnection) { auto& input = Input(inConnection.destInputNumber); // may throw if (inConnection.sourceAudioUnit != nullptr) { // connecting, not disconnecting AudioStreamBasicDescription sourceDesc; UInt32 size = sizeof(AudioStreamBasicDescription); AUSDK_Require_noerr( AudioUnitGetProperty(inConnection.sourceAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, inConnection.sourceOutputNumber, &sourceDesc, &size)); AUSDK_Require_noerr( DispatchSetProperty(kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, inConnection.destInputNumber, &sourceDesc, sizeof(AudioStreamBasicDescription))); } input.SetConnection(inConnection); PropertyChanged( kAudioUnitProperty_MakeConnection, kAudioUnitScope_Input, inConnection.destInputNumber); return noErr; } //_____________________________________________________________________________ // UInt32 AUBase::SupportedNumChannels(const AUChannelInfo** /*outInfo*/) { return 0; } //_____________________________________________________________________________ // bool AUBase::ValidFormat(AudioUnitScope /*inScope*/, AudioUnitElement /*inElement*/, const AudioStreamBasicDescription& inNewFormat) { return ASBD::IsCommonFloat32(inNewFormat) && (!ASBD::IsInterleaved(inNewFormat) || inNewFormat.mChannelsPerFrame == 1); } //_____________________________________________________________________________ // bool AUBase::IsStreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) { switch (scope) { case kAudioUnitScope_Input: { const auto& input = Input(element); if (input.HasConnection()) { return false; // can't write format when input comes from connection } [[fallthrough]]; } case kAudioUnitScope_Output: return StreamFormatWritable(scope, element); // #warning "aliasing of global scope format should be pushed to subclasses" case kAudioUnitScope_Global: return StreamFormatWritable(kAudioUnitScope_Output, 0); default: break; } return false; } //_____________________________________________________________________________ // AudioStreamBasicDescription AUBase::GetStreamFormat( AudioUnitScope inScope, AudioUnitElement inElement) AUSDK_RTSAFE_LOOSE { // #warning "aliasing of global scope format should be pushed to subclasses" AUIOElement* element = nullptr; switch (inScope) { case kAudioUnitScope_Input: element = Inputs().GetIOElement(inElement); break; case kAudioUnitScope_Output: element = Outputs().GetIOElement(inElement); break; case kAudioUnitScope_Global: // global stream description is an alias for that of output 0 element = Outputs().GetIOElement(0); break; default: Throw(kAudioUnitErr_InvalidScope); } return element->GetStreamFormat(); } OSStatus AUBase::SetBusCount(AudioUnitScope inScope, UInt32 inCount) { AUSDK_Require(!IsInitialized(), kAudioUnitErr_Initialized); GetScope(inScope).SetNumberOfElements(inCount); return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::ChangeStreamFormat(AudioUnitScope inScope, AudioUnitElement inElement, const AudioStreamBasicDescription& inPrevFormat, const AudioStreamBasicDescription& inNewFormat) { if (ASBD::IsEqual(inNewFormat, inPrevFormat)) { return noErr; } // #warning "aliasing of global scope format should be pushed to subclasses" AUIOElement* element = nullptr; switch (inScope) { case kAudioUnitScope_Input: element = Inputs().GetIOElement(inElement); break; case kAudioUnitScope_Output: element = Outputs().GetIOElement(inElement); break; case kAudioUnitScope_Global: element = Outputs().GetIOElement(0); break; default: Throw(kAudioUnitErr_InvalidScope); } element->SetStreamFormat(inNewFormat); PropertyChanged(kAudioUnitProperty_StreamFormat, inScope, inElement); return noErr; } std::vector AUBase::GetChannelLayoutTags( AudioUnitScope inScope, AudioUnitElement inElement) { return IOElement(inScope, inElement).GetChannelLayoutTags(); } UInt32 AUBase::GetAudioChannelLayout(AudioUnitScope inScope, AudioUnitElement inElement, AudioChannelLayout* outLayoutPtr, bool& outWritable) { auto& element = IOElement(inScope, inElement); return element.GetAudioChannelLayout(outLayoutPtr, outWritable); } OSStatus AUBase::RemoveAudioChannelLayout(AudioUnitScope inScope, AudioUnitElement inElement) { auto& element = IOElement(inScope, inElement); bool writable = false; if (element.GetAudioChannelLayout(nullptr, writable) > 0) { return element.RemoveAudioChannelLayout(); } return noErr; } OSStatus AUBase::SetAudioChannelLayout( AudioUnitScope inScope, AudioUnitElement inElement, const AudioChannelLayout* inLayout) { auto& element = IOElement(inScope, inElement); // the num channels of the layout HAS TO MATCH the current channels of the Element's stream // format const UInt32 currentChannels = element.GetStreamFormat().mChannelsPerFrame; const UInt32 numChannelsInLayout = AUChannelLayout::NumberChannels(*inLayout); AUSDK_Require(currentChannels == numChannelsInLayout, kAudioUnitErr_InvalidPropertyValue); const auto tags = GetChannelLayoutTags(inScope, inElement); AUSDK_Require(!tags.empty(), kAudioUnitErr_InvalidProperty); const auto iter = std::ranges::find_if(tags, [inTag = inLayout->mChannelLayoutTag](auto tag) { return tag == inTag || tag == kAudioChannelLayoutTag_UseChannelDescriptions; }); AUSDK_Require(iter != tags.end(), kAudioUnitErr_InvalidPropertyValue); return element.SetAudioChannelLayout(*inLayout); } constexpr int kCurrentSavedStateVersion = 0; static void AddNumToDictionary(CFMutableDictionaryRef dict, CFStringRef key, SInt32 value) { const CFNumberRef num = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value); CFDictionarySetValue(dict, key, num); CFRelease(num); } // NOLINTNEXTLINE(misc-no-recursion) with DispatchGetProperty OSStatus AUBase::SaveState(CFPropertyListRef* outData) { const AudioComponentDescription desc = GetComponentDescription(); auto dict = Owned::from_create(CFDictionaryCreateMutable( nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); // first step -> save the version to the data ref SInt32 value = kCurrentSavedStateVersion; AddNumToDictionary(*dict, CFSTR(kAUPresetVersionKey), value); // second step -> save the component type, subtype, manu to the data ref value = static_cast(desc.componentType); AddNumToDictionary(*dict, CFSTR(kAUPresetTypeKey), value); value = static_cast(desc.componentSubType); AddNumToDictionary(*dict, CFSTR(kAUPresetSubtypeKey), value); value = static_cast(desc.componentManufacturer); AddNumToDictionary(*dict, CFSTR(kAUPresetManufacturerKey), value); // fourth step -> save the state of all parameters on all scopes and elements auto data = Owned::from_create(CFDataCreateMutable(nullptr, 0)); for (AudioUnitScope iscope = 0; iscope < 3; ++iscope) { const auto& scope = GetScope(iscope); scope.SaveState(*data); } SaveExtendedScopes(*data); // save all this in the data section of the dictionary CFDictionarySetValue(*dict, CFSTR(kAUPresetDataKey), *data); data = nullptr; // data can be large-ish, so destroy it now. // OK - now we're going to do some properties // save the preset name... CFDictionarySetValue(*dict, CFSTR(kAUPresetNameKey), mCurrentPreset.presetName); // Does the unit support the RenderQuality property - if so, save it... OSStatus result = DispatchGetProperty(kAudioUnitProperty_RenderQuality, kAudioUnitScope_Global, 0, &value); if (result == noErr) { AddNumToDictionary(*dict, CFSTR(kAUPresetRenderQualityKey), value); } // Do we have any element names for any of our scopes? // first check to see if we have any names... bool foundName = false; for (AudioUnitScope i = 0; i < kNumScopes; ++i) { foundName = GetScope(i).HasElementWithName(); if (foundName) { break; } } // OK - we found a name away we go... if (foundName) { auto nameDict = Owned::from_create(CFDictionaryCreateMutable( nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); for (AudioUnitScope i = 0; i < kNumScopes; ++i) { GetScope(i).AddElementNamesToDict(*nameDict); } CFDictionarySetValue(*dict, CFSTR(kAUPresetElementNameKey), *nameDict); } // we're done!!! *outData = static_cast(dict.release()); // transfer ownership return noErr; } //_____________________________________________________________________________ // // NOLINTNEXTLINE(misc-no-recursion) with DispatchSetProperty OSStatus AUBase::RestoreState(CFPropertyListRef plist) { AUSDK_Require( CFGetTypeID(plist) == CFDictionaryGetTypeID(), kAudioUnitErr_InvalidPropertyValue); const AudioComponentDescription desc = GetComponentDescription(); const auto* const dict = static_cast(plist); // zeroeth step - make sure the Part key is NOT present, as this method is used // to restore the GLOBAL state of the dictionary AUSDK_Require(!CFDictionaryContainsKey(dict, CFSTR(kAUPresetPartKey)), kAudioUnitErr_InvalidPropertyValue); // first step -> check the saved version in the data ref // at this point we're only dealing with version==0 const auto* cfNum = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetVersionKey))); AUSDK_Require(cfNum != nullptr, kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(CFGetTypeID(cfNum) == CFNumberGetTypeID(), kAudioUnitErr_InvalidPropertyValue); SInt32 value = 0; CFNumberGetValue(cfNum, kCFNumberSInt32Type, &value); AUSDK_Require(value == kCurrentSavedStateVersion, kAudioUnitErr_InvalidPropertyValue); // second step -> check that this data belongs to this kind of audio unit // by checking the component subtype and manuID // We're not checking the type, since there may be different versions (effect, format-converter, // offline) of essentially the same AU cfNum = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetSubtypeKey))); AUSDK_Require(cfNum != nullptr, kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(CFGetTypeID(cfNum) == CFNumberGetTypeID(), kAudioUnitErr_InvalidPropertyValue); CFNumberGetValue(cfNum, kCFNumberSInt32Type, &value); AUSDK_Require( static_cast(value) == desc.componentSubType, kAudioUnitErr_InvalidPropertyValue); cfNum = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetManufacturerKey))); AUSDK_Require(cfNum != nullptr, kAudioUnitErr_InvalidPropertyValue); AUSDK_Require(CFGetTypeID(cfNum) == CFNumberGetTypeID(), kAudioUnitErr_InvalidPropertyValue); CFNumberGetValue(cfNum, kCFNumberSInt32Type, &value); AUSDK_Require(static_cast(value) == desc.componentManufacturer, kAudioUnitErr_InvalidPropertyValue); // fourth step -> restore the state of all of the parameters for each scope and element const auto* const data = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetDataKey))); if ((data != nullptr) && (CFGetTypeID(data) == CFDataGetTypeID())) { const UInt8* p = CFDataGetBytePtr(data); const UInt8* const pend = p + CFDataGetLength(data); // NOLINT while (p < pend) { const auto scopeIndex = DeserializeBigUInt32AndAdvance(p); const auto& scope = GetScope(scopeIndex); p = scope.RestoreState(p); } } // OK - now we're going to do some properties // restore the preset name... const auto* const name = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetNameKey))); if (mCurrentPreset.presetName != nullptr) { CFRelease(mCurrentPreset.presetName); } if ((name != nullptr) && (CFGetTypeID(name) == CFStringGetTypeID())) { mCurrentPreset.presetName = name; mCurrentPreset.presetNumber = -1; } else { // no name entry make the default one mCurrentPreset.presetName = GetPresetDefaultName(); mCurrentPreset.presetNumber = -1; } CFRetain(mCurrentPreset.presetName); PropertyChanged(kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); // Does the dict contain render quality information? cfNum = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetRenderQualityKey))); if (cfNum && (CFGetTypeID(cfNum) == CFNumberGetTypeID())) { CFNumberGetValue(cfNum, kCFNumberSInt32Type, &value); DispatchSetProperty( kAudioUnitProperty_RenderQuality, kAudioUnitScope_Global, 0, &value, sizeof(value)); } // Do we have any element names for any of our scopes? const auto nameDict = static_cast(CFDictionaryGetValue(dict, CFSTR(kAUPresetElementNameKey))); if (nameDict && (CFGetTypeID(nameDict) == CFDictionaryGetTypeID())) { for (AudioUnitScope i = 0; i < kNumScopes; ++i) { const CFStringRef key = CFStringCreateWithFormat( nullptr, nullptr, CFSTR("%u"), static_cast(i)); // NOLINT const auto elementDict = static_cast(CFDictionaryGetValue(nameDict, key)); if (elementDict && (CFGetTypeID(elementDict) == CFDictionaryGetTypeID())) { const auto restoredElements = GetScope(i).RestoreElementNames(elementDict); for (const auto& element : restoredElements) { PropertyChanged(kAudioUnitProperty_ElementName, i, element); } } CFRelease(key); } } return noErr; } OSStatus AUBase::GetPresets(CFArrayRef* /*outData*/) const { return kAudioUnitErr_InvalidProperty; } OSStatus AUBase::NewFactoryPresetSet(const AUPreset& /*inNewFactoryPreset*/) { return kAudioUnitErr_InvalidProperty; } OSStatus AUBase::NewCustomPresetSet(const AUPreset& inNewCustomPreset) { CFRelease(mCurrentPreset.presetName); mCurrentPreset = inNewCustomPreset; CFRetain(mCurrentPreset.presetName); return noErr; } // set the default preset for the unit -> the number of the preset MUST be >= 0 // and the name should be valid, or the preset WON'T take bool AUBase::SetAFactoryPresetAsCurrent(const AUPreset& inPreset) { if (inPreset.presetNumber < 0 || inPreset.presetName == nullptr) { return false; } CFRelease(mCurrentPreset.presetName); mCurrentPreset = inPreset; CFRetain(mCurrentPreset.presetName); return true; } bool AUBase::HasIcon() { #if AUSDK_HAVE_UI const CFURLRef url = CopyIconLocation(); if (url != nullptr) { CFRelease(url); return true; } #endif // AUSDK_HAVE_UI return false; } #if AUSDK_HAVE_UI CFURLRef AUBase::CopyIconLocation() { return nullptr; } #endif // AUSDK_HAVE_UI //_____________________________________________________________________________ // OSStatus AUBase::GetParameterList( AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters) { const auto& scope = GetScope(inScope); AUElement* elementWithMostParameters = nullptr; UInt32 maxNumParams = 0; const UInt32 nElems = scope.GetNumberOfElements(); for (UInt32 ielem = 0; ielem < nElems; ++ielem) { AUElement* const element = scope.GetElement(ielem); const UInt32 nParams = element->GetNumberOfParameters(); if (nParams > maxNumParams) { maxNumParams = nParams; elementWithMostParameters = element; } } if (outParameterList != nullptr && elementWithMostParameters != nullptr) { elementWithMostParameters->GetParameterList(outParameterList); } outNumParameters = maxNumParams; return noErr; } //_____________________________________________________________________________ // OSStatus AUBase::GetParameterInfo(AudioUnitScope /*inScope*/, AudioUnitParameterID /*inParameterID*/, AudioUnitParameterInfo& /*outParameterInfo*/) { return kAudioUnitErr_InvalidParameter; } //_____________________________________________________________________________ // OSStatus AUBase::GetParameterValueStrings( AudioUnitScope /*inScope*/, AudioUnitParameterID /*inParameterID*/, CFArrayRef* /*outStrings*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // OSStatus AUBase::GetParameterHistoryInfo(AudioUnitScope /*inScope*/, AudioUnitParameterID /*inParameterID*/, Float32& /*outUpdatesPerSecond*/, Float32& /*outHistoryDurationInSeconds*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // OSStatus AUBase::CopyClumpName(AudioUnitScope /*inScope*/, UInt32 /*inClumpID*/, UInt32 /*inDesiredNameLength*/, CFStringRef* /*outClumpName*/) { return kAudioUnitErr_InvalidProperty; } //_____________________________________________________________________________ // void AUBase::SetNumberOfElements(AudioUnitScope inScope, UInt32 numElements) { if (inScope == kAudioUnitScope_Global && numElements != 1) { Throw(kAudioUnitErr_InvalidScope); } GetScope(inScope).SetNumberOfElements(numElements); } //_____________________________________________________________________________ // std::unique_ptr AUBase::CreateElement(AudioUnitScope scope, AudioUnitElement /*element*/) { switch (scope) { case kAudioUnitScope_Global: return std::make_unique(*this); case kAudioUnitScope_Input: return std::make_unique(*this); case kAudioUnitScope_Output: return std::make_unique(*this); case kAudioUnitScope_Group: case kAudioUnitScope_Part: return std::make_unique(*this); default: break; } Throw(kAudioUnitErr_InvalidScope); } const char* AUBase::GetLoggingString() const noexcept { return mLogString.c_str(); } std::string AUBase::CreateLoggingString() const { const auto desc = GetComponentDescription(); std::array buf{}; [[maybe_unused]] const int printCount = snprintf( buf.data(), buf.size(), "AU (%p): ", static_cast(GetComponentInstance())); // NOLINT #if DEBUG assert(printCount < static_cast(buf.size())); #endif return buf.data() + MakeStringFrom4CC(desc.componentType) + '/' + MakeStringFrom4CC(desc.componentSubType) + '/' + MakeStringFrom4CC(desc.componentManufacturer); } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUBuffer.cpp ================================================ /*! @file AudioUnitSDK/AUBuffer.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS inline void ThrowBadAlloc() { AUSDK_LogError("AUBuffer throwing bad_alloc"); throw std::bad_alloc(); } // x: number to be rounded; y: the power of 2 to which to round constexpr uint32_t RoundUpToMultipleOfPowerOf2(uint32_t x, uint32_t y) noexcept { const uint32_t mask = y - 1u; #if DEBUG assert((mask & y) == 0u); // verifies that y is a power of 2 NOLINT #endif return (x + mask) & ~mask; } // a * b + c static UInt32 SafeMultiplyAddUInt32(UInt32 a, UInt32 b, UInt32 c) { if (a == 0 || b == 0) { return c; // prevent zero divide } if (a > (0xFFFFFFFF - c) / b) { // NOLINT magic ThrowBadAlloc(); } return a * b + c; } AllocatedBuffer* BufferAllocator::Allocate( UInt32 numberBuffers, UInt32 maxBytesPerBuffer, UInt32 /*reservedFlags*/) { constexpr size_t kAlignment = 16; constexpr size_t kMaxBufferListSize = 65536; // Check for a reasonable number of buffers (obviate a more complicated check with offsetof). if (numberBuffers > kMaxBufferListSize / sizeof(AudioBuffer)) { throw std::out_of_range("AudioBuffers::Allocate: Too many buffers"); } maxBytesPerBuffer = RoundUpToMultipleOfPowerOf2(maxBytesPerBuffer, kAlignment); const auto bufferDataSize = SafeMultiplyAddUInt32(numberBuffers, maxBytesPerBuffer, 0); void* bufferData = nullptr; if (bufferDataSize > 0) { bufferData = malloc(bufferDataSize); // don't use calloc(); it might not actually touch the memory and cause a VM fault later memset(bufferData, 0, bufferDataSize); } const auto implSize = static_cast( offsetof(AllocatedBuffer, mAudioBufferList.mBuffers[std::max(UInt32(1), numberBuffers)])); auto* const implMem = malloc(implSize); auto* const allocatedBuffer = new (implMem) AllocatedBuffer{ numberBuffers, maxBytesPerBuffer, implSize, bufferDataSize, bufferData }; allocatedBuffer->mAudioBufferList.mNumberBuffers = numberBuffers; return allocatedBuffer; } void BufferAllocator::Deallocate(AllocatedBuffer* allocatedBuffer) { if (allocatedBuffer->mBufferData != nullptr) { free(allocatedBuffer->mBufferData); } allocatedBuffer->~AllocatedBuffer(); free(allocatedBuffer); } ExpectedPtr AllocatedBuffer::PrepareOrError( UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) AUSDK_RTSAFE { if (mAudioBufferList.mNumberBuffers > mMaximumNumberBuffers) { // too many buffers return Unexpected(-1); } if (bytesPerBuffer > mMaximumBytesPerBuffer) { // insufficient capacity return Unexpected(-1); } auto* ptr = static_cast(mBufferData); auto* const ptrend = ptr + mBufferDataSize; for (UInt32 bufIdx = 0, nBufs = mAudioBufferList.mNumberBuffers; bufIdx < nBufs; ++bufIdx) { auto& buf = mAudioBufferList.mBuffers[bufIdx]; // NOLINT buf.mNumberChannels = channelsPerBuffer; buf.mDataByteSize = bytesPerBuffer; buf.mData = ptr; ptr += mMaximumBytesPerBuffer; // NOLINT ptr math } if (ptr > ptrend) { // insufficient capacity return Unexpected(-1); } return mAudioBufferList; } ExpectedPtr AllocatedBuffer::PrepareNullOrError( UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) AUSDK_RTSAFE { if (mAudioBufferList.mNumberBuffers > mMaximumNumberBuffers) { // too many buffers return Unexpected(-1); } for (UInt32 bufIdx = 0, nBufs = mAudioBufferList.mNumberBuffers; bufIdx < nBufs; ++bufIdx) { auto& buf = mAudioBufferList.mBuffers[bufIdx]; // NOLINT buf.mNumberChannels = channelsPerBuffer; buf.mDataByteSize = bytesPerBuffer; buf.mData = nullptr; } return mAudioBufferList; } ExpectedPtr AUBufferList::PrepareBufferOrError( const AudioStreamBasicDescription& format, UInt32 nFrames) AUSDK_RTSAFE { if (nFrames > mAllocatedFrames) { return Unexpected(kAudioUnitErr_TooManyFramesToProcess); } UInt32 nStreams = 0; UInt32 channelsPerStream = 0; if (ASBD::IsInterleaved(format)) { nStreams = 1; channelsPerStream = format.mChannelsPerFrame; } else { nStreams = format.mChannelsPerFrame; channelsPerStream = 1; } if (nStreams > mAllocatedStreams) { return Unexpected(kAudioUnitErr_FormatNotSupported); } auto maybeABL = mBuffers->PrepareOrError(channelsPerStream, nFrames * format.mBytesPerFrame); if (maybeABL) { mPtrState = EPtrState::ToMyMemory; } return maybeABL; } ExpectedPtr AUBufferList::PrepareNullBufferOrError( const AudioStreamBasicDescription& format, UInt32 nFrames) AUSDK_RTSAFE { UInt32 nStreams = 0; UInt32 channelsPerStream = 0; if (ASBD::IsInterleaved(format)) { nStreams = 1; channelsPerStream = format.mChannelsPerFrame; } else { nStreams = format.mChannelsPerFrame; channelsPerStream = 1; } if (nStreams > mAllocatedStreams) { return Unexpected(kAudioUnitErr_FormatNotSupported); } auto maybeABL = mBuffers->PrepareNullOrError(channelsPerStream, nFrames * format.mBytesPerFrame); if (maybeABL) { mPtrState = EPtrState::ToExternalMemory; } return maybeABL; } void AUBufferList::Allocate(const AudioStreamBasicDescription& format, UInt32 nFrames) { auto& alloc = BufferAllocator::instance(); if (mBuffers != nullptr) { alloc.Deallocate(mBuffers); mBuffers = nullptr; } const uint32_t nstreams = ASBD::IsInterleaved(format) ? 1 : format.mChannelsPerFrame; mBuffers = alloc.Allocate(nstreams, nFrames * format.mBytesPerFrame, 0u); mAllocatedFrames = nFrames; mAllocatedStreams = nstreams; mPtrState = EPtrState::Invalid; } void AUBufferList::Deallocate() { if (mBuffers != nullptr) { BufferAllocator::instance().Deallocate(mBuffers); mBuffers = nullptr; } mAllocatedFrames = 0; mAllocatedStreams = 0; mPtrState = EPtrState::Invalid; } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUBufferAllocator.cpp ================================================ /*! @file AudioUnitSDK/AUBufferAllocator.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include namespace ausdk { BufferAllocator& BufferAllocator::instance() { __attribute__((no_destroy)) static BufferAllocator global; return global; } } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUEffectBase.cpp ================================================ /*! @file AudioUnitSDK/AUEffectBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include #include /* This class does not deal as well as it should with N-M effects... The problem areas are (if the channels don't match): ProcessInPlace if the channels don't match - there will be problems if InputChan != OutputChan Bypass - its just passing the buffers through when not processing them */ namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS //_____________________________________________________________________________ // AUEffectBase::AUEffectBase(AudioComponentInstance audioUnit, bool inProcessesInPlace) : AUBase(audioUnit, 1, 1), // 1 in bus, 1 out bus mProcessesInPlace(inProcessesInPlace) #if TARGET_OS_IPHONE , mOnlyOneKernel(false) #endif { } //_____________________________________________________________________________ // void AUEffectBase::Cleanup() { mKernelList.clear(); mMainOutput = nullptr; mMainInput = nullptr; } //_____________________________________________________________________________ // OSStatus AUEffectBase::Initialize() { AUInputElement& in0 = GetInput0(); AUOutputElement& out0 = GetOutput0(); // get our current numChannels for input and output const auto auNumInputs = static_cast(in0.GetStreamFormat().mChannelsPerFrame); const auto auNumOutputs = static_cast(out0.GetStreamFormat().mChannelsPerFrame); // does the unit publish specific information about channel configurations? const AUChannelInfo* auChannelConfigs = nullptr; const UInt32 numIOconfigs = SupportedNumChannels(&auChannelConfigs); if ((numIOconfigs > 0) && (auChannelConfigs != nullptr)) { bool foundMatch = false; for (UInt32 i = 0; (i < numIOconfigs) && !foundMatch; ++i) { const SInt16 configNumInputs = auChannelConfigs[i].inChannels; // NOLINT const SInt16 configNumOutputs = auChannelConfigs[i].outChannels; // NOLINT if ((configNumInputs < 0) && (configNumOutputs < 0)) { // unit accepts any number of channels on input and output if (((configNumInputs == -1) && (configNumOutputs == -2)) || ((configNumInputs == -2) && (configNumOutputs == -1))) { // NOLINT repeated branch below foundMatch = true; // unit accepts any number of channels on input and output IFF they are the same // number on both scopes } else if (((configNumInputs == -1) && (configNumOutputs == -1)) && (auNumInputs == auNumOutputs)) { foundMatch = true; // unit has specified a particular number of channels on both scopes } else { continue; } } else { // the -1 case on either scope is saying that the unit doesn't care about the // number of channels on that scope const bool inputMatch = (auNumInputs == configNumInputs) || (configNumInputs == -1); const bool outputMatch = (auNumOutputs == configNumOutputs) || (configNumOutputs == -1); if (inputMatch && outputMatch) { foundMatch = true; } } } AUSDK_Require(foundMatch, kAudioUnitErr_FormatNotSupported); } else { // there is no specifically published channel info // so for those kinds of effects, the assumption is that the channels (whatever their // number) should match on both scopes AUSDK_Require( (auNumOutputs == auNumInputs) && (auNumOutputs != 0), kAudioUnitErr_FormatNotSupported); } MaintainKernels(); mMainOutput = &out0; mMainInput = &in0; const AudioStreamBasicDescription format = out0.GetStreamFormat(); mBytesPerFrame = format.mBytesPerFrame; return noErr; } OSStatus AUEffectBase::Reset(AudioUnitScope inScope, AudioUnitElement inElement) { for (auto& kernel : mKernelList) { if (kernel) { kernel->Reset(); } } return AUBase::Reset(inScope, inElement); } OSStatus AUEffectBase::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) { if (inScope == kAudioUnitScope_Global) { switch (inID) { case kAudioUnitProperty_BypassEffect: case kAudioUnitProperty_InPlaceProcessing: outWritable = true; outDataSize = sizeof(UInt32); return noErr; default: break; } } return AUBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); } OSStatus AUEffectBase::GetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { if (inScope == kAudioUnitScope_Global) { switch (inID) { case kAudioUnitProperty_BypassEffect: Serialize(IsBypassEffect() ? 1 : 0, outData); return noErr; case kAudioUnitProperty_InPlaceProcessing: Serialize(mProcessesInPlace ? 1 : 0, outData); return noErr; default: break; } } return AUBase::GetProperty(inID, inScope, inElement, outData); } OSStatus AUEffectBase::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { if (inScope == kAudioUnitScope_Global) { switch (inID) { case kAudioUnitProperty_BypassEffect: { AUSDK_Require(inDataSize >= sizeof(UInt32), kAudioUnitErr_InvalidPropertyValue); const bool tempNewSetting = Deserialize(inData) != 0; // we're changing the state of bypass if (tempNewSetting != IsBypassEffect()) { if (!tempNewSetting && IsBypassEffect() && IsInitialized()) { // turning bypass off and we're initialized Reset(kAudioUnitScope_Global, 0); } SetBypassEffect(tempNewSetting); } return noErr; } case kAudioUnitProperty_InPlaceProcessing: AUSDK_Require(inDataSize == sizeof(UInt32), kAudioUnitErr_InvalidPropertyValue); mProcessesInPlace = Deserialize(inData) != 0; return noErr; default: break; } } return AUBase::SetProperty(inID, inScope, inElement, inData, inDataSize); } void AUEffectBase::MaintainKernels() { #if TARGET_OS_IPHONE const UInt32 nKernels = mOnlyOneKernel ? 1 : GetNumberOfChannels(); #else const UInt32 nKernels = GetNumberOfChannels(); #endif if (mKernelList.size() < nKernels) { mKernelList.reserve(nKernels); for (auto i = static_cast(mKernelList.size()); i < nKernels; ++i) { mKernelList.push_back(NewKernel()); } } else { while (mKernelList.size() > nKernels) { mKernelList.pop_back(); } } for (UInt32 i = 0; i < nKernels; i++) { if (mKernelList[i]) { mKernelList[i]->SetChannelNum(i); } } } bool AUEffectBase::StreamFormatWritable(AudioUnitScope /*scope*/, AudioUnitElement /*element*/) { return !IsInitialized(); } OSStatus AUEffectBase::ChangeStreamFormat(AudioUnitScope inScope, AudioUnitElement inElement, const AudioStreamBasicDescription& inPrevFormat, const AudioStreamBasicDescription& inNewFormat) { AUSDK_Require_noerr(AUBase::ChangeStreamFormat(inScope, inElement, inPrevFormat, inNewFormat)); // for the moment this only dependency we know about // where a parameter's range may change is with the sample rate // and effects are only publishing parameters in the global scope! if (GetParamHasSampleRateDependency() && inPrevFormat.mSampleRate != inNewFormat.mSampleRate) { PropertyChanged(kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); } return noErr; } // ____________________________________________________________________________ // // This method is called (potentially repeatedly) by ProcessForScheduledParams() // in order to perform the actual DSP required for this portion of the entire buffer // being processed. The entire buffer can be divided up into smaller "slices" // according to the timestamps on the scheduled parameters... // OSStatus AUEffectBase::ProcessScheduledSlice(void* inUserData, UInt32 /*inStartFrameInBuffer*/, UInt32 inSliceFramesToProcess, UInt32 /*inTotalBufferFrames*/) AUSDK_RTSAFE { const ScheduledProcessParams& sliceParams = *static_cast(inUserData); AudioUnitRenderActionFlags& actionFlags = *sliceParams.actionFlags; AudioBufferList& inputBufferList = *sliceParams.inputBufferList; AudioBufferList& outputBufferList = *sliceParams.outputBufferList; UInt32 channelSize = inSliceFramesToProcess * mBytesPerFrame; // fix the size of the buffer we're operating on before we render this slice of time for (UInt32 i = 0; i < inputBufferList.mNumberBuffers; i++) { inputBufferList.mBuffers[i].mDataByteSize = // NOLINT inputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT } for (UInt32 i = 0; i < outputBufferList.mNumberBuffers; i++) { outputBufferList.mBuffers[i].mDataByteSize = // NOLINT outputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT } // process the buffer const OSStatus result = ProcessBufferLists(actionFlags, inputBufferList, outputBufferList, inSliceFramesToProcess); // we just partially processed the buffers, so increment the data pointers to the next part of // the buffer to process for (UInt32 i = 0; i < inputBufferList.mNumberBuffers; i++) { inputBufferList.mBuffers[i].mData = // NOLINT static_cast(inputBufferList.mBuffers[i].mData) + // NOLINT inputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT } for (UInt32 i = 0; i < outputBufferList.mNumberBuffers; i++) { outputBufferList.mBuffers[i].mData = // NOLINT static_cast(outputBufferList.mBuffers[i].mData) + // NOLINT outputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT } return result; } // ____________________________________________________________________________ // OSStatus AUEffectBase::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 nFrames) AUSDK_RTSAFE { AUSDK_Require(mMainInput->IsActive(), kAudioUnitErr_NoConnection); AUSDK_Require_noerr( mMainInput->PullInput(ioActionFlags, inTimeStamp, 0 /* element */, nFrames)); AudioBufferList& inputBufferList = AUSDK_UnwrapOrReturnError(mMainInput->GetBufferListOrError()); AudioBufferList& outputBufferList = AUSDK_UnwrapOrReturnError(mMainOutput->GetBufferListOrError()); if (ProcessesInPlace() && mMainOutput->WillAllocateBuffer()) { const auto setResult = mMainOutput->SetBufferListOrError(inputBufferList); if (!setResult) { return setResult.error(); } } OSStatus result = noErr; if (ShouldBypassEffect()) { // leave silence bit alone if (!ProcessesInPlace()) { auto val = mMainInput->CopyBufferContentsToOrError(outputBufferList); if (!val) { result = val.error(); } } } else { auto& paramEventList = GetParamEventList(); if (paramEventList.empty()) { // this will read/write silence bit result = ProcessBufferLists(ioActionFlags, inputBufferList, outputBufferList, nFrames); } else { // deal with scheduled parameters... ScheduledProcessParams processParams{ .actionFlags = &ioActionFlags, .inputBufferList = &inputBufferList, .outputBufferList = &outputBufferList }; // divide up the buffer into slices according to scheduled params then // do the DSP for each slice (ProcessScheduledSlice() called for each slice) result = ProcessForScheduledParams(paramEventList, nFrames, &processParams); // fixup the buffer pointers to how they were before we started const UInt32 channelSize = nFrames * mBytesPerFrame; for (UInt32 i = 0; i < inputBufferList.mNumberBuffers; i++) { const UInt32 size = inputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT inputBufferList.mBuffers[i].mData = // NOLINT static_cast(inputBufferList.mBuffers[i].mData) - size; // NOLINT inputBufferList.mBuffers[i].mDataByteSize = size; // NOLINT } for (UInt32 i = 0; i < outputBufferList.mNumberBuffers; i++) { const UInt32 size = outputBufferList.mBuffers[i].mNumberChannels * channelSize; // NOLINT outputBufferList.mBuffers[i].mData = // NOLINT static_cast(outputBufferList.mBuffers[i].mData) - size; // NOLINT outputBufferList.mBuffers[i].mDataByteSize = size; // NOLINT } } } if (((ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0u) && !ProcessesInPlace()) { AUBufferList::ZeroBuffer(outputBufferList); } return result; } OSStatus AUEffectBase::ProcessBufferLists(AudioUnitRenderActionFlags& ioActionFlags, const AudioBufferList& inBuffer, AudioBufferList& outBuffer, UInt32 inFramesToProcess) AUSDK_RTSAFE { if (ShouldBypassEffect()) { return noErr; } const bool silentInput = IsInputSilent(ioActionFlags, inFramesToProcess); ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; for (UInt32 channel = 0; channel < mKernelList.size(); ++channel) { auto& kernel = mKernelList[channel]; if (!kernel) { continue; } bool ioSilence = silentInput; const AudioBuffer* const srcBuffer = &inBuffer.mBuffers[channel]; // NOLINT subscript const AudioBuffer* const destBuffer = &outBuffer.mBuffers[channel]; // NOLINT subscript kernel->Process(static_cast(srcBuffer->mData), static_cast(destBuffer->mData), inFramesToProcess, ioSilence); if (!ioSilence) { ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; } } return noErr; } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUInputElement.cpp ================================================ /*! @file AudioUnitSDK/AUInputElement.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS constexpr bool HasGoodBufferPointers(const AudioBufferList& abl, UInt32 nBytes) noexcept { const AudioBuffer* buf = abl.mBuffers; // NOLINT for (UInt32 i = abl.mNumberBuffers; i > 0; --i, ++buf) { // NOLINT if (buf->mData == nullptr || buf->mDataByteSize < nBytes) { return false; } } return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AUInputElement::SetConnection // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void AUInputElement::SetConnection(const AudioUnitConnection& conn) { if (conn.sourceAudioUnit == nullptr) { Disconnect(); return; } mInputType = EInputType::FromConnection; mConnection = conn; AllocateBuffer(); } void AUInputElement::Disconnect() { mInputType = EInputType::NoInput; IOBuffer().Deallocate(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AUInputElement::SetInputCallback // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void AUInputElement::SetInputCallback(AURenderCallback proc, void* refCon) { if (proc == nullptr) { Disconnect(); } else { mInputType = EInputType::FromCallback; mInputProc = proc; mInputProcRefCon = refCon; AllocateBuffer(); } } OSStatus AUInputElement::SetStreamFormat(const AudioStreamBasicDescription& fmt) { const OSStatus err = AUIOElement::SetStreamFormat(fmt); if (err == noErr) { AllocateBuffer(); } return err; } OSStatus AUInputElement::PullInput(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, AudioUnitElement inElement, UInt32 nFrames) AUSDK_RTSAFE { AUSDK_Require(IsActive(), kAudioUnitErr_NoConnection); auto& iob = IOBuffer(); ExpectedPtr pullBuffer = (HasConnection() || !WillAllocateBuffer()) ? iob.PrepareNullBufferOrError(GetStreamFormat(), nFrames) : iob.PrepareBufferOrError(GetStreamFormat(), nFrames); if (!pullBuffer) { return pullBuffer.error(); } return PullInputWithBufferList(ioActionFlags, inTimeStamp, inElement, nFrames, *pullBuffer); } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUMIDIBase.cpp ================================================ /*! @file AudioUnitSDK/AUMIDIBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #if AUSDK_HAVE_MIDI #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS // MIDI CC data bytes constexpr uint8_t kMIDIController_AllSoundOff = 120u; constexpr uint8_t kMIDIController_ResetAllControllers = 121u; constexpr uint8_t kMIDIController_AllNotesOff = 123u; OSStatus AUMIDIBase::DelegateGetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) { (void)inScope; (void)inElement; (void)outDataSize; (void)outWritable; switch (inID) { // NOLINT if/else?! #if AUSDK_HAVE_XML_NAMES case kMusicDeviceProperty_MIDIXMLNames: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); AUSDK_Require(GetXMLNames(nullptr) == noErr, kAudioUnitErr_InvalidProperty); outDataSize = sizeof(CFURLRef); outWritable = false; return noErr; #endif #if AUSDK_HAVE_MIDI_MAPPING case kAudioUnitProperty_AllParameterMIDIMappings: AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); outWritable = true; outDataSize = sizeof(AUParameterMIDIMapping) * mMIDIMapper->GetNumberMaps(); return noErr; case kAudioUnitProperty_HotMapParameterMIDIMapping: case kAudioUnitProperty_AddParameterMIDIMapping: case kAudioUnitProperty_RemoveParameterMIDIMapping: AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); outWritable = true; outDataSize = sizeof(AUParameterMIDIMapping); return noErr; #endif default: return kAudioUnitErr_InvalidProperty; } } OSStatus AUMIDIBase::DelegateGetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { (void)inScope; (void)inElement; (void)outData; switch (inID) { // NOLINT if/else?! #if AUSDK_HAVE_XML_NAMES case kMusicDeviceProperty_MIDIXMLNames: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); CFURLRef url = nullptr; const auto result = GetXMLNames(&url); Serialize(url, outData); return result; } #endif #if AUSDK_HAVE_MIDI_MAPPING case kAudioUnitProperty_AllParameterMIDIMappings: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); std::vector maps(mMIDIMapper->GetNumberMaps()); mMIDIMapper->GetMaps(maps.data()); Serialize(std::span(maps), outData); return noErr; } case kAudioUnitProperty_HotMapParameterMIDIMapping: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); AUParameterMIDIMapping map{}; mMIDIMapper->GetHotParameterMap(map); Serialize(map, outData); return noErr; } #endif default: return kAudioUnitErr_InvalidProperty; } } OSStatus AUMIDIBase::DelegateSetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { (void)inScope; (void)inElement; (void)inData; (void)inDataSize; switch (inID) { #if AUSDK_HAVE_MIDI_MAPPING case kAudioUnitProperty_AddParameterMIDIMapping: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); const auto validSize = (inDataSize > 0) && (inDataSize % sizeof(AUParameterMIDIMapping)) == 0; AUSDK_Require(validSize, kAudioUnitErr_InvalidPropertyValue); const auto maps = DeserializeArray(inData, inDataSize); mMIDIMapper->AddParameterMapping( maps.data(), static_cast(maps.size()), mAUBaseInstance); mAUBaseInstance.PropertyChanged( kAudioUnitProperty_AllParameterMIDIMappings, kAudioUnitScope_Global, 0); return noErr; } case kAudioUnitProperty_RemoveParameterMIDIMapping: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); const auto validSize = (inDataSize > 0) && (inDataSize % sizeof(AUParameterMIDIMapping)) == 0; AUSDK_Require(validSize, kAudioUnitErr_InvalidPropertyValue); const auto maps = DeserializeArray(inData, inDataSize); bool didChange = false; mMIDIMapper->RemoveParameterMapping( maps.data(), static_cast(maps.size()), didChange); if (didChange) { mAUBaseInstance.PropertyChanged( kAudioUnitProperty_AllParameterMIDIMappings, kAudioUnitScope_Global, 0); } return noErr; } case kAudioUnitProperty_HotMapParameterMIDIMapping: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); AUSDK_Require(inDataSize == sizeof(AUParameterMIDIMapping), kAudioUnitErr_InvalidPropertyValue); const auto map = Deserialize(inData); mMIDIMapper->SetHotMapping(map); return noErr; } case kAudioUnitProperty_AllParameterMIDIMappings: { AUSDK_Require(mMIDIMapper, kAudioUnitErr_InvalidProperty); AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); AUSDK_Require(inElement == 0, kAudioUnitErr_InvalidElement); const auto validSize = (inDataSize > 0) && (inDataSize % sizeof(AUParameterMIDIMapping)) == 0; AUSDK_Require(validSize, kAudioUnitErr_InvalidPropertyValue); const auto maps = DeserializeArray(inData, inDataSize); mMIDIMapper->ReplaceAllMaps(maps.data(), static_cast(maps.size()), mAUBaseInstance); return noErr; } #endif default: return kAudioUnitErr_InvalidProperty; } } constexpr uint8_t MIDIStatusNibbleValue(uint8_t status) noexcept { return (status & 0xF0U) >> 4u; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AUMIDIBase::HandleMIDIEvent // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus AUMIDIBase::HandleMIDIEvent( UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame) AUSDK_RTSAFE { AUSDK_Require(mAUBaseInstance.IsInitialized(), kAudioUnitErr_Uninitialized); #if AUSDK_HAVE_MIDI_MAPPING // you potentially have a choice to make here - if a param mapping matches, do you still want to // process the MIDI event or not. The default behaviour is to continue on with the MIDI event. if (mMIDIMapper) { if (mMIDIMapper->HandleHotMapping(status, channel, data1, mAUBaseInstance)) { AUSDK_RT_UNSAFE_BEGIN("FIXME: PropertyChanged is unsafe") mAUBaseInstance.PropertyChanged( kAudioUnitProperty_HotMapParameterMIDIMapping, kAudioUnitScope_Global, 0); AUSDK_RT_UNSAFE_END } else { mMIDIMapper->FindParameterMapEventMatch( status, channel, data1, data2, inStartFrame, mAUBaseInstance); } } #endif switch (MIDIStatusNibbleValue(status)) { case kMIDICVStatusNoteOn: if (data2 != 0u) { return HandleNoteOn(channel, data1, data2, inStartFrame); } else { // zero velocity translates to note off return HandleNoteOff(channel, data1, data2, inStartFrame); } case kMIDICVStatusNoteOff: return HandleNoteOff(channel, data1, data2, inStartFrame); default: return HandleNonNoteEvent(status, channel, data1, data2, inStartFrame); } } OSStatus AUMIDIBase::HandleNonNoteEvent( UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame) AUSDK_RTSAFE { switch (MIDIStatusNibbleValue(status)) { case kMIDICVStatusPitchBend: return HandlePitchWheel(channel, data1, data2, inStartFrame); case kMIDICVStatusProgramChange: return HandleProgramChange(channel, data1); case kMIDICVStatusChannelPressure: return HandleChannelPressure(channel, data1, inStartFrame); case kMIDICVStatusControlChange: { switch (data1) { case kMIDIController_AllNotesOff: return HandleAllNotesOff(channel); case kMIDIController_ResetAllControllers: return HandleResetAllControllers(channel); case kMIDIController_AllSoundOff: return HandleAllSoundOff(channel); default: return HandleControlChange(channel, data1, data2, inStartFrame); } } case kMIDICVStatusPolyPressure: return HandlePolyPressure(channel, data1, data2, inStartFrame); default: return noErr; } } OSStatus AUMIDIBase::SysEx(const UInt8* inData, UInt32 inLength) AUSDK_RTSAFE { AUSDK_Require(mAUBaseInstance.IsInitialized(), kAudioUnitErr_Uninitialized); return HandleSysEx(inData, inLength); } } // namespace ausdk AUSDK_END_NO_RT_WARNINGS #endif // AUSDK_HAVE_MIDI ================================================ FILE: src/AudioUnitSDK/AUMIDIEffectBase.cpp ================================================ /*! @file AudioUnitSDK/AUMIDIEffectBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include namespace ausdk { AUMIDIEffectBase::AUMIDIEffectBase(AudioComponentInstance inInstance, bool inProcessesInPlace) : AUEffectBase(inInstance, inProcessesInPlace), AUMIDIBase(*static_cast(this)) { } OSStatus AUMIDIEffectBase::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) { OSStatus result = AUEffectBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateGetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); } return result; } OSStatus AUMIDIEffectBase::GetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { OSStatus result = AUEffectBase::GetProperty(inID, inScope, inElement, outData); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateGetProperty(inID, inScope, inElement, outData); } return result; } OSStatus AUMIDIEffectBase::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { OSStatus result = AUEffectBase::SetProperty(inID, inScope, inElement, inData, inDataSize); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateSetProperty(inID, inScope, inElement, inData, inDataSize); } return result; } } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUOutputElement.cpp ================================================ /*! @file AudioUnitSDK/AUOutputElement.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include #include namespace ausdk { AUOutputElement::AUOutputElement(AUBase& audioUnit) : AUIOElement(audioUnit) { AllocateBuffer(); } AUOutputElement::AUOutputElement(AUBase& audioUnit, const AudioStreamBasicDescription& format) : AUIOElement{ audioUnit, format } { AllocateBuffer(); } OSStatus AUOutputElement::SetStreamFormat(const AudioStreamBasicDescription& desc) { AUSDK_Require_noerr(AUIOElement::SetStreamFormat(desc)); // inherited AllocateBuffer(); return noErr; } } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUPlugInDispatch.cpp ================================================ /*! @file AudioUnitSDK/AUPlugInDispatch.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ // clang-format off #include // must come first // clang-format on #include #include #include #include #if AUSDK_HAVE_MUSIC_DEVICE #include #endif #if AUSDK_HAVE_IO_UNITS #include #endif #include #include #include #define AUSDK_HAVE_MUSIC_DEVICE_PREPARE_RELEASE (AUSDK_HAVE_MUSIC_DEVICE && TARGET_OS_OSX) // NOLINT namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS // ------------------------------------------------------------------------------------------------ static auto AUInstance(void* self) { return reinterpret_cast( // NOLINT reinterpret_cast &(static_cast(self)->mInstanceStorage)); } // ------------------------------------------------------------------------------------------------ class AUInstanceGuard { public: explicit AUInstanceGuard(void* self) : mGuard(AUInstance(self)->GetMutex()) {} private: const AUEntryGuard mGuard; }; // ------------------------------------------------------------------------------------------------ static bool IsValidParameterValue(AudioUnitParameterValue value) { return std::isfinite(value); } static bool AreValidParameterEvents(const AudioUnitParameterEvent* events, UInt32 numEvents) { if (events == nullptr) { return true; } for (UInt32 i = 0; i < numEvents; ++i) { const auto& event = events[i]; // NOLINT switch (event.eventType) { case kParameterEvent_Immediate: { if (!IsValidParameterValue(event.eventValues.immediate.value)) { // NOLINT return false; } break; } case kParameterEvent_Ramped: { if (!IsValidParameterValue(event.eventValues.ramp.startValue) || // NOLINT !IsValidParameterValue(event.eventValues.ramp.endValue)) { // NOLINT return false; } break; } default: break; } } return true; } // ------------------------------------------------------------------------------------------------ static OSStatus AUMethodInitialize(void* self) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->DoInitialize(); } AUSDK_Catch(result) return result; } static OSStatus AUMethodUninitialize(void* self) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); AUInstance(self)->DoCleanup(); } AUSDK_Catch(result) return result; } static OSStatus AUMethodGetPropertyInfo(void* self, AudioUnitPropertyID prop, AudioUnitScope scope, AudioUnitElement elem, UInt32* outDataSize, Boolean* outWritable) { OSStatus result = noErr; try { UInt32 dataSize = 0; // 13517289 GetPropetyInfo was returning an uninitialized value when // there is an error. This is a problem for auval. bool writable = false; const AUInstanceGuard guard(self); result = AUInstance(self)->DispatchGetPropertyInfo(prop, scope, elem, dataSize, writable); if (outDataSize != nullptr) { *outDataSize = dataSize; } if (outWritable != nullptr) { *outWritable = static_cast(writable); } } AUSDK_Catch(result) return result; } static OSStatus AUMethodGetProperty(void* self, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize) { OSStatus result = noErr; try { bool writable = false; const AUInstanceGuard guard(self); if (ioDataSize == nullptr) { AUSDK_LogError("AudioUnitGetProperty: null size pointer"); return kAudio_ParamError; } if (outData == nullptr) { UInt32 dataSize = 0; result = AUInstance(self)->DispatchGetPropertyInfo( inID, inScope, inElement, dataSize, writable); *ioDataSize = dataSize; return result; } const auto clientBufferSize = *ioDataSize; if (clientBufferSize == 0) { AUSDK_LogError("AudioUnitGetProperty: *ioDataSize == 0 on entry"); return kAudio_ParamError; } UInt32 actualPropertySize = 0; AUSDK_Require_noerr(AUInstance(self)->DispatchGetPropertyInfo( inID, inScope, inElement, actualPropertySize, writable)); std::vector tempBuffer; void* destBuffer = nullptr; if (clientBufferSize < actualPropertySize) { tempBuffer.resize(actualPropertySize); destBuffer = tempBuffer.data(); } else { destBuffer = outData; } result = AUInstance(self)->DispatchGetProperty(inID, inScope, inElement, destBuffer); if (result == noErr) { if (clientBufferSize < actualPropertySize && !tempBuffer.empty()) { memcpy(outData, tempBuffer.data(), clientBufferSize); // ioDataSize remains correct, the number of bytes we wrote } else { *ioDataSize = actualPropertySize; } } else { *ioDataSize = 0; } } AUSDK_Catch(result) return result; } static OSStatus AUMethodSetProperty(void* self, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); if ((inData != nullptr) && (inDataSize != 0u)) { result = AUInstance(self)->DispatchSetProperty(inID, inScope, inElement, inData, inDataSize); } else { if (inData == nullptr && inDataSize == 0) { result = AUInstance(self)->DispatchRemovePropertyValue(inID, inScope, inElement); } else { if (inData == nullptr) { AUSDK_LogError("AudioUnitSetProperty: inData == NULL"); return kAudio_ParamError; } if (inDataSize == 0) { AUSDK_LogError("AudioUnitSetProperty: inDataSize == 0"); return kAudio_ParamError; } } } } AUSDK_Catch(result) return result; } static OSStatus AUMethodAddPropertyListener( void* self, AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc, void* userData) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->AddPropertyListener(prop, proc, userData); } AUSDK_Catch(result) return result; } static OSStatus AUMethodRemovePropertyListener( void* self, AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->RemovePropertyListener(prop, proc, nullptr, false); } AUSDK_Catch(result) return result; } static OSStatus AUMethodRemovePropertyListenerWithUserData( void* self, AudioUnitPropertyID prop, AudioUnitPropertyListenerProc proc, void* userData) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->RemovePropertyListener(prop, proc, userData, true); } AUSDK_Catch(result) return result; } static OSStatus AUMethodAddRenderNotify(void* self, AURenderCallback proc, void* userData) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->SetRenderNotification(proc, userData); } AUSDK_Catch(result) return result; } static OSStatus AUMethodRemoveRenderNotify(void* self, AURenderCallback proc, void* userData) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->RemoveRenderNotification(proc, userData); } AUSDK_Catch(result) return result; } static OSStatus AUMethodGetParameter(void* self, AudioUnitParameterID param, AudioUnitScope scope, AudioUnitElement elem, AudioUnitParameterValue* value) { OSStatus result = noErr; try { // this is a (potentially) realtime method; no lock result = (value == nullptr ? kAudio_ParamError : AUInstance(self)->GetParameter(param, scope, elem, *value)); } AUSDK_Catch(result) return result; } static OSStatus AUMethodSetParameter(void* self, AudioUnitParameterID param, AudioUnitScope scope, AudioUnitElement elem, AudioUnitParameterValue value, UInt32 bufferOffset) { AUSDK_Require(IsValidParameterValue(value), kAudioUnitErr_InvalidParameterValue); OSStatus result = noErr; try { // this is a (potentially) realtime method; no lock result = AUInstance(self)->SetParameter(param, scope, elem, value, bufferOffset); } AUSDK_Catch(result) return result; } static OSStatus AUMethodScheduleParameters( void* self, const AudioUnitParameterEvent* events, UInt32 numEvents) { AUSDK_Require(AreValidParameterEvents(events, numEvents), kAudioUnitErr_InvalidParameterValue); OSStatus result = noErr; try { // this is a (potentially) realtime method; no lock result = AUInstance(self)->ScheduleParameter(events, numEvents); } AUSDK_Catch(result) return result; } // try/catch unneeded because DoRender is noexcept. static OSStatus AUMethodRender(void* self, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) noexcept AUSDK_RTSAFE { OSStatus result = noErr; // this is a processing method; no lock AudioUnitRenderActionFlags tempFlags{}; if (inTimeStamp == nullptr || ioData == nullptr) { result = kAudio_ParamError; } else { if (ioActionFlags == nullptr) { tempFlags = 0; ioActionFlags = &tempFlags; } result = AUInstance(self)->DoRender( *ioActionFlags, *inTimeStamp, inOutputBusNumber, inNumberFrames, *ioData); } return result; } // try/catch needed here because ComplexRender is virtual and can't be retroactively noexcept. static OSStatus AUMethodComplexRender(void* self, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberOfPackets, UInt32* outNumberOfPackets, AudioStreamPacketDescription* outPacketDescriptions, AudioBufferList* ioData, void* outMetadata, UInt32* outMetadataByteSize) noexcept AUSDK_RTSAFE { OSStatus result = noErr; try { // this is a processing method; no lock AudioUnitRenderActionFlags tempFlags{}; if (inTimeStamp == nullptr || ioData == nullptr) { result = kAudio_ParamError; } else { if (ioActionFlags == nullptr) { tempFlags = 0; ioActionFlags = &tempFlags; } result = AUInstance(self)->ComplexRender(*ioActionFlags, *inTimeStamp, inOutputBusNumber, inNumberOfPackets, outNumberOfPackets, outPacketDescriptions, *ioData, outMetadata, outMetadataByteSize); } } AUSDK_RT_UNSAFE(AUSDK_Catch(result)) return result; } static OSStatus AUMethodReset(void* self, AudioUnitScope scope, AudioUnitElement elem) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->DoReset(scope, elem); } AUSDK_Catch(result) return result; } // try/catch unneeded because DoProcess is noexcept. static OSStatus AUMethodProcess(void* self, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inNumberFrames, AudioBufferList* ioData) noexcept AUSDK_RTSAFE { OSStatus result = noErr; // this is a processing method; no lock bool doParamCheck = true; AudioUnitRenderActionFlags tempFlags{}; if (ioActionFlags == nullptr) { tempFlags = 0; ioActionFlags = &tempFlags; } else { if ((*ioActionFlags & kAudioUnitRenderAction_DoNotCheckRenderArgs) != 0u) { doParamCheck = false; } } if (doParamCheck && (inTimeStamp == nullptr || ioData == nullptr)) { result = kAudio_ParamError; } else { result = AUInstance(self)->DoProcess(*ioActionFlags, *inTimeStamp, inNumberFrames, *ioData); } return result; } // try/catch unneeded because DoProcessMultiple is noexcept. static OSStatus AUMethodProcessMultiple(void* self, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inNumberFrames, UInt32 inNumberInputBufferLists, const AudioBufferList** inInputBufferLists, UInt32 inNumberOutputBufferLists, AudioBufferList** ioOutputBufferLists) noexcept AUSDK_RTSAFE { OSStatus result = noErr; // this is a processing method; no lock bool doParamCheck = true; AudioUnitRenderActionFlags tempFlags{}; if (ioActionFlags == nullptr) { tempFlags = 0; ioActionFlags = &tempFlags; } else { if ((*ioActionFlags & kAudioUnitRenderAction_DoNotCheckRenderArgs) != 0u) { doParamCheck = false; } } if (doParamCheck && (inTimeStamp == nullptr || inInputBufferLists == nullptr || ioOutputBufferLists == nullptr)) { result = kAudio_ParamError; } else { result = AUInstance(self)->DoProcessMultiple(*ioActionFlags, *inTimeStamp, inNumberFrames, inNumberInputBufferLists, inInputBufferLists, inNumberOutputBufferLists, ioOutputBufferLists); } return result; } // ------------------------------------------------------------------------------------------------ #if AUSDK_HAVE_MUSIC_DEVICE static OSStatus AUMethodStart(void* self) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->Start(); } AUSDK_Catch(result) return result; } static OSStatus AUMethodStop(void* self) { OSStatus result = noErr; try { const AUInstanceGuard guard(self); result = AUInstance(self)->Stop(); } AUSDK_Catch(result) return result; } #endif // AUSDK_HAVE_MUSIC_DEVICE // ------------------------------------------------------------------------------------------------ #if AUSDK_HAVE_MIDI // I don't know what I'm doing here; conflicts with the multiple inheritence in MusicDeviceBase. static OSStatus AUMethodMIDIEvent( void* self, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { OSStatus result = noErr; try { // this is a potential render-time method; no lock result = AUInstance(self)->MIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); } AUSDK_Catch(result) return result; } static OSStatus AUMethodSysEx(void* self, const UInt8* inData, UInt32 inLength) { OSStatus result = noErr; try { // this is a potential render-time method; no lock result = AUInstance(self)->SysEx(inData, inLength); } AUSDK_Catch(result) return result; } #endif // AUSDK_HAVE_MIDI #if AUSDK_HAVE_MIDI2 static OSStatus AUMethodMIDIEventList( void* self, UInt32 inOffsetSampleFrame, const struct MIDIEventList* eventList) { if (eventList == nullptr) { return kAudio_ParamError; } OSStatus result = noErr; try { // this is a potential render-time method; no lock // Note that a MIDIEventList is variably-sized and can be backed by less memory than // required, so it is Undefined Behavior to form a reference to it; we must only use // pointers. result = AUInstance(self)->MIDIEventList(inOffsetSampleFrame, eventList); } AUSDK_Catch(result) return result; } #endif #if AUSDK_HAVE_MUSIC_DEVICE static OSStatus AUMethodStartNote(void* self, MusicDeviceInstrumentID inInstrument, MusicDeviceGroupID inGroupID, NoteInstanceID* outNoteInstanceID, UInt32 inOffsetSampleFrame, const MusicDeviceNoteParams* inParams) { OSStatus result = noErr; try { // this is a potential render-time method; no lock if (inParams == nullptr) { result = kAudio_ParamError; } else { result = AUInstance(self)->StartNote( inInstrument, inGroupID, outNoteInstanceID, inOffsetSampleFrame, *inParams); } } AUSDK_Catch(result) return result; } static OSStatus AUMethodStopNote(void* self, MusicDeviceGroupID inGroupID, NoteInstanceID inNoteInstanceID, UInt32 inOffsetSampleFrame) { OSStatus result = noErr; try { // this is a potential render-time method; no lock result = AUInstance(self)->StopNote(inGroupID, inNoteInstanceID, inOffsetSampleFrame); } AUSDK_Catch(result) return result; } #endif // AUSDK_HAVE_MUSIC_DEVICE #if AUSDK_HAVE_MUSIC_DEVICE_PREPARE_RELEASE static OSStatus AUMethodPrepareInstrument(void* self, MusicDeviceInstrumentID inInstrument) { OSStatus result = noErr; try { // this is a potential render-time method; no lock result = AUInstance(self)->PrepareInstrument(inInstrument); // NOLINT static via instance } AUSDK_Catch(result) return result; } static OSStatus AUMethodReleaseInstrument(void* self, MusicDeviceInstrumentID inInstrument) { OSStatus result = noErr; try { // this is a potential render-time method; no lock result = AUInstance(self)->ReleaseInstrument(inInstrument); // NOLINT static via instance } AUSDK_Catch(result) return result; } #endif // AUSDK_HAVE_MUSIC_DEVICE_PREPARE_RELEASE //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #pragma mark - #pragma mark Lookup Methods AudioComponentMethod AUBaseLookup::Lookup(SInt16 selector) { switch (selector) { case kAudioUnitInitializeSelect: return (AudioComponentMethod)AUMethodInitialize; // NOLINT cast case kAudioUnitUninitializeSelect: return (AudioComponentMethod)AUMethodUninitialize; // NOLINT cast case kAudioUnitGetPropertyInfoSelect: return (AudioComponentMethod)AUMethodGetPropertyInfo; // NOLINT cast case kAudioUnitGetPropertySelect: return (AudioComponentMethod)AUMethodGetProperty; // NOLINT cast case kAudioUnitSetPropertySelect: return (AudioComponentMethod)AUMethodSetProperty; // NOLINT cast case kAudioUnitAddPropertyListenerSelect: return (AudioComponentMethod)AUMethodAddPropertyListener; // NOLINT cast case kAudioUnitRemovePropertyListenerSelect: return (AudioComponentMethod)AUMethodRemovePropertyListener; // NOLINT cast case kAudioUnitRemovePropertyListenerWithUserDataSelect: return (AudioComponentMethod)AUMethodRemovePropertyListenerWithUserData; // NOLINT cast case kAudioUnitAddRenderNotifySelect: return (AudioComponentMethod)AUMethodAddRenderNotify; // NOLINT cast case kAudioUnitRemoveRenderNotifySelect: return (AudioComponentMethod)AUMethodRemoveRenderNotify; // NOLINT cast case kAudioUnitGetParameterSelect: return (AudioComponentMethod)AUMethodGetParameter; // NOLINT cast case kAudioUnitSetParameterSelect: return (AudioComponentMethod)AUMethodSetParameter; // NOLINT cast case kAudioUnitScheduleParametersSelect: return (AudioComponentMethod)AUMethodScheduleParameters; // NOLINT cast case kAudioUnitRenderSelect: return (AudioComponentMethod)AUMethodRender; // NOLINT cast case kAudioUnitResetSelect: return (AudioComponentMethod)AUMethodReset; // NOLINT cast default: break; } return nullptr; } AudioComponentMethod AUOutputLookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } switch (selector) { #if AUSDK_HAVE_IO_UNITS case kAudioOutputUnitStartSelect: return (AudioComponentMethod)AUMethodStart; // NOLINT cast case kAudioOutputUnitStopSelect: return (AudioComponentMethod)AUMethodStop; // NOLINT cast #endif // AUSDK_HAVE_IO_UNITS default: break; } return nullptr; } AudioComponentMethod AUComplexOutputLookup::Lookup(SInt16 selector) { AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } method = AUOutputLookup::Lookup(selector); if (method != nullptr) { return method; } if (selector == kAudioUnitComplexRenderSelect) { return (AudioComponentMethod)AUMethodComplexRender; // NOLINT cast } return nullptr; } AudioComponentMethod AUBaseProcessLookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } if (selector == kAudioUnitProcessSelect) { return (AudioComponentMethod)AUMethodProcess; // NOLINT cast } return nullptr; } AudioComponentMethod AUBaseProcessMultipleLookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } if (selector == kAudioUnitProcessMultipleSelect) { return (AudioComponentMethod)AUMethodProcessMultiple; // NOLINT cast } return nullptr; } AudioComponentMethod AUBaseProcessAndMultipleLookup::Lookup(SInt16 selector) { AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } method = AUBaseProcessMultipleLookup::Lookup(selector); if (method != nullptr) { return method; } method = AUBaseProcessLookup::Lookup(selector); if (method != nullptr) { return method; } return nullptr; } #if AUSDK_HAVE_MIDI inline AudioComponentMethod MIDI_Lookup(SInt16 selector) { switch (selector) { case kMusicDeviceMIDIEventSelect: return (AudioComponentMethod)AUMethodMIDIEvent; // NOLINT cast case kMusicDeviceSysExSelect: return (AudioComponentMethod)AUMethodSysEx; // NOLINT cast #if AUSDK_HAVE_MIDI2 case kMusicDeviceMIDIEventListSelect: return (AudioComponentMethod)AUMethodMIDIEventList; // NOLINT cast #endif // AUSDK_HAVE_MIDI2 default: break; } return nullptr; } AudioComponentMethod AUMIDILookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } return MIDI_Lookup(selector); } AudioComponentMethod AUMIDIProcessLookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseProcessLookup::Lookup(selector); if (method != nullptr) { return method; } return MIDI_Lookup(selector); } #endif // AUSDK_HAVE_MIDI #if AUSDK_HAVE_MUSIC_DEVICE AudioComponentMethod AUMusicLookup::Lookup(SInt16 selector) { const AudioComponentMethod method = AUBaseLookup::Lookup(selector); if (method != nullptr) { return method; } switch (selector) { case kMusicDeviceStartNoteSelect: return (AudioComponentMethod)AUMethodStartNote; // NOLINT cast case kMusicDeviceStopNoteSelect: return (AudioComponentMethod)AUMethodStopNote; // NOLINT cast #if AUSDK_HAVE_MUSIC_DEVICE_PREPARE_RELEASE case kMusicDevicePrepareInstrumentSelect: return (AudioComponentMethod)AUMethodPrepareInstrument; // NOLINT cast case kMusicDeviceReleaseInstrumentSelect: return (AudioComponentMethod)AUMethodReleaseInstrument; // NOLINT cast #endif // AUSDK_HAVE_MUSIC_DEVICE_PREPARE_RELEASE default: break; } #if AUSDK_HAVE_MIDI return MIDI_Lookup(selector); #else return nullptr; #endif // AUSDK_HAVE_MIDI } #endif // AUSDK_HAVE_MUSIC_DEVICE AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/AUScopeElement.cpp ================================================ /*! @file AudioUnitSDK/AUScopeElement.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS //_____________________________________________________________________________ // // By default, parameterIDs may be arbitrarily spaced, and a flat map // will be used for access. Calling UseIndexedParameters() will // instead use an STL vector for faster indexed access. // This assumes the paramIDs are numbered 0.....inNumberOfParameters-1 // Call this before defining/adding any parameters with SetParameter() // void AUElement::UseIndexedParameters(UInt32 inNumberOfParameters) { mIndexedParameters.resize(inNumberOfParameters); mUseIndexedParameters = true; } //_____________________________________________________________________________ // // Helper method. // returns whether the specified paramID is known to the element // bool AUElement::HasParameterID(AudioUnitParameterID paramID) const AUSDK_RTSAFE { if (mUseIndexedParameters) { return paramID < mIndexedParameters.size(); } return mParameters.find(paramID) != mParameters.end(); } //_____________________________________________________________________________ // // caller assumes that this is actually an immediate parameter // Expected AUElement::GetParameterOrError( AudioUnitParameterID paramID) const AUSDK_RTSAFE { if (mUseIndexedParameters) { if (paramID >= mIndexedParameters.size()) { return Unexpected{ kAudioUnitErr_InvalidParameter }; } return mIndexedParameters[paramID].load(std::memory_order_acquire); } const auto i = mParameters.find(paramID); if (i == mParameters.end()) { return Unexpected{ kAudioUnitErr_InvalidParameter }; } return i->second.load(std::memory_order_acquire); } //_____________________________________________________________________________ // Expected AUElement::SetParameterOrError(AudioUnitParameterID paramID, AudioUnitParameterValue inValue, bool okWhenInitialized) AUSDK_RTSAFE { if (mUseIndexedParameters) { if (paramID >= mIndexedParameters.size()) { return Unexpected{ kAudioUnitErr_InvalidParameter }; } mIndexedParameters[paramID].store(inValue, std::memory_order_release); } else { const auto i = mParameters.find(paramID); if (i == mParameters.end()) { if (mAudioUnit.IsInitialized() && !okWhenInitialized) { // The AU should not be creating new parameters once initialized. // If a client tries to set an undefined parameter, we could throw as follows, // but this might cause a regression. So it is better to just fail silently. // Throw(kAudioUnitErr_InvalidParameter); AUSDK_LogError_RT( "Warning: %s SetParameter for undefined param ID %u while initialized. " "Ignoring.", mAudioUnit.GetLoggingString(), static_cast(paramID)); return Unexpected{ kAudioUnitErr_InvalidParameter }; } else { // create new entry in map for the paramID (only happens first time) AUSDK_RT_UNSAFE_BEGIN("only the first time") mParameters[paramID] = ParameterValue{ inValue }; AUSDK_RT_UNSAFE_END } } else { // paramID already exists in map so simply change its value i->second.store(inValue, std::memory_order_release); } } return {}; } //_____________________________________________________________________________ // OSStatus AUElement::SetScheduledEvent(AudioUnitParameterID paramID, const AudioUnitParameterEvent& inEvent, UInt32 /*inSliceOffsetInBuffer*/, UInt32 /*inSliceDurationFrames*/, bool okWhenInitialized) AUSDK_RTSAFE { if (inEvent.eventType != kParameterEvent_Immediate) { AUSDK_LogError_RT("Warning: %s was passed a ramped parameter event but does not implement " "them. Ignoring.", mAudioUnit.GetLoggingString()); return -2; } const auto res = SetParameterOrError( paramID, inEvent.eventValues.immediate.value, okWhenInitialized); // NOLINT return res ? noErr : res.error(); } //_____________________________________________________________________________ // void AUElement::GetParameterList(AudioUnitParameterID* outList) { if (mUseIndexedParameters) { const auto numParams = std::ssize(mIndexedParameters); std::iota(outList, std::next(outList, numParams), 0); } else { std::ranges::transform( mParameters, outList, [](const auto& keyValue) { return keyValue.first; }); } } //_____________________________________________________________________________ // static void AppendBytes(CFMutableDataRef data, const TriviallyCopySerializable auto& value) { CFDataAppendBytes(data, reinterpret_cast(&value), sizeof(value)); // NOLINT } //_____________________________________________________________________________ // void AUElement::SaveState(AudioUnitScope scope, CFMutableDataRef data) { AudioUnitParameterInfo paramInfo{}; const auto countOffset = CFDataGetLength(data); uint32_t paramsWritten = 0; const auto appendParameter = [&](AudioUnitParameterID paramID, AudioUnitParameterValue value) { if (mAudioUnit.GetParameterInfo(scope, paramID, paramInfo) == noErr) { if ((paramInfo.flags & kAudioUnitParameterFlag_CFNameRelease) != 0u) { if (paramInfo.cfNameString != nullptr) { CFRelease(paramInfo.cfNameString); } if (paramInfo.unit == kAudioUnitParameterUnit_CustomUnit && paramInfo.unitName != nullptr) { CFRelease(paramInfo.unitName); } } if (((paramInfo.flags & kAudioUnitParameterFlag_OmitFromPresets) != 0u) || ((paramInfo.flags & kAudioUnitParameterFlag_MeterReadOnly) != 0u)) { return; } } AppendBytes(data, CFSwapInt32HostToBig(paramID)); AppendBytes(data, CFSwapInt32HostToBig(std::bit_cast(value))); ++paramsWritten; }; constexpr UInt32 placeholderCount = 0; AppendBytes(data, placeholderCount); if (mUseIndexedParameters) { const auto numParams = static_cast(mIndexedParameters.size()); for (UInt32 i = 0; i < numParams; i++) { appendParameter(i, mIndexedParameters[i]); } } else { for (const auto& item : mParameters) { appendParameter(item.first, item.second); } } const auto count_BE = CFSwapInt32HostToBig(paramsWritten); memcpy(CFDataGetMutableBytePtr(data) + countOffset, // NOLINT ptr math &count_BE, sizeof(count_BE)); } //_____________________________________________________________________________ // const UInt8* AUElement::RestoreState(const UInt8* state) { const UInt8* p = state; const auto numParams = DeserializeBigUInt32AndAdvance(p); for (UInt32 i = 0; i < numParams; ++i) { const auto parameterID = DeserializeBigUInt32AndAdvance(p); const auto valueBytes = DeserializeBigUInt32AndAdvance(p); const auto value = std::bit_cast(valueBytes); std::ignore = SetParameterOrError(parameterID, value); } return p; } //_____________________________________________________________________________ // AUIOElement::AUIOElement(AUBase& audioUnit) : AUElement(audioUnit), mWillAllocate(true) { mStreamFormat = AudioStreamBasicDescription{ .mSampleRate = AUBase::kAUDefaultSampleRate, .mFormatID = kAudioFormatLinearPCM, .mFormatFlags = AudioFormatFlags(kAudioFormatFlagsNativeFloatPacked) | AudioFormatFlags(kAudioFormatFlagIsNonInterleaved), // NOLINT .mBytesPerPacket = sizeof(float), .mFramesPerPacket = 1, .mBytesPerFrame = sizeof(float), .mChannelsPerFrame = 2, .mBitsPerChannel = 32, // NOLINT .mReserved = 0 }; } //_____________________________________________________________________________ // OSStatus AUIOElement::SetStreamFormat(const AudioStreamBasicDescription& format) { mStreamFormat = format; // Clear the previous channel layout if it is inconsistent with the newly set format; // preserve it if it is acceptable, in case the new format has no layout. if (ChannelLayout().IsValid() && NumberChannels() != ChannelLayout().NumberChannels()) { RemoveAudioChannelLayout(); } return noErr; } //_____________________________________________________________________________ // inFramesToAllocate == 0 implies the AudioUnit's max-frames-per-slice will be used void AUIOElement::AllocateBuffer(UInt32 inFramesToAllocate) { if (GetAudioUnit().HasBegunInitializing()) { UInt32 framesToAllocate = inFramesToAllocate > 0 ? inFramesToAllocate : GetAudioUnit().GetMaxFramesPerSlice(); mIOBuffer.Allocate( mStreamFormat, (mWillAllocate && NeedsBufferSpace()) ? framesToAllocate : 0); } } //_____________________________________________________________________________ // void AUIOElement::DeallocateBuffer() { mIOBuffer.Deallocate(); } //_____________________________________________________________________________ // // AudioChannelLayout support // return an empty vector (ie. NO channel layouts) if the AU doesn't require channel layout // knowledge std::vector AUIOElement::GetChannelLayoutTags() { return {}; } // outLayoutPtr WILL be NULL if called to determine layout size UInt32 AUIOElement::GetAudioChannelLayout(AudioChannelLayout* outLayoutPtr, bool& outWritable) { outWritable = true; UInt32 size = mChannelLayout.IsValid() ? mChannelLayout.Size() : 0; if (size > 0 && outLayoutPtr != nullptr) { memcpy(outLayoutPtr, &mChannelLayout.Layout(), size); } return size; } // the incoming channel map will be at least as big as a basic AudioChannelLayout // but its contents will determine its actual size // Subclass should overide if channel map is writable OSStatus AUIOElement::SetAudioChannelLayout(const AudioChannelLayout& inLayout) { AUSDK_Require(NumberChannels() == AUChannelLayout::NumberChannels(inLayout), kAudioUnitErr_InvalidPropertyValue); mChannelLayout = inLayout; return noErr; } // Some units support optional usage of channel maps - typically converter units // that can do channel remapping between different maps. In that optional case // the user should be able to remove a channel map if that is possible. // Typically this is NOT the case (e.g., the 3DMixer even in the stereo case // needs to know if it is rendering to speakers or headphones) OSStatus AUIOElement::RemoveAudioChannelLayout() { mChannelLayout = {}; return noErr; } //_____________________________________________________________________________ // void AUScope::SetNumberOfElements(UInt32 numElements) { if (mDelegate != nullptr) { return mDelegate->SetNumberOfElements(numElements); } if (numElements > mElements.size()) { mElements.reserve(numElements); while (numElements > mElements.size()) { auto elem = mCreator->CreateElement(GetScope(), static_cast(mElements.size())); mElements.push_back(std::move(elem)); } } else { while (numElements < mElements.size()) { mElements.pop_back(); } } } //_____________________________________________________________________________ // bool AUScope::HasElementWithName() const { for (UInt32 i = 0; i < GetNumberOfElements(); ++i) { const ExpectedPtr el = GetElementOrError(i); if (el && el->HasName()) { return true; } } return false; } //_____________________________________________________________________________ // void AUScope::AddElementNamesToDict(CFMutableDictionaryRef inNameDict) const { if (HasElementWithName()) { const auto elementDict = Owned::from_create(CFDictionaryCreateMutable( nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); for (UInt32 i = 0; i < GetNumberOfElements(); ++i) { const ExpectedPtr el = GetElementOrError(i); if (el && el->HasName()) { const auto key = Owned::from_create(CFStringCreateWithFormat( nullptr, nullptr, CFSTR("%u"), static_cast(i))); CFDictionarySetValue(*elementDict, *key, *el->GetName()); } } const auto key = Owned::from_create( CFStringCreateWithFormat(nullptr, nullptr, CFSTR("%u"), static_cast(mScope))); CFDictionarySetValue(inNameDict, *key, *elementDict); } } //_____________________________________________________________________________ // std::vector AUScope::RestoreElementNames(CFDictionaryRef inNameDict) const { // first we have to see if we have enough elements std::vector restoredElements; const auto maxElNum = GetNumberOfElements(); const auto dictSize = static_cast(std::max(CFDictionaryGetCount(inNameDict), CFIndex(0))); std::vector keys(dictSize); CFDictionaryGetKeysAndValues( inNameDict, reinterpret_cast(keys.data()), nullptr); // NOLINT for (size_t i = 0; i < dictSize; i++) { unsigned int intKey = 0; std::array string{}; CFStringGetCString(keys[i], string.data(), string.size(), kCFStringEncodingASCII); const int result = sscanf(string.data(), "%u", &intKey); // NOLINT // check if sscanf succeeded and element index is less than max elements. if ((result != 0) && (static_cast(intKey) < maxElNum)) { auto* const elName = static_cast(CFDictionaryGetValue(inNameDict, keys[i])); if ((elName != nullptr) && (CFGetTypeID(elName) == CFStringGetTypeID())) { const ExpectedPtr element = GetElementOrError(intKey); if (element) { element->SetName(elName); restoredElements.push_back(intKey); } } } } return restoredElements; } void AUScope::SaveState(CFMutableDataRef data) const { const AudioUnitElement elementCount = GetNumberOfElements(); for (AudioUnitElement elementIndex = 0; elementIndex < elementCount; ++elementIndex) { const ExpectedPtr element = GetElementOrError(elementIndex); const UInt32 parameterCount = element ? element->GetNumberOfParameters() : 0u; if (parameterCount > 0) { AppendBytes(data, CFSwapInt32HostToBig(GetScope())); AppendBytes(data, CFSwapInt32HostToBig(elementIndex)); element->SaveState(mScope, data); } } } const UInt8* AUScope::RestoreState(const UInt8* state) const { const UInt8* p = state; const auto elementIdx = DeserializeBigUInt32AndAdvance(p); const ExpectedPtr element = GetElementOrError(elementIdx); if (!element) { const auto numParams = DeserializeBigUInt32AndAdvance(p); constexpr auto entrySize = sizeof(AudioUnitParameterID) + sizeof(AudioUnitParameterValue); p += numParams * entrySize; // NOLINT } else { p = element->RestoreState(p); } return p; } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/ComponentBase.cpp ================================================ /*! @file AudioUnitSDK/ComponentBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ // self #include #include // std #include namespace ausdk { static OSStatus CB_GetComponentDescription( AudioComponentInstance inInstance, AudioComponentDescription* outDesc); std::recursive_mutex& ComponentBase::InitializationMutex() { __attribute__((no_destroy)) static std::recursive_mutex global; return global; } ComponentBase::ComponentBase(AudioComponentInstance inInstance) : mComponentInstance(inInstance) { (void)GetComponentDescription(); } void ComponentBase::DoPostConstructor() { PostConstructorInternal(); PostConstructor(); } void ComponentBase::DoPreDestructor() { PreDestructor(); PreDestructorInternal(); } OSStatus ComponentBase::AP_Open(void* self, AudioComponentInstance compInstance) { OSStatus result = noErr; const auto acpi = static_cast(self); try { const std::lock_guard guard{ InitializationMutex() }; auto* const cb = static_cast((*acpi->mConstruct)(&acpi->mInstanceStorage, compInstance)); cb->DoPostConstructor(); // allows base class to do additional initialization // once the derived class is fully constructed result = noErr; } AUSDK_Catch(result) if (result != noErr) { delete acpi; // NOLINT } return result; } OSStatus ComponentBase::AP_Close(void* self) { OSStatus result = noErr; try { const auto acpi = static_cast(self); if (const auto acImp = reinterpret_cast(&acpi->mInstanceStorage)) { // NOLINT acImp->DoPreDestructor(); (*acpi->mDestruct)(&acpi->mInstanceStorage); free(self); // NOLINT manual memory management } } AUSDK_Catch(result) return result; } AudioComponentDescription ComponentBase::GetComponentDescription() const { AudioComponentDescription desc{}; if (CB_GetComponentDescription(mComponentInstance, &desc) == noErr) { return desc; } return {}; } static OSStatus CB_GetComponentDescription( AudioComponentInstance inInstance, AudioComponentDescription* outDesc) { const AudioComponent comp = AudioComponentInstanceGetComponent(inInstance); if (comp != nullptr) { return AudioComponentGetDescription(comp, outDesc); } return kAudio_ParamError; } } // namespace ausdk ================================================ FILE: src/AudioUnitSDK/MusicDeviceBase.cpp ================================================ /*! @file AudioUnitSDK/MusicDeviceBase.cpp @copyright © 2000-2025 Apple Inc. All rights reserved. */ #include #if AUSDK_HAVE_MUSIC_DEVICE #include #include #include namespace ausdk { AUSDK_BEGIN_NO_RT_WARNINGS MusicDeviceBase::MusicDeviceBase( AudioComponentInstance inInstance, UInt32 numInputs, UInt32 numOutputs, UInt32 numGroups) : AUBase(inInstance, numInputs, numOutputs, numGroups), AUMIDIBase(*static_cast(this)) { } OSStatus MusicDeviceBase::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) { switch (inID) { // NOLINT if/else case kMusicDeviceProperty_InstrumentCount: AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); outDataSize = sizeof(UInt32); outWritable = false; return noErr; default: { auto result = AUBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateGetPropertyInfo( inID, inScope, inElement, outDataSize, outWritable); } return result; } } } OSStatus MusicDeviceBase::GetProperty( AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) { switch (inID) { // NOLINT if/else case kMusicDeviceProperty_InstrumentCount: { AUSDK_Require(inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope); UInt32 instrumentCount{}; const auto result = GetInstrumentCount(instrumentCount); Serialize(instrumentCount, outData); return result; } default: { auto result = AUBase::GetProperty(inID, inScope, inElement, outData); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateGetProperty(inID, inScope, inElement, outData); } return result; } } } OSStatus MusicDeviceBase::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize) { OSStatus result = AUBase::SetProperty(inID, inScope, inElement, inData, inDataSize); if (result == kAudioUnitErr_InvalidProperty) { result = AUMIDIBase::DelegateSetProperty(inID, inScope, inElement, inData, inDataSize); } return result; } // For a MusicDevice that doesn't support separate instruments (ie. is mono-timbral) // then this call should return an instrument count of zero and noErr OSStatus MusicDeviceBase::GetInstrumentCount(UInt32& outInstCount) const { outInstCount = 0; return noErr; } OSStatus MusicDeviceBase::HandleNoteOn( UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity, UInt32 inStartFrame) AUSDK_RTSAFE { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-warning-option" #pragma clang diagnostic ignored "-Wmissing-designated-field-initializers" const MusicDeviceNoteParams params{ .argCount = 2, .mPitch = static_cast(inNoteNumber), .mVelocity = static_cast(inVelocity) }; #pragma clang diagnostic pop return StartNote(kMusicNoteEvent_UseGroupInstrument, inChannel, nullptr, inStartFrame, params); } OSStatus MusicDeviceBase::HandleNoteOff( UInt8 inChannel, UInt8 inNoteNumber, UInt8 /*inVelocity*/, UInt32 inStartFrame) AUSDK_RTSAFE { return StopNote(inChannel, inNoteNumber, inStartFrame); } AUSDK_END_NO_RT_WARNINGS } // namespace ausdk #endif // AUSDK_HAVE_MUSIC_DEVICE ================================================ FILE: tests/AUThreadSafeListTests.mm ================================================ /*! @file AUThreadSafeListTests.mm @copyright © 2000-2025 Apple Inc. All rights reserved. */ #import #include #include #include #include #include #include #include class FauxRenderCallback { public: FauxRenderCallback() noexcept = default; bool operator==(const FauxRenderCallback& other) const noexcept = default; uint32_t mValue{ 0 }; }; @interface AUThreadSafeListTests : XCTestCase @end @implementation AUThreadSafeListTests - (BOOL)continueAfterFailure { return NO; } - (void)testAdd { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Add(cb); XCTAssertEqual(list.begin(), list.end()); list.Update(); XCTAssertEqual(*list.begin(), cb); } - (void)testAddNoUpdate { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Add(cb); // We should call list.Update() here XCTAssertEqual(list.begin(), list.end()); } - (void)testRemove { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Add(cb); XCTAssertEqual(list.begin(), list.end()); list.Update(); XCTAssertEqual(*list.begin(), cb); list.Remove(cb); XCTAssertEqual(*list.begin(), cb); list.Update(); XCTAssertEqual(list.begin(), list.end()); } - (void)testRemoveNoUpdate { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Add(cb); list.Update(); list.Remove(cb); // We should call list.Update() here XCTAssertEqual(*list.begin(), cb); } - (void)testRemoveOnEmptyList { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Remove(cb); list.Update(); XCTAssertEqual(list.begin(), list.end()); } - (void)testSingleClear { ausdk::AUThreadSafeList list; // Add FauxRenderCallback cb; cb.mValue = 56; list.Add(cb); list.Update(); list.Clear(); list.Update(); XCTAssertEqual(list.begin(), list.end()); } - (void)testBasicConsistency { static int objCounter = 0; constexpr auto kTestElements = 10000; class CountedObject { public: CountedObject() noexcept { objCounter++; } CountedObject(const CountedObject&) = delete; CountedObject(CountedObject&&) = delete; CountedObject& operator=(const CountedObject&) = default; CountedObject& operator=(CountedObject&&) = default; ~CountedObject() noexcept { objCounter--; } bool operator==(const CountedObject& other) const noexcept = default; uint32_t mValue{ 0 }; }; auto getListCount = [](const ausdk::AUThreadSafeList& list) { return std::ranges::distance(list); }; auto list = std::make_unique>(); std::vector mirrorState; // Add for (uint32_t i = 0; i < kTestElements; ++i) { CountedObject cb; cb.mValue = i; list->Add(cb); mirrorState.push_back(i); } list->Update(); XCTAssertEqual(getListCount(*list), kTestElements); uint32_t counter = 0; for (auto& callback : *list) { XCTAssertEqual(counter, callback.mValue); counter++; } // Remove for (uint32_t i = 0; i < kTestElements; i += 1000) { CountedObject cb; cb.mValue = i; list->Remove(cb); std::erase(mirrorState, i); } list->Update(); XCTAssertEqual(getListCount(*list), 9990); std::vector removedState; for (auto& node : *list) { removedState.push_back(node.mValue); } XCTAssertTrue(std::ranges::equal(removedState, mirrorState)); // Clear list->Clear(); list->Update(); XCTAssertEqual(getListCount(*list), 0); // Re-Add for (uint32_t i = 0; i < kTestElements; ++i) { CountedObject cb; cb.mValue = i; list->Add(cb); } list->Update(); XCTAssertEqual(getListCount(*list), kTestElements); list.reset(); XCTAssertEqual(objCounter, 0); } - (void)testAsyncConsistency { using sys_clock = std::chrono::system_clock; static constexpr auto kTimeout = 5; constexpr auto kTestElements = 1000; ausdk::AUThreadSafeList list; std::thread t([&list]() { for (uint32_t i = 0; i < kTestElements; i++) { FauxRenderCallback cb; cb.mValue = i; list.Add(cb); std::this_thread::sleep_for(std::chrono::milliseconds(2)); } }); t.detach(); std::thread t2([&list]() { auto start = sys_clock::now(); while (std::ranges::distance(list) < kTestElements) { list.Update(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (sys_clock::now() - start > std::chrono::seconds(kTimeout)) { break; } } }); t2.join(); XCTAssertEqual(std::ranges::distance(list), kTestElements); std::thread t3([&list]() { for (uint32_t i = 0; i < kTestElements; i++) { FauxRenderCallback cb; cb.mValue = i; list.Remove(cb); std::this_thread::sleep_for(std::chrono::milliseconds(2)); } }); t3.detach(); std::thread t4([&list]() { auto start = std::chrono::system_clock::now(); while (std::ranges::distance(list) > 0) { list.Update(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (sys_clock::now() - start > std::chrono::seconds(kTimeout)) { break; } } }); t4.join(); XCTAssertEqual(std::ranges::distance(list), 0); } @end ================================================ FILE: tests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: tests/Tests.mm ================================================ /*! @file Tests.mm @copyright © 2020-2025 Apple Inc. All rights reserved. */ #import #import #import #import #import #import #import #import #import @interface Tests : XCTestCase @end @implementation Tests - (void)testFlatMap { ausdk::flat_map uut; XCTAssertTrue(uut.empty()); uut[5] = 5.0; XCTAssertEqual(uut.size(), 1u); XCTAssertEqual(uut[5], 5.0); uut[5] = 5.5; XCTAssertEqual(uut.size(), 1u); XCTAssertEqual(uut[5], 5.5); uut[1] = 1.0; XCTAssertEqual(uut.size(), 2u); XCTAssertEqual(uut[1], 1.0); uut[15] = 15.0; XCTAssertEqual(uut.size(), 3u); XCTAssertEqual(uut[15], 15.0); XCTAssertEqual(uut.find(0), uut.end()); XCTAssertEqual(uut[1], 1.0); XCTAssertEqual(uut[5], 5.5); XCTAssertEqual(uut[15], 15.0); } - (void)testAUBufferList { // constexpr unsigned kLargeBufSize = 512; auto checkBuf = [](const AudioBuffer& buf, unsigned nch, unsigned nbytes, bool nullBuf) { XCTAssertEqual(buf.mNumberChannels, nch); XCTAssertEqual(buf.mDataByteSize, nbytes); if (nullBuf) { XCTAssertEqual(buf.mData, nullptr); } else { XCTAssertNotEqual(buf.mData, nullptr); } }; auto checkABL = [&](const AudioBufferList& abl, unsigned nbufs, unsigned nch, unsigned nbytes, bool nullBuf) { XCTAssertEqual(abl.mNumberBuffers, nbufs); for (unsigned idx = 0; idx < nbufs; ++idx) { checkBuf(abl.mBuffers[idx], nch, nbytes, nullBuf); } }; auto test = [&](const unsigned kNumBufs, const unsigned kFrameCount) { const auto kBufSize = kFrameCount * sizeof(float); ausdk::AUBufferList uut; const auto asbd = ausdk::ASBD::CreateCommonFloat32(44100.0, kNumBufs); uut.Allocate(asbd, kFrameCount); // Prepare 0 bytes checkABL(uut.PrepareBuffer(asbd, 0), kNumBufs, 1, 0, kBufSize == 0); if (kBufSize == 0) { // XCTAssertThrows(uut.PrepareBuffer(asbd, kLargeBufSize)); } else { checkABL(uut.PrepareBuffer(asbd, kFrameCount), kNumBufs, 1, kFrameCount * sizeof(float), false); } checkABL(uut.PrepareNullBuffer(asbd, kFrameCount), kNumBufs, 1, kFrameCount * sizeof(float), true); }; constexpr unsigned kTypicalFrameCount = 512; test(0, 0); test(1, 0); test(1, kTypicalFrameCount); test(2, kTypicalFrameCount); test(3, kTypicalFrameCount); test(4, kTypicalFrameCount); } - (void)testSerialize { constexpr float value = 123456789.f; std::vector data(sizeof(value)); ausdk::Serialize(value, data.data()); XCTAssertEqual(std::memcmp(&value, data.data(), data.size()), 0); // unaligned memory constexpr size_t offset = 1; const auto valueMemory = std::make_unique(sizeof(value) + offset); const auto valueAddress = valueMemory.get() + offset; ausdk::Serialize(value, valueAddress); XCTAssertEqual(std::memcmp(&value, valueAddress, sizeof(value)), 0); const std::vector values({ 1, 2, 3, 4, 5, 6, 7, 8, 9 }); data.clear(); data.resize(std::span(values).size_bytes()); ausdk::Serialize(std::span(values), data.data()); XCTAssertEqual(std::memcmp(values.data(), data.data(), data.size()), 0); } - (void)testDeserialize { constexpr float value = 987654321.f; XCTAssertEqual(ausdk::Deserialize(&value), value); // unaligned memory constexpr size_t offset = 1; const auto valueMemory = std::make_unique(sizeof(value) + offset); const auto valueAddress = valueMemory.get() + offset; std::memcpy(valueAddress, &value, sizeof(value)); XCTAssertEqual(ausdk::Deserialize(valueAddress), value); const std::vector values({ 9, 8, 7, 6, 5, 4, 3, 2, 1 }); XCTAssertTrue(std::ranges::equal( ausdk::DeserializeArray(values.data(), std::span(values).size_bytes()), values)); } - (void)testDeserializeBigUInt32AndAdvance { const auto data = std::to_array({ CFSwapInt32HostToBig(1), CFSwapInt32HostToBig(11), CFSwapInt32HostToBig(1'000'000'000), CFSwapInt32HostToBig(0), CFSwapInt32HostToBig(99) }); auto pointer = reinterpret_cast(data.data()); XCTAssertEqual(ausdk::DeserializeBigUInt32AndAdvance(pointer), 1u); XCTAssertEqual(ausdk::DeserializeBigUInt32AndAdvance(pointer), 11u); XCTAssertEqual(ausdk::DeserializeBigUInt32AndAdvance(pointer), 1'000'000'000u); XCTAssertEqual(ausdk::DeserializeBigUInt32AndAdvance(pointer), 0u); XCTAssertEqual(ausdk::DeserializeBigUInt32AndAdvance(pointer), 99u); XCTAssertEqual(pointer, static_cast(data.cend())); } - (void)testMakeStringFrom4CC { XCTAssertEqual(ausdk::MakeStringFrom4CC('abcd'), "abcd"); XCTAssertEqual(ausdk::MakeStringFrom4CC('1234' + 0x7F), "123."); } #if AUSDK_LOOSE_RT_SAFETY namespace { class ThrowsDuringRender : public ausdk::AUBase { public: ThrowsDuringRender() : ausdk::AUBase(nullptr, 1, 1) {} bool CanScheduleParameters() const noexcept AUSDK_RTSAFE override { return false; } bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override { return true; } AUSDK_BEGIN_NO_RT_NOEXCEPT_WARNINGS virtual OSStatus Render(AudioUnitRenderActionFlags& /*ioActionFlags*/, const AudioTimeStamp& /*inTimeStamp*/, UInt32 /*inNumberFrames*/) AUSDK_RTSAFE override { throw OSStatus(42); } virtual OSStatus ProcessBufferLists(AudioUnitRenderActionFlags&, const AudioBufferList&, AudioBufferList&, UInt32) AUSDK_RTSAFE override { throw OSStatus(43); } AUSDK_END_NO_RT_NOEXCEPT_WARNINGS }; class ThrowsDuringRenderFixture { public: constexpr static UInt32 kRenderFrameCount = 512; ThrowsDuringRender uut; ausdk::AUBufferList buf; ThrowsDuringRenderFixture() { uut.DoPostConstructor(); auto inputProc = +[](void*, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, AudioBufferList*) -> OSStatus { return noErr; }; uut.Input(0).SetInputCallback(inputProc, nullptr); OSStatus err = uut.DoInitialize(); XCTAssertEqual(err, noErr); AudioStreamBasicDescription format = uut.GetStreamFormat(kAudioUnitScope_Output, 0); buf.Allocate(format, kRenderFrameCount); buf.PrepareNullBuffer(format, kRenderFrameCount); } void Render() { AudioUnitRenderActionFlags flags = 0; AudioTimeStamp ts{}; OSStatus err = uut.DoRender(flags, ts, 0, kRenderFrameCount, buf.GetBufferList()); XCTAssertEqual(err, 42); } void Process() { AudioUnitRenderActionFlags flags = 0; AudioTimeStamp ts{}; OSStatus err = uut.DoProcess(flags, ts, kRenderFrameCount, buf.GetBufferList()); XCTAssertEqual(err, 43); } }; } // anonymous namespace - (void)testThrowsDuringRender { ThrowsDuringRenderFixture f; f.Render(); } - (void)testThrowsDuringProcess { ThrowsDuringRenderFixture f; f.Process(); } #endif // AUSDK_LOOSE_RT_SAFETY @end ================================================ FILE: tools/FindUB.sh ================================================ #! /bin/sh # Certain types can only safely be accessed through pointers, not C++ references. # This is due to their being variably-sized. # Find code that uses references to the problem types. SearchDir="$1" if [ -z "$SearchDir" ] ; then SearchDir=. fi egrep -r "(AURenderEvent|MIDI(Packet|Event)List)\s*(const\s*)?&" "$SearchDir" if [ $? -eq 0 ]; then echo "error: forming reference to a type which causes UB " exit 1 fi ================================================ FILE: tools/build.sh ================================================ #! /bin/sh # Assumes the root of the project is the current directory SDK=$1 DEST="$2" if [ -z "$DEST" ] ; then echo "usage: build.sh SDK DEST" exit 1 fi xcodebuild install -configuration Release -sdk $SDK DSTROOT="$DEST" || exit 1