Add optional debug logging in release builds.

Clicking the status bar icon with the option key held now reveals a
setting that enables debug logging in BGMApp. It's enabled by default in
debug builds.

It doesn't enable debug logging in BGMDriver or BGMXPCHelper yet.

This will hopefully make it easier for people to include logs when they
report bugs that don't occur with most hardware or are otherwise hard to
reproduce.

Enabling debug logging should be unlikely to cause audio glitches, but I
haven't tried to make it completely safe.

Also,
 - add some basic unit tests for BGMPlayThrough and expand the mocks for
   the CoreAudio HAL API,
 - fix the UI tests so you can run them without code signing them, and
 - update copyright years.
This commit is contained in:
Kyle Neideck 2020-01-23 22:18:13 +11:00
parent 8ca17bb5f8
commit 2c1677305d
No known key found for this signature in database
GPG key ID: CAA8D9B8E39EC18C
56 changed files with 3186 additions and 443 deletions

View file

@ -8,12 +8,21 @@
/* Begin PBXBuildFile section */
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMPlayThroughRTLogger.cpp"; }; };
19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMVolumeChangeListener.cpp"; }; };
19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };
19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMMusic.m"; }; };
19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };
19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };
19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMDebugLogging.c"; }; };
19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */; };
19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMStatusBarItem.mm"; }; };
1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAutoPauseMusicPrefs.mm"; }; };
@ -56,6 +65,15 @@
1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; };
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; };
1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */; };
1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */; };
1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */; };
1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */; };
1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */; };
1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */; };
1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C62FE5423D423D700B9B68E /* XCTest.framework */; };
1C62FE5823D4278300B9B68E /* travis-skip.py in Resources */ = {isa = PBXBuildFile; fileRef = 1C62FE5623D4278300B9B68E /* travis-skip.py */; };
1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */; };
1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMSystemSoundsVolume.mm"; }; };
1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; };
1C8034D520B0347A004BC50C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8034D420B0347A004BC50C /* Security.framework */; };
@ -93,7 +111,6 @@
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */; };
1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */; };
1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */; };
1CCC4F4E1E581C40008053E4 /* Mock_CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */; };
1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F611E584100008053E4 /* BGMAppUITests.mm */; };
1CD1FD301BDDEAF2004F7E1B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };
1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAppVolumesController.mm"; }; };
@ -138,9 +155,10 @@
1CD989581ECFFD250014BBBF /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; };
1CD989591ECFFD250014BBBF /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; };
1CD9895A1ECFFD250014BBBF /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; };
1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMDebugLoggingMenuItem.m"; }; };
1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };
1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };
1CE7064C1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMOutputDeviceMenuSection.mm"; }; };
1CEACF4D1F34793700FEC143 /* CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */; };
1CEACF4F1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */; };
1CED61691C3081C2002CAFCF /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 1CED61681C3081C2002CAFCF /* LICENSE */; };
1CED616C1C316E1A002CAFCF /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAudioDeviceManager.mm"; }; };
1CF2D58F1F944773008B6E35 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1963021BCAC160008A4DF7 /* CoreAudio.framework */; };
@ -229,9 +247,14 @@
/* Begin PBXFileReference section */
19FE70CF6C93F5007940CE91 /* BGMMusic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMMusic.h; path = "Music Players/BGMMusic.h"; sourceTree = "<group>"; };
19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMVolumeChangeListener.cpp; sourceTree = "<group>"; };
19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMPlayThroughRTLogger.h; sourceTree = "<group>"; };
19FE73389459BF65748F531F /* BGMDebugLogging.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = BGMDebugLogging.c; path = PublicUtility/BGMDebugLogging.c; sourceTree = "<group>"; };
19FE73822ADD50BA9120AB05 /* BGMMusic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMMusic.m; path = "Music Players/BGMMusic.m"; sourceTree = "<group>"; };
19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughTests.mm; path = UnitTests/BGMPlayThroughTests.mm; sourceTree = "<group>"; };
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMStatusBarItem.mm; sourceTree = "<group>"; };
19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMDebugLogging.h; path = PublicUtility/BGMDebugLogging.h; sourceTree = "<group>"; };
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMStatusBarItem.h; sourceTree = "<group>"; };
19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMPlayThroughRTLogger.cpp; sourceTree = "<group>"; };
19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMVolumeChangeListener.h; sourceTree = "<group>"; };
1C0BD0A31BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAutoPauseMusicPrefs.h; path = Preferences/BGMAutoPauseMusicPrefs.h; sourceTree = "<group>"; };
1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAutoPauseMusicPrefs.mm; path = Preferences/BGMAutoPauseMusicPrefs.mm; sourceTree = "<group>"; };
@ -282,7 +305,7 @@
1C3D36711ED90E8600F98E66 /* BGMDeviceControlsList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlsList.h; sourceTree = "<group>"; };
1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMAppVolumes.m; sourceTree = "<group>"; };
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumes.h; sourceTree = "<group>"; };
1C43DABE22F582780004AF35 /* Background Music.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Background Music.entitlements"; sourceTree = "<group>"; };
1C43DABE22F582780004AF35 /* BGMApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BGMApp.entitlements; sourceTree = "<group>"; };
1C4699461BD5C0E400F78043 /* BGMiTunes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMiTunes.m; path = "Music Players/BGMiTunes.m"; sourceTree = "<group>"; };
1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMDeviceControlSync.cpp; sourceTree = "<group>"; };
1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlSync.h; sourceTree = "<group>"; };
@ -290,6 +313,19 @@
1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMPreferredOutputDevices.mm; sourceTree = "<group>"; };
1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = "<group>"; };
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = "<group>"; };
1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObject.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioObject.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioObject.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObjects.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioSystemObject.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioDevice.h; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.h; sourceTree = SOURCE_ROOT; };
1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioDevice.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioDevice.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioDevice.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp; sourceTree = SOURCE_ROOT; };
1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObject.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.h; sourceTree = SOURCE_ROOT; };
1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObjects.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.h; sourceTree = SOURCE_ROOT; };
1C62FE5423D423D700B9B68E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
1C62FE5623D4278300B9B68E /* travis-skip.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = "travis-skip.py"; path = "UITests/travis-skip.py"; sourceTree = "<group>"; };
1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "BGMApp-Debug.entitlements"; sourceTree = "<group>"; };
1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughRTLoggerTests.mm; path = UnitTests/BGMPlayThroughRTLoggerTests.mm; sourceTree = "<group>"; };
1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMSystemSoundsVolume.h; sourceTree = "<group>"; };
1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMSystemSoundsVolume.mm; sourceTree = "<group>"; };
1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
@ -329,7 +365,6 @@
1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMXPCHelperTests.m; path = BGMXPCHelperTests/BGMXPCHelperTests.m; sourceTree = SOURCE_ROOT; };
1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "BGMAppUnitTests-Info.plist"; path = "UnitTests/BGMAppUnitTests-Info.plist"; sourceTree = "<group>"; };
1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMMusicPlayersUnitTests.mm; path = UnitTests/BGMMusicPlayersUnitTests.mm; sourceTree = "<group>"; };
1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioObject.cpp; path = UnitTests/Mock_CAHALAudioObject.cpp; sourceTree = "<group>"; };
1CCC4F541E584081008053E4 /* BGMAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "BGMAppUITests-Info.plist"; path = "UITests/BGMAppUITests-Info.plist"; sourceTree = "<group>"; };
1CCC4F611E584100008053E4 /* BGMAppUITests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAppUITests.mm; path = BGMAppTests/UITests/BGMAppUITests.mm; sourceTree = SOURCE_ROOT; };
@ -337,9 +372,10 @@
1CD410D21F9EDDAD0070A094 /* BGMAppVolumesController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumesController.h; sourceTree = "<group>"; };
1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAppVolumesController.mm; sourceTree = "<group>"; };
1CDE224022CBB95B0008E3AC /* Music.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Music.h; path = "Music Players/Music.h"; sourceTree = "<group>"; };
1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMDebugLoggingMenuItem.h; sourceTree = "<group>"; };
1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BGMDebugLoggingMenuItem.m; sourceTree = "<group>"; };
1CE7064A1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputDeviceMenuSection.h; sourceTree = "<group>"; };
1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputDeviceMenuSection.mm; sourceTree = "<group>"; };
1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = UnitTests/Mock_CAHALAudioSystemObject.cpp; sourceTree = "<group>"; };
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = "<group>"; };
1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = "<group>"; };
@ -414,6 +450,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */,
1C8B0C6B21645355008C5679 /* AVFoundation.framework in Frameworks */,
1CD989401ECFFCC50014BBBF /* AudioToolbox.framework in Frameworks */,
);
@ -499,6 +536,8 @@
1C8034C31BDAFD5700668E00 /* CAPThread.h */,
1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */,
1C1962E31BC94E15008A4DF7 /* CARingBuffer.h */,
19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */,
19FE73389459BF65748F531F /* BGMDebugLogging.c */,
);
name = PublicUtility;
sourceTree = "<group>";
@ -561,10 +600,25 @@
name = "Music Players";
sourceTree = "<group>";
};
1C62FE4423D3EAC500B9B68E /* Mocks */ = {
isa = PBXGroup;
children = (
1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */,
1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */,
1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */,
1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */,
1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */,
1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */,
1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */,
1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */,
1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */,
);
path = Mocks;
sourceTree = "<group>";
};
1CB8B32D1BBA75EF000E2DD1 = {
isa = PBXGroup;
children = (
1C43DABE22F582780004AF35 /* Background Music.entitlements */,
1CB8B3381BBA75EF000E2DD1 /* BGMApp */,
1CB8B34C1BBA75F0000E2DD1 /* BGMApp Tests */,
27379B901C7F57DB0084A24C /* BGMXPCHelper */,
@ -598,6 +652,8 @@
1C80DED220A6718600045BBE /* BGMAppWatcher.m */,
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */,
1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */,
1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */,
1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */,
1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */,
1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */,
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */,
@ -626,6 +682,8 @@
1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */,
1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,
1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,
19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */,
19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */,
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */,
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */,
1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,
@ -646,6 +704,8 @@
1CB8B3391BBA75EF000E2DD1 /* Supporting Files */ = {
isa = PBXGroup;
children = (
1C43DABE22F582780004AF35 /* BGMApp.entitlements */,
1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */,
275343BF1DFD01BC00DF3858 /* SystemPreferences.h */,
1CED61681C3081C2002CAFCF /* LICENSE */,
1CC1DF951BE8607700FB8FE4 /* Images.xcassets */,
@ -691,6 +751,7 @@
children = (
1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */,
1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */,
1C62FE5623D4278300B9B68E /* travis-skip.py */,
);
name = "Supporting Files";
sourceTree = "<group>";
@ -699,8 +760,9 @@
isa = PBXGroup;
children = (
1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */,
1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */,
1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */,
19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */,
1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */,
1C62FE4423D3EAC500B9B68E /* Mocks */,
);
name = "Unit Tests";
sourceTree = "<group>";
@ -756,6 +818,7 @@
2743CA1B1D86DA9B0089613B /* Frameworks */ = {
isa = PBXGroup;
children = (
1C62FE5423D423D700B9B68E /* XCTest.framework */,
1C8034D420B0347A004BC50C /* Security.framework */,
1C8B0C69216205BF008C5679 /* AVFoundation.framework */,
270A84501E0044EE00F13C99 /* ScriptingBridge.framework */,
@ -902,7 +965,7 @@
};
2743C9F51D86CFF90089613B = {
CreatedOnToolsVersion = 8.0;
ProvisioningStyle = Automatic;
ProvisioningStyle = Manual;
};
};
};
@ -948,6 +1011,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1C62FE5823D4278300B9B68E /* travis-skip.py in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1013,6 +1077,7 @@
files = (
1C86DA6A1F91EE3B000C8CCF /* CAPThread.cpp in Sources */,
1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,
1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */,
1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,
1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */,
@ -1067,6 +1132,8 @@
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */,
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */,
19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */,
19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */,
19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1076,6 +1143,7 @@
files = (
1C8104B022AD082E00B35517 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,
1C8D830520423E1C00A838F2 /* BGMSwinsian.m in Sources */,
1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,
1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,
1CC6593D1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
@ -1130,6 +1198,8 @@
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */,
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */,
19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */,
19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */,
19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1158,6 +1228,7 @@
27D643C01C9FB99200737F6E /* BGMXPCHelperService.mm in Sources */,
27D643C11C9FB99200737F6E /* main.m in Sources */,
277170161CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m in Sources */,
19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1165,10 +1236,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */,
1C8104AF22AD07E200B35517 /* BGMAppWatcher.m in Sources */,
1C8D830620423E2400A838F2 /* BGMSwinsian.m in Sources */,
1C227C0B1FA4C48200A95B6D /* BGMAppVolumes.m in Sources */,
1CEACF4D1F34793700FEC143 /* CAHALAudioDevice.cpp in Sources */,
1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,
1C8D830C2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,
1C3D36741ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,
@ -1176,14 +1247,16 @@
27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */,
27FB8C071DD75D0A0084DB9D /* BGMHermes.m in Sources */,
2743CA211D86DE780089613B /* BGMDeviceControlSync.cpp in Sources */,
1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */,
1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
2743CA0D1D86D7FA0089613B /* CACFDictionary.cpp in Sources */,
2743CA0E1D86D7FA0089613B /* CACFNumber.cpp in Sources */,
2743CA0F1D86D7FA0089613B /* CACFString.cpp in Sources */,
1CCC4F4E1E581C40008053E4 /* Mock_CAHALAudioObject.cpp in Sources */,
2743CA101D86D7FA0089613B /* CADebugger.cpp in Sources */,
1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */,
2743CA111D86D7FA0089613B /* CADebugMacros.cpp in Sources */,
1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */,
2743CA121D86D7FA0089613B /* CADebugPrintf.cpp in Sources */,
1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */,
2743CA141D86D7FA0089613B /* CAHALAudioStream.cpp in Sources */,
@ -1193,7 +1266,6 @@
2743CA191D86D7FA0089613B /* CARingBuffer.cpp in Sources */,
2743CA0A1D86D52D0089613B /* BGMAudioDeviceManager.mm in Sources */,
2743CA031D86D41C0089613B /* BGMScriptingBridge.m in Sources */,
1CEACF4F1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp in Sources */,
2743CA041D86D41C0089613B /* BGMMusicPlayer.m in Sources */,
1CD410D61F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,
2743CA051D86D41C0089613B /* BGMDecibel.m in Sources */,
@ -1201,14 +1273,21 @@
2743CA071D86D41C0089613B /* BGMVLC.m in Sources */,
1CF5423D1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */,
2743CA081D86D41C0089613B /* BGMVOX.m in Sources */,
1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */,
1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */,
2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */,
1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */,
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,
1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */,
2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */,
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */,
1C9258492090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,
19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */,
19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */,
19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */,
19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1217,6 +1296,7 @@
buildActionMask = 2147483647;
files = (
1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */,
19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1336,13 +1416,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_USE_OPTIMIZATION_PROFILE = NO;
CODE_SIGN_ENTITLEMENTS = "Background Music.entitlements";
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEPLOYMENT_POSTPROCESSING = NO;
DWARF_DSYM_FILE_NAME = "$(EXECUTABLE_NAME).dSYM";
DWARF_DSYM_FOLDER_PATH = "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)";
ENABLE_HARDENED_RUNTIME = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
INFOPLIST_FILE = BGMApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@ -1522,13 +1601,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_USE_OPTIMIZATION_PROFILE = NO;
CODE_SIGN_ENTITLEMENTS = "Background Music.entitlements";
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEPLOYMENT_POSTPROCESSING = NO;
DWARF_DSYM_FILE_NAME = "$(EXECUTABLE_NAME).dSYM";
DWARF_DSYM_FOLDER_PATH = "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)";
ENABLE_HARDENED_RUNTIME = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
INFOPLIST_FILE = BGMApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@ -1544,13 +1622,21 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_USE_OPTIMIZATION_PROFILE = NO;
CODE_SIGN_ENTITLEMENTS = "Background Music.entitlements";
CODE_SIGN_ENTITLEMENTS = BGMApp/BGMApp.entitlements;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEPLOYMENT_POSTPROCESSING = YES;
DWARF_DSYM_FILE_NAME = "$(EXECUTABLE_NAME).dSYM";
DWARF_DSYM_FOLDER_PATH = "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)";
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=0",
"CoreAudio_Debug=0",
"CoreAudio_StopOnAssert=0",
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
"CoreAudio_StopOnThrow=0",
"CoreAudio_UseSysLog=1",
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
INFOPLIST_FILE = BGMApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
@ -1568,14 +1654,17 @@
1CCC4F5C1E584081008053E4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TEST_TARGET_NAME = "Background Music";
WARNING_CFLAGS = "";
};
@ -1584,13 +1673,16 @@
1CCC4F5D1E584081008053E4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TEST_TARGET_NAME = "Background Music";
WARNING_CFLAGS = "";
};
@ -1599,14 +1691,17 @@
1CCC4F5E1E584081008053E4 /* DebugOpt */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TEST_TARGET_NAME = "Background Music";
WARNING_CFLAGS = "";
};
@ -1672,11 +1767,25 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=1",
"CoreAudio_StopOnAssert=1",
"CoreAudio_ThreadStampMessages=0",
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
"CoreAudio_StopOnThrow=0",
"BGM_UnitTest=1",
);
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
WARNING_CFLAGS = "";
};
name = Debug;
@ -1689,11 +1798,23 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=0",
"CoreAudio_Debug=0",
"CoreAudio_StopOnAssert=0",
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
"CoreAudio_StopOnThrow=0",
"BGM_UnitTest=1",
);
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
WARNING_CFLAGS = "";
};
name = Release;
@ -1706,11 +1827,25 @@
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=1",
"CoreAudio_StopOnAssert=1",
"CoreAudio_ThreadStampMessages=0",
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
"CoreAudio_StopOnThrow=0",
"BGM_UnitTest=1",
);
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
WARNING_CFLAGS = "";
};
name = DebugOpt;

View file

@ -26,9 +26,42 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
BuildableName = "BGMXPCHelper.xpc"
BlueprintName = "BGMXPCHelper"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
BuildableName = "BGMXPCHelper.xpc"
BlueprintName = "BGMXPCHelper"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
@ -41,24 +74,14 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
BuildableName = "BGMXPCHelper.xpc"
BlueprintName = "BGMXPCHelper"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
language = ""
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -82,6 +105,16 @@
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction

View file

@ -26,11 +26,47 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
BuildableName = "Background Music.app"
BlueprintName = "Background Music"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
BuildableName = "Background Music.app"
BlueprintName = "Background Music"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2743C9F51D86CFF90089613B"
@ -50,23 +86,14 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
BuildableName = "Background Music.app"
BlueprintName = "Background Music"
ReferencedContainer = "container:BGMApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -101,6 +128,16 @@
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<!--
Without this key, AddressSanitizer and the UI tests would only work when BGMApp is code signed.
-->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View file

@ -17,7 +17,7 @@
// BGMAppDelegate.h
// BGMApp
//
// Copyright © 2016, 2017 Kyle Neideck
// Copyright © 2016, 2017, 2020 Kyle Neideck
//
// Sets up and tears down the app.
//
@ -50,6 +50,7 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;
@property (unsafe_unretained) IBOutlet NSTextView* aboutPanelLicenseView;
@property (weak) IBOutlet NSMenuItem* autoPauseMenuItemUnwrapped;
@property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped;
@property (readonly) BGMAudioDeviceManager* audioDevices;

View file

@ -17,7 +17,7 @@
// BGMAppDelegate.mm
// BGMApp
//
// Copyright © 2016-2019 Kyle Neideck
// Copyright © 2016-2020 Kyle Neideck
//
// Self Include
@ -28,6 +28,7 @@
#import "BGMAppVolumesController.h"
#import "BGMAutoPauseMusic.h"
#import "BGMAutoPauseMenuItem.h"
#import "BGMDebugLoggingMenuItem.h"
#import "BGMMusicPlayers.h"
#import "BGMOutputDeviceMenuSection.h"
#import "BGMOutputVolumeMenuItem.h"
@ -66,6 +67,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
BGMAppVolumesController* appVolumes;
BGMOutputDeviceMenuSection* outputDeviceMenuSection;
BGMPreferencesMenu* prefsMenu;
BGMDebugLoggingMenuItem* debugLoggingMenuItem;
BGMXPCListener* xpcListener;
BGMPreferredOutputDevices* preferredOutputDevices;
}
@ -251,6 +253,11 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
aboutPanel:self.aboutPanel
aboutPanelLicenseView:self.aboutPanelLicenseView];
// Enable/disable debug logging. Hidden unless you option-click the status bar icon.
debugLoggingMenuItem =
[[BGMDebugLoggingMenuItem alloc] initWithMenuItem:self.debugLoggingMenuItemUnwrapped];
[statusBarItem setDebugLoggingMenuItem:debugLoggingMenuItem];
// Handle events about the main menu. (See the NSMenuDelegate methods below.)
self.bgmMenu.delegate = self;
}

View file

@ -17,7 +17,7 @@
// BGMAppVolumes.m
// BGMApp
//
// Copyright © 2016-2018 Kyle Neideck
// Copyright © 2016-2020 Kyle Neideck
// Copyright © 2017 Andrew Tonner
//
@ -196,14 +196,15 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
BGMAssert(button, "!button");
BGMAssert(menuItem, "!menuItem");
CGFloat width = menuItem.view.frame.size.width;
CGFloat height = menuItem.view.frame.size.height;
#if DEBUG
const char* appName = [((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];
CGFloat height = menuItem.view.frame.size.height;
#endif
const char* appName =
[((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];
// Using this function (instead of just ==) shouldn't be necessary, but just in case.
BOOL(^nearEnough)(CGFloat x, CGFloat y) = ^BOOL(CGFloat x, CGFloat y) {
return fabs(x - y) < 0.01; // We don't need much precision.

View file

@ -48,7 +48,7 @@ BGMAudioDevice::BGMAudioDevice(const CAHALAudioDevice& inDevice)
:
BGMAudioDevice(inDevice.GetObjectID())
{
};
}
BGMAudioDevice::~BGMAudioDevice()
{

View file

@ -16,7 +16,7 @@
// BGMAudioDevice.h
// BGMApp
//
// Copyright © 2017 Kyle Neideck
// Copyright © 2017, 2020 Kyle Neideck
//
// A HAL audio device. Note that this class's only state is the AudioObjectID of the device.
//
@ -59,7 +59,8 @@ public:
operator AudioObjectID() const { return GetObjectID(); }
/*!
@return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice.)
@return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice, not
the instance used for UI sounds.)
@throws CAException If the HAL returns an error when queried.
*/
bool IsBGMDevice() const { return IsBGMDevice(false); };

View file

@ -0,0 +1,48 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMDebugLoggingMenuItem.h
// BGMApp
//
// Copyright © 2020 Kyle Neideck
//
// A menu item in the main menu that enables/disables debug logging. Only visible if you hold the
// option down when you click the status bar icon to reveal the main menu.
//
// TODO: It would be better to have this menu item in the Preferences menu (maybe in an Advanced
// section) and always visible, but first we'd need to add something that tells the user how
// to view the log messages. Or better yet, something that automatically opens them.
//
// System Includes
#import <Cocoa/Cocoa.h>
#pragma clang assume_nonnull begin
@interface BGMDebugLoggingMenuItem : NSObject
- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem;
// True if the main menu is showing hidden items/options because the user held the option key when
// they clicked the icon. This class makes the debug logging menu item visible if this property has
// been set true or if debug logging is enabled.
@property (nonatomic) BOOL menuShowingExtraOptions;
@end
#pragma clang assume_nonnull end

View file

@ -0,0 +1,73 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMDebugLoggingMenuItem.m
// BGMApp
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#import "BGMDebugLoggingMenuItem.h"
// PublicUtility Includes
#import "BGMDebugLogging.h"
#import "CADebugMacros.h"
#pragma clang assume_nonnull begin
@implementation BGMDebugLoggingMenuItem {
NSMenuItem* _menuItem;
BOOL _menuShowingExtraOptions;
}
- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem {
if ((self = [super init])) {
_menuItem = menuItem;
_menuItem.state =
BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
[self setMenuShowingExtraOptions:NO];
// Enable/disable debug logging when the menu item is clicked.
menuItem.target = self;
menuItem.action = @selector(toggleDebugLogging);
}
return self;
}
- (void) setMenuShowingExtraOptions:(BOOL)showingExtra {
_menuShowingExtraOptions = showingExtra;
_menuItem.hidden = !BGMDebugLoggingIsEnabled() && !showingExtra;
DebugMsg("BGMDebugLoggingMenuItem::menuShowingExtraOptions: %s the menu item",
_menuItem.hidden ? "Hiding" : "Showing");
}
- (void) toggleDebugLogging {
BGMSetDebugLoggingEnabled(!BGMDebugLoggingIsEnabled());
_menuItem.state = BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
DebugMsg("BGMDebugLoggingMenuItem::toggleDebugLogging: Debug logging %s",
BGMDebugLoggingIsEnabled() ? "enabled" : "disabled");
}
@end
#pragma clang assume_nonnull end

View file

@ -17,7 +17,7 @@
// BGMPlayThrough.cpp
// BGMApp
//
// Copyright © 2016, 2017 Kyle Neideck
// Copyright © 2016, 2017, 2020 Kyle Neideck
//
// Self Include
@ -569,20 +569,21 @@ OSStatus BGMPlayThrough::WaitForOutputDeviceToStart() noexcept
while((theError != KERN_SUCCESS) && // Signalled from the IOProc.
(state == IOState::Starting) && // IO state changed.
(waitedNsec < kStartIOTimeoutNsec)); // Timed out.
#if DEBUG
UInt64 startedBy = mach_absolute_time();
struct mach_timebase_info baseInfo = { 0, 0 };
mach_timebase_info(&baseInfo);
UInt64 base = baseInfo.numer / baseInfo.denom;
DebugMsg("BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f ms "
"after entering WaitForOutputDeviceToStart.",
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,
static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);
#endif
if(BGMDebugLoggingIsEnabled())
{
UInt64 startedBy = mach_absolute_time();
struct mach_timebase_info baseInfo = { 0, 0 };
mach_timebase_info(&baseInfo);
UInt64 base = baseInfo.numer / baseInfo.denom;
DebugMsg("BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f "
"ms after entering WaitForOutputDeviceToStart.",
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,
static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);
}
// Figure out which error code to return.
switch (theError)
{
@ -604,7 +605,7 @@ OSStatus BGMPlayThrough::WaitForOutputDeviceToStart() noexcept
// Release any threads waiting for the output device to start. This function doesn't take mStateMutex
// because it gets called on the IO thread, which is realtime priority.
void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart() const
void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart()
{
if(mActive)
{
@ -612,13 +613,10 @@ void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart() const
if(semaphore != SEMAPHORE_NULL)
{
DebugMsg("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart: Releasing waiting threads");
mRTLogger.LogReleasingWaitingThreads();
kern_return_t theError = semaphore_signal_all(semaphore);
// TODO: Tell another thread to log this error, since we might be on a realtime thread.
BGM_Utils::LogIfMachError("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart",
"semaphore_signal_all",
theError);
mRTLogger.LogIfMachError_ReleaseWaitingThreadsSignal(theError);
}
}
}
@ -870,9 +868,8 @@ void BGMPlayThrough::HandleBGMDeviceIsRunning(BGMPlayThrough* refCon)
if(isRunningSomewhereOtherThanBGMApp)
{
#if DEBUG
refCon->mToldOutputDeviceToStartAt = mach_absolute_time();
#endif
// TODO: Handle expected exceptions (mostly CAExceptions from PublicUtility classes) in Start.
// For any that can't be handled sensibly in Start, catch them here and retry a few
// times (with a very short delay) before handling them by showing an unobtrusive error
@ -932,6 +929,7 @@ OSStatus BGMPlayThrough::InputDeviceIOProc(AudioObjectID inDevice,
IOState state;
UpdateIOProcState("InputDeviceIOProc",
refCon->mRTLogger,
refCon->mInputDeviceIOProcState,
refCon->mInputDeviceIOProcID,
refCon->mInputDevice,
@ -956,9 +954,8 @@ OSStatus BGMPlayThrough::InputDeviceIOProc(AudioObjectID inDevice,
refCon->mBuffer.Store(inInputData,
framesToStore,
static_cast<CARingBuffer::SampleTime>(inInputTime->mSampleTime));
HandleRingBufferError(err, "InputDeviceIOProc", "mBuffer.Store");
refCon->mRTLogger.LogIfRingBufferError_Store(err);
CAMemoryBarrier();
refCon->mLastInputSampleTime = inInputTime->mSampleTime;
@ -981,6 +978,7 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
IOState state;
const bool didChangeState = UpdateIOProcState("OutputDeviceIOProc",
refCon->mRTLogger,
refCon->mOutputDeviceIOProcState,
refCon->mOutputDeviceIOProcID,
refCon->mOutputDevice,
@ -1020,13 +1018,8 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - refCon->mLastInputSampleTime;
// Log if we dropped frames
if(refCon->mFirstInputSampleTime != refCon->mLastInputSampleTime)
{
DebugMsg("BGMPlayThrough::OutputDeviceIOProc: Dropped %f frames before output started. %s%f %s%f",
(refCon->mLastInputSampleTime - refCon->mFirstInputSampleTime),
"mFirstInputSampleTime=", refCon->mFirstInputSampleTime,
"mLastInputSampleTime=", refCon->mLastInputSampleTime);
}
refCon->mRTLogger.LogIfDroppedFrames(refCon->mFirstInputSampleTime,
refCon->mLastInputSampleTime);
}
CARingBuffer::SampleTime readHeadSampleTime =
@ -1054,11 +1047,10 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
}
if(lastInputSampleTime < readHeadSampleTime || outOfBounds)
{
DebugMsg("BGMPlayThrough::OutputDeviceIOProc: No input samples ready at output sample time. %s%lld %s%lld %s%f",
"lastInputSampleTime=", lastInputSampleTime,
"readHeadSampleTime=", readHeadSampleTime,
"mInToOutSampleOffset=", refCon->mInToOutSampleOffset);
refCon->mRTLogger.LogNoSamplesReady(lastInputSampleTime,
readHeadSampleTime,
refCon->mInToOutSampleOffset);
// Recalculate the in-to-out offset and read head
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - lastInputSampleTime;
readHeadSampleTime = static_cast<CARingBuffer::SampleTime>(inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);
@ -1066,16 +1058,20 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
// Copy the frames from the ring buffer
err = refCon->mBuffer.Fetch(outOutputData, framesToOutput, readHeadSampleTime);
HandleRingBufferError(err, "OutputDeviceIOProc", "mBuffer.Fetch");
// TODO: Not sure what else we should do to handle these errors, if anything. There's code in
// Apple's CAPlayThrough.cpp sample code that handles them. (Look for
// "kCARingBufferError_OK" around line 707.) Should be easy enough to use, but it's more
// complicated that just directly copying it.
refCon->mRTLogger.LogIfRingBufferError_Fetch(err);
refCon->mLastOutputSampleTime = inOutputTime->mSampleTime;
return noErr;
}
// static
bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
bool BGMPlayThrough::UpdateIOProcState(const char* inCallerName,
BGMPlayThroughRTLogger& inRTLogger,
std::atomic<IOState>& inState,
AudioDeviceIOProcID __nullable inIOProcID,
BGMAudioDevice& inDevice,
@ -1088,6 +1084,9 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
//
// compare_exchange_strong will return true iff it changed inState from Starting to Running.
// Otherwise it will set prevState to the current value of inState.
//
// TODO: We probably don't actually need memory_order_seq_cst (the default). Would it be worth
// changing? Might be worth checking for the other atomics/barriers in this class, too.
IOState prevState = IOState::Starting;
bool didChangeState = inState.compare_exchange_strong(prevState, IOState::Running);
@ -1105,21 +1104,28 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
{
// The IOProc isn't Starting or Running, so it must be Stopping. That is, it's been
// told to stop itself.
BGMAssert(outNewState == IOState::Stopping,
"BGMPlayThrough::UpdateIOProcState: Unexpected state: %d",
outNewState);
bool stoppedSuccessfully = false;
BGMLogAndSwallowExceptionsMsg("BGMPlayThrough::UpdateIOProcState", callerName, [&]() {
// TODO: If this throws, tell another thread to log the exception rather than
// logging it from a real-time thread.
try
{
inDevice.StopIOProc(inIOProcID);
// StopIOProc didn't throw, so the IOProc won't be called again until the next
// time playthrough is started.
stoppedSuccessfully = true;
});
}
catch(CAException e)
{
inRTLogger.LogExceptionStoppingIOProc(inCallerName, e.GetError());
}
catch(...)
{
inRTLogger.LogExceptionStoppingIOProc(inCallerName);
}
if(stoppedSuccessfully)
{
@ -1132,7 +1138,7 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
//
// Stop won't return until the IOProc has changed inState to Stopped, unless it
// times out, so Stop should still be waiting. And since Start and Stop are
// mutually exclusive, so this should be safe.
// mutually exclusive, this should be safe.
//
// But if Stop has timed out and inState has changed, we leave it in its new
// state (unless there's some ABA problem thing happening), which I suspect is
@ -1145,8 +1151,8 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
}
else
{
DebugMsg("BGMPlayThrough::UpdateIOProcState: inState changed since last read "
"outNewState = %d", outNewState);
inRTLogger.LogUnexpectedIOStateAfterStopping(inCallerName,
static_cast<int>(outNewState));
}
}
}
@ -1155,30 +1161,3 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
return didChangeState;
}
// static
void BGMPlayThrough::HandleRingBufferError(CARingBufferError inErr,
const char* inMethodName,
const char* inCallReturningErr)
{
#if DEBUG
if(inErr != kCARingBufferError_OK)
{
const char* errStr = (inErr == kCARingBufferError_TooMuch ? "kCARingBufferError_TooMuch" :
(inErr == kCARingBufferError_CPUOverload ? "kCARingBufferError_CPUOverload" : "unknown error"));
DebugMsg("BGMPlayThrough::%s: %s returned %s (%d)", inMethodName, inCallReturningErr, errStr, inErr);
// kCARingBufferError_CPUOverload wouldn't mean we have a bug, but I think kCARingBufferError_TooMuch would
if(inErr != kCARingBufferError_CPUOverload)
{
Throw(CAException(inErr));
}
}
#else
// Not sure what we should do to handle these errors in release builds, if anything.
// TODO: There's code in Apple's CAPlayThrough.cpp sample code that handles them. (Look for "kCARingBufferError_OK"
// around line 707.) Should be easy enough to use, but it's more complicated that just directly copying it.
#pragma unused (inErr, inMethodName, inCallReturningErr)
#endif
}

View file

@ -17,7 +17,7 @@
// BGMPlayThrough.h
// BGMApp
//
// Copyright © 2016, 2017 Kyle Neideck
// Copyright © 2016, 2017, 2020 Kyle Neideck
//
// Reads audio from an input device and immediately writes it to an output device. We currently use this class with the input
// device always set to BGMDevice and the output device set to the one selected in the preferences menu.
@ -41,10 +41,11 @@
// Local Includes
#include "BGMAudioDevice.h"
#include "BGMPlayThroughRTLogger.h"
// PublicUtility Includes
#include "CARingBuffer.h"
#include "CAMutex.h"
#include "CARingBuffer.h"
// STL Includes
#include <atomic>
@ -115,7 +116,8 @@ public:
OSStatus WaitForOutputDeviceToStart() noexcept;
private:
void ReleaseThreadsWaitingForOutputToStart() const;
/*! Real-time safe. */
void ReleaseThreadsWaitingForOutputToStart();
public:
OSStatus Stop();
@ -156,16 +158,13 @@ private:
// The IOProcs call this to update their IOState member. Also stops the IOProc if its state has been set to Stopping.
// Returns true if it changes the state.
static bool UpdateIOProcState(const char* __nullable callerName,
static bool UpdateIOProcState(const char* inCallerName,
BGMPlayThroughRTLogger& inRTLogger,
std::atomic<IOState>& inState,
AudioDeviceIOProcID __nullable inIOProcID,
BGMAudioDevice& inDevice,
IOState& outNewState);
static void HandleRingBufferError(CARingBufferError err,
const char* methodName,
const char* callReturningErr);
private:
CARingBuffer mBuffer;
@ -200,7 +199,9 @@ private:
// Subtract this from the output time to get the input time.
Float64 mInToOutSampleOffset { 0.0 };
BGMPlayThroughRTLogger mRTLogger;
};
#pragma clang assume_nonnull end

View file

@ -0,0 +1,496 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMPlayThroughRTLogger.cpp
// BGMApp
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "BGMPlayThroughRTLogger.h"
// Local Includes
#include "BGM_Utils.h"
// PublicUtility Includes
#include "CADebugMacros.h"
// STL Includes
#include <atomic>
// System Includes
#include <CoreAudio/CoreAudio.h>
#include <mach/mach_init.h>
#include <mach/task.h>
#include <unistd.h>
#pragma clang assume_nonnull begin
// Track the number of messages logged when built for the unit tests.
#if BGM_UnitTest
#define LogSync_Debug(inFormat, ...) do { \
mNumDebugMessagesLogged++; \
DebugMsg(inFormat, ## __VA_ARGS__); \
} while (0)
#else
#define LogSync_Debug(inFormat, ...) DebugMsg(inFormat, ## __VA_ARGS__)
#endif
#pragma mark Construction/Destruction
BGMPlayThroughRTLogger::BGMPlayThroughRTLogger()
{
// Create the semaphore we use to wake up the logging thread when it has messages to log.
mWakeUpLoggingThreadSemaphore = CreateSemaphore();
// Create the logging thread last because it starts immediately and expects the other member
// variables to be initialised.
mLoggingThread = std::thread(&BGMPlayThroughRTLogger::LoggingThreadEntry, this);
}
// static
semaphore_t BGMPlayThroughRTLogger::CreateSemaphore()
{
// TODO: Make a BGMMachSemaphore class to reduce some of this repetitive semaphore code.
// Create the semaphore.
semaphore_t semaphore;
kern_return_t error =
semaphore_create(mach_task_self(), &semaphore, SYNC_POLICY_FIFO, 0);
// Check the error code.
BGM_Utils::ThrowIfMachError("BGMPlayThroughRTLogger::CreateSemaphore",
"semaphore_create",
error);
ThrowIf(semaphore == SEMAPHORE_NULL,
CAException(kAudioHardwareUnspecifiedError),
"BGMPlayThroughRTLogger::CreateSemaphore: Failed to create semaphore");
return semaphore;
}
BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger()
{
// Stop the logging thread.
mLoggingThreadShouldExit = true;
kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger",
"semaphore_signal",
error);
if(error == KERN_SUCCESS)
{
// Wait for it to stop.
mLoggingThread.join();
// Destroy the semaphore.
error = semaphore_destroy(mach_task_self(), mWakeUpLoggingThreadSemaphore);
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger",
"semaphore_destroy",
error);
}
else
{
// If we couldn't tell it to wake up, it's not safe to wait for it to stop or to destroy the
// semaphore. We have to detach it so its destructor doesn't cause a crash.
mLoggingThread.detach();
}
}
#pragma mark Log Messages
void BGMPlayThroughRTLogger::LogReleasingWaitingThreads()
{
if(!BGMDebugLoggingIsEnabled())
{
return;
}
if(!mLogReleasingWaitingThreadsMsg.is_lock_free())
{
// Modifying mLogReleasingWaitingThreadsMsg might cause the thread to lock a mutex that
// isn't safe to lock on a realtime thread, so just give up.
return;
}
// Set the flag that tells the logging thread to log the message.
mLogReleasingWaitingThreadsMsg = true;
// Wake the logging thread so it can log the message.
WakeLoggingThread();
}
void BGMPlayThroughRTLogger::LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError)
{
if(inError == KERN_SUCCESS)
{
// No error.
return;
}
if(!mReleaseWaitingThreadsSignalError.is_lock_free())
{
// Modifying mReleaseWaitingThreadsSignalError might cause the thread to lock a mutex that
// isn't safe to lock on a realtime thread, so just give up.
return;
}
mReleaseWaitingThreadsSignalError = inError;
WakeLoggingThread();
}
void BGMPlayThroughRTLogger::LogIfDroppedFrames(Float64 inFirstInputSampleTime,
Float64 inLastInputSampleTime)
{
if(inFirstInputSampleTime == inLastInputSampleTime || !BGMDebugLoggingIsEnabled())
{
// Either we didn't drop any initial frames or we don't need to log a message about it.
return;
}
LogAsync(mDroppedFrames, [&]()
{
// Store the data to include in the log message.
mDroppedFrames.firstInputSampleTime = inFirstInputSampleTime;
mDroppedFrames.lastInputSampleTime = inLastInputSampleTime;
});
}
void BGMPlayThroughRTLogger::LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,
CARingBuffer::SampleTime inReadHeadSampleTime,
Float64 inInToOutSampleOffset)
{
if(!BGMDebugLoggingIsEnabled())
{
return;
}
LogAsync(mNoSamplesReady, [&]()
{
// Store the data to include in the log message.
mNoSamplesReady.lastInputSampleTime = inLastInputSampleTime;
mNoSamplesReady.readHeadSampleTime = inReadHeadSampleTime;
mNoSamplesReady.inToOutSampleOffset = inInToOutSampleOffset;
});
}
void BGMPlayThroughRTLogger::LogExceptionStoppingIOProc(const char* inCallerName,
OSStatus inError,
bool inErrorKnown)
{
LogAsync(mExceptionStoppingIOProc, [&]()
{
// Store the data to include in the log message.
mExceptionStoppingIOProc.callerName = inCallerName;
mExceptionStoppingIOProc.error = inError;
mExceptionStoppingIOProc.errorKnown = inErrorKnown;
});
}
void BGMPlayThroughRTLogger::LogUnexpectedIOStateAfterStopping(const char* inCallerName,
int inIOState)
{
LogAsync(mUnexpectedIOStateAfterStopping, [&]()
{
// Store the data to include in the log message.
mUnexpectedIOStateAfterStopping.callerName = inCallerName;
mUnexpectedIOStateAfterStopping.ioState = inIOState;
});
}
void BGMPlayThroughRTLogger::LogIfRingBufferError(CARingBufferError inError,
std::atomic<CARingBufferError>& outError)
{
if(inError == kCARingBufferError_OK)
{
// No error.
return;
}
if(!outError.is_lock_free())
{
// Modifying outError might cause the thread to lock a mutex that isn't safe to lock on
// a realtime thread, so just give up.
return;
}
// Store the error.
outError = inError;
// Wake the logging thread so it can log the error.
WakeLoggingThread();
}
template <typename T, typename F>
void BGMPlayThroughRTLogger::LogAsync(T& inMessageData, F&& inStoreMessageData)
{
if(!inMessageData.shouldLogMessage.is_lock_free())
{
// Modifying shouldLogMessage might cause the thread to lock a mutex that isn't safe to
// lock on a realtime thread, so just give up.
return;
}
if(inMessageData.shouldLogMessage)
{
// The logging thread could be reading inMessageData.
return;
}
// Store the data to include in the log message.
//
// std::forward lets the compiler treat inStoreMessageData as an rvalue if the caller gave it as
// an rvalue. No idea if that actually does anything.
std::forward<F>(inStoreMessageData)();
// shouldLogMessage is a std::atomic, so this store also makes sure that the non-atomic stores
// in inStoreMessageData will be visible to the logger thread (since the default memory order is
// memory_order_seq_cst).
inMessageData.shouldLogMessage = true;
WakeLoggingThread();
}
void BGMPlayThroughRTLogger::LogSync_Warning(const char* inFormat, ...)
{
va_list args;
va_start(args, inFormat);
#if BGM_UnitTest
mNumWarningMessagesLogged++;
#endif
LogWarning(inFormat, args);
va_end(args);
}
void BGMPlayThroughRTLogger::LogSync_Error(const char* inFormat, ...)
{
va_list args;
va_start(args, inFormat);
#if BGM_UnitTest
mNumErrorMessagesLogged++;
if(!mContinueOnErrorLogged)
{
LogError(inFormat, args);
}
#else
LogError(inFormat, args);
#endif
va_end(args);
}
#pragma mark Logging Thread
void BGMPlayThroughRTLogger::WakeLoggingThread()
{
kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);
BGMAssert(error == KERN_SUCCESS, "semaphore_signal (%d)", error);
// We can't do anything useful with the error in release builds. At least, not easily.
(void)error;
}
void BGMPlayThroughRTLogger::LogMessages()
{
// Log the messages/errors from the realtime threads (if any).
LogSync_ReleasingWaitingThreads();
LogSync_ReleaseWaitingThreadsSignalError();
LogSync_DroppedFrames();
LogSync_NoSamplesReady();
LogSync_ExceptionStoppingIOProc();
LogSync_UnexpectedIOStateAfterStopping();
LogSync_RingBufferError(mRingBufferStoreError, "InputDeviceIOProc");
LogSync_RingBufferError(mRingBufferFetchError, "OutputDeviceIOProc");
}
void BGMPlayThroughRTLogger::LogSync_ReleasingWaitingThreads()
{
if(mLogReleasingWaitingThreadsMsg)
{
LogSync_Debug("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart: "
"Releasing waiting threads");
// Reset it.
mLogReleasingWaitingThreadsMsg = false;
}
}
void BGMPlayThroughRTLogger::LogSync_ReleaseWaitingThreadsSignalError()
{
if(mReleaseWaitingThreadsSignalError != KERN_SUCCESS)
{
BGM_Utils::LogIfMachError("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart",
"semaphore_signal_all",
mReleaseWaitingThreadsSignalError);
// Reset it.
mReleaseWaitingThreadsSignalError = KERN_SUCCESS;
}
}
void BGMPlayThroughRTLogger::LogSync_DroppedFrames()
{
if(mDroppedFrames.shouldLogMessage)
{
LogSync_Debug("BGMPlayThrough::OutputDeviceIOProc: "
"Dropped %f frames before output started. %s%f %s%f",
(mDroppedFrames.lastInputSampleTime - mDroppedFrames.firstInputSampleTime),
"mFirstInputSampleTime=",
mDroppedFrames.firstInputSampleTime,
"mLastInputSampleTime=",
mDroppedFrames.lastInputSampleTime);
mDroppedFrames.shouldLogMessage = false;
}
}
void BGMPlayThroughRTLogger::LogSync_NoSamplesReady()
{
if(mNoSamplesReady.shouldLogMessage)
{
LogSync_Debug("BGMPlayThrough::OutputDeviceIOProc: "
"No input samples ready at output sample time. %s%lld %s%lld %s%f",
"lastInputSampleTime=", mNoSamplesReady.lastInputSampleTime,
"readHeadSampleTime=", mNoSamplesReady.readHeadSampleTime,
"mInToOutSampleOffset=", mNoSamplesReady.inToOutSampleOffset);
mNoSamplesReady.shouldLogMessage = false;
}
}
void BGMPlayThroughRTLogger::LogSync_ExceptionStoppingIOProc()
{
if(mExceptionStoppingIOProc.shouldLogMessage)
{
const char error4CC[5] = CA4CCToCString(mExceptionStoppingIOProc.error);
LogSync_Error("BGMPlayThrough::UpdateIOProcState: "
"Exception while stopping IOProc %s: %s (%d)",
mExceptionStoppingIOProc.callerName,
mExceptionStoppingIOProc.errorKnown ? error4CC : "unknown",
mExceptionStoppingIOProc.error);
mExceptionStoppingIOProc.shouldLogMessage = false;
}
}
void BGMPlayThroughRTLogger::LogSync_UnexpectedIOStateAfterStopping()
{
if(mUnexpectedIOStateAfterStopping.shouldLogMessage)
{
LogSync_Warning("BGMPlayThrough::UpdateIOProcState: "
"%s IO state changed since last read. state = %d",
mUnexpectedIOStateAfterStopping.callerName,
mUnexpectedIOStateAfterStopping.ioState);
mUnexpectedIOStateAfterStopping.shouldLogMessage = false;
}
}
void BGMPlayThroughRTLogger::LogSync_RingBufferError(
std::atomic<CARingBufferError>& ioRingBufferError,
const char* inMethodName)
{
CARingBufferError error = ioRingBufferError;
switch(error)
{
case kCARingBufferError_OK:
// No error.
return;
case kCARingBufferError_CPUOverload:
// kCARingBufferError_CPUOverload might not be our fault, so just log a warning.
LogSync_Warning("BGMPlayThrough::%s: Ring buffer error: "
"kCARingBufferError_CPUOverload (%d)",
inMethodName,
error);
break;
default:
// Other types of CARingBuffer errors should never occur. This will crash debug builds.
LogSync_Error("BGMPlayThrough::%s: Ring buffer error: %s (%d)",
inMethodName,
(error == kCARingBufferError_TooMuch ?
"kCARingBufferError_TooMuch" :
"unknown error"),
error);
break;
};
// Reset it.
ioRingBufferError = kCARingBufferError_OK;
}
// static
void* __nullable BGMPlayThroughRTLogger::LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon)
{
DebugMsg("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: "
"Starting the IOProc logging thread");
while(!inRefCon->mLoggingThreadShouldExit)
{
// Log the messages, if there are any to log.
inRefCon->LogMessages();
// Wait until woken up.
kern_return_t error = semaphore_wait(inRefCon->mWakeUpLoggingThreadSemaphore);
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry",
"semaphore_wait",
error);
}
DebugMsg("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: IOProc logging thread exiting");
return nullptr;
}
#if BGM_UnitTest
#pragma mark Test Helpers
bool BGMPlayThroughRTLogger::WaitUntilLoggerThreadIdle()
{
int msWaited = 0;
while(mLogReleasingWaitingThreadsMsg ||
mReleaseWaitingThreadsSignalError != KERN_SUCCESS ||
mDroppedFrames.shouldLogMessage ||
mNoSamplesReady.shouldLogMessage ||
mUnexpectedIOStateAfterStopping.shouldLogMessage ||
mExceptionStoppingIOProc.shouldLogMessage ||
mRingBufferStoreError != kCARingBufferError_OK ||
mRingBufferFetchError != kCARingBufferError_OK)
{
// Poll until the logger thread has nothing left to log. (Ideally we'd use a semaphore
// instead of polling, but it isn't worth the effort at this point.)
usleep(10 * 1000);
msWaited += 10;
// Time out after 5 seconds.
if(msWaited > 5000)
{
return false;
}
}
return true;
}
#endif /* BGM_UnitTest */
#pragma clang assume_nonnull end

View file

@ -0,0 +1,219 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMPlayThroughRTLogger.h
// BGMApp
//
// Copyright © 2020 Kyle Neideck
//
// A real-time safe logger for BGMPlayThrough. The messages are logged asynchronously by a
// non-realtime thread.
//
// For the sake of simplicity, this class is very closely coupled with BGMPlayThrough and its
// methods make assumptions about where they will be called. Also, if the same logging method is
// called multiple times before the logging thread next checks for messages, it will only log the
// message for one of those calls and ignore the others.
//
// This class's methods are real-time safe in that they return in a bounded amount of time and we
// think they're probably fast enough that the callers won't miss their deadlines, but we don't try
// to guarantee it. Some of them should only be called in unusual cases where it's worth increasing
// the risk of a thread missing its deadline.
//
#ifndef BGMApp__BGMPlayThroughRTLogger
#define BGMApp__BGMPlayThroughRTLogger
// PublicUtility Includes
#include "CARingBuffer.h"
// STL Includes
#include <thread>
// System Includes
#include <mach/error.h>
#include <mach/semaphore.h>
#pragma clang assume_nonnull begin
class BGMPlayThroughRTLogger
{
#pragma mark Construction/Destruction
public:
BGMPlayThroughRTLogger();
~BGMPlayThroughRTLogger();
BGMPlayThroughRTLogger(const BGMPlayThroughRTLogger&) = delete;
BGMPlayThroughRTLogger& operator=(
const BGMPlayThroughRTLogger&) = delete;
private:
static semaphore_t CreateSemaphore();
#pragma mark Log Messages
public:
/*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
void LogReleasingWaitingThreads();
/*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
void LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError);
/*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */
void LogIfDroppedFrames(Float64 inFirstInputSampleTime,
Float64 inLastInputSampleTime);
/*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */
void LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,
CARingBuffer::SampleTime inReadHeadSampleTime,
Float64 inInToOutSampleOffset);
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
void LogExceptionStoppingIOProc(const char* inCallerName)
{
LogExceptionStoppingIOProc(inCallerName, noErr, false);
}
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
void LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError)
{
LogExceptionStoppingIOProc(inCallerName, inError, true);
}
private:
void LogExceptionStoppingIOProc(const char* inCallerName,
OSStatus inError,
bool inErrorKnown);
public:
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
void LogUnexpectedIOStateAfterStopping(const char* inCallerName,
int inIOState);
/*! For BGMPlayThrough::OutputDeviceIOProc. */
void LogIfRingBufferError_Fetch(CARingBufferError inError)
{
LogIfRingBufferError(inError, mRingBufferFetchError);
}
/*! For BGMPlayThrough::InputDeviceIOProc. */
void LogIfRingBufferError_Store(CARingBufferError inError)
{
LogIfRingBufferError(inError, mRingBufferStoreError);
}
private:
void LogIfRingBufferError(CARingBufferError inError,
std::atomic<CARingBufferError>& outError);
template <typename T, typename F>
void LogAsync(T& inMessageData, F&& inStoreMessageData);
// Wrapper methods used to mock out the logging for unit tests.
void LogSync_Warning(const char* inFormat, ...) __printflike(2, 3);
void LogSync_Error(const char* inFormat, ...) __printflike(2, 3);
#pragma mark Logging Thread
private:
void WakeLoggingThread();
void LogMessages();
void LogSync_ReleasingWaitingThreads();
void LogSync_ReleaseWaitingThreadsSignalError();
void LogSync_DroppedFrames();
void LogSync_NoSamplesReady();
void LogSync_ExceptionStoppingIOProc();
void LogSync_UnexpectedIOStateAfterStopping();
void LogSync_RingBufferError(
std::atomic<CARingBufferError>& ioRingBufferError,
const char* inMethodName);
// The entry point of the logging thread (mLoggingThread).
static void* __nullable LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon);
#if BGM_UnitTest
#pragma mark Test Helpers
public:
/*!
* @return True if the logger thread finished logging the requested messages. False if it still
* had messages to log after 5 seconds.
*/
bool WaitUntilLoggerThreadIdle();
#endif /* BGM_UnitTest */
private:
// For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart
std::atomic<bool> mLogReleasingWaitingThreadsMsg { false };
std::atomic<kern_return_t> mReleaseWaitingThreadsSignalError { KERN_SUCCESS };
// For BGMPlayThrough::InputDeviceIOProc and BGMPlayThrough::OutputDeviceIOProc
struct {
Float64 firstInputSampleTime;
Float64 lastInputSampleTime;
std::atomic<bool> shouldLogMessage { false };
} mDroppedFrames;
struct {
CARingBuffer::SampleTime lastInputSampleTime;
CARingBuffer::SampleTime readHeadSampleTime;
Float64 inToOutSampleOffset;
std::atomic<bool> shouldLogMessage { false };
} mNoSamplesReady;
// For BGMPlayThrough::UpdateIOProcState
struct {
const char* callerName;
int ioState;
std::atomic<bool> shouldLogMessage { false };
} mUnexpectedIOStateAfterStopping;
struct {
const char* callerName;
OSStatus error;
bool errorKnown; // If false, we didn't get an error code from the exception.
std::atomic<bool> shouldLogMessage { false };
} mExceptionStoppingIOProc;
// For BGMPlayThrough::OutputDeviceIOProc
std::atomic<CARingBufferError> mRingBufferStoreError { kCARingBufferError_OK };
// For BGMPlayThrough::InputDeviceIOProc.
std::atomic<CARingBufferError> mRingBufferFetchError { kCARingBufferError_OK };
// Signalled to wake up the mLoggingThread when it has messages to log.
semaphore_t mWakeUpLoggingThreadSemaphore;
std::atomic<bool> mLoggingThreadShouldExit { false };
// The thread that actually logs the messages.
std::thread mLoggingThread;
#if BGM_UnitTest
public:
// Tests normally crash (abort) if LogError is called. This flag lets us test the code that
// would otherwise call LogError.
bool mContinueOnErrorLogged { false };
int mNumDebugMessagesLogged { 0 };
int mNumWarningMessagesLogged { 0 };
int mNumErrorMessagesLogged { 0 };
#endif /* BGM_UnitTest */
};
#pragma clang assume_nonnull end
#endif /* BGMApp__BGMPlayThroughRTLogger */

View file

@ -17,7 +17,7 @@
// BGMStatusBarItem.h
// BGMApp
//
// Copyright © 2019 Kyle Neideck
// Copyright © 2019, 2020 Kyle Neideck
//
// The button in the system status bar (the bar with volume, battery, clock, etc.) to show the main
// menu for the app. These are called "menu bar extras" in the Human Interface Guidelines.
@ -25,6 +25,7 @@
// Local Includes
#import "BGMAudioDeviceManager.h"
#import "BGMDebugLoggingMenuItem.h"
// System Includes
#import <Cocoa/Cocoa.h>
@ -57,6 +58,10 @@ static BGMStatusBarIcon const kBGMStatusBarIconDefaultValue = BGMFermataStatusBa
// same as the icon for the macOS volume status bar item.
@property BGMStatusBarIcon icon;
// If the user holds down the option key when they click the status bar icon, this menu item will be
// shown in the main menu.
- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem;
@end
#pragma clang assume_nonnull end

View file

@ -17,7 +17,7 @@
// BGMStatusBarItem.m
// BGMApp
//
// Copyright © 2019 Kyle Neideck
// Copyright © 2019, 2020 Kyle Neideck
//
// Self Include
@ -48,11 +48,16 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
NSImage* volumeIcon3SoundWaves;
NSStatusItem* statusBarItem;
BGMDebugLoggingMenuItem* debugLoggingMenuItem;
BGMVolumeChangeListener* volumeChangeListener;
id __nullable clickEventHandler;
BGMStatusBarIcon _icon;
}
#pragma mark Initialisation
- (instancetype) initWithMenu:(NSMenu*)bgmMenu
audioDevices:(BGMAudioDeviceManager*)devices
userDefaults:(BGMUserDefaults*)defaults {
@ -72,6 +77,10 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
// Set the menu item to open the main menu.
statusBarItem.menu = bgmMenu;
// Monitor click events so we can show extra options in the menu if the user was holding the
// option key.
clickEventHandler = [self addClickMonitor];
// Set the accessibility label to "Background Music". (We intentionally don't set a title or
// a tooltip.)
if ([BGMStatusBarItem buttonAvailable]) {
@ -92,8 +101,25 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
return self;
}
- (id __nullable) addClickMonitor {
NSEvent* __nullable (^handlerBlock)(NSEvent*) =
^NSEvent* __nullable (NSEvent* event) {
[self statusBarItemWasClicked:event];
return event;
};
// TODO: I doubt this works well with VoiceOver.
return [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown
handler:handlerBlock];
}
- (void) dealloc {
delete volumeChangeListener;
if (clickEventHandler) {
[NSEvent removeMonitor:(id)clickEventHandler];
clickEventHandler = nil;
}
}
- (void) initIcons {
@ -142,6 +168,8 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
[volumeIcon3SoundWaves setTemplate:YES];
}
#pragma mark Accessors
+ (BOOL) buttonAvailable {
// NSStatusItem doesn't have the "button" property on OS X 10.9.
return (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10);
@ -188,6 +216,8 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
});
}
#pragma mark Volume Icon
- (void) bgmDeviceVolumeDidChange {
if (self.icon == BGMVolumeStatusBarIcon) {
[self updateVolumeStatusBarIcon];
@ -251,6 +281,21 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
statusBarItem.image.name.UTF8String);
}
#pragma mark Debug Logging Menu Item
- (void) statusBarItemWasClicked:(NSEvent* __nonnull)event {
if ((event.modifierFlags & NSEventModifierFlagOption) != 0) {
DebugMsg("BGMStatusBarItem::statusBarItemWasClicked: Option key held");
[debugLoggingMenuItem setMenuShowingExtraOptions:YES];
} else {
[debugLoggingMenuItem setMenuShowingExtraOptions:NO];
}
}
- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem {
debugLoggingMenuItem = menuItem;
}
@end
#pragma clang assume_nonnull end

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14835.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<development version="8000" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14835.7"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -21,6 +20,7 @@
<outlet property="appVolumeView" destination="MWB-XH-kFI" id="eFA-RN-VMC"/>
<outlet property="autoPauseMenuItemUnwrapped" destination="nHv-T8-1nb" id="Lie-Cx-jw6"/>
<outlet property="bgmMenu" destination="8AN-nh-rEe" id="UWn-BX-eLy"/>
<outlet property="debugLoggingMenuItemUnwrapped" destination="sc9-vO-KyP" id="Zyd-0v-0RN"/>
<outlet property="outputVolumeLabel" destination="wfC-C6-SLv" id="Nuf-mo-osG"/>
<outlet property="outputVolumeSlider" destination="9Ru-Sc-dqC" id="wv0-Md-BwF"/>
<outlet property="outputVolumeView" destination="JOz-H1-mj9" id="xeJ-fk-NMI"/>
@ -68,6 +68,9 @@
</items>
</menu>
</menuItem>
<menuItem title="Debug Logging" hidden="YES" toolTip="Log detailed messages to help diagnose bugs. Search for &quot;bgm&quot; or &quot;background music&quot; in Console.app." id="sc9-vO-KyP">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Quit Background Music" id="Nj2-gJ-DhW">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@ -128,7 +131,7 @@
<rect key="frame" x="162" y="-1" width="12" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="L" id="hgE-7A-bez">
<font key="font" metaFont="miniSystem"/>
<font key="font" metaFont="menu" size="9"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -137,7 +140,7 @@
<rect key="frame" x="228" y="-1" width="12" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="R" id="lzr-NO-0Na">
<font key="font" metaFont="miniSystem"/>
<font key="font" metaFont="menu" size="9"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -244,7 +247,7 @@
<rect key="frame" x="413" y="20" width="203" height="11"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="The AirPlay Logo is a trademark of Apple Inc." id="lx7-k3-q16">
<font key="font" metaFont="miniSystem"/>
<font key="font" metaFont="menu" size="9"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -252,7 +255,7 @@
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="2" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vy4-dv-jQB">
<rect key="frame" x="18" y="75" width="346" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Copyright © 2016-2019 Background Music contributors" placeholderString="" id="ctF-95-uVu">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Copyright © 2016-2020 Background Music contributors" placeholderString="" id="ctF-95-uVu">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -330,53 +333,53 @@
<image name="buttonCell:IXo-C7-3uE:image" width="1" height="1">
<mutableData key="keyedArchiveRepresentation">
YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwXGB0iIycoLzI1Oz5VJG51bGzVDQ4PEBESExQVFlZOU1Np
emVWJGNsYXNzXE5TSW1hZ2VGbGFnc1ZOU1JlcHNXTlNDb2xvcoACgA0SIMMAAIADgAtWezEsIDF90hkO
GhxaTlMub2JqZWN0c6EbgASACtIZDh4hoh8ggAWABoAJEADSJA4lJl8QFE5TVElGRlJlcHJlc2VudGF0
aW9ugAeACE8RCMRNTQAqAAAACgAAABABAAADAAAAAQABAAABAQADAAAAAQABAAABAgADAAAAAgAIAAgB
AwADAAAAAQABAAABBgADAAAAAQABAAABCgADAAAAAQABAAABEQAEAAAAAQAAAAgBEgADAAAAAQABAAAB
FQADAAAAAQACAAABFgADAAAAAQABAAABFwAEAAAAAQAAAAIBHAADAAAAAQABAAABKAADAAAAAQACAAAB
UgADAAAAAQABAAABUwADAAAAAgABAAGHcwAHAAAH9AAAANAAAAAAAAAH9GFwcGwCIAAAbW50ckdSQVlY
WVogB9AAAgAOAAwAAAAAYWNzcEFQUEwAAAAAbm9uZQAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1h
cHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFZGVzYwAAAMAA
AABvZHNjbQAAATAAAAZmY3BydAAAB5gAAAA4d3RwdAAAB9AAAAAUa1RSQwAAB+QAAAAOZGVzYwAAAAAA
AAAVR2VuZXJpYyBHcmF5IFByb2ZpbGUAAAAAAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAHwAAAAxz
a1NLAAAAKgAAAYRlblVTAAAAKAAAAa5jYUVTAAAALAAAAdZ2aVZOAAAALAAAAgJwdEJSAAAAKgAAAi51
a1VBAAAALAAAAlhmckZVAAAAKgAAAoRodUhVAAAALgAAAq56aFRXAAAAEAAAAtxuYk5PAAAALAAAAuxr
b0tSAAAAGAAAAxhjc0NaAAAAJAAAAzBoZUlMAAAAIAAAA1Ryb1JPAAAAJAAAA3RkZURFAAAAOgAAA5hp
dElUAAAALgAAA9JzdlNFAAAALgAABAB6aENOAAAAEAAABC5qYUpQAAAAFgAABD5lbEdSAAAAJAAABFRw
dFBPAAAAOAAABHhubE5MAAAAKgAABLBlc0VTAAAAKAAABNp0aFRIAAAAJAAABQJ0clRSAAAAIgAABSZm
aUZJAAAALAAABUhockhSAAAAOgAABXRwbFBMAAAANgAABa5ydVJVAAAAJgAABeRhckVHAAAAKAAABgpk
YURLAAAANAAABjIAVgFhAGUAbwBiAGUAYwBuAP0AIABzAGkAdgD9ACAAcAByAG8AZgBpAGwARwBlAG4A
ZQByAGkAYwAgAEcAcgBhAHkAIABQAHIAbwBmAGkAbABlAFAAZQByAGYAaQBsACAAZABlACAAZwByAGkA
cwAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4A
ZwBQAGUAcgBmAGkAbAAgAEMAaQBuAHoAYQAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgE
OQAgBD8EQAQ+BEQEMAQ5BDsAIABHAHIAYQB5AFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUA
IABnAHIAaQBzAMEAbAB0AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABwAHIAbwBmAGkAbJAadShw
cJaOgnJfaWPPj/AARwBlAG4AZQByAGkAcwBrACAAZwByAOUAdABvAG4AZQBwAHIAbwBmAGkAbMd8vBgA
IABHAHIAYQB5ACDVBLhc0wzHfABPAGIAZQBjAG4A/QAgAWEAZQBkAP0AIABwAHIAbwBmAGkAbAXkBegF
1QXkBdkF3AAgAEcAcgBhAHkAIAXbBdwF3AXZAFAAcgBvAGYAaQBsACAAZwByAGkAIABnAGUAbgBlAHIA
aQBjAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkA
bABQAHIAbwBmAGkAbABvACAAZwByAGkAZwBpAG8AIABnAGUAbgBlAHIAaQBjAG8ARwBlAG4AZQByAGkA
cwBrACAAZwByAOUAcwBrAGEAbABlAHAAcgBvAGYAaQBsZm6QGnBwXqZjz4/wZYdO9k4AgiwwsDDsMKQw
1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgA7MDugPBA7kAUABlAHIAZgBpAGwA
IABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwBBAGwAZwBlAG0AZQBlAG4A
IABnAHIAaQBqAHMAcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAcgBpAHMAIABnAGUAbgDpAHIA
aQBjAG8OQg4bDiMORA4fDiUOTA4qDjUOQA4XDjIOFw4xDkgOJw5EDhsARwBlAG4AZQBsACAARwByAGkA
IABQAHIAbwBmAGkAbABpAFkAbABlAGkAbgBlAG4AIABoAGEAcgBtAGEAYQBwAHIAbwBmAGkAaQBsAGkA
RwBlAG4AZQByAGkBDQBrAGkAIABwAHIAbwBmAGkAbAAgAHMAaQB2AGkAaAAgAHQAbwBuAG8AdgBhAFUA
bgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAcwB6AGEAcgBvAVsAYwBpBB4EMQRJBDgE
OQAgBEEENQRABEsEOQAgBD8EQAQ+BEQEOAQ7BEwGRQZEBkEAIAYqBjkGMQZKBkEAIABHAHIAYQB5ACAG
JwZEBjkGJwZFAEcAZQBuAGUAcgBlAGwAIABnAHIA5QB0AG8AbgBlAGIAZQBzAGsAcgBpAHYAZQBsAHMA
ZQAAdGV4dAAAAABDb3B5cmlnaHQgMjAwNyBBcHBsZSBJbmMuLCBhbGwgcmlnaHRzIHJlc2VydmVkLgBY
WVogAAAAAAAA81EAAQAAAAEWzGN1cnYAAAAAAAAAAQHNAADSKSorLFokY2xhc3NuYW1lWCRjbGFzc2Vz
XxAQTlNCaXRtYXBJbWFnZVJlcKMrLS5aTlNJbWFnZVJlcFhOU09iamVjdNIpKjAxV05TQXJyYXmiMC7S
KSozNF5OU011dGFibGVBcnJheaMzMC7TNjcOODk6V05TV2hpdGVcTlNDb2xvclNwYWNlRDAgMAAQA4AM
0ikqPD1XTlNDb2xvcqI8LtIpKj9AV05TSW1hZ2WiPy4ACAARABoAJAApADIANwBJAEwAUQBTAGIAaABz
AHoAgQCOAJUAnQCfAKEApgCoAKoAsQC2AMEAwwDFAMcAzADPANEA0wDVANcA3ADzAPUA9wm/CcQJzwnY
CesJ7wn6CgMKCAoQChMKGAonCisKMgo6CkcKTApOClAKVQpdCmAKZQptAAAAAAAAAgEAAAAAAAAAQQAA
AAAAAAAAAAAAAAAACnA
S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCgpMDM2PD9VJG51bGzWDQ4PEBESExQVFhcYVk5T
U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN
EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdIlDyYnXxAU
TlNUSUZGUmVwcmVzZW50YXRpb26AB4AITxEIxE1NACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAAB
AAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAAB
AAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEWAAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAAB
AAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFTAAMAAAACAAEAAYdzAAcAAAf0AAAA0AAAAAAAAAf0
YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAAAABhY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAA
AAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAABmZjcHJ0AAAHmAAAADh3dHB0AAAH0AAAABRrVFJD
AAAH5AAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAAAAAAAAAAFUdlbmVyaWMg
R3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
bWx1YwAAAAAAAAAfAAAADHNrU0sAAAAqAAABhGVuVVMAAAAoAAABrmNhRVMAAAAsAAAB1nZpVk4AAAAs
AAACAnB0QlIAAAAqAAACLnVrVUEAAAAsAAACWGZyRlUAAAAqAAAChGh1SFUAAAAuAAACrnpoVFcAAAAQ
AAAC3G5iTk8AAAAsAAAC7GtvS1IAAAAYAAADGGNzQ1oAAAAkAAADMGhlSUwAAAAgAAADVHJvUk8AAAAk
AAADdGRlREUAAAA6AAADmGl0SVQAAAAuAAAD0nN2U0UAAAAuAAAEAHpoQ04AAAAQAAAELmphSlAAAAAW
AAAEPmVsR1IAAAAkAAAEVHB0UE8AAAA4AAAEeG5sTkwAAAAqAAAEsGVzRVMAAAAoAAAE2nRoVEgAAAAk
AAAFAnRyVFIAAAAiAAAFJmZpRkkAAAAsAAAFSGhySFIAAAA6AAAFdHBsUEwAAAA2AAAFrnJ1UlUAAAAm
AAAF5GFyRUcAAAAoAAAGCmRhREsAAAA0AAAGMgBWAWEAZQBvAGIAZQBjAG4A/QAgAHMAaQB2AP0AIABw
AHIAbwBmAGkAbABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAFAAcgBvAGYAaQBsAGUAUABlAHIAZgBp
AGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6AByAGkAYwBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAg
AHgA4QBtACAAQwBoAHUAbgBnAFAAZQByAGYAaQBsACAAQwBpAG4AegBhACAARwBlAG4A6QByAGkAYwBv
BBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAEcAcgBhAHkAUAByAG8AZgBpAGwAIABn
AOkAbgDpAHIAaQBxAHUAZQAgAGcAcgBpAHMAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/AByAGsAZQAg
AHAAcgBvAGYAaQBskBp1KHBwlo6Ccl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QB0AG8AbgBl
AHAAcgBvAGYAaQBsx3y8GAAgAEcAcgBhAHkAINUEuFzTDMd8AE8AYgBlAGMAbgD9ACABYQBlAGQA/QAg
AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAARwByAGEAeQAgBdsF3AXcBdkAUAByAG8AZgBpAGwAIABn
AHIAaQAgAGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBm
AGUAbgAtAFAAcgBvAGYAaQBsAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBp
AGMAbwBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QBzAGsAYQBsAGUAcAByAG8AZgBpAGxmbpAacHBepmPP
j/Blh072TgCCLDCwMOwwpDDXMO0w1TChMKQw6wOTA7UDvQO5A7oDzAAgA8ADwQO/A8YDrwO7ACADswO6
A8EDuQBQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGMAaQBuAHoAZQBuAHQAbwBz
AEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwBwAHIAbwBmAGkAZQBsAFAAZQByAGYAaQBsACAAZwBy
AGkAcwAgAGcAZQBuAOkAcgBpAGMAbw5CDhsOIw5EDh8OJQ5MDioONQ5ADhcOMg4XDjEOSA4nDkQOGwBH
AGUAbgBlAGwAIABHAHIAaQAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBh
AHAAcgBvAGYAaQBpAGwAaQBHAGUAbgBlAHIAaQENAGsAaQAgAHAAcgBvAGYAaQBsACAAcwBpAHYAaQBo
ACAAdABvAG4AbwB2AGEAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABzAHoAYQBy
AG8BWwBjAGkEHgQxBEkEOAQ5ACAEQQQ1BEAESwQ5ACAEPwRABD4ERAQ4BDsETAZFBkQGQQAgBioGOQYx
BkoGQQAgAEcAcgBhAHkAIAYnBkQGOQYnBkUARwBlAG4AZQByAGUAbAAgAGcAcgDlAHQAbwBuAGUAYgBl
AHMAawByAGkAdgBlAGwAcwBlAAB0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCBy
aWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAAAAABAc0AANIqKywtWiRj
bGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoywuL1pOU0ltYWdlUmVwWE5TT2JqZWN0
0iorMTJXTlNBcnJheaIxL9IqKzQ1Xk5TTXV0YWJsZUFycmF5ozQxL9M3OA85OjtXTlNXaGl0ZVxOU0Nv
bG9yU3BhY2VEMCAwABADgAzSKis9PldOU0NvbG9yoj0v0iorQEFXTlNJbWFnZaJALwAIABEAGgAkACkA
MgA3AEkATABRAFMAYgBoAHUAfACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA
5gDoAO0BBAEGAQgJ0AnVCeAJ6Qn8CgAKCwoUChkKIQokCikKOAo8CkMKSwpYCl0KXwphCmYKbgpxCnYK
fgAAAAAAAAIBAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAqBA
</mutableData>
</image>
</resources>

View file

@ -33,7 +33,7 @@
<key>NSAppleScriptEnabled</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016-2019 Background Music contributors</string>
<string>Copyright © 2016-2020 Background Music contributors</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSMicrophoneUsageDescription</key>

View file

@ -18,7 +18,7 @@
// BGMApp
//
// Copyright © 2016-2018 Kyle Neideck
// Portions copyright (C) 2012 Peter Ljunglöf. All rights reserved.
// Copyright (C) 2012 Peter Ljunglöf. All rights reserved.
//
// Self Include

View file

@ -17,7 +17,7 @@
// BGMAppUITests.mm
// BGMAppUITests
//
// Copyright © 2017, 2018 Kyle Neideck
// Copyright © 2017, 2018, 2020 Kyle Neideck
//
// You might want to use Xcode's UI test recording feature if you add new tests.
//
@ -30,6 +30,9 @@
// Scripting Bridge Includes
#import "BGMApp.h"
// System Includes
#import <XCTest/XCTest.h>
// TODO: Skip these tests if macOS SDK 10.11 or higher isn't available.
// TODO: Mock BGMDevice and music players.
@ -99,13 +102,15 @@
[menuItems[@"Quit Background Music"] click];
// BGMApp should quit.
XCTAssert(!app.exists);
for (NSRunningApplication* runningApp : [[NSWorkspace sharedWorkspace] runningApplications]) {
XCTAssertFalse([[runningApp bundleIdentifier] isEqualToString:@kBGMAppBundleID]);
}
[super tearDown];
}
- (void) testCycleOutputDevices {
const int NUM_CYCLES = 2;
const int NUM_CYCLES = 1;
// sbApp lets us use AppleScript to query BGMApp and check the test has made the changes to its
// settings we expect.

View file

@ -17,24 +17,28 @@
// BGMMusicPlayersUnitTests.mm
// BGMAppUnitTests
//
// Copyright © 2016-2018 Kyle Neideck
// Copyright © 2016-2020 Kyle Neideck
//
// Unit include
#import "BGMMusicPlayers.h"
// Local includes
#import "BGM_TestUtils.h"
// BGM includes
#import "BGM_Types.h"
#import "BGMAudioDeviceManager.h"
#import "BGMiTunes.h"
#import "BGMVLC.h"
#import "BGMDecibel.h"
#import "BGMSpotify.h"
#import "BGMVLC.h"
// Local includes
#import "BGM_TestUtils.h"
#import "MockAudioObject.h"
#import "MockAudioObjects.h"
// System includes
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
// Note that the PublicUtility classes that we use to communicate with the HAL, CAHALAudioObject and
@ -72,32 +76,6 @@
// -------------------------------------------------------------------------------------------------
class BGMMockBackgroundMusicDevice
:
public BGMBackgroundMusicDevice
{
public:
CFStringRef GetMusicPlayerBundleID() const;
void SetMusicPlayerBundleID(CFStringRef inBundleID);
private:
CFStringRef mMusicPlayerBundleID = CFSTR("");
};
CFStringRef BGMMockBackgroundMusicDevice::GetMusicPlayerBundleID() const
{
return mMusicPlayerBundleID;
}
void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID)
{
mMusicPlayerBundleID = inBundleID;
}
// -------------------------------------------------------------------------------------------------
@interface BGMMockAudioDeviceManager : BGMAudioDeviceManager
@end
@ -127,6 +105,10 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
- (void) setUp {
[super setUp];
// Mock BGMDevice.
MockAudioObjects::CreateMockDevice(kBGMDeviceUID);
MockAudioObjects::CreateMockDevice(kBGMDeviceUID_UISounds);
devices = [BGMMockAudioDeviceManager new];
defaults = [BGMMockUserDefaults new];
@ -137,9 +119,10 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
- (void) tearDown {
[super tearDown];
MockAudioObjects::DestroyMocks();
}
- (void) testNoSelectedMusicPlayerStored {
- (void) testNoSelectedMusicPlayerStored_iTunesDefault {
// Test the case where the user has never changed the music player preference.
// Test with iTunes as the default.
@ -156,16 +139,20 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, [BGMiTunes sharedMusicPlayerID]);
XCTAssertEqualObjects(players.selectedMusicPlayer.name, @"iTunes");
[self resetDevice];
}
- (void) testNoSelectedMusicPlayerStored_vlcDefault {
// Test the case where the user has never changed the music player preference.
// Test with VLC as the default.
players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
defaultMusicPlayerID:vlcID
musicPlayerClasses:@[ BGMiTunes.class,
BGMVLC.class,
BGMDecibel.class ]
userDefaults:defaults];
BGMMusicPlayers* players =
[[BGMMusicPlayers alloc] initWithAudioDevices:devices
defaultMusicPlayerID:vlcID
musicPlayerClasses:@[ BGMiTunes.class,
BGMVLC.class,
BGMDecibel.class ]
userDefaults:defaults];
XCTAssertEqual(players.musicPlayers.count, 3);
@ -193,15 +180,15 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, spotifyID);
XCTAssertEqualObjects(players.selectedMusicPlayer.name, @"Spotify");
[self resetDevice];
}
- (void) testUnrecognizedSelectedMusicPlayerInUserDefaults {
// If there's an unrecognized ID in user defaults, the default music player should be selected.
defaults.selectedPlayerID = [[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-0000-000000000000"];
// This initializer sets iTunes as the default music player and adds all the other music players.
players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
userDefaults:defaults];
BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
userDefaults:defaults];
XCTAssert(players.musicPlayers.count >= 6);
@ -226,10 +213,5 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
// TODO: Test setting the selectedMusicPlayer property
- (void) resetDevice {
// Reset the mock BGMDevice.
[devices bgmDevice].SetMusicPlayerBundleID(CFSTR(""));
}
@end

View file

@ -0,0 +1,174 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMPlayThroughRTLoggerTests.mm
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Unit Include
#import "BGMPlayThroughRTLogger.h"
// PublicUtility Includes
#import "CARingBuffer.h"
// System Includes
#import <CoreAudio/CoreAudio.h>
#import <XCTest/XCTest.h>
@interface BGMPlayThroughRTLoggerTests : XCTestCase
@end
@implementation BGMPlayThroughRTLoggerTests
{
BGMPlayThroughRTLogger* logger;
}
- (void) setUp {
[super setUp];
logger = new BGMPlayThroughRTLogger;
}
- (void) tearDown {
[super tearDown];
delete logger;
}
- (void) testLogReleasingWaitingThreads {
logger->LogReleasingWaitingThreads();
[self assertLoggedOneDebugMessage];
}
- (void) testLogIfMachError_ReleaseWaitingThreadsSignal {
logger->LogIfMachError_ReleaseWaitingThreadsSignal(KERN_SUCCESS);
[self assertLoggedNoMessages];
}
- (void) testLogIfDroppedFrames_didNotDropFrames {
logger->LogIfDroppedFrames(11256.0, 11256.0);
[self assertLoggedNoMessages];
}
- (void) testLogIfDroppedFrames_didDropFrames {
logger->LogIfDroppedFrames(11256.0, 11768.0);
[self assertLoggedOneDebugMessage];
}
- (void) testLogNoSamplesReady {
logger->LogNoSamplesReady(512, 1024, 512.0);
[self assertLoggedOneDebugMessage];
}
- (void) testLogExceptionStoppingIOProc_withoutErrorCode {
// Set a test-only flag that keeps it from calling abort() after logging the error when the
// tests are compiled with the debug configuration.
logger->mContinueOnErrorLogged = true;
logger->LogExceptionStoppingIOProc("InputDeviceIOProc");
[self assertLoggedOneErrorMessage];
}
- (void) testLogExceptionStoppingIOProc_withErrorCode {
// Set a test-only flag that keeps it from calling abort() after logging the error when the
// tests are compiled with the debug configuration.
logger->mContinueOnErrorLogged = true;
logger->LogExceptionStoppingIOProc("OutputDeviceIOProc", kAudioHardwareUnknownPropertyError);
[self assertLoggedOneErrorMessage];
}
- (void) testLogUnexpectedIOStateAfterStopping {
logger->LogUnexpectedIOStateAfterStopping("OutputDeviceIOProc", 1);
[self assertLoggedOneWarningMessage];
}
- (void) testLogIfRingBufferError_Fetch_noError {
logger->LogIfRingBufferError_Fetch(kCARingBufferError_OK);
[self assertLoggedNoMessages];
}
- (void) testLogIfRingBufferError_Fetch_errorCPUOverload {
logger->LogIfRingBufferError_Fetch(kCARingBufferError_CPUOverload);
[self assertLoggedOneWarningMessage];
}
- (void) testLogIfRingBufferError_Fetch_errorTooMuch {
// Set a test-only flag that keeps it from calling abort() after logging the error when the
// tests are compiled with the debug configuration.
logger->mContinueOnErrorLogged = true;
logger->LogIfRingBufferError_Fetch(kCARingBufferError_TooMuch);
[self assertLoggedOneErrorMessage];
}
- (void) testLogIfRingBufferError_Store_noError {
logger->LogIfRingBufferError_Store(kCARingBufferError_OK);
[self assertLoggedNoMessages];
}
- (void) testLogIfRingBufferError_Store_errorCPUOverload {
logger->LogIfRingBufferError_Store(kCARingBufferError_CPUOverload);
[self assertLoggedOneWarningMessage];
}
- (void) testLogIfRingBufferError_Store_errorTooMuch {
// Set a test-only flag that keeps it from calling abort() after logging the error when the
// tests are compiled with the debug configuration.
logger->mContinueOnErrorLogged = true;
logger->LogIfRingBufferError_Store(kCARingBufferError_TooMuch);
[self assertLoggedOneErrorMessage];
}
- (void) waitForLoggingThread {
// Wait for it to finish logging the messages.
bool noMessagesLeft = logger->WaitUntilLoggerThreadIdle();
XCTAssert(noMessagesLeft);
}
- (void) assertLoggedNoMessages {
[self waitForLoggingThread];
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
}
- (void) assertLoggedOneDebugMessage {
[self waitForLoggingThread];
XCTAssertEqual(1, logger->mNumDebugMessagesLogged);
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
}
- (void) assertLoggedOneWarningMessage {
[self waitForLoggingThread];
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
XCTAssertEqual(1, logger->mNumWarningMessagesLogged);
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
}
- (void) assertLoggedOneErrorMessage {
[self waitForLoggingThread];
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
XCTAssertEqual(1, logger->mNumErrorMessagesLogged);
}
@end

View file

@ -0,0 +1,106 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMPlayThroughTests.mm
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Unit Include
#import "BGMPlayThrough.h"
// Local Includes
#import "MockAudioDevice.h"
#import "MockAudioObjects.h"
// BGM Includes
#import "BGM_Types.h"
#import "BGMAudioDevice.h"
// STL Includes
#import <memory>
// System Includes
#import <XCTest/XCTest.h>
@interface BGMPlayThroughTests : XCTestCase
@end
@implementation BGMPlayThroughTests {
BGMAudioDevice inputDevice;
BGMAudioDevice outputDevice;
// The unit tests use mock implementations of CAHALAudioObject and CAHALAudioDevice, which are
// the superclasses of BGMAudioDevice. When BGMPlayThrough calls methods on inputDevice or
// outputDevice, some of them will update these objects.
std::shared_ptr<MockAudioDevice> mockInputDevice;
std::shared_ptr<MockAudioDevice> mockOutputDevice;
}
- (void) setUp {
[super setUp];
// Set up the mocks.
mockInputDevice = MockAudioObjects::CreateMockDevice(kBGMDeviceUID);
mockOutputDevice = MockAudioObjects::CreateMockDevice("Mock Output Device");
inputDevice = BGMAudioDevice(mockInputDevice->GetObjectID());
outputDevice = BGMAudioDevice(mockOutputDevice->GetObjectID());
}
- (void) tearDown {
[super tearDown];
MockAudioObjects::DestroyMocks();
}
- (void) testActivate {
// Set the mock output device's sample rate and IO buffer size.
outputDevice.SetNominalSampleRate(12345.0);
outputDevice.SetIOBufferSize(123);
// Create an instance and activate it.
BGMPlayThrough playThrough(inputDevice, outputDevice);
playThrough.Activate();
// It should set the input device's sample rate and IO buffer size to match the output device.
XCTAssertEqual(12345.0, inputDevice.GetNominalSampleRate());
XCTAssertEqual(123, inputDevice.GetIOBufferSize());
// It should add the property listeners it needs.
std::set<AudioObjectPropertySelector> expectedProperties {
kAudioDevicePropertyDeviceIsRunning,
kAudioDeviceProcessorOverload,
kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp
};
XCTAssertEqual(expectedProperties, mockInputDevice->mPropertiesWithListeners);
}
- (void) testDeactivate {
BGMPlayThrough playThrough(inputDevice, outputDevice);
playThrough.Activate();
playThrough.Deactivate();
// It should remove the property listeners added by Activate.
XCTAssert(mockInputDevice->mPropertiesWithListeners.empty());
}
@end

View file

@ -0,0 +1,61 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioDevice.cpp
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "MockAudioDevice.h"
// BGM Includes
#include "BGM_Types.h"
// STL Includes
#include <functional>
MockAudioDevice::MockAudioDevice(const std::string& inUID)
:
mUID(inUID),
mNominalSampleRate(44100.0),
mIOBufferSize(512),
MockAudioObject(static_cast<AudioObjectID>(std::hash<std::string>{}(inUID)))
{
}
CACFString MockAudioDevice::GetPlayerBundleID() const
{
if(mUID != kBGMDeviceUID)
{
throw "Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID";
}
return mPlayerBundleID;
}
void MockAudioDevice::SetPlayerBundleID(const CACFString& inPlayerBundleID)
{
if(mUID != kBGMDeviceUID)
{
throw "Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID";
}
mPlayerBundleID = inPlayerBundleID;
}

View file

@ -0,0 +1,73 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioObject.h
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
#ifndef BGMAppUnitTests__MockAudioDevice
#define BGMAppUnitTests__MockAudioDevice
// Superclass Includes
#include "MockAudioObject.h"
// STL Includes
#include <string>
/*!
* A mock audio device in our mock CoreAudio HAL. In the HAL's API class hierarchy, the base class
* for audio devices, kAudioDeviceClassID, is the audio objects class, kAudioObjectClassID.
*
* The unit tests generally use instances of this class to verify the HAL is being queried correctly
* and to control the responses that the code they're testing will receive from the mock HAL.
*/
class MockAudioDevice
:
public MockAudioObject
{
public:
MockAudioDevice(const std::string& inUID);
/*!
* @return This device's music player bundle ID property.
* @throws If this device isn't a mock of BGMDevice.
*/
CACFString GetPlayerBundleID() const;
/*!
* Set this device's music player bundle ID property.
* @throws If this device isn't a mock of BGMDevice.
*/
void SetPlayerBundleID(const CACFString& inPlayerBundleID);
/*!
* The device's UID. The UID is a persistent token used to identify a particular audio device
* across boot sessions.
*/
const std::string mUID;
Float64 mNominalSampleRate;
UInt32 mIOBufferSize;
private:
CACFString mPlayerBundleID { "" };
};
#endif /* BGMAppUnitTests__MockAudioDevice */

View file

@ -0,0 +1,37 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioObject.cpp
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "MockAudioObject.h"
MockAudioObject::MockAudioObject(AudioObjectID inAudioObjectID)
:
mAudioObjectID(inAudioObjectID)
{
}
AudioObjectID MockAudioObject::GetObjectID() const
{
return mAudioObjectID;
}

View file

@ -0,0 +1,61 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioObject.h
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
#ifndef BGMAppUnitTests__MockAudioObject
#define BGMAppUnitTests__MockAudioObject
// PublicUtility Includes
#include "CACFString.h"
// STL Includes
#include <set>
// System Includes
#include <CoreAudio/CoreAudio.h>
/*!
* The base class for mock audio objects in our mock CoreAudio HAL. Maps to kAudioObjectClassID
* (AudioHardwareBase.h) in the HAL's API class hierarchy.
*/
class MockAudioObject
{
public:
MockAudioObject(AudioObjectID inAudioObjectID);
virtual ~MockAudioObject() = default;
AudioObjectID GetObjectID() const;
/*!
* The properties that callers have added listeners for (and haven't since removed). See
* CAHALAudioObject::AddPropertyListener and CAHALAudioObject::RemovePropertyListener.
*/
std::set<AudioObjectPropertySelector> mPropertiesWithListeners;
private:
AudioObjectID mAudioObjectID;
};
#endif /* BGMAppUnitTests__MockAudioObject */

View file

@ -0,0 +1,123 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioObjects.cpp
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "MockAudioObjects.h"
// PublicUtility Includes
#include "CACFString.h"
// static
MockAudioObjects::MockDeviceMap MockAudioObjects::sDevices;
// static
MockAudioObjects::MockDeviceMapByUID MockAudioObjects::sDevicesByUID;
// static
std::shared_ptr<MockAudioDevice> MockAudioObjects::CreateMockDevice(const std::string& inUID)
{
std::shared_ptr<MockAudioDevice> mockDevice = std::make_shared<MockAudioDevice>(inUID);
sDevices.insert(MockDeviceMap::value_type(mockDevice->GetObjectID(), mockDevice));
sDevicesByUID.insert(MockDeviceMapByUID::value_type(inUID, mockDevice));
return mockDevice;
}
// static
void MockAudioObjects::DestroyMocks()
{
sDevices.clear();
}
// static
std::shared_ptr<MockAudioObject> MockAudioObjects::GetAudioObject(AudioObjectID inAudioObjectID)
{
auto device = GetAudioDeviceOrNull(inAudioObjectID);
if(device)
{
return device;
}
// Devices are the only audio objects we currently mock.
// Tests have to create mocks for all of the audio objects they expect the code they test to
// access. They should fail if it accesses any others.
throw "Mock audio object not found.";
}
// static
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(AudioObjectID inAudioObjectID)
{
auto device = GetAudioDeviceOrNull(inAudioObjectID);
if(device)
{
return device;
}
// Tests have to create mocks for all of the audio devices they expect the code they test to
// access. They should fail if it accesses any others.
throw "Mock audio device not found.";
}
// static
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(CFStringRef inUID)
{
// Convert inUID to a std::string.
UInt32 uidCStringLen = CACFString::GetStringByteLength(inUID) + 1;
char uidCString[uidCStringLen];
CACFString::GetCString(inUID, uidCString, uidCStringLen);
std::string uid = std::string(uidCString);
return GetAudioDevice(uid);
}
// static
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(const std::string& inUID)
{
auto device = sDevicesByUID.find(inUID);
if(device != sDevicesByUID.end())
{
return device->second;
}
return nullptr;
}
// static
std::shared_ptr<MockAudioDevice>
MockAudioObjects::GetAudioDeviceOrNull(AudioObjectID inAudioObjectID)
{
auto device = sDevices.find(inAudioObjectID);
if(device != sDevices.end())
{
return device->second;
}
return nullptr;
}

View file

@ -0,0 +1,90 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// MockAudioObjects.h
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
#ifndef BGMAppUnitTests__MockAudioObjects
#define BGMAppUnitTests__MockAudioObjects
// Local Includes
#include "MockAudioObject.h"
#include "MockAudioDevice.h"
// STL Includes
#include <map>
#include <memory>
#include <string>
// System Includes
#include <CoreAudio/CoreAudio.h>
class MockAudioObjects
{
public:
/*!
* Create a mock audio device in the mock CoreAudio HAL.
*
* The mock device will then be accessible using GetAudioObject and GetAudioDevice. The
* Mock_CAHAL* implementations will access the mock device when they query the mock HAL.
*
* Unit tests can check the mock device to verify the code they're testing has called the mocked
* CAHAL classes correctly. They can also modify the mock device to control the Mock_CAHAL*
* implementations, e.g. to have CAHALAudioDevice::IsAlive return false so the test can cover
* the case where a device is being removed from the system.
*
* @param inUID The UID string to give the device. The UID is a persistent token used to
* identify a particular audio device across boot sessions.
* @return The mock device.
*/
static std::shared_ptr<MockAudioDevice> CreateMockDevice(const std::string& inUID);
/*!
* Remove all mock audio objects from the mock HAL. (Currently, mock devices are the only mock
* objects that can be created.)
*/
static void DestroyMocks();
/*! Get a mock audio object by its ID. */
static std::shared_ptr<MockAudioObject> GetAudioObject(AudioObjectID inAudioObjectID);
/*! Get a mock audio device by its ID. */
static std::shared_ptr<MockAudioDevice> GetAudioDevice(AudioObjectID inAudioDeviceID);
/*! Get a mock audio device by its UID. */
static std::shared_ptr<MockAudioDevice> GetAudioDevice(const std::string& inUID);
/*! Get a mock audio device by its UID. */
static std::shared_ptr<MockAudioDevice> GetAudioDevice(CFStringRef inUID);
private:
typedef std::map<AudioObjectID, std::shared_ptr<MockAudioDevice>> MockDeviceMap;
typedef std::map<std::string, std::shared_ptr<MockAudioDevice>> MockDeviceMapByUID;
static std::shared_ptr<MockAudioDevice> GetAudioDeviceOrNull(AudioObjectID inAudioDeviceID);
/*! Maps IDs to mocked audio devices. */
static MockDeviceMap sDevices;
/*! Maps UIDs (ID strings) to mocked audio devices. */
static MockDeviceMapByUID sDevicesByUID;
};
#endif /* BGMAppUnitTests__MockAudioObjects */

View file

@ -0,0 +1,691 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// Mock_CAHALAudioDevice.cpp
// BGMAppUnitTests
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "CAHALAudioDevice.h"
// Local Includes
#include "MockAudioDevice.h"
#include "MockAudioObjects.h"
// BGM Includes
#include "BGM_Types.h"
// PublicUtility Includes
#include "CACFString.h"
#include "CAHALAudioSystemObject.h"
#include "CAPropertyAddress.h"
#pragma clang diagnostic ignored "-Wunused-parameter"
CAHALAudioDevice::CAHALAudioDevice(AudioObjectID inObjectID)
:
CAHALAudioObject(inObjectID)
{
}
CAHALAudioDevice::CAHALAudioDevice(CFStringRef inUID)
:
CAHALAudioObject(CAHALAudioSystemObject().GetAudioDeviceForUID(inUID))
{
}
CAHALAudioDevice::~CAHALAudioDevice()
{
}
void CAHALAudioDevice::GetCurrentVirtualFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const
{
ioNumberStreams = 1;
CAPropertyAddress theAddress(kAudioStreamPropertyVirtualFormat);
UInt32 theSize = sizeof(AudioStreamBasicDescription);
GetPropertyData(theAddress, 0, NULL, theSize, outFormats);
}
UInt32 CAHALAudioDevice::GetIOBufferSize() const
{
return MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize;
}
void CAHALAudioDevice::SetIOBufferSize(UInt32 inBufferSize)
{
MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize = inBufferSize;
}
bool CAHALAudioDevice::IsAlive() const
{
return true;
}
AudioDeviceIOProcID CAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc inIOProc, void* inClientData)
{
return reinterpret_cast<AudioDeviceIOProcID>(0x99990000);
}
void CAHALAudioDevice::DestroyIOProcID(AudioDeviceIOProcID inIOProcID)
{
}
Float64 CAHALAudioDevice::GetNominalSampleRate() const
{
return MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate;
}
void CAHALAudioDevice::SetNominalSampleRate(Float64 inSampleRate)
{
MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate = inSampleRate;
}
CFStringRef CAHALAudioDevice::CopyDeviceUID() const
{
std::string uid = MockAudioObjects::GetAudioDevice(GetObjectID())->mUID;
return CACFString(uid.c_str()).CopyCFString();
}
#pragma mark Unimplemented Methods
bool CAHALAudioDevice::HasModelUID() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFStringRef CAHALAudioDevice::CopyModelUID() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFStringRef CAHALAudioDevice::CopyConfigurationApplicationBundleID() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFURLRef CAHALAudioDevice::CopyIconLocation() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetTransportType() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::CanBeDefaultDevice(bool inIsInput, bool inIsSystem) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasDevicePlugInStatus() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
OSStatus CAHALAudioDevice::GetDevicePlugInStatus() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsHidden() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
pid_t CAHALAudioDevice::GetHogModeOwner() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsHogModeSettable() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::TakeHogMode()
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::ReleaseHogMode()
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasPreferredStereoChannels(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetPreferredStereoChannels(bool inIsInput, UInt32& outLeft, UInt32& outRight) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, UInt32 inLeft, UInt32 inRight)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasPreferredChannelLayout(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetPreferredChannelLayout(bool inIsInput, AudioChannelLayout& outChannelLayout) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, AudioChannelLayout& inChannelLayout)
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberRelatedAudioDevices() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetRelatedAudioDevices(UInt32& ioNumberRelatedDevices, AudioObjectID* outRelatedDevices) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
AudioObjectID CAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIndex) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberStreams(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetStreams(bool inIsInput, UInt32& ioNumberStreams, AudioObjectID* outStreamList) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
AudioObjectID CAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 inIndex) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetCurrentPhysicalFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsRunning() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsRunningSomewhere() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetLatency(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetSafetyOffset(bool inIsInput) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasClockDomain() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetClockDomain() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float64 CAHALAudioDevice::GetActualSampleRate() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetAvailableNominalSampleRateRanges(UInt32& ioNumberRanges, AudioValueRange* outRanges) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetAvailableNominalSampleRateRangeByIndex(UInt32 inIndex, Float64& outMinimum, Float64& outMaximum) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsValidNominalSampleRate(Float64 inSampleRate) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::IsIOBufferSizeSettable() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::UsesVariableIOBufferSizes() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetMaximumVariableIOBufferSize() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasIOBufferSizeRange() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetIOBufferSizeRange(UInt32& outMinimum, UInt32& outMaximum) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::StartIOProc(AudioDeviceIOProcID inIOProcID)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::StartIOProcAtTime(AudioDeviceIOProcID inIOProcID, AudioTimeStamp& ioStartTime, bool inIsInput, bool inIgnoreHardware)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::StopIOProc(AudioDeviceIOProcID inIOProcID)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, bool* outStreamUsage) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, const bool* inStreamUsage)
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetIOCycleUsage() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetIOCycleUsage(Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetCurrentTime(AudioTimeStamp& outTime)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::TranslateTime(const AudioTimeStamp& inTime, AudioTimeStamp& outTime)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetNearestStartTime(AudioTimeStamp& ioTime, bool inIsInput, bool inIgnoreHardware)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::VolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasSubVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::SubVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::MuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::GetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasSoloControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::SoloControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::GetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasStereoPanControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::StereoPanControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
Float32 CAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetStereoPanControlChannels(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& outLeftChannel, UInt32& outRightChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasJackControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::GetJackControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasSubMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::SubMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::GetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasiSubOwnerControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::iSubOwnerControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::GetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasDataSourceControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::DataSourceControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetCurrentDataSourceByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberSources, UInt32* outSources) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFStringRef CAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasDataDestinationControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::DataDestinationControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetCurrentDataDestinationByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberDestinations, UInt32* outDestinations) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFStringRef CAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::HasClockSourceControl() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
bool CAHALAudioDevice::ClockSourceControlIsSettable() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetCurrentClockSourceID() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::SetCurrentClockSourceByID(UInt32 inID)
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetNumberAvailableClockSources() const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioDevice::GetAvailableClockSources(UInt32& ioNumberSources, UInt32* outSources) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
CFStringRef CAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const
{
Throw(new CAException(kAudio_UnimplementedError));
}

View file

@ -17,25 +17,24 @@
// Mock_CAHALAudioObject.cpp
// BGMAppUnitTests
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2016, 2020 Kyle Neideck
//
// Self include
// Self Include
#include "CAHALAudioObject.h"
// Local Includes
#include "MockAudioObjects.h"
// BGM Includes
#include "BGM_Types.h"
// System includes
#include <CoreAudio/AudioHardware.h>
// PublicUtility Includes
#include "CACFString.h"
#pragma clang diagnostic ignored "-Wunused-parameter"
// The value of the music player bundle ID property. Tests should set this back to "" when they finish. (Has
// to be static because we can't add to the real class's interface.)
static CFStringRef playerBundleID = CFSTR("");
CAHALAudioObject::CAHALAudioObject(AudioObjectID inObjectID)
:
mObjectID(inObjectID)
@ -53,21 +52,89 @@ AudioObjectID CAHALAudioObject::GetObjectID() const
void CAHALAudioObject::GetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32& ioDataSize, void* outData) const
{
if(inAddress.mSelector == kAudioDeviceCustomPropertyMusicPlayerBundleID)
switch(inAddress.mSelector)
{
*reinterpret_cast<CFStringRef*>(outData) = playerBundleID;
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
*reinterpret_cast<CFStringRef*>(outData) =
MockAudioObjects::GetAudioDevice(GetObjectID())->
GetPlayerBundleID().CopyCFString();
break;
case kAudioDevicePropertyStreams:
reinterpret_cast<AudioObjectID*>(outData)[0] = 1;
if(inAddress.mScope == kAudioObjectPropertyScopeGlobal)
{
reinterpret_cast<AudioObjectID*>(outData)[1] = 2;
}
break;
case kAudioDevicePropertyBufferFrameSize:
*reinterpret_cast<UInt32*>(outData) = 512;
break;
case kAudioDevicePropertyDeviceIsAlive:
*reinterpret_cast<UInt32*>(outData) = 1;
break;
case kAudioStreamPropertyVirtualFormat:
{
AudioStreamBasicDescription* outASBD =
reinterpret_cast<AudioStreamBasicDescription*>(outData);
outASBD->mSampleRate = 44100.0;
outASBD->mFormatID = kAudioFormatLinearPCM;
outASBD->mFormatFlags =
kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
outASBD->mBytesPerPacket = 8;
outASBD->mFramesPerPacket = 1;
outASBD->mBytesPerFrame = 8;
outASBD->mChannelsPerFrame = 2;
outASBD->mBitsPerChannel = 32;
break;
}
default:
Throw(new CAException(kAudio_UnimplementedError));
}
}
void CAHALAudioObject::SetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
if(inAddress.mSelector == kAudioDeviceCustomPropertyMusicPlayerBundleID)
switch(inAddress.mSelector)
{
playerBundleID = *reinterpret_cast<const CFStringRef*>(inData);
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
MockAudioObjects::GetAudioDevice(GetObjectID())->SetPlayerBundleID(
CACFString(*reinterpret_cast<const CFStringRef*>(inData), false));
break;
default:
break;
}
}
#pragma mark Unimplemented methods
UInt32 CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
{
switch(inAddress.mSelector)
{
case kAudioDevicePropertyStreams:
return (inAddress.mScope == kAudioObjectPropertyScopeGlobal ? 2 : 1) *
sizeof(AudioObjectID);
default:
Throw(new CAException(kAudio_UnimplementedError));
}
}
void CAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
{
MockAudioObjects::GetAudioObject(GetObjectID())->
mPropertiesWithListeners.insert(inAddress.mSelector);
}
void CAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
{
MockAudioObjects::GetAudioObject(GetObjectID())->
mPropertiesWithListeners.erase(inAddress.mSelector);
}
#pragma mark Unimplemented Methods
void CAHALAudioObject::SetObjectID(AudioObjectID inObjectID)
{
@ -144,18 +211,3 @@ bool CAHALAudioObject::IsPropertySettable(const AudioObjectPropertyAddress& inAd
Throw(new CAException(kAudio_UnimplementedError));
}
UInt32 CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
{
Throw(new CAException(kAudio_UnimplementedError));
}
void CAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
{
Throw(new CAException(kAudio_UnimplementedError));
}

View file

@ -17,12 +17,18 @@
// Mock_CAHALAudioSystemObject.cpp
// BGMAppUnitTests
//
// Copyright © 2017 Kyle Neideck
// Copyright © 2017, 2020 Kyle Neideck
//
// Self include
#include "CAHALAudioSystemObject.h"
// BGM Includes
#include "BGM_Types.h"
// Local Includes
#include "MockAudioObjects.h"
CAHALAudioSystemObject::CAHALAudioSystemObject()
:
@ -36,19 +42,17 @@ CAHALAudioSystemObject::~CAHALAudioSystemObject()
AudioObjectID CAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef inUID) const
{
AudioObjectID id = kAudioObjectUnknown;
auto device = MockAudioObjects::GetAudioDevice(inUID);
// Generate a deterministic and random-ish ID from the UID string. Ideally we would ensure the
// IDs are unique, but this is probably fine.
for(int i = 0; i < CFStringGetLength(inUID); i++)
if(device)
{
id += 37 * CFStringGetCharacterAtIndex(inUID, i);
return device->GetObjectID();
}
return id;
return kAudioObjectUnknown;
}
#pragma mark Unimplemented methods
#pragma mark Unimplemented Methods
#pragma clang diagnostic ignored "-Wunused-parameter"

View file

@ -23,7 +23,7 @@
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016-2019 Background Music contributors</string>
<string>Copyright © 2016-2020 Background Music contributors</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>

View file

@ -0,0 +1,50 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMDebugLogging.c
// PublicUtility
//
// Copyright © 2020 Kyle Neideck
//
// Self Include
#include "BGMDebugLogging.h"
#pragma clang assume_nonnull begin
// It's probably not ideal to use a global variable for this, but it's a lot easier.
#if DEBUG || CoreAudio_Debug
// Enable debug logging by default in debug builds.
int gDebugLoggingIsEnabled = 1;
#else
int gDebugLoggingIsEnabled = 0;
#endif
// We don't bother synchronising accesses of gDebugLoggingIsEnabled because it isn't really
// necessary and would complicate code that accesses it on realtime threads.
int BGMDebugLoggingIsEnabled()
{
return gDebugLoggingIsEnabled;
}
void BGMSetDebugLoggingEnabled(int inEnabled)
{
gDebugLoggingIsEnabled = inEnabled;
}
#pragma clang assume_nonnull end

View file

@ -0,0 +1,63 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMDebugLogging.h
// PublicUtility
//
// Copyright © 2020 Kyle Neideck
//
// Functions to globally enable/disable debug logging, i.e. more detailed logging to help diagnose
// bugs. If debug logging is enabled, the DebugMsg macro from CADebugMacros.h (and possibly others)
// will log messages. If not, it won't do anything.
//
// If the preprocessor macro CoreAudio_UseSysLog is true, which is currently the case for all build
// variants (see BGMApp/BGMApp.xcodeproj/project.pbxproj and
// BGMDriver/BGMDriver.xcodeproj/project.pbxproj), those messages will be logged using syslog and
// can be read using Console.app. Try searching for "background music", "bgm" or "coreaudiod".
//
// Debug logging is enabled by default in debug builds, but in release builds you have to enable it
// by option-clicking the status bar icon and then checking the Debug Logging menu item. Enabling
// debug logging probably won't cause glitches, but we don't try to guarantee that and it's not
// well tested.
//
#ifndef PublicUtility__BGMDebugLogging
#define PublicUtility__BGMDebugLogging
#pragma clang assume_nonnull begin
/*!
* @return Non-zero if debug logging is globally enabled. (Probably -- it's not synchronised.)
* Real-time safe.
*/
#if defined(__cplusplus)
extern "C"
#endif
int BGMDebugLoggingIsEnabled(void);
/*!
* @param inEnabled Non-zero to globally enable debug logging, zero to disable it. The change might
* not be visible to other threads immediately.
*/
#if defined(__cplusplus)
extern "C"
#endif
void BGMSetDebugLoggingEnabled(int inEnabled);
#pragma clang assume_nonnull end
#endif /* PublicUtility__BGMDebugLogging */

View file

@ -1,3 +1,28 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// CADebugMacros.h
// PublicUtility
//
// Copyright (C) 2014 Apple Inc. All Rights Reserved.
// Copyright © 2016, 2020 Kyle Neideck
//
// Original license header follows.
//
/*
File: CADebugMacros.h
Abstract: Part of CoreAudio Utility Classes
@ -57,6 +82,8 @@
#include "CoreAudioTypes.h"
#endif
#include "CADebugPrintf.h"
//=============================================================================
// CADebugMacros
//=============================================================================
@ -92,42 +119,41 @@
#pragma mark Basic Definitions
// basic debugging print routines
#if TARGET_OS_MAC && !TARGET_API_MAC_CARBON
extern void DebugStr(const unsigned char* debuggerMsg);
#define DebugMessage(msg) DebugStr("\p"msg)
#define DebugMessageN1(msg, N1)
#define DebugMessageN2(msg, N1, N2)
#define DebugMessageN3(msg, N1, N2, N3)
#else
#if (CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)
#define FlushRtn ,fflush(DebugPrintfFile)
#else
#define FlushRtn
#endif
#if CoreAudio_ThreadStampMessages
#include <pthread.h>
#include "CAHostTimeBase.h"
#if TARGET_RT_64_BIT
#define DebugPrintfThreadIDFormat "%16p"
#else
#define DebugPrintfThreadIDFormat "%8p"
#endif
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " DebugPrintfThreadIDFormat " " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn
#elif CoreAudio_TimeStampMessages
#include "CAHostTimeBase.h"
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn
#else
#define DebugMsg(inFormat, ...) DebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn
#endif
#endif
#if DEBUG || CoreAudio_Debug
// can be used to break into debugger immediately, also see CADebugger
#define BusError() { long* p=NULL; *p=0; }
// basic debugging print routines
#if TARGET_OS_MAC && !TARGET_API_MAC_CARBON
extern void DebugStr(const unsigned char* debuggerMsg);
#define DebugMessage(msg) DebugStr("\p"msg)
#define DebugMessageN1(msg, N1)
#define DebugMessageN2(msg, N1, N2)
#define DebugMessageN3(msg, N1, N2, N3)
#else
#include "CADebugPrintf.h"
#if (CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)
#define FlushRtn ,fflush(DebugPrintfFile)
#else
#define FlushRtn
#endif
#if CoreAudio_ThreadStampMessages
#include <pthread.h>
#include "CAHostTimeBase.h"
#if TARGET_RT_64_BIT
#define DebugPrintfThreadIDFormat "%16p"
#else
#define DebugPrintfThreadIDFormat "%8p"
#endif
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " DebugPrintfThreadIDFormat " " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn
#elif CoreAudio_TimeStampMessages
#include "CAHostTimeBase.h"
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn
#else
#define DebugMsg(inFormat, ...) DebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn
#endif
#endif
void DebugPrint(const char *fmt, ...); // can be used like printf
#ifndef DEBUGPRINT
#define DEBUGPRINT(msg) DebugPrint msg // have to double-parenthesize arglist (see Debugging.h)
@ -172,7 +198,7 @@
#endif
#else
#define DebugMsg(inFormat, ...)
#ifndef DEBUGPRINT
#define DEBUGPRINT(msg)
#endif

View file

@ -1,3 +1,28 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// CADebugPrintf.cpp
// PublicUtility
//
// Copyright (C) 2014 Apple Inc. All Rights Reserved.
// Copyright © 2020 Kyle Neideck
//
// Original license header follows.
//
/*
File: CADebugPrintf.cpp
Abstract: CADebugPrintf.h
@ -51,39 +76,39 @@
// Self Include
#include "CADebugPrintf.h"
#if DEBUG || CoreAudio_Debug
#if TARGET_OS_WIN32
#include <stdarg.h>
#include <stdio.h>
#include <Windows.h>
extern "C"
int CAWin32DebugPrintf(char* inFormat, ...)
{
#if TARGET_OS_WIN32
#include <stdarg.h>
#include <stdio.h>
#include <Windows.h>
extern "C"
int CAWin32DebugPrintf(char* inFormat, ...)
{
if (BGMDebugLoggingIsEnabled()) {
char theMessage[1024];
va_list theArguments;
va_start(theArguments, inFormat);
_vsnprintf(theMessage, 1024, inFormat, theArguments);
va_end(theArguments);
OutputDebugString(theMessage);
return 0;
}
#endif
#if defined(CoreAudio_UseSideFile)
#include <unistd.h>
FILE* sDebugPrintfSideFile = NULL;
extern "C"
void OpenDebugPrintfSideFile()
{
if(sDebugPrintfSideFile == NULL)
{
char theFileName[1024];
snprintf(theFileName, sizeof(theFileName), CoreAudio_UseSideFile, getpid());
sDebugPrintfSideFile = fopen(theFileName, "a+");
DebugPrintfRtn(DebugPrintfFileComma "\n------------------------------\n");
}
}
#endif
return 0;
}
#endif
#if defined(CoreAudio_UseSideFile)
#include <unistd.h>
FILE* sDebugPrintfSideFile = NULL;
extern "C"
void OpenDebugPrintfSideFile()
{
if(sDebugPrintfSideFile == NULL)
{
char theFileName[1024];
snprintf(theFileName, sizeof(theFileName), CoreAudio_UseSideFile, getpid());
sDebugPrintfSideFile = fopen(theFileName, "a+");
DebugPrintfRtn(DebugPrintfFileComma "\n------------------------------\n");
}
}
#endif

View file

@ -57,6 +57,8 @@
#include "CoreAudioTypes.h"
#endif
#include "BGMDebugLogging.h"
//=============================================================================
// Macros to redirect debugging output to various logging services
//=============================================================================
@ -64,52 +66,48 @@
//#define CoreAudio_UseSysLog 1
//#define CoreAudio_UseSideFile "/CoreAudio-%d.txt"
#if DEBUG || CoreAudio_Debug
#if TARGET_OS_WIN32
#if TARGET_OS_WIN32
#if defined(__cplusplus)
extern "C"
#endif
extern int CAWin32DebugPrintf(char* inFormat, ...);
#define DebugPrintfRtn CAWin32DebugPrintf
#define DebugPrintfFile
#define DebugPrintfLineEnding "\n"
#define DebugPrintfFileComma
#else
#if CoreAudio_UseSysLog
#include <sys/syslog.h>
#define DebugPrintfRtn syslog
#define DebugPrintfFile LOG_NOTICE
#define DebugPrintfLineEnding ""
#define DebugPrintfFileComma DebugPrintfFile,
#elif defined(CoreAudio_UseSideFile)
#include <stdio.h>
#if defined(__cplusplus)
extern "C"
#endif
extern int CAWin32DebugPrintf(char* inFormat, ...);
#define DebugPrintfRtn CAWin32DebugPrintf
#define DebugPrintfFile
void OpenDebugPrintfSideFile();
extern FILE* sDebugPrintfSideFile;
#define DebugPrintfRtn fprintf
#define DebugPrintfFile ((sDebugPrintfSideFile != NULL) ? sDebugPrintfSideFile : stderr)
#define DebugPrintfLineEnding "\n"
#define DebugPrintfFileComma
#define DebugPrintfFileComma DebugPrintfFile,
#else
#if CoreAudio_UseSysLog
#include <sys/syslog.h>
#define DebugPrintfRtn syslog
#define DebugPrintfFile LOG_NOTICE
#define DebugPrintfLineEnding ""
#define DebugPrintfFileComma DebugPrintfFile,
#elif defined(CoreAudio_UseSideFile)
#include <stdio.h>
#if defined(__cplusplus)
extern "C"
#endif
void OpenDebugPrintfSideFile();
extern FILE* sDebugPrintfSideFile;
#define DebugPrintfRtn fprintf
#define DebugPrintfFile ((sDebugPrintfSideFile != NULL) ? sDebugPrintfSideFile : stderr)
#define DebugPrintfLineEnding "\n"
#define DebugPrintfFileComma DebugPrintfFile,
#else
#include <stdio.h>
#define DebugPrintfRtn fprintf
#define DebugPrintfFile stderr
#define DebugPrintfLineEnding "\n"
#define DebugPrintfFileComma DebugPrintfFile,
#endif
#include <stdio.h>
#define DebugPrintfRtn fprintf
#define DebugPrintfFile stderr
#define DebugPrintfLineEnding "\n"
#define DebugPrintfFileComma DebugPrintfFile,
#endif
#define DebugPrintf(inFormat, ...) DebugPrintfRtn(DebugPrintfFileComma inFormat DebugPrintfLineEnding, ## __VA_ARGS__)
#else
#define DebugPrintfRtn
#define DebugPrintfFile
#define DebugPrintfLineEnding
#define DebugPrintfFileComma
#define DebugPrintf(inFormat, ...)
#endif
#define DebugPrintf(inFormat, ...) \
do { \
if (BGMDebugLoggingIsEnabled()) { \
DebugPrintfRtn(DebugPrintfFileComma inFormat DebugPrintfLineEnding, ## __VA_ARGS__); \
} \
} while (0)
#endif

View file

@ -26,21 +26,12 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C8034D91BDD073B00668E00"
BuildableName = "BGMDriverTests.xctest"
BlueprintName = "BGMDriverTests"
ReferencedContainer = "container:BGMDriver.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -50,15 +41,38 @@
ReferencedContainer = "container:BGMDriver.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1CB8B3631BBBB78D000E2DD1"
BuildableName = "Background Music Device.driver"
BlueprintName = "Background Music Device"
ReferencedContainer = "container:BGMDriver.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C8034D91BDD073B00668E00"
BuildableName = "BGMDriverTests.xctest"
BlueprintName = "BGMDriverTests"
ReferencedContainer = "container:BGMDriver.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
debugAsWhichUser = "root"
language = ""
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
launchStyle = "1"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -85,8 +99,6 @@
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View file

@ -26,19 +26,22 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
disableMainThreadChecker = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
enableAddressSanitizer = "YES"
enableASanStackUseAfterReturn = "YES"
enableUBSanitizer = "YES"
disableMainThreadChecker = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -61,8 +64,6 @@
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Self Include

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Self Include

View file

@ -17,9 +17,10 @@
// BGM_Device.cpp
// BGMDriver
//
// Copyright © 2016, 2017 Kyle Neideck
// Copyright © 2016, 2017, 2019 Kyle Neideck
// Copyright © 2017 Andrew Tonner
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright © 2019 Gordon Childs
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Device.cpp from Apple's SimpleAudioDriver Plug-In sample code. Also uses a few sections from Apple's
// NullAudio.c sample code (found in the same sample project).

View file

@ -17,8 +17,9 @@
// BGM_Device.h
// BGMDriver
//
// Copyright © 2016, 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright © 2016, 2017, 2019 Kyle Neideck
// Copyright © 2019 Gordon Childs
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Device.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Object.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Object.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
@ -46,7 +46,7 @@
//
// This is the base class for objects managed by BGM_ObjectMap. It's only job is to ensure that
// objects of this type have the proper external semantics for a reference counted object. This
// means that the desctructor is protected so that these objects cannot be deleted directly. Also,
// means that the destructor is protected so that these objects cannot be deleted directly. Also,
// these objects many not make a copy of another object or be assigned from another object. Note
// that the reference count of the object is tracked and owned by the BGM_ObjectMap.
//

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016, 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016, 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples

View file

@ -17,7 +17,7 @@
// BGMDriver
//
// Copyright © 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Self Include

View file

@ -18,7 +18,7 @@
// BGMDriver
//
// Copyright © 2016, 2017 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Self Include

View file

@ -37,7 +37,7 @@
</array>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016-2019 Background Music contributors</string>
<string>Copyright © 2016-2020 Background Music contributors</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>

View file

@ -205,7 +205,7 @@ Some are in listed in [TODO.md](/TODO.md).
## License
Copyright © 2016-2019 [Background Music contributors](https://github.com/kyleneideck/BackgroundMusic/graphs/contributors).
Copyright © 2016-2020 [Background Music contributors](https://github.com/kyleneideck/BackgroundMusic/graphs/contributors).
Licensed under [GPLv2](https://www.gnu.org/licenses/gpl-2.0.html), or any later version.
**Background Music** includes code from:

View file

@ -17,7 +17,7 @@
// BGM_Utils.h
// SharedSource
//
// Copyright © 2016-2018 Kyle Neideck
// Copyright © 2016-2020 Kyle Neideck
//
#ifndef SharedSource__BGM_Utils
@ -41,13 +41,21 @@
#pragma mark Macros
// The Assert macro from CADebugMacros with support for format strings added.
#define BGMAssert(inCondition, inMessage, ...) \
if(!(inCondition)) \
{ \
DebugMsg(inMessage, ## __VA_ARGS__); \
__ASSERT_STOP; \
}
// The Assert macro from CADebugMacros with support for format strings and line numbers added.
#if DEBUG
#define BGMAssert(inCondition, inMessage, ...) \
if(!(inCondition)) \
{ \
DebugMsg("%s:%d:%s: " inMessage, \
__FILE__, \
__LINE__, \
__FUNCTION__, \
## __VA_ARGS__); \
__ASSERT_STOP; \
}
#else
#define BGMAssert(inCondition, inMessage, ...)
#endif /* DEBUG */
#define BGMAssertNonNull(expression) \
BGMAssertNonNull2((expression), #expression)