[
  {
    "path": ".editorconfig",
    "content": "# This file was added to get GitHub to display our source code correctly when\n# it has mixed tabs and spaces. (I didn't realise the sample code BGMDriver is\n# based on used tabs and it's too late to fix it now.)\n#\n# See http://editorconfig.org.\n\n# This is the top-most .editorconfig file.\nroot = true\n\n# Set tabs to the width of 4 spaces in C, C++, Objective-C and Objective-C++\n# source files.\n[*.{h,c,cpp,m,mm}]\ntab_width = 4\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n## Example bug report template\n\n> Don't worry if you have trouble getting some of this info. Just leave it out.\n\n**Description of the bug**\n> Please don't just say it's \"not working\".\n\n**Steps to reproduce**\n> Steps to reproduce the bug. This usually doesn't need to be super detailed.\n1. Go to '...'\n2. Click on '...'\n3. See error message '...'\n\n**Versions**\n> Please complete the following information.\n - Background Music: [e.g. \"0.4.3\" or \"0.4.0-SNAPSHOT-c0ab98b\". `Preferences > About Background Music`]\n - macOS: [e.g. \"11.3 Beta (20E5172i)\" or \"Big Sur\". ` > About This Mac`]\n\n**Hardware**\n> Delete this part if you think it's probably not necessary.\n - Computer: [e.g. \"MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)\". ` > About This Mac`]\n - Audio Device: [e.g. \"Built-in Output. Manufacturer: Apple Inc. Output Channels: 2 [...]\". `System Information app > Hardware > Audio`]\n\n**Debug logs**\n> If you think the developers might not be able to reproduce the bug on their computers, e.g. because an important feature is completely broken and they would have noticed, it can help to include [debug logs](https://github.com/kyleneideck/BackgroundMusic/wiki/Getting-Debug-Logs). This takes a little effort, so feel free to leave it out at first.\n\n[Debug logs attached here](https://github.com/example/background-music-debug-logs.txt)\n\n**Other info**\n> Anything else you want to add?\n\n---\n\n> Tips\n> (Delete this section before posting.)\n>  - https://github.com/kyleneideck/BackgroundMusic#troubleshooting\n>  - Try the latest SNAPSHOT version from https://github.com/kyleneideck/BackgroundMusic/releases (if it's newer than the latest non-SNAPSHOT release).\n>  - If your bug is one of these common issues, consider leaving a comment or a +1 (👍) on an existing issue:\n>     - Background Music currently only supports audio devices with two channels. Bluetooth devices often only have one.\n>     - Volumes having no effect for certain apps: Microsoft Teams ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/268#issuecomment-604977210)), Zoom ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/396#issuecomment-741992157)), Discord ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/210#issuecomment-507048957), [see also](https://github.com/kyleneideck/BackgroundMusic/issues/267#issuecomment-617327850)), Chrome (sometimes)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "content": "---\nname: Other\nabout: Feature request, question, support request or anything else\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n> There's no template for this issue type. I just wanted to make it clear that it's OK to submit other types of issues.\n"
  },
  {
    "path": ".github/workflows/build-test-release.yml",
    "content": "# TODO: Split this into multiple .yml files? Multiple jobs?\nname: Build, Test and Release\n\non:\n  push:\n    branches:\n      - '*'\n    tags:\n      - '*'\n  pull_request:\n    branches:\n      - '*'\n\njobs:\n  # Build and test in the same job because the UI tests expect BGMDriver to be installed.\n  build-and-test:\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n    strategy:\n      matrix:\n        # TODO: Add older macOS versions.\n        os:\n          - macos-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Work in a case-sensitive disk image.\n        # This lets us catch failures that only happen on case-sensitive filesystems.\n        run: |\n          hdiutil create \\\n            -type SPARSEBUNDLE \\\n            -fs 'Case-sensitive Journaled HFS+' \\\n            -volname bgmbuild \\\n            -nospotlight \\\n            -verbose \\\n            -attach \\\n            -size 100m \\\n            bgmbuild.dmg\n          sudo cp -r . /Volumes/bgmbuild\n          cd /Volumes/bgmbuild\n      - name: Install coreutils for actions/runner/issues/884 workaround.\n        # See https://github.com/actions/runner/issues/884#issuecomment-1018851327\n        run: brew install coreutils\n      - name: Build and install Background Music.\n        run: |\n          # `sudo` and `tput` expect this to be set.\n          export TERM=xterm-256color\n          genv --default-signal=PIPE yes | sudo ./build_and_install.sh\n      - name: Print the log file.\n        if: always()\n        run: cat build_and_install.log\n      - name: Log some checksums.\n        run: 'find */build/Release/*/ -type f -exec md5 {} \\;'\n      - name: Log the installed audio devices and their IDs.\n        run: |\n          system_profiler SPAudioDataType\n          say -a '?'\n      - name: Check the BGM dirs and files were installed.\n        run: |\n          # These commands fail if the dir/file isn't found.\n          ls -la \"/Applications/Background Music.app\"\n          ls -la \"/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\n          ls -la \"/usr/local/libexec/BGMXPCHelper.xpc\" \\\n            || ls -la \"/Library/Application Support/Background Music/BGMXPCHelper.xpc\"\n          ls -la \"/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\"\n      - name: Close BGMApp (which the install script opened).\n        run: >-\n          osascript -e 'tell application \"Background Music\" to quit'\n          || killall \"Background Music\"\n      - name: Skip the UI tests. (They don't work on GitHub Actions yet.)\n        run: BGMApp/BGMAppTests/UITests/skip-ui-tests.py\n      - name: Run the tests.\n        run: |\n          echo '::group::BGMDriver Tests'\n          xcodebuild \\\n            -quiet \\\n            -workspace BGM.xcworkspace \\\n            -scheme 'Background Music Device' \\\n            test\n          echo '::endgroup::'\n\n          echo '::group::BGMXPCHelper Tests'\n          xcodebuild \\\n            -quiet \\\n            -workspace BGM.xcworkspace \\\n            -scheme 'BGMXPCHelper' \\\n            test\n          echo '::endgroup::'\n\n          # Grant BGMApp authorization to use input devices.\n          # This is necessary for the UI tests because accepting the \"Background Music would like to\n          # use the microphone\" dialog programmatically isn't reliable.\n          # TODO: Commented out because we would need to generate the csreq (codesign signature)\n          #       value to match the BGMApp bundle the tests will run against.\n          # dbPath=\"$HOME/Library/Application Support/com.apple.TCC/TCC.db\"\n          # values=\"'kTCCServiceMicrophone','com.bearisdriving.BGM.App',0,2,2,1,X'FADE0C000000004800000001000000070000000800000014545ABE68FAF437700B14984BB24117EDDA1BBF2C0000000800000014386FB63B9CD6BA6E83CEDEAF4EDEE177C1FAEA92',NULL,NULL,'UNUSED',NULL,0,1652845317\"\n          # sqlQuery=\"INSERT OR IGNORE INTO access VALUES($values);\"\n          # sqlite3 \"$dbPath\" \"$sqlQuery\" || (echo \"Failed to modify $dbPath\"; exit 1)\n          # # Log the added TCC.db entry.\n          # sqlite3 \"$dbPath\" \"select * from access where client like '%BGM%';\"\n\n          echo '::group::BGMApp Tests'\n          # TODO: Commented out in case it uses too much CPU.\n          # log stream --info \\\n          #   --predicate 'process == \"coreaudiod\" or\n          #                process == \"Background Music\" or\n          #                process == \"BGMXPCHelper\" or\n          #                composedMessage contains[cd] \"Background Music\" or\n          #                composedMessage contains \"BGM\"' > app.log &\n          xcodebuild \\\n            -quiet \\\n            -workspace BGM.xcworkspace \\\n            -scheme 'Background Music' \\\n            test\n          echo '::endgroup::'\n      - name: Upload the test results.\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: bgm-test-results\n          path: |\n            /Users/runner/Library/Developer/Xcode/DerivedData/*/Logs/Test/*.xcresult\n            app.log\n            /Users/runner/Library/Logs/CrashReporter/*\n            /Users/runner/Library/Logs/DiagnosticReports/*\n      - name: Uninstall Background Music.\n        run: |\n          # `tput` expects this to be set.\n          export TERM=xterm-256color\n          genv --default-signal=PIPE yes | sudo ./uninstall.sh\n      - name: Check the BGM dirs and files were removed.\n        run: |\n          if ls -la \"/Applications/Background Music.app\"; then exit 1; fi\n          if ls -la \"/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"; then exit 1; fi\n          if ls -la \"/usr/local/libexec/BGMXPCHelper.xpc\"; then exit 1; fi\n          if ls -la \"/Library/Application Support/Background Music/BGMXPCHelper.xpc\"; then\n            exit 1\n          fi\n          if ls -la \"/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\"; then exit 1; fi\n  release:\n    runs-on: macos-latest\n    timeout-minutes: 15\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Build the .pkg installer.\n        run: |\n          # `sudo` and `tput` expect this to be set.\n          export TERM=xterm-256color\n          # If this build is for a tag with \"DEBUG\" in its name, build a debug package. (More\n          # detailed logging, no optimization, etc.)\n          if [[ \"$GITHUB_REF\" =~ .*DEBUG.* ]]; then\n            sudo ./package.sh -d\n          else\n            sudo ./package.sh\n          fi\n      - name: Install the .pkg.\n        # Delete archives/ first because it contains a copy of Background Music.app.\n        # Background Music.app is \"relocatable\", which means that if the user moves it and then\n        # installs a new version, macOS will put the new version in the same place. This makes sure\n        # the installer puts Background Music.app in /Applications so the build won't fail when we\n        # check that later.\n        #\n        # package.sh puts the archives in a zipfile next to the .pkg, so we can still upload them\n        # after deleting the directory here.\n        #\n        # TODO: On TravisCI, this was failing for debug builds. We couldn't figure out why, so we\n        #       might have to ignore that with\n        #       || [[ \"$GITHUB_REF\" =~ .*DEBUG.* ]]\n        run: |\n          sudo rm -rf archives\n          sudo installer \\\n            -pkg Background-Music-*/BackgroundMusic-*.pkg \\\n            -target / \\\n            -verbose \\\n            -dumplog\n      - name: Print the installer logs.\n        if: always()\n        # This trims the start of the log to save space.\n        run: grep -E -A 9999 -B 20 'Background.?Music' /var/log/install.log\n      - name: Check the BGM dirs and files were installed.\n        if: always()\n        run: |\n          ls -la \"/Applications/Background Music.app\"\n          ls -la \"/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\n          ls -la \"/usr/local/libexec/BGMXPCHelper.xpc\" \\\n            || ls -la \"/Library/Application Support/Background Music/BGMXPCHelper.xpc\"\n          ls -la \"/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\"\n      - name: Upload the .pkg installer and archives.\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: pkg-installer\n          path: Background-Music-*\n      - name: Upload the log file from the package.sh build.\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: build-and-install-log-for-pkg\n          path: build_and_install.log\n# TODO: Create a GitHub release. This is the Travis YAML that was handling it:\n# deploy:\n#  provider: releases\n#  api_key:\n#    secure: j5Gd[...]\n#  file_glob: true\n#  file: Background-Music-*/*\n#  skip_cleanup: true\n#  name: $TRAVIS_TAG\n#  prerelease: true\n#  draft: true\n#  on:\n#    repo: kyleneideck/BackgroundMusic\n#    tags: true\n#    # TODO: Use \"condition\" to build master and tags?\n#    condition: $DEPLOY = true"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.*.swp\n/BGMDriver/BGMDriver/quick_install.conf\n/build_and_install.log\n.idea/\ntags\ncmake-build-debug/\n/Background-Music-*/\nBGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist\nImages/*.aux\nImages/*.log\n/archives/\n\n# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore\n\n## Build generated\nbuild/\nDerivedData\n\n## Various settings\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n\n## Other\n*.xccheckout\n*.moved-aside\n*.xcuserstate\n*.xcscmblueprint\n\n## Obj-C/Swift specific\n*.hmap\n*.ipa\n"
  },
  {
    "path": "BGM.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:BGMDriver/BGMDriver.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:BGMApp/BGMApp.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:README.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:MANUAL-INSTALL.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:MANUAL-UNINSTALL.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:TODO.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:DEVELOPING.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:CONTRIBUTING.md\">\n   </FileRef>\n   <FileRef\n      location = \"group:LICENSE\">\n   </FileRef>\n   <FileRef\n      location = \"group:LICENSE-Apple-Sample-Code\">\n   </FileRef>\n   <FileRef\n      location = \"group:build_and_install.sh\">\n   </FileRef>\n   <FileRef\n      location = \"group:uninstall.sh\">\n   </FileRef>\n   <FileRef\n      location = \"group:package.sh\">\n   </FileRef>\n   <FileRef\n      location = \"group:pkg\">\n   </FileRef>\n   <FileRef\n      location = \"group:Images\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMApp-Debug.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.automation.apple-events</key>\n\t<true/>\n\t<key>com.apple.security.device.audio-input</key>\n\t<true/>\n    <!--\n    Without this key, AddressSanitizer and the UI tests would only work when BGMApp is code signed.\n    -->\n\t<key>com.apple.security.cs.disable-library-validation</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMApp.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.automation.apple-events</key>\n\t<true/>\n\t<key>com.apple.security.device.audio-input</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppDelegate.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppDelegate.h\n//  BGMApp\n//\n//  Copyright © 2016, 2017, 2020 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//\n//  Sets up and tears down the app.\n//\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n@class BGMAudioDeviceManager;\n@class BGMAppVolumesController;\n\n// Tags for UI elements in MainMenu.xib\nstatic NSInteger const kVolumesHeadingMenuItemTag = 3;\nstatic NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;\n\n@interface BGMAppDelegate : NSObject <NSApplicationDelegate, NSMenuDelegate>\n\n@property (weak) IBOutlet NSMenu* bgmMenu;\n\n@property (weak) IBOutlet NSView* outputVolumeView;\n@property (weak) IBOutlet NSTextField* outputVolumeLabel;\n@property (weak) IBOutlet NSSlider* outputVolumeSlider;\n\n@property (weak) IBOutlet NSView* systemSoundsView;\n@property (weak) IBOutlet NSSlider* systemSoundsSlider;\n\n@property (weak) IBOutlet NSView* appVolumeView;\n\n@property (weak) IBOutlet NSPanel* aboutPanel;\n@property (unsafe_unretained) IBOutlet NSTextView* aboutPanelLicenseView;\n\n@property (weak) IBOutlet NSMenuItem* autoPauseMenuItemUnwrapped;\n@property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped;\n\n@property (readonly) BGMAudioDeviceManager* audioDevices;\n@property BGMAppVolumesController* appVolumes;\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppDelegate.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppDelegate.mm\n//  BGMApp\n//\n//  Copyright © 2016-2022 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//\n\n// Self Include\n#import \"BGMAppDelegate.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMAppVolumes.h\"\n#import \"BGMAppVolumesController.h\"\n#import \"BGMAutoPauseMusic.h\"\n#import \"BGMAutoPauseMenuItem.h\"\n#import \"BGMDebugLoggingMenuItem.h\"\n#import \"BGMMusicPlayers.h\"\n#import \"BGMOutputDeviceMenuSection.h\"\n#import \"BGMOutputVolumeMenuItem.h\"\n#import \"BGMPreferencesMenu.h\"\n#import \"BGMPreferredOutputDevices.h\"\n#import \"BGMStatusBarItem.h\"\n#import \"BGMSystemSoundsVolume.h\"\n#import \"BGMTermination.h\"\n#import \"BGMUserDefaults.h\"\n#import \"BGMXPCListener.h\"\n#import \"SystemPreferences.h\"\n\n// System Includes\n#import <AVFoundation/AVCaptureDevice.h>\n\n\n#pragma clang assume_nonnull begin\n\nstatic NSString* const kOptNoPersistentData  = @\"--no-persistent-data\";\nstatic NSString* const kOptShowDockIcon      = @\"--show-dock-icon\";\n\n@implementation BGMAppDelegate {\n    // The button in the system status bar that shows the main menu.\n    BGMStatusBarItem* statusBarItem;\n    \n    // Only show the 'BGMXPCHelper is missing' error dialog once.\n    BOOL haveShownXPCHelperErrorMessage;\n\n    // Persistently stores user settings and data.\n    BGMUserDefaults* userDefaults;\n\n    BGMAutoPauseMusic* autoPauseMusic;\n    BGMAutoPauseMenuItem* autoPauseMenuItem;\n    BGMMusicPlayers* musicPlayers;\n    BGMSystemSoundsVolume* systemSoundsVolume;\n    BGMOutputDeviceMenuSection* outputDeviceMenuSection;\n    BGMPreferencesMenu* prefsMenu;\n    BGMDebugLoggingMenuItem* debugLoggingMenuItem;\n    BGMXPCListener* xpcListener;\n    BGMPreferredOutputDevices* preferredOutputDevices;\n}\n\n@synthesize audioDevices = audioDevices;\n@synthesize appVolumes = appVolumes;\n\n- (void) awakeFromNib {\n    [super awakeFromNib];\n    \n    // Show BGMApp in the dock, if the command-line option for that was passed. This is used by the\n    // UI tests.\n    if ([NSProcessInfo.processInfo.arguments indexOfObject:kOptShowDockIcon] != NSNotFound) {\n        [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];\n    }\n    \n    haveShownXPCHelperErrorMessage = NO;\n\n    // Set up audioDevices, which coordinates BGMDevice and the output device. It manages\n    // playthrough, volume/mute controls, etc.\n    if (![self initAudioDeviceManager]) {\n        return;\n    }\n\n    // Stored user settings\n    userDefaults = [self createUserDefaults];\n\n    // Add the status bar item. (The thing you click to show BGMApp's main menu.)\n    statusBarItem = [[BGMStatusBarItem alloc] initWithMenu:self.bgmMenu\n                                              audioDevices:audioDevices\n                                              userDefaults:userDefaults];\n}\n\n- (void) applicationDidFinishLaunching:(NSNotification*)aNotification {\n    #pragma unused (aNotification)\n    \n    // Log the version/build number.\n    //\n    // TODO: NSLog should only be used for logging errors.\n    // TODO: Automatically add the commit ID to the end of the build number for unreleased builds. (In the\n    //       Info.plist or something -- not here.)\n    NSLog(@\"BGMApp version: %@, BGMApp build number: %@\",\n          NSBundle.mainBundle.infoDictionary[@\"CFBundleShortVersionString\"],\n          NSBundle.mainBundle.infoDictionary[@\"CFBundleVersion\"]);\n\n    // Handles changing (or not changing) the output device when devices are added or removed. Must\n    // be initialised before calling setBGMDeviceAsDefault.\n    preferredOutputDevices =\n        [[BGMPreferredOutputDevices alloc] initWithDevices:audioDevices userDefaults:userDefaults];\n\n    // Skip this if we're compiling on a version of macOS before 10.14 as won't compile and it\n    // isn't needed.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400  // MAC_OS_X_VERSION_10_14\n    if (@available(macOS 10.14, *)) {\n        // On macOS 10.14+ we need to get the user's permission to use input devices before we can\n        // use BGMDevice for playthrough (see BGMPlayThrough), so we wait until they've given it\n        // before making BGMDevice the default device. This way, if the user is playing audio when\n        // they open Background Music, we won't interrupt it while we're waiting for them to click\n        // OK.\n        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio\n                                 completionHandler:^(BOOL granted) {\n            dispatch_async(dispatch_get_main_queue(), ^{\n                if (granted) {\n                    DebugMsg(\"BGMAppDelegate::applicationDidFinishLaunching: Permission granted\");\n                    [self continueLaunchAfterInputDevicePermissionGranted];\n                } else {\n                    NSLog(@\"BGMAppDelegate::applicationDidFinishLaunching: Permission denied\");\n                    // If they don't accept, Background Music won't work at all and the only way to\n                    // fix it is in System Preferences, so show an error dialog with instructions.\n                    //\n                    // TODO: It would be nice if this dialog had a shortcut to open the System\n                    //       Preferences panel. See showSetDeviceAsDefaultError.\n                    [self showErrorMessage:@\"Background Music needs permission to use microphones.\"\n                           informativeText:@\"It uses a virtual microphone to access your system's \"\n                                            \"audio.\\n\\nYou can grant the permission by going to \"\n                                            \"System Preferences > Security and Privacy > \"\n                                            \"Microphone and checking the box for Background Music.\"\n                 exitAfterMessageDismissed:YES];\n                }\n            });\n        }];\n    }\n    else\n#endif\n    {\n        // We can change the device immediately on older versions of macOS because they don't\n        // require user permission for input devices.\n        [self continueLaunchAfterInputDevicePermissionGranted];\n    }\n}\n\n- (void) continueLaunchAfterInputDevicePermissionGranted {\n    // Choose an output device for BGMApp to use to play audio.\n    if (![self setInitialOutputDevice]) {\n        return;\n    }\n\n    // Make BGMDevice the default device.\n    [self setBGMDeviceAsDefault];\n\n    // Handle some of the unusual reasons BGMApp might have to exit, mostly crashes.\n    BGMTermination::SetUpTerminationCleanUp(audioDevices);\n\n    // Set up the rest of the UI and other external interfaces.\n    musicPlayers = [[BGMMusicPlayers alloc] initWithAudioDevices:audioDevices\n                                                    userDefaults:userDefaults];\n\n    autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices\n                                                        musicPlayers:musicPlayers\n                                                        userDefaults:userDefaults];\n\n    [self setUpMainMenu];\n\n    xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices\n                                  helperConnectionErrorHandler:^(NSError* error) {\n        NSLog(@\"BGMAppDelegate::continueLaunchAfterInputDevicePermissionGranted: \"\n              \"(helperConnectionErrorHandler) BGMXPCHelper connection error: %@\",\n              error);\n        [self showXPCHelperErrorMessage:error];\n    }];\n}\n\n// Returns NO if (and only if) BGMApp is about to terminate because of a fatal error.\n- (BOOL) initAudioDeviceManager {\n    audioDevices = [BGMAudioDeviceManager new];\n\n    if (!audioDevices) {\n        [self showBGMDeviceNotFoundErrorMessageAndExit];\n        return NO;\n    }\n\n    return YES;\n}\n\n// Returns NO if (and only if) BGMApp is about to terminate because of a fatal error.\n- (BOOL) setInitialOutputDevice {\n    AudioObjectID preferredDevice = [preferredOutputDevices findPreferredDevice];\n\n    if (preferredDevice != kAudioObjectUnknown) {\n        NSError* __nullable error = [audioDevices setOutputDeviceWithID:preferredDevice\n                                                        revertOnFailure:NO];\n        if (error) {\n            // Show the error message.\n            [self showFailedToSetOutputDeviceErrorMessage:BGMNN(error)\n                                          preferredDevice:preferredDevice];\n        }\n    } else {\n        // We couldn't find a device to use, so show an error message and quit.\n        [self showOutputDeviceNotFoundErrorMessageAndExit];\n        return NO;\n    }\n\n    return YES;\n}\n\n// Sets the \"Background Music\" virtual audio device (BGMDevice) as the user's default audio device.\n- (void) setBGMDeviceAsDefault {\n    NSError* error = [audioDevices setBGMDeviceAsOSDefault];\n\n    if (error) {\n        [self showSetDeviceAsDefaultError:error\n                                  message:@\"Could not set the Background Music device as your\"\n                                           \"default audio device.\"\n                          informativeText:@\"You might be able to change it yourself.\"];\n    }\n}\n\n- (void) menuWillOpen:(NSMenu*)menu {\n    if (@available(macOS 10.16, *)) {\n        // Set menu offset and check for any active menu items\n        float menuOffset = 12.0;\n        for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {\n            if (menuItem.state == NSControlStateValueOn && menuItem.indentationLevel == 0) {\n                menuOffset += 10;\n                break;\n            }\n        }\n        \n        // Align volume output device and slider\n        for (NSView* subview in self.outputVolumeView.subviews) {\n            CGRect newSubview = subview.frame;\n            newSubview.origin.x = menuOffset;\n            subview.frame = newSubview;\n        }\n\n        // Align system sounds and app volumes\n        double appIconTitleOffset = 0;\n        for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {\n            if (menuItem.view.subviews.count == 7 || menuItem.view.subviews.count == 3) {\n                NSTextField* appTitle;\n                NSImageView* appIcon;\n                \n                for (NSView* subview in menuItem.view.subviews) {\n                    if (menuItem.view.subviews.count == 3) {\n                        // System sounds\n                        if ([subview isKindOfClass:[NSTextField class]]) {\n                            appTitle = (NSTextField*)subview;\n                        }\n                        if ([subview isKindOfClass:[NSImageView class]]) {\n                            appIcon = (NSImageView*)subview;\n                        }\n                    } else if (menuItem.view.subviews.count == 7) {\n                        // App volumes\n                        if ([subview isKindOfClass:[BGMAVM_AppNameLabel class]]) {\n                            appTitle = (NSTextField*)subview;\n                        }\n                        if ([subview isKindOfClass:[BGMAVM_AppIcon class]]) {\n                            appIcon = (NSImageView*)subview;\n                        }\n                    }\n                }\n \n                if (appIconTitleOffset == 0) {\n                    appIconTitleOffset = appTitle.frame.origin.x - appIcon.frame.origin.x;\n                }\n                \n                CGRect newAppIcon = appIcon.frame;\n                newAppIcon.origin.x = menuOffset;\n                appIcon.frame = newAppIcon;\n                CGRect newAppTitle = appTitle.frame;\n                newAppTitle.origin.x = menuOffset + appIconTitleOffset;\n                appTitle.frame = newAppTitle;\n            }\n        }\n    }\n}\n\n- (void) setUpMainMenu {\n    autoPauseMenuItem =\n        [[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped\n                                        autoPauseMusic:autoPauseMusic\n                                          musicPlayers:musicPlayers\n                                          userDefaults:userDefaults];\n\n    [self initVolumesMenuSection];\n\n    // Output device selection.\n    outputDeviceMenuSection =\n            [[BGMOutputDeviceMenuSection alloc] initWithBGMMenu:self.bgmMenu\n                                                   audioDevices:audioDevices\n                                               preferredDevices:preferredOutputDevices];\n    [audioDevices setOutputDeviceMenuSection:outputDeviceMenuSection];\n\n    // Preferences submenu.\n    prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu\n                                               audioDevices:audioDevices\n                                               musicPlayers:musicPlayers\n                                              statusBarItem:statusBarItem\n                                                 aboutPanel:self.aboutPanel\n                                      aboutPanelLicenseView:self.aboutPanelLicenseView\n                                               userDefaults:userDefaults];\n\n    // Enable/disable debug logging. Hidden unless you option-click the status bar icon.\n    debugLoggingMenuItem =\n        [[BGMDebugLoggingMenuItem alloc] initWithMenuItem:self.debugLoggingMenuItemUnwrapped];\n    [statusBarItem setDebugLoggingMenuItem:debugLoggingMenuItem];\n\n    // Handle events about the main menu. (See the NSMenuDelegate methods below.)\n    self.bgmMenu.delegate = self;\n}\n\n- (BGMUserDefaults*) createUserDefaults {\n    BOOL persistentDefaults =\n        [NSProcessInfo.processInfo.arguments indexOfObject:kOptNoPersistentData] == NSNotFound;\n    NSUserDefaults* wrappedDefaults = persistentDefaults ? [NSUserDefaults standardUserDefaults] : nil;\n    return [[BGMUserDefaults alloc] initWithDefaults:wrappedDefaults];\n}\n\n- (void) initVolumesMenuSection {\n    // Create the menu item with the (main) output volume slider.\n    BGMOutputVolumeMenuItem* outputVolume =\n            [[BGMOutputVolumeMenuItem alloc] initWithAudioDevices:audioDevices\n                                                             view:self.outputVolumeView\n                                                           slider:self.outputVolumeSlider\n                                                      deviceLabel:self.outputVolumeLabel];\n    [audioDevices setOutputVolumeMenuItem:outputVolume];\n\n    NSInteger headingIdx = [self.bgmMenu indexOfItemWithTag:kVolumesHeadingMenuItemTag];\n\n    // Add it to the main menu below the \"Volumes\" heading.\n    [self.bgmMenu insertItem:outputVolume atIndex:(headingIdx + 1)];\n\n    // Add the volume control for system (UI) sounds to the menu.\n    BGMAudioDevice uiSoundsDevice = [audioDevices bgmDevice].GetUISoundsBGMDeviceInstance();\n\n    systemSoundsVolume =\n        [[BGMSystemSoundsVolume alloc] initWithUISoundsDevice:uiSoundsDevice\n                                                         view:self.systemSoundsView\n                                                       slider:self.systemSoundsSlider];\n\n    [self.bgmMenu insertItem:systemSoundsVolume.menuItem atIndex:(headingIdx + 2)];\n\n    // Add the app volumes to the menu.\n    appVolumes = [[BGMAppVolumesController alloc] initWithMenu:self.bgmMenu\n                                                 appVolumeView:self.appVolumeView\n                                                  audioDevices:audioDevices];\n}\n\n- (void) applicationWillTerminate:(NSNotification*)aNotification {\n    #pragma unused (aNotification)\n    \n    DebugMsg(\"BGMAppDelegate::applicationWillTerminate\");\n\n    // Change the user's default output device back.\n    NSError* error = [audioDevices unsetBGMDeviceAsOSDefault];\n    \n    if (error) {\n        [self showSetDeviceAsDefaultError:error\n                                  message:@\"Failed to reset your system's audio output device.\"\n                          informativeText:@\"You'll have to change it yourself to get audio working again.\"];\n    }\n}\n\n#pragma mark Error messages\n\n- (void) showBGMDeviceNotFoundErrorMessageAndExit {\n    // BGMDevice wasn't found on the system. Most likely, BGMDriver isn't installed. Show an error\n    // dialog and exit.\n    //\n    // TODO: Check whether the driver files are in /Library/Audio/Plug-Ins/HAL? Might even want to\n    //       offer to install them if not.\n    [self showErrorMessage:@\"Could not find the Background Music virtual audio device.\"\n           informativeText:@\"Make sure you've installed Background Music Device.driver to \"\n                            \"/Library/Audio/Plug-Ins/HAL and restarted coreaudiod (e.g. \\\"sudo \"\n                            \"killall coreaudiod\\\").\"\n exitAfterMessageDismissed:YES];\n}\n\n- (void) showFailedToSetOutputDeviceErrorMessage:(NSError*)error\n                                 preferredDevice:(BGMAudioDevice)device {\n    NSLog(@\"Failed to set initial output device. Error: %@\", error);\n\n    dispatch_async(dispatch_get_main_queue(), ^{\n        NSAlert* alert = [NSAlert alertWithError:BGMNN(error)];\n        alert.messageText = @\"Failed to set the output device.\";\n\n        NSString* __nullable name = nil;\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            name = (__bridge NSString* __nullable)device.CopyName();\n        });\n\n        alert.informativeText =\n                [NSString stringWithFormat:@\"Could not start the device '%@'. (Error: %ld)\",\n                        name, error.code];\n\n        [alert runModal];\n    });\n}\n\n- (void) showOutputDeviceNotFoundErrorMessageAndExit {\n    // We couldn't find any output devices. Show an error dialog and exit.\n    [self showErrorMessage:@\"Could not find an audio output device.\"\n           informativeText:@\"If you do have one installed, this is probably a bug. Sorry about \"\n                            \"that. Feel free to file an issue on GitHub.\"\n exitAfterMessageDismissed:YES];\n}\n\n- (void) showXPCHelperErrorMessage:(NSError*)error {\n    if (!haveShownXPCHelperErrorMessage) {\n        haveShownXPCHelperErrorMessage = YES;\n        \n        // NSAlert should only be used on the main thread.\n        dispatch_async(dispatch_get_main_queue(), ^{\n            NSAlert* alert = [NSAlert new];\n            \n            // TODO: Offer to install BGMXPCHelper if it's missing.\n            // TODO: Show suppression button?\n            [alert setMessageText:@\"Error connecting to BGMXPCHelper.\"];\n            [alert setInformativeText:[NSString stringWithFormat:@\"%s%s%@ (%lu)\",\n                                       \"Make sure you have BGMXPCHelper installed. There are instructions in the \"\n                                       \"README.md file.\\n\\n\"\n                                       \"Background Music might still work, but it won't work as well as it could.\",\n                                       \"\\n\\nDetails:\\n\",\n                                       [error localizedDescription],\n                                       [error code]]];\n            [alert runModal];\n        });\n    }\n}\n\n- (void) showErrorMessage:(NSString*)message\n          informativeText:(NSString*)informativeText\nexitAfterMessageDismissed:(BOOL)fatal {\n    // NSAlert should only be used on the main thread.\n    dispatch_async(dispatch_get_main_queue(), ^{\n        NSAlert* alert = [NSAlert new];\n        [alert setMessageText:message];\n        [alert setInformativeText:informativeText];\n\n        // This crashes if built with Xcode 9.0.1, but works with versions of Xcode before 9 and\n        // with 9.1.\n        [alert runModal];\n\n        if (fatal) {\n            [NSApp terminate:self];\n        }\n    });\n}\n\n- (void) showSetDeviceAsDefaultError:(NSError*)error\n                             message:(NSString*)msg\n                     informativeText:(NSString*)info {\n    dispatch_async(dispatch_get_main_queue(), ^{\n        NSLog(@\"%@ %@ Error: %@\", msg, info, error);\n        \n        NSAlert* alert = [NSAlert alertWithError:error];\n        alert.messageText = msg;\n        alert.informativeText = info;\n        \n        [alert addButtonWithTitle:@\"OK\"];\n        [alert addButtonWithTitle:@\"Open Sound in System Preferences\"];\n        \n        NSModalResponse buttonClicked = [alert runModal];\n        \n        if (buttonClicked != NSAlertFirstButtonReturn) {  // 'OK' is the first button.\n            [self openSysPrefsSoundOutput];\n        }\n    });\n}\n\n- (void) openSysPrefsSoundOutput {\n    SystemPreferencesApplication* __nullable sysPrefs =\n        [SBApplication applicationWithBundleIdentifier:@\"com.apple.systempreferences\"];\n    \n    if (!sysPrefs) {\n        NSLog(@\"Could not open System Preferences\");\n        return;\n    }\n    \n    // In System Preferences, go to the \"Output\" tab on the \"Sound\" pane.\n    for (SystemPreferencesPane* pane : [sysPrefs panes]) {\n        DebugMsg(\"BGMAppDelegate::openSysPrefsSoundOutput: pane = %s\", [pane.name UTF8String]);\n        \n        if ([pane.id isEqualToString:@\"com.apple.preference.sound\"]) {\n            sysPrefs.currentPane = pane;\n            \n            for (SystemPreferencesAnchor* anchor : [pane anchors]) {\n                DebugMsg(\"BGMAppDelegate::openSysPrefsSoundOutput: anchor = %s\", [anchor.name UTF8String]);\n                \n                if ([[anchor.name lowercaseString] isEqualToString:@\"output\"]) {\n                    DebugMsg(\"BGMAppDelegate::openSysPrefsSoundOutput: Showing Output in Sound pane.\");\n                    \n                    [anchor reveal];\n                }\n            }\n        }\n    }\n    \n    // Bring System Preferences to the foreground.\n    [sysPrefs activate];\n}\n\n#pragma mark NSMenuDelegate\n\n- (void) menuNeedsUpdate:(NSMenu*)menu {\n    if ([menu isEqual:self.bgmMenu]) {\n        [autoPauseMenuItem parentMenuNeedsUpdate];\n    } else {\n        DebugMsg(\"BGMAppDelegate::menuNeedsUpdate: Warning: unexpected menu. menu=%s\", menu.description.UTF8String);\n    }\n}\n\n- (void) menu:(NSMenu*)menu willHighlightItem:(NSMenuItem* __nullable)item {\n    if ([menu isEqual:self.bgmMenu]) {\n        [autoPauseMenuItem parentMenuItemWillHighlight:item];\n    } else {\n        DebugMsg(\"BGMAppDelegate::menu: Warning: unexpected menu. menu=%s\", menu.description.UTF8String);\n    }\n}\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumes.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppVolumes.h\n//  BGMApp\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//  Copyright © 2026 TwelfthFace\n//\n\n// Local Includes\n#import \"BGMAppVolumesController.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAppVolumes : NSObject\n\n- (id) initWithController:(BGMAppVolumesController*)inController\n                  bgmMenu:(NSMenu*)inMenu\n            appVolumeView:(NSView*)inView;\n\n// Pass -1 for initialVolume or kAppPanNoValue for initialPan to leave the volume/pan at its default level.\n- (void) insertMenuItemForApp:(NSRunningApplication*)app\n                initialVolume:(int)volume\n                   initialPan:(int)pan;\n\n- (void) removeMenuItemForApp:(NSRunningApplication*)app;\n\n- (void) removeAllAppVolumeMenuItems;\n\n- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app;\n- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;\n\n@end\n\n// Protocol for the UI custom classes\n\n@protocol BGMAppVolumeMenuItemSubview <NSObject>\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)item;\n\n@end\n\n// Custom classes for the UI elements in the app volume menu items\n\n@interface BGMAVM_VolumeMute: NSButton <BGMAppVolumeMenuItemSubview>\n\n- (void) bgm_syncForVolume:(int)vol;\n\n@end\n\n\n@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeMenuItemSubview>\n@end\n\n@interface BGMAVM_AppNameLabel : NSTextField <BGMAppVolumeMenuItemSubview>\n@end\n\n@interface BGMAVM_ShowMoreControlsButton : NSButton <BGMAppVolumeMenuItemSubview>\n@end\n\n@interface BGMAVM_VolumeSlider : NSSlider <BGMAppVolumeMenuItemSubview>\n\n- (void) setRelativeVolume:(int)relativeVolume;\n\n@end\n\n@interface BGMAVM_PanSlider : NSSlider <BGMAppVolumeMenuItemSubview>\n\n- (void) setPanPosition:(int)panPosition;\n\n@end\n\n#pragma clang assume_nonnull end\n\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumes.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppVolumes.m\n//  BGMApp\n//\n//  Copyright © 2016-2020, 2026 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//  Copyright © 2021 Marcus Wu\n//  Copyright © 2022 Jon Egan\n//  Copyright © 2026 TwelfthFace\n//\n\n// Self Include\n#import \"BGMAppVolumes.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n#import \"BGMAppDelegate.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\nstatic float const     kSlidersSnapWithin          = 5;\nstatic CGFloat const   kAppVolumeViewInitialHeight = 20;\nstatic NSString* const kMoreAppsMenuTitle          = @\"More Apps\";\n\n@implementation BGMAppVolumes {\n    BGMAppVolumesController* controller;\n\n    NSMenu* bgmMenu;\n    NSMenu* moreAppsMenu;\n    \n    NSView* appVolumeView;\n    CGFloat appVolumeViewFullHeight;\n\n    // The number of menu items this class has added to bgmMenu. Doesn't include the More Apps menu.\n    NSInteger numMenuItems;\n}\n\n- (id) initWithController:(BGMAppVolumesController*)inController\n                  bgmMenu:(NSMenu*)inMenu\n            appVolumeView:(NSView*)inView {\n    if ((self = [super init])) {\n        controller = inController;\n        bgmMenu = inMenu;\n        moreAppsMenu = [[NSMenu alloc] initWithTitle:kMoreAppsMenuTitle];\n        appVolumeView = inView;\n        appVolumeViewFullHeight = appVolumeView.frame.size.height;\n        numMenuItems = 0;\n\n        // Add the More Apps menu to the main menu.\n        NSMenuItem* moreAppsMenuItem =\n            [[NSMenuItem alloc] initWithTitle:kMoreAppsMenuTitle action:nil keyEquivalent:@\"\"];\n        moreAppsMenuItem.submenu = moreAppsMenu;\n\n        [bgmMenu insertItem:moreAppsMenuItem atIndex:([self lastMenuItemIndex] + 1)];\n        numMenuItems++;\n\n        // Put an empty menu item above the More Apps menu item to fix its top margin.\n        NSMenuItem* spacer = [[NSMenuItem alloc] initWithTitle:@\"\" action:nil keyEquivalent:@\"\"];\n        spacer.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 4)];\n        spacer.hidden = YES;  // Tells accessibility clients to ignore this menu item.\n\n        [bgmMenu insertItem:spacer atIndex:[self lastMenuItemIndex]];\n        numMenuItems++;\n    }\n    \n    return self;\n}\n\n#pragma mark UI Modifications\n\n- (void) insertMenuItemForApp:(NSRunningApplication*)app\n                initialVolume:(int)volume\n                   initialPan:(int)pan {\n    NSMenuItem* appVolItem = [self createBlankAppVolumeMenuItem];\n\n    // Look through the menu item's subviews for the ones we want to set up\n    for (NSView* subview in appVolItem.view.subviews) {\n        if ([subview conformsToProtocol:@protocol(BGMAppVolumeMenuItemSubview)]) {\n            [(NSView<BGMAppVolumeMenuItemSubview>*)subview setUpWithApp:app\n                                                                context:self\n                                                             controller:controller\n                                                               menuItem:appVolItem];\n        }\n    }\n\n    // Store the NSRunningApplication object with the menu item so when the app closes we can find the item to remove it\n    appVolItem.representedObject = app;\n\n    // Set the slider to the volume for this app if we got one from the driver\n    [self setVolumeOfMenuItem:appVolItem relativeVolume:volume panPosition:pan];\n\n    // NSMenuItem didn't implement NSAccessibility before OS X SDK 10.12.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200  // MAC_OS_X_VERSION_10_12\n    if ([appVolItem respondsToSelector:@selector(setAccessibilityTitle:)]) {\n        // TODO: This doesn't show up in Accessibility Inspector for me. Not sure why.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        appVolItem.accessibilityTitle = [NSString stringWithFormat:@\"%@\", [app localizedName]];\n#pragma clang diagnostic pop\n    }\n#endif\n\n    // Add the menu item to its menu.\n    if (app.activationPolicy == NSApplicationActivationPolicyRegular) {\n        [bgmMenu insertItem:appVolItem atIndex:[self firstMenuItemIndex]];\n        numMenuItems++;\n    } else if (app.activationPolicy == NSApplicationActivationPolicyAccessory) {\n        [moreAppsMenu insertItem:appVolItem atIndex:0];\n    }\n}\n\n- (NSMenuItem*) getMenuItemForApp:(NSRunningApplication*)app {\n    NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;\n\n    for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {\n        NSMenuItem* item = [bgmMenu itemAtIndex:i];\n        NSRunningApplication* itemApp = item.representedObject;\n        BGMAssert(itemApp, \"!itemApp for %s\", item.title.UTF8String);\n\n        if ([itemApp isEqual:app]) {\n            return item;\n        }\n    }\n    for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {\n        NSMenuItem* item = [moreAppsMenu itemAtIndex:i];\n        NSRunningApplication* itemApp = item.representedObject;\n        BGMAssert(itemApp, \"!itemApp for %s\", item.title.UTF8String);\n\n        if ([itemApp isEqual:app]) {\n            return item;\n        }\n    }\n\n    return nil;\n}\n\n- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app {\n    BGMAppVolumeAndPan result = {\n        .volume = -1,\n        .pan = kAppPanNoValue\n    };\n\n    NSMenuItem *item = [self getMenuItemForApp:app];\n\n    if (item == nil) {\n        return result;\n    }\n\n    for (NSView* subview in item.view.subviews) {\n        // Get the volume.\n        if ([subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {\n            result.volume = [(BGMAVM_VolumeSlider*)subview intValue];\n        }\n\n        // Get the pan position.\n        if ([subview isKindOfClass:[BGMAVM_PanSlider class]]) {\n            result.pan = [(BGMAVM_PanSlider*)subview intValue];\n        }\n    }\n\n    return result;\n}\n\n- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {\n    NSMenuItem *item = [self getMenuItemForApp:app];\n\n    if (item == nil) {\n        return;\n    }\n\n    for (NSView* subview in item.view.subviews) {\n        // Set the volume.\n        if (volumeAndPan.volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {\n            [(BGMAVM_VolumeSlider*)subview setRelativeVolume:volumeAndPan.volume];\n        }\n\n        // Set the pan position.\n        if (volumeAndPan.pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {\n            [(BGMAVM_PanSlider*)subview setPanPosition:volumeAndPan.pan];\n        }\n    }\n\n}\n\n// Create a blank menu item to copy as a template.\n- (NSMenuItem*) createBlankAppVolumeMenuItem {\n    NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@\"\" action:nil keyEquivalent:@\"\"];\n    \n    menuItem.view = appVolumeView;\n    menuItem = [menuItem copy];  // So we can modify a copy of the view, rather than the template itself.\n    \n    return menuItem;\n}\n\n- (void) setVolumeOfMenuItem:(NSMenuItem*)menuItem relativeVolume:(int)volume panPosition:(int)pan {\n    // Update the sliders.\n    for (NSView* subview in menuItem.view.subviews) {\n        // Set the volume.\n        if (volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {\n            [(BGMAVM_VolumeSlider*)subview setRelativeVolume:volume];\n        }\n\n        // Set the pan position.\n        if (pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {\n            [(BGMAVM_PanSlider*)subview setPanPosition:pan];\n        }\n    }\n}\n\n- (NSInteger) firstMenuItemIndex {\n    return [self lastMenuItemIndex] - numMenuItems + 1;\n}\n\n- (NSInteger) lastMenuItemIndex {\n    return [bgmMenu indexOfItemWithTag:kSeparatorBelowVolumesMenuItemTag] - 1;\n}\n\n- (void) removeMenuItemForApp:(NSRunningApplication*)app {\n    // Subtract two extra positions to skip the More Apps menu and the spacer menu item above it.\n    NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;\n\n    // Check each app volume menu item and remove the item that controls the given app.\n\n    // Look through the main menu.\n    for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {\n        NSMenuItem* item = [bgmMenu itemAtIndex:i];\n        NSRunningApplication* itemApp = item.representedObject;\n        BGMAssert(itemApp, \"!itemApp for %s\", item.title.UTF8String);\n\n        if ([itemApp isEqual:app]) {\n            [bgmMenu removeItem:item];\n            numMenuItems--;\n            return;\n        }\n    }\n\n    // Look through the More Apps menu.\n    for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {\n        NSMenuItem* item = [moreAppsMenu itemAtIndex:i];\n        NSRunningApplication* itemApp = item.representedObject;\n        BGMAssert(itemApp, \"!itemApp for %s\", item.title.UTF8String);\n\n        if ([itemApp isEqual:app]) {\n            [moreAppsMenu removeItem:item];\n            return;\n        }\n    }\n}\n\n- (void) showHideExtraControls:(BGMAVM_ShowMoreControlsButton*)button {\n    // Show or hide an app's extra controls, currently only pan, in its App Volumes menu item.\n    \n    NSMenuItem* menuItem = button.cell.representedObject;\n    \n    BGMAssert(button, \"!button\");\n    BGMAssert(menuItem, \"!menuItem\");\n\n    CGFloat width = menuItem.view.frame.size.width;\n#if DEBUG\n    CGFloat height = menuItem.view.frame.size.height;\n#endif\n\n    const char* appName =\n        [((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];\n\n    // Using this function (instead of just ==) shouldn't be necessary, but just in case.\n#if DEBUG\n    BOOL(^nearEnough)(CGFloat x, CGFloat y) = ^BOOL(CGFloat x, CGFloat y) {\n        return fabs(x - y) < 0.01;  // We don't need much precision.\n    };\n#endif\n    \n    bool allSubviewsShowing = true;\n    for (NSView* subview in menuItem.view.subviews) {\n        if (subview.hidden) {\n            allSubviewsShowing = false;\n            break;\n        }\n        //DebugMsg(\"BGMAppVolumes:: subview hash / hidden: (%lu) / (%hhd)\", (unsigned long)subview.hash, subview.hidden);\n    }\n\n    if (allSubviewsShowing) {\n        // Hide extra controls\n        DebugMsg(\"BGMAppVolumes::showHideExtraControls: Hiding extra controls (%s)\", appName);\n        \n        BGMAssert(nearEnough(height, appVolumeViewFullHeight), \"Extra controls were already hidden\");\n        \n        // Make the menu item shorter to hide the extra controls. Keep the width unchanged.\n        menuItem.view.frameSize = NSMakeSize(width, kAppVolumeViewInitialHeight);\n        // Turn the button upside down so the arrowhead points down.\n        button.frameCenterRotation = 180.0;\n        // Move the button up slightly so it aligns with the volume slider.\n        [button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y - 1)];\n\n        // Set the extra controls, and anything else below the fold, to hidden so accessibility\n        // clients can skip over them.\n        for (NSView* subview in menuItem.view.subviews) {\n            CGFloat top = subview.frame.origin.y + subview.frame.size.height;\n            if (top <= 0.0) {\n                subview.hidden = YES;\n            }\n        }\n    } else {\n        // Show extra controls\n        DebugMsg(\"BGMAppVolumes::showHideExtraControls: Showing extra controls (%s)\", appName);\n        \n        BGMAssert(nearEnough(button.frameCenterRotation, 180.0), \"Unexpected button rotation\");\n        BGMAssert(nearEnough(height, kAppVolumeViewInitialHeight), \"Extra controls were already shown\");\n        \n        // Make the menu item taller to show the extra controls. Keep the width unchanged.\n        menuItem.view.frameSize = NSMakeSize(width, appVolumeViewFullHeight);\n        // Turn the button rightside up so the arrowhead points up.\n        button.frameCenterRotation = 0.0;\n        // Move the button down slightly, back to its original position.\n        [button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y + 1)];\n\n        // Set all of the UI elements in the menu item to \"not hidden\" for accessibility clients.\n        for (NSView* subview in menuItem.view.subviews) {\n            subview.hidden = NO;\n        }\n    }\n}\n\n- (void) removeAllAppVolumeMenuItems {\n    // Remove all of the menu items this class adds to the menu except for the last two, which are\n    // the More Apps menu item and the invisible spacer above it.\n    while (numMenuItems > 2) {\n        [bgmMenu removeItemAtIndex:[self firstMenuItemIndex]];\n        numMenuItems--;\n    }\n\n    // The More Apps menu only contains app volume menu items, so we can just remove everything.\n    [moreAppsMenu removeAllItems];\n}\n\n@end\n\n#pragma mark Custom Classes (IB)\n\n// Custom classes for the UI elements in the app volume menu items\n\n@implementation BGMAVM_AppIcon\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)menuItem {\n    #pragma unused (ctx, ctrl, menuItem)\n    \n    self.image = app.icon;\n\n    // Remove the icon from the accessibility hierarchy.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000  // MAC_OS_X_VERSION_10_10\n    if ([self.cell respondsToSelector:@selector(setAccessibilityElement:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        self.cell.accessibilityElement = NO;\n#pragma clang diagnostic pop\n    }\n#endif\n}\n\n@end\n\n@implementation BGMAVM_AppNameLabel\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)menuItem {\n    #pragma unused (ctx, ctrl, menuItem)\n    \n    NSString* name = app.localizedName ? (NSString*)app.localizedName : @\"\";\n    self.stringValue = name;\n}\n\n@end\n\n@implementation BGMAVM_ShowMoreControlsButton\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)menuItem {\n    #pragma unused (app, ctrl)\n    \n    // Set up the button that show/hide the extra controls (currently only a pan slider) for the app.\n    self.cell.representedObject = menuItem;\n    self.target = ctx;\n    self.action = @selector(showHideExtraControls:);\n    \n    // The menu item starts out with the extra controls visible, so we hide them here.\n    //\n    // TODO: Leave them visible if any of the controls are set to non-default values. The user has no way to\n    //       tell otherwise. Maybe we should also make this button look different if the controls are hidden\n    //       when they have non-default values.\n    [ctx showHideExtraControls:self];\n\n    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        self.accessibilityTitle = @\"More options\";\n#pragma clang diagnostic pop\n    }\n}\n\n@end\n\n@implementation BGMAVM_VolumeMute {\n    pid_t appProcessID;\n    NSString* __nullable appBundleID;\n    BGMAppVolumesController* controller;\n}\n\n- (NSString*) lastNonZeroVolumeDefaultsKey {\n    if (appBundleID.length > 0) {\n        return [NSString stringWithFormat:@\"BGMAVM_LastNonZeroVolume_%@\", appBundleID];\n    }\n    return [NSString stringWithFormat:@\"BGMAVM_LastNonZeroVolume_pid_%d\", appProcessID];\n}\n\n- (BOOL) isMuted:(int)value {\n    return value <= kAppRelativeVolumeMinRawValue;\n}\n\n- (int) defaultRestoreVolume {\n    return (int)((kAppRelativeVolumeMaxRawValue + kAppRelativeVolumeMinRawValue) / 2);\n}\n\n- (BGMAVM_VolumeSlider* __nullable) findSiblingVolumeSlider {\n    for (NSView* view in self.superview.subviews) {\n        if ([view isKindOfClass:[BGMAVM_VolumeSlider class]]) {\n            return (BGMAVM_VolumeSlider*)view;\n        }\n    }\n    return nil;\n}\n\n- (void) updateButtonForVolume:(int)volume {\n    BOOL muted = [self isMuted:volume];\n\n    if ([NSImage respondsToSelector:@selector(imageWithSystemSymbolName:accessibilityDescription:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        NSString* symbol = muted ? @\"speaker.slash.fill\" : @\"speaker.wave.2.fill\";\n        NSString* description = muted ? @\"Unmute\" : @\"Mute\";\n        self.image = [NSImage imageWithSystemSymbolName:symbol accessibilityDescription:description];\n#pragma clang diagnostic pop\n        self.imagePosition = NSImageOnly;\n        self.title = @\"\";\n    } else {\n        self.title = muted ? @\"Unmute\" : @\"Mute\";\n    }\n}\n\n- (void) bgm_syncForVolume:(int)volume {\n    [self updateButtonForVolume:volume];\n}\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)menuItem {\n#pragma unused (ctx, menuItem)\n\n    controller = ctrl;\n    appProcessID = app.processIdentifier;\n    appBundleID = app.bundleIdentifier;\n\n    self.target = self;\n    self.action = @selector(mutePressed:);\n\n    BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider];\n    int currentVol = slider ? slider.intValue : kAppRelativeVolumeMinRawValue;\n    [self updateButtonForVolume:currentVol];\n}\n\n- (IBAction) mutePressed:(id)sender {\n#pragma unused(sender)\n\n    BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider];\n    if (!slider) {\n        DebugMsg(\"Mute button: no slider found\");\n        return;\n    }\n\n    int currentVol = slider.intValue;\n    BOOL mutedNow = [self isMuted:currentVol];\n\n    if (!mutedNow) {\n        // Store last volume\n        [[NSUserDefaults standardUserDefaults] setInteger:currentVol\n                                                   forKey:[self lastNonZeroVolumeDefaultsKey]];\n\n        [slider setRelativeVolume:kAppRelativeVolumeMinRawValue];\n    } else {\n        NSInteger last = [[NSUserDefaults standardUserDefaults] integerForKey:[self lastNonZeroVolumeDefaultsKey]];\n        int restoreVol = (int)last;\n\n        if (restoreVol <= kAppRelativeVolumeMinRawValue ||\n            restoreVol > kAppRelativeVolumeMaxRawValue) {\n            restoreVol = [self defaultRestoreVolume];\n        }\n\n        [slider setRelativeVolume:restoreVol];\n    }\n\n    [controller setVolume:slider.intValue\n      forAppWithProcessID:appProcessID\n                 bundleID:appBundleID];\n\n    [self updateButtonForVolume:slider.intValue];\n}\n\n@end\n\n@implementation BGMAVM_VolumeSlider {\n    // Will be set to -1 for apps without a pid\n    pid_t appProcessID;\n    NSString* __nullable appBundleID;\n    BGMAppVolumesController* controller;\n\n    // Keep the menu item so we can sync the mute button when the slider changes.\n    __weak NSMenuItem* menuItem;\n}\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)inMenuItem {\n    #pragma unused (ctx)\n    \n    controller = ctrl;\n    menuItem = inMenuItem;\n    \n    self.target = self;\n    self.action = @selector(appVolumeChanged);\n    \n    appProcessID = app.processIdentifier;\n    appBundleID = app.bundleIdentifier;\n    \n    self.maxValue = kAppRelativeVolumeMaxRawValue;\n    self.minValue = kAppRelativeVolumeMinRawValue;\n\n    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        self.accessibilityTitle = [NSString stringWithFormat:@\"Volume for %@\", [app localizedName]];\n#pragma clang diagnostic pop\n    }\n}\n\n// We have to handle snapping for volume sliders ourselves because adding a tick mark (snap point) in Interface Builder\n// changes how the slider looks.\n- (void) snap {\n    // Snap to the 50% point.\n    float midPoint = (float)((self.maxValue + self.minValue) / 2);\n    if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) {\n        self.floatValue = midPoint;\n    }\n}\n\n- (void) setRelativeVolume:(int)relativeVolume {\n    self.intValue = relativeVolume;\n    [self snap];\n}\n\n- (void) appVolumeChanged {\n    // TODO: This (sending updates to the driver) should probably be rate-limited. It uses a fair bit of CPU for me.\n    \n    DebugMsg(\"BGMAppVolumes::appVolumeChanged: App volume for %s (%d) changed to %d\",\n             appBundleID.UTF8String,\n             appProcessID,\n             self.intValue);\n    \n    [self snap];\n\n    // The values from our sliders are in\n    // [kAppRelativeVolumeMinRawValue, kAppRelativeVolumeMaxRawValue] already.\n    [controller setVolume:self.intValue forAppWithProcessID:appProcessID bundleID:appBundleID];\n\n    // Sync the mute button so it reflects muted/unmuted when the user drags the slider.\n    for (NSView* subview in menuItem.view.subviews) {\n        if ([subview isKindOfClass:[BGMAVM_VolumeMute class]]) {\n            [(BGMAVM_VolumeMute*)subview bgm_syncForVolume:self.intValue];\n        }\n    }\n}\n\n@end\n\n@implementation BGMAVM_PanSlider {\n    // Will be set to -1 for apps without a pid\n    pid_t appProcessID;\n    NSString* __nullable appBundleID;\n    BGMAppVolumesController* controller;\n}\n\n- (void) setUpWithApp:(NSRunningApplication*)app\n              context:(BGMAppVolumes*)ctx\n           controller:(BGMAppVolumesController*)ctrl\n             menuItem:(NSMenuItem*)menuItem {\n    #pragma unused (ctx, menuItem)\n    \n    controller = ctrl;\n    \n    self.target = self;\n    self.action = @selector(appPanPositionChanged);\n    \n    appProcessID = app.processIdentifier;\n    appBundleID = app.bundleIdentifier;\n    \n    self.minValue = kAppPanLeftRawValue;\n    self.maxValue = kAppPanRightRawValue;\n\n    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        self.accessibilityTitle = [NSString stringWithFormat:@\"Pan for %@\", [app localizedName]];\n#pragma clang diagnostic pop\n    }\n}\n\n- (void) setPanPosition:(int)panPosition {\n    self.intValue = panPosition;\n}\n\n- (void) appPanPositionChanged {\n    // TODO: This (sending updates to the driver) should probably be rate-limited. It uses a fair bit of CPU for me.\n    \n    DebugMsg(\"BGMAppVolumes::appPanPositionChanged: App pan position for %s changed to %d\", appBundleID.UTF8String, self.intValue);\n\n    // The values from our sliders are in [kAppPanLeftRawValue, kAppPanRightRawValue] already.\n    [controller setPanPosition:self.intValue forAppWithProcessID:appProcessID bundleID:appBundleID];\n}\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumesController.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppVolumesController.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\ntypedef struct BGMAppVolumeAndPan {\n    int volume;\n    int pan;\n} BGMAppVolumeAndPan;\n\n@interface BGMAppVolumesController : NSObject\n\n- (id) initWithMenu:(NSMenu*)menu\n      appVolumeView:(NSView*)view\n       audioDevices:(BGMAudioDeviceManager*)audioDevices;\n\n// See BGMBackgroundMusicDevice::SetAppVolume.\n- (void)  setVolume:(SInt32)volume\nforAppWithProcessID:(pid_t)processID\n           bundleID:(NSString* __nullable)bundleID;\n\n// See BGMBackgroundMusicDevice::SetPanVolume.\n- (void) setPanPosition:(SInt32)pan\n    forAppWithProcessID:(pid_t)processID\n               bundleID:(NSString* __nullable)bundleID;\n\n- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app;\n- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumesController.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppVolumesController.mm\n//  BGMApp\n//\n//  Copyright © 2017, 2018 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//  Copyright © 2021 Marcus Wu\n//\n\n// Self Include\n#import \"BGMAppVolumesController.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n#import \"BGMAppVolumes.h\"\n\n// PublicUtility Includes\n#import \"CACFArray.h\"\n#import \"CACFDictionary.h\"\n#import \"CACFString.h\"\n\n// System Includes\n#include <libproc.h>\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMAppVolumesController {\n    // The App Volumes UI.\n    BGMAppVolumes* appVolumes;\n    BGMAudioDeviceManager* audioDevices;\n}\n\n#pragma mark Initialisation\n\n- (id) initWithMenu:(NSMenu*)menu\n      appVolumeView:(NSView*)view\n       audioDevices:(BGMAudioDeviceManager*)devices {\n    if ((self = [super init])) {\n        audioDevices = devices;\n        appVolumes = [[BGMAppVolumes alloc] initWithController:self\n                                                       bgmMenu:menu\n                                                 appVolumeView:view];\n\n        // Create the menu items for controlling app volumes.\n        NSArray<NSRunningApplication*>* apps = [[NSWorkspace sharedWorkspace] runningApplications];\n        [self insertMenuItemsForApps:apps];\n\n        // Register for notifications when the user opens or closes apps, so we can update the menu.\n        auto opts = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;\n        [[NSWorkspace sharedWorkspace] addObserver:self\n                                        forKeyPath:@\"runningApplications\"\n                                           options:opts\n                                           context:nil];\n    }\n\n    return self;\n}\n\n- (void) dealloc {\n    [[NSWorkspace sharedWorkspace] removeObserver:self\n                                       forKeyPath:@\"runningApplications\"\n                                          context:nil];\n}\n\n// Adds a volume control menu item for each given app.\n- (void) insertMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {\n    NSAssert([NSThread isMainThread], @\"insertMenuItemsForApps is not thread safe\");\n\n    // TODO: Handle the C++ exceptions this method can throw. They can cause crashes because this\n    //       method is called in a KVO handler.\n\n    // Get the app volumes currently set on the device\n    CACFArray volumesFromBGMDevice([audioDevices bgmDevice].GetAppVolumes(), false);\n\n    for (NSRunningApplication* app in apps) {\n        if ([self shouldBeIncludedInMenu:app]) {\n            BGMAppVolumeAndPan initial = [self getVolumeAndPanForApp:app\n                                                         fromVolumes:volumesFromBGMDevice];\n            [appVolumes insertMenuItemForApp:app\n                               initialVolume:initial.volume\n                                  initialPan:initial.pan];\n        }\n    }\n}\n\n- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app {\n    return [appVolumes getVolumeAndPanForApp:app];\n}\n\n- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {\n    [appVolumes setVolumeAndPan:volumeAndPan forApp:app];\n    if (volumeAndPan.volume != -1) {\n        [self setVolume:volumeAndPan.volume forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];\n    }\n    if (volumeAndPan.pan != kAppPanNoValue) {\n        [self setPanPosition:volumeAndPan.pan forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];\n    }\n}\n\n- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app\n                                 fromVolumes:(const CACFArray&)volumes {\n    BGMAppVolumeAndPan volumeAndPan = {\n        .volume = -1,\n        .pan = kAppPanNoValue\n    };\n\n    for (UInt32 i = 0; i < volumes.GetNumberItems(); i++) {\n        CACFDictionary appVolume(false);\n        volumes.GetCACFDictionary(i, appVolume);\n\n        // Match the app to the volume/pan by pid or bundle ID.\n        CACFString bundleID;\n        bundleID.DontAllowRelease();\n        appVolume.GetCACFString(CFSTR(kBGMAppVolumesKey_BundleID), bundleID);\n\n        pid_t pid;\n        appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), pid);\n\n        if ((app.processIdentifier == pid) ||\n            [app.bundleIdentifier isEqualToString:(__bridge NSString*)bundleID.GetCFString()]) {\n            // Found a match, so read the volume and pan.\n            appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), volumeAndPan.volume);\n            appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), volumeAndPan.pan);\n            break;\n        }\n    }\n\n    return volumeAndPan;\n}\n\n- (BOOL) shouldBeIncludedInMenu:(NSRunningApplication*)app {\n    // Ignore hidden apps and Background Music itself.\n    // TODO: Would it be better to only show apps that are registered as HAL clients?\n    BOOL isHidden = app.activationPolicy != NSApplicationActivationPolicyRegular &&\n                    app.activationPolicy != NSApplicationActivationPolicyAccessory;\n\n    NSString* bundleID = app.bundleIdentifier;\n    BOOL isBGMApp = bundleID && [@kBGMAppBundleID isEqualToString:BGMNN(bundleID)];\n\n    return !isHidden && !isBGMApp;\n}\n\n- (void) removeMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {\n    NSAssert([NSThread isMainThread], @\"removeMenuItemsForApps is not thread safe\");\n\n    for (NSRunningApplication* app in apps) {\n        [appVolumes removeMenuItemForApp:app];\n    }\n}\n\n#pragma mark Accessors\n\n- (void)  setVolume:(SInt32)volume\nforAppWithProcessID:(pid_t)processID\n           bundleID:(NSString* __nullable)bundleID {\n    // Update the app's volume.\n    audioDevices.bgmDevice.SetAppVolume(volume, processID, (__bridge_retained CFStringRef)bundleID);\n\n    // If this volume is for FaceTime, set the volume for the avconferenced process as well. This\n    // works around FaceTime not playing its own audio. It plays UI sounds through\n    // systemsoundserverd and call audio through avconferenced.\n    //\n    // This isn't ideal because other apps might play audio through avconferenced, but I don't see a\n    // good way we could find out which app is actually playing the audio. We could probably figure\n    // it out from reading avconferenced's logs, at least, if it turns out to be important. See\n    // https://github.com/kyleneideck/BackgroundMusic/issues/139.\n    if ([bundleID isEqual:@\"com.apple.FaceTime\"]) {\n        [self setAvconferencedVolume:volume];\n    }\n}\n\n- (void) setAvconferencedVolume:(SInt32)volume {\n    // TODO: This volume will be lost if avconferenced is restarted.\n    pid_t pids[1024];\n    size_t procCount = proc_listallpids(pids, 1024);\n    char path[PROC_PIDPATHINFO_MAXSIZE];\n\n    for (int i = 0; i < procCount; i++) {\n        pid_t pid = pids[i];\n\n        if (proc_pidpath(pid, path, sizeof(path)) > 0 &&\n            strncmp(path, \"/usr/libexec/avconferenced\", sizeof(path)) == 0) {\n            DebugMsg(\"Setting avconferenced volume: %d\", volume);\n            audioDevices.bgmDevice.SetAppVolume(volume, pid, nullptr);\n            return;\n        }\n    }\n\n    LogWarning(\"Failed to set avconferenced volume.\");\n}\n\n- (void) setPanPosition:(SInt32)pan\n    forAppWithProcessID:(pid_t)processID\n               bundleID:(NSString* __nullable)bundleID {\n    audioDevices.bgmDevice.SetAppPanPosition(pan,\n                                             processID,\n                                             (__bridge_retained CFStringRef)bundleID);\n}\n\n#pragma mark KVO\n\n- (void) observeValueForKeyPath:(NSString* __nullable)keyPath\n                       ofObject:(id __nullable)object\n                         change:(NSDictionary* __nullable)change\n                        context:(void* __nullable)context\n{\n    #pragma unused (object, context)\n\n    // KVO callback for the apps currently running on the system. Adds/removes the associated menu\n    // items.\n    if (keyPath && change && [keyPath isEqualToString:@\"runningApplications\"]) {\n        NSArray<NSRunningApplication*>* newApps = change[NSKeyValueChangeNewKey];\n        NSArray<NSRunningApplication*>* oldApps = change[NSKeyValueChangeOldKey];\n\n        int changeKind = [change[NSKeyValueChangeKindKey] intValue];\n\n        switch (changeKind) {\n            case NSKeyValueChangeInsertion:\n                [self insertMenuItemsForApps:newApps];\n                break;\n\n            case NSKeyValueChangeRemoval:\n                [self removeMenuItemsForApps:oldApps];\n                break;\n\n            case NSKeyValueChangeReplacement:\n                [self removeMenuItemsForApps:oldApps];\n                [self insertMenuItemsForApps:newApps];\n                break;\n\n            case NSKeyValueChangeSetting:\n                [appVolumes removeAllAppVolumeMenuItems];\n                [self insertMenuItemsForApps:newApps];\n                break;\n        }\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppWatcher.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppWatcher.h\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n//  Calls callback functions when a given application is launched or terminated. Starts watching\n//  after being initialised, stops after being destroyed.\n//\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAppWatcher : NSObject\n\n// appLaunched will be called when the application is launched and appTerminated will be called when\n// it's terminated. Background apps, status bar apps, etc. are ignored.\n- (instancetype) initWithBundleID:(NSString*)bundleID\n                      appLaunched:(void(^)(void))appLaunched\n                    appTerminated:(void(^)(void))appTerminated;\n\n// With this constructor, when an application is launched or terminated, isMatchingBundleID will be\n// called first to decide whether or not the callback should be called.\n- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched\n                       appTerminated:(void(^)(void))appTerminated\n                  isMatchingBundleID:(BOOL(^)(NSString* appBundleID))isMatchingBundleID;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppWatcher.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppWatcher.m\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMAppWatcher.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMAppWatcher {\n    // Tokens for the notification observers so we can remove them in dealloc.\n    id<NSObject> didLaunchToken;\n    id<NSObject> didTerminateToken;\n}\n\n- (instancetype) initWithBundleID:(NSString*)bundleID\n                      appLaunched:(void(^)(void))appLaunched\n                    appTerminated:(void(^)(void))appTerminated {\n    return [self initWithAppLaunched:appLaunched\n                       appTerminated:appTerminated\n                  isMatchingBundleID:^BOOL(NSString* appBundleID) {\n                      return [bundleID isEqualToString:appBundleID];\n                  }];\n}\n\n- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched\n                       appTerminated:(void(^)(void))appTerminated\n                  isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID\n{\n    if ((self = [super init])) {\n        NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;\n\n        didLaunchToken =\n            [center addObserverForName:NSWorkspaceDidLaunchApplicationNotification\n                                object:nil\n                                 queue:nil\n                            usingBlock:^(NSNotification* notification) {\n                                if ([BGMAppWatcher shouldBeHandled:notification\n                                                isMatchingBundleID:isMatchingBundleID]) {\n                                    appLaunched();\n                                }\n                            }];\n\n        didTerminateToken =\n            [center addObserverForName:NSWorkspaceDidTerminateApplicationNotification\n                                object:nil\n                                 queue:nil\n                            usingBlock:^(NSNotification* notification) {\n                                if ([BGMAppWatcher shouldBeHandled:notification\n                                                isMatchingBundleID:isMatchingBundleID]) {\n                                    appTerminated();\n                                }\n                            }];\n    }\n\n    return self;\n}\n\n// Returns YES if we should call the app launch/termination callback for this NSNotification.\n+ (BOOL) shouldBeHandled:(NSNotification*)notification\n      isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID {\n    NSString* __nullable notifiedBundleID =\n        [notification.userInfo[NSWorkspaceApplicationKey] bundleIdentifier];\n\n    // Ignore the notification if the app doesn't have a bundle ID or isMatchingBundleID returns NO.\n    return notifiedBundleID && isMatchingBundleID((NSString*)notifiedBundleID);\n}\n\n- (void) dealloc {\n    // Remove the application launch/termination observers.\n    NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;\n\n    if (didLaunchToken) {\n        [center removeObserver:didLaunchToken];\n        didLaunchToken = nil;\n    }\n\n    if (didTerminateToken) {\n        [center removeObserver:didTerminateToken];\n        didTerminateToken = nil;\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAudioDevice.cpp\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMAudioDevice.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// System Includes\n#include <AudioToolbox/AudioServices.h>\n\n\n#pragma mark Construction/Destruction\n\nBGMAudioDevice::BGMAudioDevice(AudioObjectID inAudioDevice)\n:\n    CAHALAudioDevice(inAudioDevice)\n{\n}\n\nBGMAudioDevice::BGMAudioDevice(CFStringRef inUID)\n:\n    CAHALAudioDevice(inUID)\n{\n}\n\nBGMAudioDevice::BGMAudioDevice(const CAHALAudioDevice& inDevice)\n:\n    BGMAudioDevice(inDevice.GetObjectID())\n{\n}\n\nBGMAudioDevice::~BGMAudioDevice()\n{\n}\n\nbool    BGMAudioDevice::CanBeOutputDeviceInBGMApp() const\n{\n    CFStringRef uid = CopyDeviceUID();\n    assert(uid != nullptr);\n\n    bool isNullDevice = CFEqual(uid, CFSTR(kBGMNullDeviceUID));\n    CFRelease(uid);\n\n    bool hasOutputChannels = GetTotalNumberChannels(/* inIsInput = */ false) > 0;\n    bool canBeDefault = CanBeDefaultDevice(/* inIsInput = */ false, /* inIsSystem = */ false);\n\n    return !IsBGMDeviceInstance() &&\n            !isNullDevice &&\n            !IsHidden() &&\n            hasOutputChannels &&\n            canBeDefault;\n}\n\n#pragma mark Available Controls\n\nbool    BGMAudioDevice::HasSettableMasterVolume(AudioObjectPropertyScope inScope) const\n{\n    return HasVolumeControl(inScope, kMasterChannel) &&\n        VolumeControlIsSettable(inScope, kMasterChannel);\n}\n\nbool    BGMAudioDevice::HasSettableVirtualMasterVolume(AudioObjectPropertyScope inScope) const\n{\n    AudioObjectPropertyAddress virtualMasterVolumeAddress = {\n        kAudioHardwareServiceDeviceProperty_VirtualMainVolume,\n        inScope,\n        kAudioObjectPropertyElementMaster\n    };\n\n    // TODO: Replace these calls deprecated AudioToolbox functions. There are more below.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    Boolean virtualMasterVolumeIsSettable;\n    OSStatus err = AudioHardwareServiceIsPropertySettable(GetObjectID(),\n                                                          &virtualMasterVolumeAddress,\n                                                          &virtualMasterVolumeIsSettable);\n    virtualMasterVolumeIsSettable &= (err == kAudioServicesNoError);\n\n    bool hasVirtualMasterVolume =\n        AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterVolumeAddress);\n#pragma clang diagnostic pop\n\n    return hasVirtualMasterVolume && virtualMasterVolumeIsSettable;\n}\n\nbool    BGMAudioDevice::HasSettableMasterMute(AudioObjectPropertyScope inScope) const\n{\n    return HasMuteControl(inScope, kMasterChannel) &&\n        MuteControlIsSettable(inScope, kMasterChannel);\n}\n\n#pragma mark Control Values Accessors\n\nvoid    BGMAudioDevice::CopyMuteFrom(const BGMAudioDevice inDevice,\n                                     AudioObjectPropertyScope inScope)\n{\n    // TODO: Support for devices that have per-channel mute controls but no master mute control\n    if(HasSettableMasterMute(inScope) && inDevice.HasMuteControl(inScope, kMasterChannel))\n    {\n        SetMuteControlValue(inScope,\n                            kMasterChannel,\n                            inDevice.GetMuteControlValue(inScope, kMasterChannel));\n    }\n}\n\nvoid    BGMAudioDevice::CopyVolumeFrom(const BGMAudioDevice inDevice,\n                                       AudioObjectPropertyScope inScope)\n{\n    // Get the volume of the other device.\n    bool didGetVolume = false;\n    Float32 volume = FLT_MIN;\n\n    if(inDevice.HasVolumeControl(inScope, kMasterChannel))\n    {\n        volume = inDevice.GetVolumeControlScalarValue(inScope, kMasterChannel);\n        didGetVolume = true;\n    }\n\n    // Use the average channel volume of the other device if it has no master volume.\n    if(!didGetVolume)\n    {\n        UInt32 numChannels =\n            inDevice.GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);\n        volume = 0;\n\n        for(UInt32 channel = 1; channel <= numChannels; channel++)\n        {\n            if(inDevice.HasVolumeControl(inScope, channel))\n            {\n                volume += inDevice.GetVolumeControlScalarValue(inScope, channel);\n                didGetVolume = true;\n            }\n        }\n\n        if(numChannels > 0)  // Avoid divide by zero.\n        {\n            volume /= numChannels;\n        }\n    }\n\n    // Set the volume of this device.\n    if(didGetVolume && volume != FLT_MIN)\n    {\n        bool didSetVolume = false;\n\n        try\n        {\n            didSetVolume = SetMasterVolumeScalar(inScope, volume);\n        }\n        catch(CAException e)\n        {\n            OSStatus err = e.GetError();\n            char err4CC[5] = CA4CCToCString(err);\n            CFStringRef uid = CopyDeviceUID();\n            LogWarning(\"BGMAudioDevice::CopyVolumeFrom: CAException '%s' trying to set master \"\n                       \"volume of %s\",\n                       err4CC,\n                       CFStringGetCStringPtr(uid, kCFStringEncodingUTF8));\n            CFRelease(uid);\n        }\n\n        if(!didSetVolume)\n        {\n            // Couldn't find a master volume control to set, so try to find a virtual one\n            Float32 virtualMasterVolume;\n            bool success = inDevice.GetVirtualMasterVolumeScalar(inScope, virtualMasterVolume);\n            if(success)\n            {\n                didSetVolume = SetVirtualMasterVolumeScalar(inScope, virtualMasterVolume);\n            }\n        }\n\n        if(!didSetVolume)\n        {\n            // Couldn't set a master or virtual master volume, so as a fallback try to set each\n            // channel individually.\n            UInt32 numChannels = GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);\n            for(UInt32 channel = 1; channel <= numChannels; channel++)\n            {\n                if(HasVolumeControl(inScope, channel) && VolumeControlIsSettable(inScope, channel))\n                {\n                    SetVolumeControlScalarValue(inScope, channel, volume);\n                }\n            }\n        }\n    }\n}\n\nbool    BGMAudioDevice::SetMasterVolumeScalar(AudioObjectPropertyScope inScope, Float32 inVolume)\n{\n    if(HasSettableMasterVolume(inScope))\n    {\n        SetVolumeControlScalarValue(inScope, kMasterChannel, inVolume);\n        return true;\n    }\n\n    return false;\n}\n\nbool    BGMAudioDevice::GetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,\n                                                     Float32& outVirtualMasterVolume) const\n{\n    AudioObjectPropertyAddress virtualMasterVolumeAddress = {\n        kAudioHardwareServiceDeviceProperty_VirtualMainVolume,\n        inScope,\n        kAudioObjectPropertyElementMaster\n    };\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    if(!AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterVolumeAddress))\n    {\n        return false;\n    }\n#pragma clang diagnostic pop\n\n    UInt32 virtualMasterVolumePropertySize = sizeof(Float32);\n    return kAudioServicesNoError == AHSGetPropertyData(GetObjectID(),\n                                                       &virtualMasterVolumeAddress,\n                                                       &virtualMasterVolumePropertySize,\n                                                       &outVirtualMasterVolume);\n}\n\nbool    BGMAudioDevice::SetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,\n                                                     Float32 inVolume)\n{\n    // TODO: For me, setting the virtual master volume sets all the device's channels to the same volume, meaning you can't\n    //       keep any channels quieter than the others. The expected behaviour is to scale the channel volumes\n    //       proportionally. So to do this properly I think we'd have to store BGMDevice's previous volume and calculate\n    //       each channel's new volume from its current volume and the distance between BGMDevice's old and new volumes.\n    //\n    //       The docs kAudioHardwareServiceDeviceProperty_VirtualMasterVolume for say\n    //           \"If the device has individual channel volume controls, this property will apply to those identified by the\n    //           device's preferred multi-channel layout (or preferred stereo pair if the device is stereo only). Note that\n    //           this control maintains the relative balance between all the channels it affects.\n    //       so I'm not sure why that's not working here. As a workaround we take the to device's (virtual master) balance\n    //       before changing the volume and set it back after, but of course that'll only work for stereo devices.\n\n    bool didSetVolume = false;\n\n    if(HasSettableVirtualMasterVolume(inScope))\n    {\n        // Not sure why, but setting the virtual master volume sets all channels to the same volume. As a workaround, we store\n        // the current balance here so we can reset it after setting the volume.\n        Float32 virtualMasterBalance;\n        bool didGetVirtualMasterBalance = GetVirtualMasterBalance(inScope, virtualMasterBalance);\n\n        AudioObjectPropertyAddress virtualMasterVolumeAddress = {\n            kAudioHardwareServiceDeviceProperty_VirtualMainVolume,\n            inScope,\n            kAudioObjectPropertyElementMaster\n        };\n\n        didSetVolume = (kAudioServicesNoError == AHSSetPropertyData(GetObjectID(),\n                                                                    &virtualMasterVolumeAddress,\n                                                                    sizeof(Float32),\n                                                                    &inVolume));\n\n        // Reset the balance\n        AudioObjectPropertyAddress virtualMasterBalanceAddress = {\n            kAudioHardwareServiceDeviceProperty_VirtualMainBalance,\n            inScope,\n            kAudioObjectPropertyElementMaster\n        };\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n        if(didSetVolume &&\n           didGetVirtualMasterBalance &&\n           AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterBalanceAddress))\n        {\n            Boolean balanceIsSettable;\n            OSStatus err = AudioHardwareServiceIsPropertySettable(GetObjectID(),\n                                                                  &virtualMasterBalanceAddress,\n                                                                  &balanceIsSettable);\n            if(err == kAudioServicesNoError && balanceIsSettable)\n            {\n                AHSSetPropertyData(GetObjectID(),\n                                   &virtualMasterBalanceAddress,\n                                   sizeof(Float32),\n                                   &virtualMasterBalance);\n            }\n        }\n#pragma clang diagnostic pop\n    }\n\n    return didSetVolume;\n}\n\nbool    BGMAudioDevice::GetVirtualMasterBalance(AudioObjectPropertyScope inScope,\n                                                Float32& outVirtualMasterBalance) const\n{\n    AudioObjectPropertyAddress virtualMasterBalanceAddress = {\n        kAudioHardwareServiceDeviceProperty_VirtualMainBalance,\n        inScope,\n        kAudioObjectPropertyElementMaster\n    };\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    if(!AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterBalanceAddress))\n    {\n        return false;\n    }\n#pragma clang diagnostic pop\n\n    UInt32 virtualMasterVolumePropertySize = sizeof(Float32);\n    return kAudioServicesNoError == AHSGetPropertyData(GetObjectID(),\n                                                       &virtualMasterBalanceAddress,\n                                                       &virtualMasterVolumePropertySize,\n                                                       &outVirtualMasterBalance);\n}\n\n#pragma mark Implementation\n\nbool    BGMAudioDevice::IsBGMDevice(bool inIncludeUISoundsInstance) const\n{\n    bool isBGMDevice = false;\n\n    if(GetObjectID() != kAudioObjectUnknown)\n    {\n        // Check the device's UID to see whether it's BGMDevice.\n        CFStringRef uid = CopyDeviceUID();\n        if (uid == nullptr) {\n            return isBGMDevice;\n        }\n\n        isBGMDevice =\n            CFEqual(uid, CFSTR(kBGMDeviceUID)) ||\n                    (inIncludeUISoundsInstance && CFEqual(uid, CFSTR(kBGMDeviceUID_UISounds)));\n\n        CFRelease(uid);\n    }\n\n    return isBGMDevice;\n}\n\n// static\nOSStatus    BGMAudioDevice::AHSGetPropertyData(AudioObjectID inObjectID,\n                                               const AudioObjectPropertyAddress* inAddress,\n                                               UInt32* ioDataSize,\n                                               void* outData)\n{\n    // The docs for AudioHardwareServiceGetPropertyData specifically allow passing NULL for\n    // inQualifierData as we do here, but it's declared in an assume_nonnull section so we have to\n    // disable the warning here. I'm not sure why inQualifierData isn't __nullable. I'm assuming\n    // it's either a backwards compatibility thing or just a bug.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wnonnull\"\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    // The non-depreciated version of this (and the setter below) doesn't seem to support devices\n    // other than the default\n    return AudioHardwareServiceGetPropertyData(inObjectID, inAddress, 0, NULL, ioDataSize, outData);\n#pragma clang diagnostic pop\n}\n\n// static\nOSStatus    BGMAudioDevice::AHSSetPropertyData(AudioObjectID inObjectID,\n                                               const AudioObjectPropertyAddress* inAddress,\n                                               UInt32 inDataSize,\n                                               const void* inData)\n{\n    // See the explanation about these pragmas in AHSGetPropertyData\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wnonnull\"\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    return AudioHardwareServiceSetPropertyData(inObjectID, inAddress, 0, NULL, inDataSize, inData);\n#pragma clang diagnostic pop\n}\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//  BGMAudioDevice.h\n//  BGMApp\n//\n//  Copyright © 2017, 2020 Kyle Neideck\n//\n//  A HAL audio device. Note that this class's only state is the AudioObjectID of the device.\n//\n\n#ifndef BGMApp__BGMAudioDevice\n#define BGMApp__BGMAudioDevice\n\n// PublicUtility Includes\n#include \"CAHALAudioDevice.h\"\n\n\nclass BGMAudioDevice\n:\n    public CAHALAudioDevice\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                       BGMAudioDevice(AudioObjectID inAudioDevice);\n    /*!\n     Creates a BGMAudioDevice with the Audio Object ID of the device whose UID is inUID or, if no\n     such device is found, kAudioObjectUnknown.\n\n     @throws CAException If the HAL returns an error when queried for the device's ID.\n     @see kAudioPlugInPropertyTranslateUIDToDevice in AudioHardwareBase.h.\n     */\n                       BGMAudioDevice(CFStringRef inUID);\n                       BGMAudioDevice(const CAHALAudioDevice& inDevice);\n    virtual            ~BGMAudioDevice();\n\n#if defined(__OBJC__)\n\n    // Hack/workaround for Objective-C classes so we don't have to use pointers for instance\n    // variables.\n                       BGMAudioDevice() : BGMAudioDevice(kAudioObjectUnknown) { }\n\n#endif /* defined(__OBJC__) */\n\n                       operator AudioObjectID() const { return GetObjectID(); }\n\n    /*!\n     @return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice, not\n             the instance used for UI sounds.)\n     @throws CAException If the HAL returns an error when queried.\n     */\n    bool               IsBGMDevice() const { return IsBGMDevice(false); };\n    /*!\n     @return True if this device is either the main instance of BGMDevice (the device named\n             \"Background Music\") or the instance used for UI sounds (the device named \"Background\n             Music (UI Sounds)\").\n     @throws CAException If the HAL returns an error when queried.\n     */\n    bool               IsBGMDeviceInstance() const { return IsBGMDevice(true); };\n\n    /*!\n     @return True if this device can be set as the output device in BGMApp.\n     @throws CAException If the HAL returns an error when queried.\n     */\n    bool               CanBeOutputDeviceInBGMApp() const;\n\n#pragma mark Available Controls\n\n    bool               HasSettableMasterVolume(AudioObjectPropertyScope inScope) const;\n    bool               HasSettableVirtualMasterVolume(AudioObjectPropertyScope inScope) const;\n    bool               HasSettableMasterMute(AudioObjectPropertyScope inScope) const;\n\n#pragma mark Control Values Accessors\n\n    void               CopyMuteFrom(const BGMAudioDevice inDevice,\n                                    AudioObjectPropertyScope inScope);\n    void               CopyVolumeFrom(const BGMAudioDevice inDevice,\n                                      AudioObjectPropertyScope inScope);\n\n    bool               SetMasterVolumeScalar(AudioObjectPropertyScope inScope, Float32 inVolume);\n    \n    bool               GetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,\n                                                    Float32& outVirtualMasterVolume) const;\n    bool               SetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,\n                                                    Float32 inVolume);\n\n    bool               GetVirtualMasterBalance(AudioObjectPropertyScope inScope,\n                                               Float32& outVirtualMasterBalance) const;\n\n#pragma mark Implementation\n\nprivate:\n    bool               IsBGMDevice(bool inIncludingUISoundsInstance) const;\n\n    static OSStatus    AHSGetPropertyData(AudioObjectID inObjectID,\n                                          const AudioObjectPropertyAddress* inAddress,\n                                          UInt32* ioDataSize,\n                                          void* outData);\n    static OSStatus    AHSSetPropertyData(AudioObjectID inObjectID,\n                                          const AudioObjectPropertyAddress* inAddress,\n                                          UInt32 inDataSize,\n                                          const void* inData);\n\n};\n\n#endif /* BGMApp__BGMAudioDevice */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDeviceManager.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAudioDeviceManager.h\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n//  Manages BGMDevice and the output device. Sets the system's current default device as the output\n//  device on init, then starts playthrough and mirroring the devices' controls.\n//\n\n#if defined(__cplusplus)\n\n// Local Includes\n#import \"BGMBackgroundMusicDevice.h\"\n\n// PublicUtility Includes\n#import \"CAHALAudioDevice.h\"\n\n#endif /* defined(__cplusplus) */\n\n// System Includes\n#import <Foundation/Foundation.h>\n#import <CoreAudio/AudioHardwareBase.h>\n\n// Forward Declarations\n@class BGMOutputVolumeMenuItem;\n@class BGMOutputDeviceMenuSection;\n\n\n#pragma clang assume_nonnull begin\n\nstatic const int kBGMErrorCode_OutputDeviceNotFound = 1;\nstatic const int kBGMErrorCode_ReturningEarly       = 2;\n\n@interface BGMAudioDeviceManager : NSObject\n\n// Returns nil if BGMDevice isn't installed.\n- (instancetype) init;\n\n// Set the BGMOutputVolumeMenuItem to be notified when the output device is changed.\n- (void) setOutputVolumeMenuItem:(BGMOutputVolumeMenuItem*)item;\n\n// Set the BGMOutputDeviceMenuSection to be notified when the output device is changed.\n- (void) setOutputDeviceMenuSection:(BGMOutputDeviceMenuSection*)menuSection;\n\n// Set BGMDevice as the default audio device for all processes\n- (NSError* __nullable) setBGMDeviceAsOSDefault;\n// Replace BGMDevice as the default device with the output device\n- (NSError* __nullable) unsetBGMDeviceAsOSDefault;\n\n#ifdef __cplusplus\n// The virtual device published by BGMDriver.\n- (BGMBackgroundMusicDevice) bgmDevice;\n\n// The device BGMApp will play audio through, making it, from the user's perspective, the system's\n// default output device.\n- (CAHALAudioDevice) outputDevice;\n#endif\n\n- (BOOL) isOutputDevice:(AudioObjectID)deviceID;\n- (BOOL) isOutputDataSource:(UInt32)dataSourceID;\n\n// Set the audio output device that BGMApp uses.\n//\n// Returns an error if the output device couldn't be changed. If revertOnFailure is true in that case,\n// this method will attempt to set the output device back to the original device. If it fails to\n// revert, an additional error will be included in the error's userInfo with the key \"revertError\".\n//\n// Both errors' codes will be the code of the exception that caused the failure, if any, generally one\n// of the error constants from AudioHardwareBase.h.\n//\n// Blocks while the old device stops IO (if there was one).\n- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID\n                              revertOnFailure:(BOOL)revertOnFailure;\n\n// As above, but also sets the new output device's data source. See kAudioDevicePropertyDataSource in\n// AudioHardware.h.\n- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID\n                                 dataSourceID:(UInt32)dataSourceID\n                              revertOnFailure:(BOOL)revertOnFailure;\n\n// Start playthrough synchronously. Blocks until IO has started on the output device and playthrough\n// is running. See BGMPlayThrough.\n//\n// Returns one of the error codes defined by this class or BGMPlayThrough, or an AudioHardware error\n// code received from the HAL.\n- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice;\n\n// When the output device is changed, BGMAudioDeviceManager will send the ID of the new output\n// device to BGMXPCHelper through this connection.\n- (void) setBGMXPCHelperConnection:(NSXPCConnection* __nullable)connection;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDeviceManager.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAudioDeviceManager.mm\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMAudioDeviceManager.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n#import \"BGMAudioDevice.h\"\n#import \"BGMDeviceControlSync.h\"\n#import \"BGMOutputDeviceMenuSection.h\"\n#import \"BGMOutputVolumeMenuItem.h\"\n#import \"BGMPlayThrough.h\"\n#import \"BGMXPCProtocols.h\"\n\n// PublicUtility Includes\n#import \"CAAtomic.h\"\n#import \"CAAutoDisposer.h\"\n#import \"CAHALAudioSystemObject.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMAudioDeviceManager {\n    // This ivar is a pointer so that BGMBackgroundMusicDevice's constructor doesn't get called\n    // during [BGMAudioDeviceManager alloc] when the ivars are initialised. It queries the HAL for\n    // BGMDevice's AudioObject ID, which might throw a CAException, most likely because BGMDevice\n    // isn't installed.\n    //\n    // That would be the only way for [BGMAudioDeviceManager alloc] to throw a CAException, so we\n    // could wrap that call in a try/catch block instead, but it would make the code a bit\n    // confusing.\n    BGMBackgroundMusicDevice* bgmDevice;\n    BGMAudioDevice outputDevice;\n    \n    BGMDeviceControlSync deviceControlSync;\n    BGMPlayThrough playThrough;\n    BGMPlayThrough playThrough_UISounds;\n\n    // A connection to BGMXPCHelper so we can send it the ID of the output device.\n    NSXPCConnection* __nullable bgmXPCHelperConnection;\n\n    BGMOutputVolumeMenuItem* __nullable outputVolumeMenuItem;\n    BGMOutputDeviceMenuSection* __nullable outputDeviceMenuSection;\n\n    NSRecursiveLock* stateLock;\n}\n\n#pragma mark Construction/Destruction\n\n- (instancetype) init {\n    if ((self = [super init])) {\n        stateLock = [NSRecursiveLock new];\n        bgmXPCHelperConnection = nil;\n        outputVolumeMenuItem = nil;\n        outputDeviceMenuSection = nil;\n        outputDevice = kAudioObjectUnknown;\n\n        try {\n            bgmDevice = new BGMBackgroundMusicDevice;\n        } catch (const CAException& e) {\n            LogError(\"BGMAudioDeviceManager::init: BGMDevice not found. (%d)\", e.GetError());\n            self = nil;\n            return self;\n        }\n    }\n    \n    return self;\n}\n\n- (void) dealloc {\n    @try {\n        [stateLock lock];\n\n        if (bgmDevice) {\n            delete bgmDevice;\n            bgmDevice = nullptr;\n        }\n    } @finally {\n        [stateLock unlock];\n    }\n}\n\n- (void) setOutputVolumeMenuItem:(BGMOutputVolumeMenuItem*)item {\n    outputVolumeMenuItem = item;\n}\n\n- (void) setOutputDeviceMenuSection:(BGMOutputDeviceMenuSection*)menuSection {\n    outputDeviceMenuSection = menuSection;\n}\n\n#pragma mark Systemwide Default Device\n\n// Note that there are two different \"default\" output devices on OS X: \"output\" and \"system output\". See\n// kAudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h.\n\n- (NSError* __nullable) setBGMDeviceAsOSDefault {\n    try {\n        // Intentionally avoid taking stateLock before making calls to the HAL. See\n        // startPlayThroughSync.\n        CAMemoryBarrier();\n        bgmDevice->SetAsOSDefault();\n    } catch (const CAException& e) {\n        BGMLogExceptionIn(\"BGMAudioDeviceManager::setBGMDeviceAsOSDefault\", e);\n        return [NSError errorWithDomain:@kBGMAppBundleID code:e.GetError() userInfo:nil];\n    }\n\n    return nil;\n}\n\n- (NSError* __nullable) unsetBGMDeviceAsOSDefault {\n    // Copy the devices so we can call the HAL without holding stateLock. See startPlayThroughSync.\n    BGMBackgroundMusicDevice* bgmDeviceCopy;\n    AudioDeviceID outputDeviceID;\n\n    @try {\n        [stateLock lock];\n        bgmDeviceCopy = bgmDevice;\n        outputDeviceID = outputDevice.GetObjectID();\n    } @finally {\n        [stateLock unlock];\n    }\n\n    if (outputDeviceID == kAudioObjectUnknown) {\n        return [NSError errorWithDomain:@kBGMAppBundleID\n                                   code:kBGMErrorCode_OutputDeviceNotFound\n                               userInfo:nil];\n    }\n\n    try {\n        bgmDeviceCopy->UnsetAsOSDefault(outputDeviceID);\n    } catch (const CAException& e) {\n        BGMLogExceptionIn(\"BGMAudioDeviceManager::unsetBGMDeviceAsOSDefault\", e);\n        return [NSError errorWithDomain:@kBGMAppBundleID code:e.GetError() userInfo:nil];\n    }\n    \n    return nil;\n}\n\n#pragma mark Accessors\n\n- (BGMBackgroundMusicDevice) bgmDevice {\n    return *bgmDevice;\n}\n\n- (CAHALAudioDevice) outputDevice {\n    return outputDevice;\n}\n\n- (BOOL) isOutputDevice:(AudioObjectID)deviceID {\n    @try {\n        [stateLock lock];\n        return deviceID == outputDevice.GetObjectID();\n    } @finally {\n        [stateLock unlock];\n    }\n}\n\n- (BOOL) isOutputDataSource:(UInt32)dataSourceID {\n    BOOL isOutputDataSource = NO;\n\n    @try {\n        [stateLock lock];\n        \n        try {\n            AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;\n            UInt32 channel = 0;\n            \n            isOutputDataSource =\n                    outputDevice.HasDataSourceControl(scope, channel) &&\n                            (dataSourceID == outputDevice.GetCurrentDataSourceID(scope, channel));\n        } catch (const CAException& e) {\n            BGMLogException(e);\n        }\n    } @finally {\n        [stateLock unlock];\n    }\n\n    return isOutputDataSource;\n}\n\n#pragma mark Output Device\n\n- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID\n                              revertOnFailure:(BOOL)revertOnFailure {\n    return [self setOutputDeviceWithIDImpl:deviceID\n                              dataSourceID:nil\n                           revertOnFailure:revertOnFailure];\n}\n\n- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID\n                                 dataSourceID:(UInt32)dataSourceID\n                              revertOnFailure:(BOOL)revertOnFailure {\n    return [self setOutputDeviceWithIDImpl:deviceID\n                              dataSourceID:&dataSourceID\n                           revertOnFailure:revertOnFailure];\n}\n\n- (NSError* __nullable) setOutputDeviceWithIDImpl:(AudioObjectID)newDeviceID\n                                     dataSourceID:(UInt32* __nullable)dataSourceID\n                                  revertOnFailure:(BOOL)revertOnFailure {\n    DebugMsg(\"BGMAudioDeviceManager::setOutputDeviceWithIDImpl: Setting output device. newDeviceID=%u\",\n             newDeviceID);\n    \n    @try {\n        [stateLock lock];\n\n        AudioDeviceID currentDeviceID = outputDevice.GetObjectID();  // (Doesn't throw.)\n\n        try {\n            [self setOutputDeviceWithIDImpl:newDeviceID\n                               dataSourceID:dataSourceID\n                            currentDeviceID:currentDeviceID];\n        } catch (const CAException& e) {\n            BGMAssert(e.GetError() != kAudioHardwareNoError,\n                      \"CAException with kAudioHardwareNoError\");\n            \n            return [self failedToSetOutputDevice:newDeviceID\n                                       errorCode:e.GetError()\n                                        revertTo:(revertOnFailure ? &currentDeviceID : nullptr)];\n        } catch (...) {\n            return [self failedToSetOutputDevice:newDeviceID\n                                       errorCode:kAudioHardwareUnspecifiedError\n                                        revertTo:(revertOnFailure ? &currentDeviceID : nullptr)];\n        }\n\n        // Tell other classes and BGMXPCHelper that we changed the output device.\n        [self propagateOutputDeviceChange];\n    } @finally {\n        [stateLock unlock];\n    }\n\n    return nil;\n}\n\n// Throws CAException.\n- (void) setOutputDeviceWithIDImpl:(AudioObjectID)newDeviceID\n                      dataSourceID:(UInt32* __nullable)dataSourceID\n                   currentDeviceID:(AudioObjectID)currentDeviceID {\n    if (newDeviceID != currentDeviceID) {\n        BGMAudioDevice newOutputDevice(newDeviceID);\n        [self setOutputDeviceForPlaythroughAndControlSync:newOutputDevice];\n        outputDevice = newOutputDevice;\n    }\n\n    // Set the output device to use the new data source.\n    if (dataSourceID) {\n        // TODO: If this fails, ideally we'd still start playthrough and return an error, but not\n        //       revert the device. It would probably be a bit awkward, though.\n        [self setDataSource:*dataSourceID device:outputDevice];\n    }\n\n    if (newDeviceID != currentDeviceID) {\n        // We successfully changed to the new device. Start playthrough on it, since audio might be\n        // playing. (If we only changed the data source, playthrough will already be running if it\n        // needs to be.)\n        playThrough.Start();\n        playThrough_UISounds.Start();\n        // But stop playthrough if audio isn't playing, since it uses CPU.\n        playThrough.StopIfIdle();\n        playThrough_UISounds.StopIfIdle();\n    }\n\n    CFStringRef outputDeviceUID = outputDevice.CopyDeviceUID();\n    DebugMsg(\"BGMAudioDeviceManager::setOutputDeviceWithIDImpl: Set output device to %s (%d)\",\n             CFStringGetCStringPtr(outputDeviceUID, kCFStringEncodingUTF8),\n             outputDevice.GetObjectID());\n    CFRelease(outputDeviceUID);\n}\n\n// Changes the output device that playthrough plays audio to and that BGMDevice's controls are\n// kept in sync with. Throws CAException.\n- (void) setOutputDeviceForPlaythroughAndControlSync:(const BGMAudioDevice&)newOutputDevice {\n    // Deactivate playthrough rather than stopping it so it can't be started by HAL notifications\n    // while we're updating deviceControlSync.\n    playThrough.Deactivate();\n    playThrough_UISounds.Deactivate();\n\n    deviceControlSync.SetDevices(*bgmDevice, newOutputDevice);\n    deviceControlSync.Activate();\n\n    // Stream audio from BGMDevice to the new output device. This blocks while the old device stops\n    // IO.\n    playThrough.SetDevices(bgmDevice, &newOutputDevice);\n    playThrough.Activate();\n\n    // TODO: Support setting different devices as the default output device and the default system\n    //       output device the way OS X does?\n    BGMAudioDevice uiSoundsDevice = bgmDevice->GetUISoundsBGMDeviceInstance();\n    playThrough_UISounds.SetDevices(&uiSoundsDevice, &newOutputDevice);\n    playThrough_UISounds.Activate();\n}\n\n- (void) setDataSource:(UInt32)dataSourceID device:(BGMAudioDevice&)device {\n    BGMLogAndSwallowExceptions(\"BGMAudioDeviceManager::setDataSource\", ([&] {\n        AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;\n        UInt32 channel = 0;\n\n        if (device.DataSourceControlIsSettable(scope, channel)) {\n            DebugMsg(\"BGMAudioDeviceManager::setOutputDeviceWithID: Setting dataSourceID=%u\",\n                     dataSourceID);\n            \n            device.SetCurrentDataSourceByID(scope, channel, dataSourceID);\n        }\n    }));\n}\n\n- (void) propagateOutputDeviceChange {\n    // Tell BGMXPCHelper that the output device has changed.\n    [self sendOutputDeviceToBGMXPCHelper];\n\n    // Update the menu item for the volume of the output device.\n    [outputVolumeMenuItem outputDeviceDidChange];\n    [outputDeviceMenuSection outputDeviceDidChange];\n}\n\n- (NSError*) failedToSetOutputDevice:(AudioDeviceID)deviceID\n                           errorCode:(OSStatus)errorCode\n                            revertTo:(AudioDeviceID*)revertTo {\n    // Using LogWarning from PublicUtility instead of NSLog here crashes from a bad access. Not sure why.\n    // TODO: Possibly caused by a bug in CADebugMacros.cpp. See commit ab9d4cd.\n    NSLog(@\"BGMAudioDeviceManager::failedToSetOutputDevice: Couldn't set device with ID %u as output device. \"\n          \"%s%d. %@\",\n          deviceID,\n          \"Error: \", errorCode,\n          (revertTo ? [NSString stringWithFormat:@\"Will attempt to revert to the previous device. \"\n                                                  \"Previous device ID: %u.\", *revertTo] : @\"\"));\n    \n    NSDictionary* __nullable info = nil;\n    \n    if (revertTo) {\n        // Try to reactivate the original device listener and playthrough. (Sorry about the mutual recursion.)\n        NSError* __nullable revertError = [self setOutputDeviceWithID:*revertTo revertOnFailure:NO];\n        \n        if (revertError) {\n            info = @{ @\"revertError\": (NSError*)revertError };\n        }\n    } else {\n        // TODO: Handle this error better in callers. Maybe show an error dialog and try to set the original\n        //       default device as the output device.\n        NSLog(@\"BGMAudioDeviceManager::failedToSetOutputDevice: Failed to revert to the previous device.\");\n    }\n    \n    return [NSError errorWithDomain:@kBGMAppBundleID code:errorCode userInfo:info];\n}\n\n- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice {\n    // We can only try for stateLock because setOutputDeviceWithID might have already taken it, then made a\n    // HAL request to BGMDevice and be waiting for the response. Some of the requests setOutputDeviceWithID\n    // makes to BGMDevice block in the HAL if another thread is in BGM_Device::StartIO.\n    //\n    // Since BGM_Device::StartIO calls this method (via XPC), waiting for setOutputDeviceWithID to release\n    // stateLock could cause deadlocks. Instead we return early with an error code that BGMDriver knows to\n    // ignore, since the output device is (almost certainly) being changed and we can't avoid dropping frames\n    // while the output device starts up.\n    OSStatus err;\n    BOOL gotLock;\n    \n    @try {\n        gotLock = [stateLock tryLock];\n\n        BOOL isBigSur = NO;\n        if (@available(macOS 11.0, *)) {\n            isBigSur = YES;\n        }\n\n        // Always start playthrough asynchronously. Temp workaround for deadlock on Big Sur.\n        if (!isBigSur && gotLock) {\n            BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);\n\n            // Playthrough might not have been notified that BGMDevice is starting yet, so make sure\n            // playthrough is starting. This way we won't drop any frames while waiting for the HAL to send\n            // that notification. We can't be completely sure this is safe from deadlocking, though, since\n            // CoreAudio is closed-source.\n            //\n            // TODO: Test this on older OS X versions. Differences in the CoreAudio implementations could\n            //       cause deadlocks.\n            BGMLogAndSwallowExceptionsMsg(\"BGMAudioDeviceManager::startPlayThroughSync\",\n                                          \"Starting playthrough\", [&] {\n                pt.Start();\n            });\n\n            err = pt.WaitForOutputDeviceToStart();\n            BGMAssert(err != BGMPlayThrough::kDeviceNotStarting, \"Playthrough didn't start\");\n        } else {\n            LogWarning(\"BGMAudioDeviceManager::startPlayThroughSync: Didn't get state lock. Returning \"\n                       \"early with kBGMErrorCode_ReturningEarly.\");\n            err = kBGMErrorCode_ReturningEarly;\n\n            dispatch_async(BGMGetDispatchQueue_PriorityUserInteractive(), ^{\n                @try {\n                    [stateLock lock];\n\n                    BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);\n                    \n                    BGMLogAndSwallowExceptionsMsg(\"BGMAudioDeviceManager::startPlayThroughSync\",\n                                                  \"Starting playthrough (dispatched)\", [&] {\n                        pt.Start();\n                    });\n\n                    BGMLogAndSwallowExceptions(\"BGMAudioDeviceManager::startPlayThroughSync\", [&] {\n                        pt.StopIfIdle();\n                    });\n                } @finally {\n                    [stateLock unlock];\n                }\n            });\n        }\n    } @finally {\n        if (gotLock) {\n            [stateLock unlock];\n        }\n    }\n    \n    return err;\n}\n\n#pragma mark BGMXPCHelper Communication\n\n- (void) setBGMXPCHelperConnection:(NSXPCConnection* __nullable)connection {\n    bgmXPCHelperConnection = connection;\n\n    // Tell BGMXPCHelper which device is the output device, since it might not be up-to-date.\n    [self sendOutputDeviceToBGMXPCHelper];\n}\n\n- (void) sendOutputDeviceToBGMXPCHelper {\n    NSXPCConnection* __nullable connection = bgmXPCHelperConnection;\n\n    if (connection)\n    {\n        id<BGMXPCHelperXPCProtocol> helperProxy =\n                [connection remoteObjectProxyWithErrorHandler:^(NSError* error) {\n                    // We could wait a bit and try again, but it isn't that important.\n                    NSLog(@\"BGMAudioDeviceManager::sendOutputDeviceToBGMXPCHelper: Connection\"\n                           \"error: %@\", error);\n                }];\n\n        [helperProxy setOutputDeviceToMakeDefaultOnAbnormalTermination:outputDevice.GetObjectID()];\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMenuItem.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMenuItem.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMAutoPauseMusic.h\"\n#import \"BGMMusicPlayers.h\"\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAutoPauseMenuItem : NSObject\n\n- (instancetype) initWithMenuItem:(NSMenuItem*)item\n                   autoPauseMusic:(BGMAutoPauseMusic*)autoPause\n                     musicPlayers:(BGMMusicPlayers*)players\n                     userDefaults:(BGMUserDefaults*)defaults;\n\n// Handle events passed along by the delegate (NSMenuDelegate) of the menu containing this menu item.\n- (void) parentMenuNeedsUpdate;\n- (void) parentMenuItemWillHighlight:(NSMenuItem* __nullable)item;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMenuItem.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMenuItem.m\n//  BGMApp\n//\n//  Copyright © 2016, 2019 Kyle Neideck\n//  Copyright © 2016 Tanner Hoke\n//\n\n// Self Include\n#import \"BGMAutoPauseMenuItem.h\"\n\n// Local Includes\n#import \"BGMAppWatcher.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic NSString* const kMenuItemTitleFormat = @\"Auto-pause %@\";\nstatic NSString* const kMenuItemDisabledToolTipFormat = @\"%@ doesn't appear to be running.\";\n\n// Wait time to disable/enable the auto-pause menu item, in seconds.\nstatic SInt64 const kMenuItemUpdateWaitTime = 1;\n\n@implementation BGMAutoPauseMenuItem {\n    BGMUserDefaults* userDefaults;\n    NSMenuItem* menuItem;\n    BGMAutoPauseMusic* autoPauseMusic;\n    BGMMusicPlayers* musicPlayers;\n    BGMAppWatcher* appWatcher;\n}\n\n- (instancetype) initWithMenuItem:(NSMenuItem*)item\n                   autoPauseMusic:(BGMAutoPauseMusic*)autoPause\n                     musicPlayers:(BGMMusicPlayers*)players\n                     userDefaults:(BGMUserDefaults*)defaults {\n    if ((self = [super init])) {\n        menuItem = item;\n        autoPauseMusic = autoPause;\n        musicPlayers = players;\n        userDefaults = defaults;\n        \n        // Enable/disable auto-pause to match the user's preferences setting.\n        if (userDefaults.autoPauseMusicEnabled) {\n            menuItem.state = NSOnState;\n            [autoPauseMusic enable];\n        } else {\n            menuItem.state = NSOffState;\n            [autoPauseMusic disable];\n        }\n        \n        // Toggle auto-pause when the menu item is clicked.\n        menuItem.target = self;\n        menuItem.action = @selector(toggleAutoPauseMusic);\n\n        [self initMenuItemTitle];\n    }\n    \n    return self;\n}\n\n- (void) initMenuItemTitle {\n    // Set the initial text, tool-tip, state, etc.\n    [self updateMenuItemTitle];\n\n    // Avoid retain cycles in case we ever want to destroy instances of this class.\n    BGMAutoPauseMenuItem* __weak weakSelf = self;\n\n    // Add a callback that enables/disables the Auto-pause Music menu item when the music player\n    // is launched/terminated.\n    void (^callback)(void) = ^{\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMenuItemUpdateWaitTime * NSEC_PER_SEC),\n                       dispatch_get_main_queue(),\n                       ^{\n                           BGMAutoPauseMenuItem* strongSelf = weakSelf;\n                           [strongSelf updateMenuItemTitle];\n                       });\n    };\n\n    appWatcher = [[BGMAppWatcher alloc] initWithAppLaunched:callback\n                                              appTerminated:callback\n                                         isMatchingBundleID:^BOOL(NSString* appBundleID) {\n        BGMAutoPauseMenuItem* strongSelf = weakSelf;\n        NSString* __nullable playerBundleID =\n                strongSelf->musicPlayers.selectedMusicPlayer.bundleID;\n        return playerBundleID && [appBundleID isEqualToString:(NSString*)playerBundleID];\n    }];\n}\n\n- (void) toggleAutoPauseMusic {\n    // The menu item was clicked.\n    \n    if (menuItem.state == NSOnState) {\n        menuItem.state = NSOffState;\n        [autoPauseMusic disable];\n    } else {\n        menuItem.state = NSOnState;\n        [autoPauseMusic enable];\n    }\n    \n    // Persist the change in the user's preferences.\n    userDefaults.autoPauseMusicEnabled = (menuItem.state == NSOnState);\n}\n\n- (void) updateMenuItemTitle {\n    [self updateMenuItemTitleWithHighlight:menuItem.isHighlighted];\n}\n\n- (void) updateMenuItemTitleWithHighlight:(BOOL)highlight {\n    // Set the title of the Auto-pause Music menu item, including the name of the selected music player.\n    NSString* musicPlayerName = musicPlayers.selectedMusicPlayer.name;\n    menuItem.title = [NSString stringWithFormat:kMenuItemTitleFormat, musicPlayerName];\n    \n    // Make the Auto-pause Music menu item appear disabled if the application is not running.\n    //\n    // We don't actually disable it just in case the user decides to disable auto-pause and their music player isn't\n    // running. E.g. someone who only recently installed Background Music and doesn't want to use auto-pause at all.\n    if (musicPlayers.selectedMusicPlayer.running) {\n        menuItem.attributedTitle = nil;\n        menuItem.toolTip = nil;\n    } else {\n        // Hardcode the text colour grey to match disabled menu items (unless the menu item is highlighted, in which\n        // case use white).\n        //\n        // I couldn't figure out a way to do this without hardcoding the colours. There's no colour constant for this,\n        // except possibly disabledControlTextColor, which just leaves the text black for me. I also couldn't get the\n        // colours from the built-in NSColorLists.\n        //\n        // TODO: Can we make the tick mark grey as well?\n        NSString* __nullable appleInterfaceStyle =\n            [[NSUserDefaults standardUserDefaults] stringForKey:@\"AppleInterfaceStyle\"];\n        BOOL darkMode = [appleInterfaceStyle isEqualToString:@\"Dark\"];\n        NSColor* textColor = [NSColor colorWithHue:0\n                                        saturation:0\n                                        brightness:(highlight ? 1 : (darkMode ? 0.25 : 0.75))\n                                             alpha:1];\n        \n        NSDictionary* attributes = @{ NSFontAttributeName: [NSFont menuBarFontOfSize:0],  // Default font size\n                                      NSForegroundColorAttributeName: textColor };\n        NSAttributedString* pseudoDisabledTitle = [[NSAttributedString alloc] initWithString:menuItem.title\n                                                                                  attributes:attributes];\n        menuItem.attributedTitle = pseudoDisabledTitle;\n\n        menuItem.toolTip = [NSString stringWithFormat:kMenuItemDisabledToolTipFormat, musicPlayerName];\n    }\n}\n\n#pragma mark Parent menu events\n\n- (void) parentMenuNeedsUpdate {\n    [self updateMenuItemTitle];\n}\n\n- (void) parentMenuItemWillHighlight:(NSMenuItem* __nullable)item {\n    // Used to make the auto-pause menu item's text white when it's highlighted and change it back after.\n    //\n    // TODO: If you click the auto-pause menu item while it's disabled, it will initially appear highlighted next time\n    //       you open the main menu.\n    \n    // If item is nil or any other menu item, the auto-pause menu item will be unhighlighted.\n    BOOL willHighlightMenuItem = [item isEqual:menuItem];\n    \n    // Only update the menu item if it's changing (from highlighted to unhighlighted or vice versa) to save a little\n    // CPU.\n    if (willHighlightMenuItem != menuItem.highlighted) {\n        [self updateMenuItemTitleWithHighlight:willHighlightMenuItem];\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMusic.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMusic.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  When enabled, BGMAutoPauseMusic listens for notifications from BGMDevice to tell when music is playing and\n//  pauses the music player if other audio starts.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMMusicPlayers.h\"\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAutoPauseMusic : NSObject\n\n- (id) initWithAudioDevices:(BGMAudioDeviceManager*)inAudioDevices musicPlayers:(BGMMusicPlayers*)inMusicPlayers userDefaults:(BGMUserDefaults*)inUserDefaults;\n\n- (void) enable;\n- (void) disable;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMusic.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMusic.m\n//  BGMApp\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMAutoPauseMusic.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#import \"BGMMusicPlayer.h\"\n\n// STL Includes\n#import <algorithm>  // std::max, std::min\n\n// System Includes\n#include <CoreAudio/AudioHardware.h>\n#include <mach/mach_time.h>\n\n\n// We multiply the time spent paused by this factor to calculate the delay before we consider unpausing.\nstatic Float32 const kUnpauseDelayWeightingFactor = 0.1f;\n\n@implementation BGMAutoPauseMusic {\n    BOOL enabled;\n    \n    BGMAudioDeviceManager* audioDevices;\n    BGMMusicPlayers* musicPlayers;\n    BGMUserDefaults* userDefaults;\n    \n    dispatch_queue_t listenerQueue;\n    // Have to keep track of the listener block we add so we can remove it later.\n    AudioObjectPropertyListenerBlock listenerBlock;\n    \n    dispatch_queue_t pauseUnpauseMusicQueue;\n    \n    // True if BGMApp has paused musicPlayer and hasn't unpaused it yet. (Will be out of sync with the music player app if the\n    // user has unpaused it themselves.)\n    BOOL wePaused;\n    // The times, in absolute time, that the BGMDevice last changed its audible state to silent...\n    UInt64 wentSilent;\n    // ...and to audible.\n    UInt64 wentAudible;\n}\n\n- (id) initWithAudioDevices:(BGMAudioDeviceManager*)inAudioDevices musicPlayers:(BGMMusicPlayers*)inMusicPlayers userDefaults:(BGMUserDefaults*)inUserDefaults {\n    if ((self = [super init])) {\n        audioDevices = inAudioDevices;\n        musicPlayers = inMusicPlayers;\n        userDefaults = inUserDefaults;\n        \n        enabled = NO;\n        wePaused = NO;\n        \n        dispatch_queue_attr_t attr;\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        if (&dispatch_queue_attr_make_with_qos_class) {\n            attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);\n        } else {\n            // OS X 10.9 fallback\n            attr = DISPATCH_QUEUE_SERIAL;\n        }\n#pragma clang diagnostic pop\n\n        listenerQueue = dispatch_queue_create(\"com.bearisdriving.BGM.AutoPauseMusic.Listener\", attr);\n        pauseUnpauseMusicQueue = dispatch_queue_create(\"com.bearisdriving.BGM.AutoPauseMusic.PauseUnpauseMusic\", attr);\n        \n        [self initListenerBlock];\n    }\n    \n    return self;\n}\n\n- (void) dealloc {\n    [self disable];\n}\n\n- (void) initListenerBlock {\n    // To avoid retain cycle\n    __unsafe_unretained BGMAutoPauseMusic* weakSelf = self;\n    \n    listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress * _Nonnull inAddresses) {\n        // inAddresses \"may contain addresses for properties for which the listener is not signed up to receive notifications\",\n        // so we have to check them all\n        for (int i = 0; i < inNumberAddresses; i++) {\n            if (inAddresses[i].mSelector == kAudioDeviceCustomPropertyDeviceAudibleState) {\n                BGMDeviceAudibleState audibleState = [weakSelf deviceAudibleState];\n                \n#if DEBUG\n                const char audibleStateStr[5] = CA4CCToCString(audibleState);\n                DebugMsg(\"BGMAutoPauseMusic::initListenerBlock: kAudioDeviceCustomPropertyDeviceAudibleState property changed to '%s'\",\n                         audibleStateStr);\n#endif\n                \n                // TODO: We shouldn't assume this block will only get called when BGMDevice's audible state changes. (Even if\n                //       the Core Audio docs did specify that, there's no reason not to be fault tolerant.)\n                if (audibleState == kBGMDeviceIsAudible) {\n                    [weakSelf queuePauseBlock];\n                } else if (audibleState == kBGMDeviceIsSilent) {\n                    [weakSelf queueUnpauseBlock];\n                } else if (audibleState == kBGMDeviceIsSilentExceptMusic) {\n                    // If we pause the music player and then the user unpauses it before the other audio stops, we need to set\n                    // wePaused to false at some point before the other audio starts again so we know we should pause\n                    DebugMsg(\"BGMAutoPauseMusic: Device is silent except music, resetting wePaused flag\");\n                    wePaused = NO;\n                }\n                // TODO: Add a fourth audible state, something like \"AudibleAndMusicPlaying\", and check it here to\n                //       handle the user unpausing and then repausing music while also playing other audio?\n            }\n        }\n    };\n}\n\n- (BGMDeviceAudibleState) deviceAudibleState {\n    return [audioDevices bgmDevice].GetAudibleState();\n}\n\n- (void) queuePauseBlock {\n    UInt64 now = mach_absolute_time();\n    wentAudible = now;\n    UInt64 startedPauseDelay = now;\n    \n    UInt64 pauseDelayMS = userDefaults.pauseDelayMS;\n    \n    // If pause delay is 0, pause immediately (no delay)\n    if (pauseDelayMS == 0) {\n        DebugMsg(\"BGMAutoPauseMusic::queuePauseBlock: Pause delay is 0, pausing immediately\");\n        \n        // Pause immediately if device is audible and we haven't already paused\n        if (!wePaused && ([self deviceAudibleState] == kBGMDeviceIsAudible)) {\n            wePaused = ([musicPlayers.selectedMusicPlayer pause] || wePaused);\n        }\n        return;\n    }\n    \n    UInt64 pauseDelayNSec = pauseDelayMS * NSEC_PER_MSEC;\n    \n    DebugMsg(\"BGMAutoPauseMusic::queuePauseBlock: Dispatching pause block at %llu\", now);\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, pauseDelayNSec),\n                   pauseUnpauseMusicQueue,\n                   ^{\n                       BOOL stillAudible = ([self deviceAudibleState] == kBGMDeviceIsAudible);\n                       \n                       DebugMsg(\"BGMAutoPauseMusic::queuePauseBlock: Running pause block dispatched at %llu.%s wentAudible=%llu\",\n                                startedPauseDelay,\n                                stillAudible ? \"\" : \" Not pausing because the device isn't audible.\",\n                                wentAudible);\n                       \n                       // Pause if this is the most recent pause block and the device is still audible, which means the audible\n                       // state hasn't changed since this block was queued. Also set wePaused to true if the player wasn't\n                       // already paused.\n                       if (!wePaused && (startedPauseDelay == wentAudible) && stillAudible) {\n                           wePaused = ([musicPlayers.selectedMusicPlayer pause] || wePaused);\n                       }\n                   });\n}\n\n- (void) queueUnpauseBlock {\n    UInt64 now = mach_absolute_time();\n    wentSilent = now;\n    UInt64 startedUnpauseDelay = now;\n    \n    // Get user-configurable max delay\n    UInt64 maxUnpauseDelayMS = userDefaults.maxUnpauseDelayMS;\n    \n    // If max unpause delay is 0, unpause immediately (no delay)\n    if (maxUnpauseDelayMS == 0) {\n        DebugMsg(\"BGMAutoPauseMusic::queueUnpauseBlock: Max unpause delay is 0, unpausing immediately\");\n        \n        // Unpause immediately if we were the one who paused and device is still silent\n        BGMDeviceAudibleState currentState = [self deviceAudibleState];\n        DebugMsg(\"BGMAutoPauseMusic::queueUnpauseBlock: Immediate unpause - wePaused=%s, currentState=%s\", \n                 wePaused ? \"YES\" : \"NO\", \n                 currentState == kBGMDeviceIsSilent ? \"Silent\" : \n                 (currentState == kBGMDeviceIsAudible ? \"Audible\" : \"SilentExceptMusic\"));\n        \n        if (wePaused && (currentState == kBGMDeviceIsSilent)) {\n            wePaused = NO;\n            [musicPlayers.selectedMusicPlayer unpause];\n        }\n        return;\n    }\n    \n    // Unpause sooner if we've only been paused for a short time. This is so a notification sound causing an auto-pause is\n    // less of an interruption.\n    //\n    // TODO: Fading in and out would make short pauses a lot less jarring because, if they were short enough, we wouldn't\n    //       actually pause the music player. So you'd hear a dip in the music's volume rather than a gap.\n    UInt64 unpauseDelayNsec =\n        static_cast<UInt64>((wentSilent - wentAudible) * kUnpauseDelayWeightingFactor);\n    \n    // Convert from absolute time to nanos.\n    mach_timebase_info_data_t info;\n    mach_timebase_info(&info);\n    unpauseDelayNsec = unpauseDelayNsec * info.numer / info.denom;\n    \n    // Clamp using user-configurable max delay and calculated min delay.\n    UInt64 maxUnpauseDelayNSec = maxUnpauseDelayMS * NSEC_PER_MSEC;\n    UInt64 minUnpauseDelayNSec = maxUnpauseDelayNSec / 10;\n    unpauseDelayNsec = std::min(maxUnpauseDelayNSec, unpauseDelayNsec);\n    unpauseDelayNsec = std::max(minUnpauseDelayNSec, unpauseDelayNsec);\n    \n    DebugMsg(\"BGMAutoPauseMusic::queueUnpauseBlock: Dispatched unpause block at %llu. unpauseDelayNsec=%llu\",\n             now,\n             unpauseDelayNsec);\n    \n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, unpauseDelayNsec),\n                   pauseUnpauseMusicQueue,\n                   ^{\n                       BGMDeviceAudibleState currentState = [self deviceAudibleState];\n                       BOOL stillSilent = (currentState == kBGMDeviceIsSilent);\n                       BOOL isLatestUnpause = (startedUnpauseDelay == wentSilent);\n                       \n                       DebugMsg(\"BGMAutoPauseMusic::queueUnpauseBlock: Running unpause block dispatched at %llu. wePaused=%s, isLatest=%s, currentState=%s, wentSilent=%llu\",\n                                startedUnpauseDelay,\n                                wePaused ? \"YES\" : \"NO\",\n                                isLatestUnpause ? \"YES\" : \"NO\",\n                                currentState == kBGMDeviceIsSilent ? \"Silent\" : \n                                (currentState == kBGMDeviceIsAudible ? \"Audible\" : \"SilentExceptMusic\"),\n                                wentSilent);\n                       \n                       // Unpause if we were the one who paused. Also check that this is the most recent unpause block and the\n                       // device is still silent, which means the audible state hasn't changed since this block was queued.\n                       if (wePaused && isLatestUnpause && stillSilent) {\n                           DebugMsg(\"BGMAutoPauseMusic::queueUnpauseBlock: Unpausing music player\");\n                           wePaused = NO;\n                           [musicPlayers.selectedMusicPlayer unpause];\n                       }\n                   });\n}\n\n- (void) enable {\n    if (!enabled) {\n        [audioDevices bgmDevice].AddPropertyListenerBlock(kBGMAudibleStateAddress, listenerQueue, listenerBlock);\n        enabled = YES;\n    }\n}\n\n- (void) disable {\n    if (enabled) {\n        [audioDevices bgmDevice].RemovePropertyListenerBlock(kBGMAudibleStateAddress, listenerQueue, listenerBlock);\n        enabled = NO;\n    }\n}\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMBackgroundMusicDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMBackgroundMusicDevice.cpp\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//\n\n// Self Include\n#include \"BGMBackgroundMusicDevice.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAHALAudioSystemObject.h\"\n#include \"CACFArray.h\"\n#include \"CACFDictionary.h\"\n\n// STL Includes\n#include <map>\n\n\n#pragma clang assume_nonnull begin\n\n#pragma mark Construction/Destruction\n\nBGMBackgroundMusicDevice::BGMBackgroundMusicDevice()\n:\n    BGMAudioDevice(CFSTR(kBGMDeviceUID)),\n    mUISoundsBGMDevice(CFSTR(kBGMDeviceUID_UISounds))\n{\n    if((GetObjectID() == kAudioObjectUnknown) || (mUISoundsBGMDevice == kAudioObjectUnknown))\n    {\n        LogError(\"BGMBackgroundMusicDevice::BGMBackgroundMusicDevice: Error getting BGMDevice ID\");\n        Throw(CAException(kAudioHardwareIllegalOperationError));\n    }\n};\n\nBGMBackgroundMusicDevice::~BGMBackgroundMusicDevice()\n{\n}\n\n#pragma mark Systemwide Default Device\n\nvoid BGMBackgroundMusicDevice::SetAsOSDefault()\n{\n    DebugMsg(\"BGMBackgroundMusicDevice::SetAsOSDefault: Setting the system's default audio device \"\n             \"to BGMDevice\");\n\n    CAHALAudioSystemObject audioSystem;\n\n    AudioDeviceID defaultDevice = audioSystem.GetDefaultAudioDevice(false, false);\n    AudioDeviceID systemDefaultDevice = audioSystem.GetDefaultAudioDevice(false, true);\n\n    if(systemDefaultDevice == defaultDevice)\n    {\n        // The default system device is the same as the default device, so change both of them.\n        //\n        // Use the UI sounds instance of BGMDevice because the default system output device is the\n        // device \"to use for system related sound\". The allows BGMDriver to tell when the audio it\n        // receives is UI-related.\n        audioSystem.SetDefaultAudioDevice(false, true, mUISoundsBGMDevice);\n    }\n\n    audioSystem.SetDefaultAudioDevice(false, false, GetObjectID());\n}\n\nvoid BGMBackgroundMusicDevice::UnsetAsOSDefault(AudioDeviceID inOutputDeviceID)\n{\n    CAHALAudioSystemObject audioSystem;\n\n    // Set BGMApp's output device as OS X's default output device.\n    bool bgmDeviceIsDefault =\n            (audioSystem.GetDefaultAudioDevice(false, false) == GetObjectID());\n\n    if(bgmDeviceIsDefault)\n    {\n        DebugMsg(\"BGMBackgroundMusicDevice::UnsetAsOSDefault: Setting the system's default output \"\n                 \"device back to device %d\", inOutputDeviceID);\n\n        audioSystem.SetDefaultAudioDevice(false, false, inOutputDeviceID);\n    }\n\n    // Set BGMApp's output device as OS X's default system output device.\n    bool bgmDeviceIsSystemDefault =\n            (audioSystem.GetDefaultAudioDevice(false, true) == mUISoundsBGMDevice);\n\n    // If we changed the default system output device to BGMDevice, which we only do if it's set to\n    // the same device as the default output device, change it back to the previous device.\n    if(bgmDeviceIsSystemDefault)\n    {\n        DebugMsg(\"BGMBackgroundMusicDevice::UnsetAsOSDefault: Setting the system's default system \"\n                 \"output device back to device %d\", inOutputDeviceID);\n\n        audioSystem.SetDefaultAudioDevice(false, true, inOutputDeviceID);\n    }\n}\n\n#pragma mark App Volumes\n\nCFArrayRef BGMBackgroundMusicDevice::GetAppVolumes() const\n{\n    CFTypeRef appVolumes = GetPropertyData_CFType(kBGMAppVolumesAddress);\n\n    ThrowIfNULL(appVolumes,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGMBackgroundMusicDevice::GetAppVolumes: !appVolumes\");\n    ThrowIf(CFGetTypeID(appVolumes) != CFArrayGetTypeID(),\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMBackgroundMusicDevice::GetAppVolumes: Expected CFArray value\");\n\n    return static_cast<CFArrayRef>(appVolumes);\n}\n\nvoid BGMBackgroundMusicDevice::SetAppVolume(SInt32 inVolume,\n                                            pid_t inAppProcessID,\n                                            CFStringRef __nullable inAppBundleID)\n{\n    BGMAssert((kAppRelativeVolumeMinRawValue <= inVolume) &&\n                      (inVolume <= kAppRelativeVolumeMaxRawValue),\n              \"BGMBackgroundMusicDevice::SetAppVolume: Volume out of bounds\");\n\n    // Clamp the volume to [kAppRelativeVolumeMinRawValue, kAppPanRightRawValue].\n    inVolume = std::max(kAppRelativeVolumeMinRawValue, inVolume);\n    inVolume = std::min(kAppRelativeVolumeMaxRawValue, inVolume);\n\n    SendAppVolumeOrPanToBGMDevice(inVolume,\n                                  CFSTR(kBGMAppVolumesKey_RelativeVolume),\n                                  inAppProcessID,\n                                  inAppBundleID);\n}\n\nvoid BGMBackgroundMusicDevice::SetAppPanPosition(SInt32 inPanPosition,\n                                                 pid_t inAppProcessID,\n                                                 CFStringRef __nullable inAppBundleID)\n{\n    BGMAssert((kAppPanLeftRawValue <= inPanPosition) && (inPanPosition <= kAppPanRightRawValue),\n              \"BGMBackgroundMusicDevice::SetAppPanPosition: Pan position out of bounds\");\n\n    // Clamp the pan position to [kAppPanLeftRawValue, kAppPanRightRawValue].\n    inPanPosition = std::max(kAppPanLeftRawValue, inPanPosition);\n    inPanPosition = std::min(kAppPanRightRawValue, inPanPosition);\n\n    SendAppVolumeOrPanToBGMDevice(inPanPosition,\n                                  CFSTR(kBGMAppVolumesKey_PanPosition),\n                                  inAppProcessID,\n                                  inAppBundleID);\n}\n\nvoid BGMBackgroundMusicDevice::SendAppVolumeOrPanToBGMDevice(SInt32 inNewValue,\n                                                             CFStringRef inVolumeTypeKey,\n                                                             pid_t inAppProcessID,\n                                                             CFStringRef __nullable inAppBundleID)\n{\n    CACFArray appVolumeChanges(true);\n\n    auto addVolumeChange = [&] (pid_t pid, CFStringRef bundleID)\n    {\n        CACFDictionary appVolumeChange(true);\n\n        appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), pid);\n        appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID), bundleID);\n        appVolumeChange.AddSInt32(inVolumeTypeKey, inNewValue);\n\n        appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());\n    };\n\n    addVolumeChange(inAppProcessID, inAppBundleID);\n\n    // Add the same change for each process the app is responsible for.\n    for(CACFString responsibleBundleID : ResponsibleBundleIDsOf(CACFString(inAppBundleID)))\n    {\n        // Send -1 as the PID so this volume will only ever be matched by bundle ID.\n        addVolumeChange(-1, responsibleBundleID.GetCFString());\n    }\n\n    CFPropertyListRef changesPList = appVolumeChanges.AsPropertyList();\n\n    // Send the change to BGMDevice.\n    SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);\n\n    // Also send it to the instance of BGMDevice that handles UI sounds.\n    mUISoundsBGMDevice.SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);\n}\n\n// This is a temporary solution that lets us control the volumes of some multiprocess apps, i.e.\n// apps that play their audio from a process with a different bundle ID.\n//\n// We can't just check the child processes of the apps' main processes because they're usually\n// created with launchd rather than being actual child processes. There's a private API to get the\n// processes that an app is \"responsible for\", so we'll try to use it in the proper fix and only use\n// this list if the API doesn't work.\n//\n// static\nstd::vector<CACFString>\nBGMBackgroundMusicDevice::ResponsibleBundleIDsOf(CACFString inParentBundleID)\n{\n    if(!inParentBundleID.IsValid())\n    {\n        return {};\n    }\n\n    std::map<CACFString, std::vector<CACFString>> bundleIDMap = {\n        // Finder\n        { \"com.apple.finder\",\n            { \"com.apple.quicklook.ui.helper\",\n              \"com.apple.quicklook.QuickLookUIService\" } },\n        // Safari\n        { \"com.apple.Safari\", { \"com.apple.WebKit.WebContent\" } },\n        // Firefox\n        { \"org.mozilla.firefox\", { \"org.mozilla.plugincontainer\" } },\n        // Firefox Nightly\n        { \"org.mozilla.nightly\", { \"org.mozilla.plugincontainer\" } },\n        // VMWare Fusion\n        { \"com.vmware.fusion\", { \"com.vmware.vmware-vmx\" } },\n        // Parallels\n        { \"com.parallels.desktop.console\", { \"com.parallels.vm\" } },\n        // MPlayer OSX Extended\n        { \"hu.mplayerhq.mplayerosx.extended\",\n                { \"ch.sttz.mplayerosx.extended.binaries.officialsvn\" } },\n        // Discord\n        { \"com.hnc.Discord\",\n            { \"com.hnc.Discord.helper\",\n              \"com.hnc.Discord.helper.Renderer\",\n              \"com.hnc.Discord.helper.Plugin\" } },\n        // Brave\n        { \"com.brave.Browser\",\n            { \"com.brave.Browser.helper\",\n              \"com.brave.Browser.helper.plugin\" } },\n        // Skype\n        { \"com.skype.skype\", { \"com.skype.skype.Helper\" } },\n        // Google Chrome\n        { \"com.google.Chrome\", { \"com.google.Chrome.helper\" } },\n        // Microsoft Edge\n        { \"com.microsoft.edgemac\", { \"com.microsoft.edgemac.helper\" } },\n        // Arc\n        { \"company.thebrowser.Browser\", { \"company.thebrowser.browser.helper\" } }\n    };\n\n    // Parallels' VM \"dock helper\" apps have bundle IDs like\n    // com.parallels.winapp.87f6bfc236d64d70a81c47f6243add4c.f5a25fdede514f7aa0a475a1873d3287.fs\n    if(inParentBundleID.StartsWith(CFSTR(\"com.parallels.winapp.\")))\n    {\n        return { \"com.parallels.vm\" };\n    }\n\n    return bundleIDMap[inParentBundleID];\n}\n\n#pragma mark Audible State\n\nBGMDeviceAudibleState BGMBackgroundMusicDevice::GetAudibleState() const\n{\n    CFTypeRef propertyDataRef = GetPropertyData_CFType(kBGMAudibleStateAddress);\n\n    ThrowIfNULL(propertyDataRef,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGMBackgroundMusicDevice::GetAudibleState: !propertyDataRef\");\n\n    ThrowIf(CFGetTypeID(propertyDataRef) != CFNumberGetTypeID(),\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMBackgroundMusicDevice::GetAudibleState: Property was not a CFNumber\");\n\n    CFNumberRef audibleStateRef = static_cast<CFNumberRef>(propertyDataRef);\n\n    BGMDeviceAudibleState audibleState;\n    Boolean success = CFNumberGetValue(audibleStateRef, kCFNumberSInt32Type, &audibleState);\n    CFRelease(audibleStateRef);\n\n    ThrowIf(!success,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMBackgroundMusicDevice::GetMusicPlayerProcessID: CFNumberGetValue failed\");\n\n    return audibleState;\n}\n\n#pragma mark Music Player\n\npid_t BGMBackgroundMusicDevice::GetMusicPlayerProcessID() const\n{\n    CFTypeRef propertyDataRef = GetPropertyData_CFType(kBGMMusicPlayerProcessIDAddress);\n\n    ThrowIfNULL(propertyDataRef,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGMBackgroundMusicDevice::GetMusicPlayerProcessID: !propertyDataRef\");\n\n    ThrowIf(CFGetTypeID(propertyDataRef) != CFNumberGetTypeID(),\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMBackgroundMusicDevice::GetMusicPlayerProcessID: Property was not a CFNumber\");\n\n    CFNumberRef pidRef = static_cast<CFNumberRef>(propertyDataRef);\n\n    pid_t pid;\n    Boolean success = CFNumberGetValue(pidRef, kCFNumberIntType, &pid);\n    CFRelease(pidRef);\n\n    ThrowIf(!success,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMBackgroundMusicDevice::GetMusicPlayerProcessID: CFNumberGetValue failed\");\n\n    return pid;\n}\n\nCFStringRef BGMBackgroundMusicDevice::GetMusicPlayerBundleID() const\n{\n    CFStringRef bundleID = GetPropertyData_CFString(kBGMMusicPlayerBundleIDAddress);\n\n    ThrowIfNULL(bundleID,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGMBackgroundMusicDevice::GetMusicPlayerBundleID: !bundleID\");\n\n    return bundleID;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMBackgroundMusicDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMBackgroundMusicDevice.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  The interface to BGMDevice, the main virtual device published by BGMDriver, and the second\n//  instance of that device, which handles UI-related audio. In most cases, users of this class\n//  should be able to think of it as representing a single device.\n//\n//  BGMDevice is the device that appears as \"Background Music\" in programs that list the output\n//  devices, e.g. System Preferences. It receives the system's audio, processes it and sends it to\n//  BGMApp by publishing an input stream. BGMApp then plays the audio on the user's real output\n//  device.\n//\n//  See BGMDriver/BGMDriver/BGM_Device.h.\n//\n\n#ifndef BGMApp__BGMBackgroundMusicDevice\n#define BGMApp__BGMBackgroundMusicDevice\n\n// Superclass Includes\n#include \"BGMAudioDevice.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n\n// STL Includes\n#include <vector>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMBackgroundMusicDevice\n:\n    public BGMAudioDevice\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n    /*!\n     @throws CAException If BGMDevice is not found or the HAL returns an error when queried for\n                         BGMDevice's current Audio Object ID.\n     */\n                        BGMBackgroundMusicDevice();\n    virtual            ~BGMBackgroundMusicDevice();\n\n#pragma mark Systemwide Default Device\n\npublic:\n    /*!\n     Set BGMDevice as the default audio device for all processes.\n\n     @throws CAException If the HAL responds with an error.\n     */\n    void                SetAsOSDefault();\n    /*!\n     Replace BGMDevice as the default device with the output device.\n\n     @throws CAException If the HAL responds with an error.\n     */\n    void                UnsetAsOSDefault(AudioDeviceID inOutputDeviceID);\n\n#pragma mark App Volumes\n\npublic:\n    /*!\n     @return The current value of BGMDevice's kAudioDeviceCustomPropertyAppVolumes property. See\n             BGM_Types.h.\n     @throws CAException If the HAL returns an error or a non-array type. Callers are responsible\n                         for validating and type-checking the values contained in the array.\n     */\n    CFArrayRef          GetAppVolumes() const;\n    /*!\n     @param inVolume A value between kAppRelativeVolumeMinRawValue and kAppRelativeVolumeMaxRawValue\n                     from BGM_Types.h. See kBGMAppVolumesKey_RelativeVolume in BGM_Types.h.\n     @param inAppProcessID The ID of app's main process (or the process it uses to play audio, if\n                           you've managed to figure that out). If an app has multiple audio\n                           processes, you can just set the volume for each of them. Pass -1 to omit\n                           this param.\n     @param inAppBundleID The app's bundle ID. Pass null to omit this param.\n     @throws CAException If the HAL returns an error when this function sends the volume change to\n                         BGMDevice.\n     */\n    void                SetAppVolume(SInt32 inVolume,\n                                     pid_t inAppProcessID,\n                                     CFStringRef __nullable inAppBundleID);\n    /*!\n     @param inPanPosition A value between kAppPanLeftRawValue and kAppPanRightRawValue from\n                          BGM_Types.h. A negative value has a higher proportion of left channel, and\n                          a positive value has a higher proportion of right channel.\n     @param inAppProcessID The ID of app's main process (or the process it uses to play audio, if\n                           you've managed to figure that out). If an app has multiple audio\n                           processes, you can just set the pan position for each of them. Pass -1 to\n                           omit this param.\n     @param inAppBundleID The app's bundle ID. Pass null to omit this param.\n     @throws CAException If the HAL returns an error when this function sends the pan position\n                         change to BGMDevice.\n     */\n    void                SetAppPanPosition(SInt32 inPanPosition,\n                                          pid_t inAppProcessID,\n                                          CFStringRef __nullable inAppBundleID);\n\nprivate:\n    void                SendAppVolumeOrPanToBGMDevice(SInt32 inNewValue,\n                                                      CFStringRef inVolumeTypeKey,\n                                                      pid_t inAppProcessID,\n                                                      CFStringRef __nullable inAppBundleID);\n\n    static std::vector<CACFString>\n                        ResponsibleBundleIDsOf(CACFString inParentBundleID);\n\n#pragma mark Audible State\n\npublic:\n    /*!\n     @return BGMDevice's current \"audible state\", which can be either silent, silent except for the\n             user's music player or audible, meaning a program other than the music player is\n             playing audio.\n     @throws CAException If the HAL returns an error or invalid data when queried.\n     @see kAudioDeviceCustomPropertyDeviceAudibleState in BGM_Types.h.\n     */\n    BGMDeviceAudibleState GetAudibleState() const;\n\n#pragma mark Music Player\n\npublic:\n    /*!\n     @return The value of BGMDevice's property for the selected music player's process ID. Zero if\n             the property is unset. (We assume kernel_task will never be the user's music player.)\n     @throws CAException If the HAL returns an error or an invalid PID when queried.\n     @see kAudioDeviceCustomPropertyMusicPlayerProcessID in BGM_Types.h.\n     */\n    virtual pid_t       GetMusicPlayerProcessID() const;\n    /*!\n     Set the value of BGMDevice's property for the selected music player's process ID. Pass zero to\n     unset the property. Setting this property will unset the bundle ID version of the property.\n\n     @throws CAException If the HAL returns an error.\n     @see kAudioDeviceCustomPropertyMusicPlayerProcessID in BGM_Types.h.\n     */\n    virtual void        SetMusicPlayerProcessID(CFNumberRef inProcessID) {\n                            SetPropertyData_CFType(kBGMMusicPlayerProcessIDAddress, inProcessID); }\n    /*!\n     @return The value of BGMDevice's property for the selected music player's bundle ID. The empty\n             string if the property is unset.\n     @throws CAException If the HAL returns an error or an invalid bundle ID when queried.\n     @see kAudioDeviceCustomPropertyMusicPlayerBundleID in BGM_Types.h.\n     */\n    virtual CFStringRef GetMusicPlayerBundleID() const;\n    /*!\n     Set the value of BGMDevice's property for the selected music player's bundle ID. Pass the empty\n     string to unset the property. Setting this property will unset the process ID version of the\n     property.\n\n     @throws CAException If the HAL returns an error.\n     @see kAudioDeviceCustomPropertyMusicPlayerBundleID in BGM_Types.h.\n     */\n    virtual void        SetMusicPlayerBundleID(CFStringRef inBundleID) {\n                            SetPropertyData_CFString(kBGMMusicPlayerBundleIDAddress, inBundleID); }\n\n#pragma mark UI Sounds Instance\n\npublic:\n    /*! @return The instance of BGMDevice that handles UI sounds. */\n    BGMAudioDevice      GetUISoundsBGMDeviceInstance() { return mUISoundsBGMDevice; }\n\nprivate:\n    /*! The instance of BGMDevice that handles UI sounds. */\n    BGMAudioDevice      mUISoundsBGMDevice;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMBackgroundMusicDevice */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDebugLoggingMenuItem.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDebugLoggingMenuItem.h\n//  BGMApp\n//\n//  Copyright © 2020 Kyle Neideck\n//\n//  A menu item in the main menu that enables/disables debug logging. Only visible if you hold the\n//  option down when you click the status bar icon to reveal the main menu.\n//\n//  TODO: It would be better to have this menu item in the Preferences menu (maybe in an Advanced\n//        section) and always visible, but first we'd need to add something that tells the user how\n//        to view the log messages. Or better yet, something that automatically opens them.\n//\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMDebugLoggingMenuItem : NSObject\n\n- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem;\n\n// True if the main menu is showing hidden items/options because the user held the option key when\n// they clicked the icon. This class makes the debug logging menu item visible if this property has\n// been set true or if debug logging is enabled.\n@property (nonatomic) BOOL menuShowingExtraOptions;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDebugLoggingMenuItem.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDebugLoggingMenuItem.m\n//  BGMApp\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMDebugLoggingMenuItem.h\"\n\n// PublicUtility Includes\n#import \"BGMDebugLogging.h\"\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMDebugLoggingMenuItem {\n    NSMenuItem* _menuItem;\n    BOOL _menuShowingExtraOptions;\n}\n\n- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem {\n    if ((self = [super init])) {\n        _menuItem = menuItem;\n        _menuItem.state =\n                BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;\n\n        [self setMenuShowingExtraOptions:NO];\n\n        // Enable/disable debug logging when the menu item is clicked.\n        menuItem.target = self;\n        menuItem.action = @selector(toggleDebugLogging);\n    }\n\n    return self;\n}\n\n- (void) setMenuShowingExtraOptions:(BOOL)showingExtra {\n    _menuShowingExtraOptions = showingExtra;\n    _menuItem.hidden = !BGMDebugLoggingIsEnabled() && !showingExtra;\n\n    DebugMsg(\"BGMDebugLoggingMenuItem::menuShowingExtraOptions: %s the menu item\",\n             _menuItem.hidden ? \"Hiding\" : \"Showing\");\n}\n\n- (void) toggleDebugLogging {\n    BGMSetDebugLoggingEnabled(!BGMDebugLoggingIsEnabled());\n    _menuItem.state = BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;\n\n    DebugMsg(\"BGMDebugLoggingMenuItem::toggleDebugLogging: Debug logging %s\",\n             BGMDebugLoggingIsEnabled() ? \"enabled\" : \"disabled\");\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlSync.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDeviceControlSync.cpp\n//  BGMApp\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMDeviceControlSync.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CAPropertyAddress.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic const AudioObjectPropertyAddress kMutePropertyAddress =\n    { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMaster };\n\nstatic const AudioObjectPropertyAddress kVolumePropertyAddress =\n    { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMaster };\n\n#pragma mark Construction/Destruction\n\nBGMDeviceControlSync::BGMDeviceControlSync(AudioObjectID inBGMDevice,\n                                           AudioObjectID inOutputDevice,\n                                           CAHALAudioSystemObject inAudioSystem)\n:\n    mBGMDevice(inBGMDevice),\n    mOutputDevice(inOutputDevice),\n    mAudioSystem(inAudioSystem),\n    mBGMDeviceControlsList(inBGMDevice)\n{\n}\n\nBGMDeviceControlSync::~BGMDeviceControlSync()\n{\n    BGMLogAndSwallowExceptions(\"BGMDeviceControlSync::~BGMDeviceControlSync\", [&] {\n        CAMutex::Locker locker(mMutex);\n\n        Deactivate();\n    });\n}\n\nvoid    BGMDeviceControlSync::Activate()\n{\n    CAMutex::Locker locker(mMutex);\n\n    ThrowIf((mBGMDevice.GetObjectID() == kAudioObjectUnknown || mOutputDevice.GetObjectID() == kAudioObjectUnknown),\n            BGM_DeviceNotSetException(),\n            \"BGMDeviceControlSync::Activate: Both the output device and BGMDevice must be set to start synchronizing their controls\");\n\n    if(!mActive)\n    {\n        DebugMsg(\"BGMDeviceControlSync::Activate: Activating control sync\");\n\n        // Disable BGMDevice controls that the output device doesn't have and reenable any that were\n        // disabled for the previous output device.\n        //\n        // Continue anyway if this fails because it's better to have extra/missing controls than to\n        // be unable to use the device.\n        BGMLogAndSwallowExceptionsMsg(\"BGMDeviceControlSync::Activate\", \"Controls list\", [&] {\n            bool wasUpdated = mBGMDeviceControlsList.MatchControlsListOf(mOutputDevice);\n            if(wasUpdated)\n            {\n                mBGMDeviceControlsList.PropagateControlListChange();\n            }\n        });\n\n        // Init BGMDevice controls to match output device\n        mBGMDevice.CopyVolumeFrom(mOutputDevice, kAudioObjectPropertyScopeOutput);\n        mBGMDevice.CopyMuteFrom(mOutputDevice, kAudioObjectPropertyScopeOutput);\n\n        // Register listeners for volume and mute values\n        mBGMDevice.AddPropertyListener(kVolumePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);\n        \n        try\n        {\n            mBGMDevice.AddPropertyListener(kMutePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);\n        }\n        catch(CAException)\n        {\n            CATry\n            mBGMDevice.RemovePropertyListener(kVolumePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);\n            CACatch\n            \n            throw;\n        }\n        \n        mActive = true;\n    }\n    else\n    {\n        DebugMsg(\"BGMDeviceControlSync::Activate: Already active\");\n    }\n}\n\nvoid    BGMDeviceControlSync::Deactivate()\n{\n    CAMutex::Locker locker(mMutex);\n\n    if(mActive)\n    {\n        DebugMsg(\"BGMDeviceControlSync::Deactivate: Deactivating control sync\");\n\n        // Deregister listeners\n        if(mBGMDevice.GetObjectID() != kAudioDeviceUnknown)\n        {\n            BGMLogAndSwallowExceptions(\"BGMDeviceControlSync::Deactivate\", [&] {\n                mBGMDevice.RemovePropertyListener(kVolumePropertyAddress,\n                                                  &BGMDeviceControlSync::BGMDeviceListenerProc,\n                                                  this);\n            });\n\n            BGMLogAndSwallowExceptions(\"BGMDeviceControlSync::Deactivate\", [&] {\n                mBGMDevice.RemovePropertyListener(kMutePropertyAddress,\n                                                  &BGMDeviceControlSync::BGMDeviceListenerProc,\n                                                  this);\n            });\n        }\n\n        mActive = false;\n    }\n    else\n    {\n        DebugMsg(\"BGMDeviceControlSync::Deactivate: Not active\");\n    }\n}\n\n#pragma mark Accessors\n\nvoid    BGMDeviceControlSync::SetDevices(AudioObjectID inBGMDevice, AudioObjectID inOutputDevice)\n{\n    CAMutex::Locker locker(mMutex);\n\n    bool wasActive = mActive;\n\n    Deactivate();\n\n    mBGMDevice = inBGMDevice;\n    mBGMDeviceControlsList.SetBGMDevice(inBGMDevice);\n    mOutputDevice = inOutputDevice;\n    \n    if(wasActive)\n    {\n        Activate();\n    }\n}\n\n#pragma mark Listener Procs\n\n// static\nOSStatus    BGMDeviceControlSync::BGMDeviceListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses, void* __nullable inClientData)\n{\n    // refCon (reference context) is the instance that registered this listener proc.\n    BGMDeviceControlSync* refCon = static_cast<BGMDeviceControlSync*>(inClientData);\n\n    auto checkState = [&] {\n        if(!refCon)\n        {\n            LogError(\"BGMDeviceControlSync::BGMDeviceListenerProc: !refCon\");\n            return false;\n        }\n\n        if(!refCon->mActive ||\n           (refCon->mBGMDevice.GetObjectID() == kAudioObjectUnknown) ||\n           (refCon->mOutputDevice.GetObjectID() == kAudioObjectUnknown))\n        {\n            return false;\n        }\n\n        if(inObjectID != refCon->mBGMDevice.GetObjectID())\n        {\n            LogError(\"BGMDeviceControlSync::BGMDeviceListenerProc: notified about audio object other than BGMDevice\");\n            return false;\n        }\n        \n        return true;\n    };\n\n    for(int i = 0; i < inNumberAddresses; i++)\n    {\n        AudioObjectPropertyScope scope = inAddresses[i].mScope;\n        \n        switch(inAddresses[i].mSelector)\n        {\n            case kAudioDevicePropertyVolumeScalar:\n                {\n                    CAMutex::Locker locker(refCon->mMutex);\n\n                    // Update the output device's volume.\n                    if(checkState())\n                    {\n                        refCon->mOutputDevice.CopyVolumeFrom(refCon->mBGMDevice, scope);\n                    }\n                }\n                break;\n                \n            case kAudioDevicePropertyMute:\n                {\n                    CAMutex::Locker locker(refCon->mMutex);\n\n                    // Update the output device's mute control. Note that this also runs when you\n                    // change the volume (on BGMDevice).\n                    if(checkState())\n                    {\n                        refCon->mOutputDevice.CopyMuteFrom(refCon->mBGMDevice, scope);\n                    }\n                }\n                break;\n        }\n    }\n\n    // \"The return value [of an AudioObjectPropertyListenerProc] is currently unused and should always be 0.\"\n    return 0;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlSync.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDeviceControlSync.h\n//  BGMApp\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n//  Synchronises BGMDevice's controls (just volume and mute currently) with the output device's\n//  controls. This allows the user to control the output device normally while BGMDevice is set as\n//  the default device.\n//\n//  BGMDeviceControlSync disables any BGMDevice controls that the output device doesn't also have.\n//  When the value of one of BGMDevice's controls is changed, BGMDeviceControlSync copies the new\n//  value to the output device.\n//\n//  Thread safe.\n//\n\n#ifndef BGMApp__BGMDeviceControlSync\n#define BGMApp__BGMDeviceControlSync\n\n// Local Includes\n#include \"BGMAudioDevice.h\"\n#include \"BGMDeviceControlsList.h\"\n\n// PublicUtility Includes\n#include \"CAHALAudioSystemObject.h\"\n#include \"CAMutex.h\"\n\n// System Includes\n#include <AudioToolbox/AudioServices.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMDeviceControlSync\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                        BGMDeviceControlSync(AudioObjectID inBGMDevice,\n                                             AudioObjectID inOutputDevice,\n                                             CAHALAudioSystemObject inAudioSystem\n                                                     = CAHALAudioSystemObject());\n                        ~BGMDeviceControlSync();\n                        // Disallow copying\n                        BGMDeviceControlSync(const BGMDeviceControlSync&) = delete;\n                        BGMDeviceControlSync& operator=(const BGMDeviceControlSync&) = delete;\n\n#ifdef __OBJC__\n                        // Only intended as a convenience for Objective-C instance vars\n                        BGMDeviceControlSync()\n                        : BGMDeviceControlSync(kAudioObjectUnknown, kAudioObjectUnknown) { };\n#endif\n\n    /*!\n     Begin synchronising BGMDevice's controls with the output device's.\n\n     @throws BGM_DeviceNotSetException if BGMDevice isn't set.\n     @throws CAException if the HAL or one of the devices returns an error when this function\n                         registers for device property notifications or when it copies the current\n                         values of the output device's controls to BGMDevice. This\n                         BGMDeviceControlSync will remain inactive if this function throws.\n     */\n    void                Activate();\n    /*! Stop synchronising BGMDevice's controls with the output device's. */\n    void                Deactivate();\n\n#pragma mark Accessors\n\n    /*!\n     Set the IDs of BGMDevice and the output device to synchronise with.\n\n     @throws BGM_DeviceNotSetException if BGMDevice isn't set.\n     @throws CAException if the HAL or one of the new devices returns an error while restarting\n                         synchronisation. This BGMDeviceControlSync will be deactivated if this\n                         function throws, but its devices will still be set.\n     */\n    void                SetDevices(AudioObjectID inBGMDevice, AudioObjectID inOutputDevice);\n\n#pragma mark Listener Procs\n    \nprivate:\n    /*! Receives HAL notifications about the BGMDevice properties this class listens to. */\n    static OSStatus     BGMDeviceListenerProc(AudioObjectID inObjectID,\n                                              UInt32 inNumberAddresses,\n                                              const AudioObjectPropertyAddress* inAddresses,\n                                              void* __nullable inClientData);\n    \nprivate:\n    CAMutex             mMutex         { \"Device Control Sync\" };\n    bool                mActive        = false;\n\n    CAHALAudioSystemObject mAudioSystem;\n    \n    BGMAudioDevice      mBGMDevice     { (AudioObjectID)kAudioObjectUnknown };\n    BGMAudioDevice      mOutputDevice  { (AudioObjectID)kAudioObjectUnknown };\n\n    BGMDeviceControlsList mBGMDeviceControlsList;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMDeviceControlSync */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlsList.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDeviceControlsList.cpp\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMDeviceControlsList.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CAPropertyAddress.h\"\n#include \"CACFArray.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic const SInt64 kToggleDeviceInitialDelay = 50 * NSEC_PER_MSEC;\nstatic const SInt64 kToggleDeviceBackDelay    = 500 * NSEC_PER_MSEC;\nstatic const SInt64 kDisableNullDeviceDelay   = 500 * NSEC_PER_MSEC;\nstatic const SInt64 kDisableNullDeviceTimeout = 5000 * NSEC_PER_MSEC;\n\n#pragma mark Construction/Destruction\n\nBGMDeviceControlsList::BGMDeviceControlsList(AudioObjectID inBGMDevice,\n                                             CAHALAudioSystemObject inAudioSystem)\n:\n    mBGMDevice(inBGMDevice),\n    mAudioSystem(inAudioSystem)\n{\n    BGMAssert((mBGMDevice.IsBGMDevice() || mBGMDevice.GetObjectID() == kAudioObjectUnknown),\n              \"BGMDeviceControlsList::BGMDeviceControlsList: Given device is not BGMDevice\");\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    mCanToggleDeviceOnSystem = (&dispatch_block_wait &&\n                                &dispatch_block_cancel &&\n                                &dispatch_block_testcancel &&\n                                &dispatch_queue_attr_make_with_qos_class);\n#pragma clang diagnostic pop\n}\n\nBGMDeviceControlsList::~BGMDeviceControlsList()\n{\n    CAMutex::Locker locker(mMutex);\n\n    if(!mDeviceTogglingInitialised)\n    {\n        return;\n    }\n\n    if(mListenerQueue && mListenerBlock)\n    {\n        BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::~BGMDeviceControlsList\", ([&] {\n            mAudioSystem.RemovePropertyListenerBlock(\n                    CAPropertyAddress(kAudioHardwarePropertyDevices),\n                    mListenerQueue,\n                    mListenerBlock);\n        }));\n    }\n\n    // If we're in the middle of toggling the default device, block until we've finished.\n    if(mDisableNullDeviceBlock && mDeviceToggleState != ToggleState::NotToggling)\n    {\n        DebugMsg(\"BGMDeviceControlsList::~BGMDeviceControlsList: Waiting for device toggle\");\n\n        // Copy the reference so we can unlock the mutex and allow any remaining blocks to run.\n        dispatch_block_t disableNullDeviceBlock = mDisableNullDeviceBlock;\n\n        CAMutex::Unlocker unlocker(mMutex);\n\n        // Note that if mDisableNullDeviceBlock is currently running this will return after it\n        // finishes and if it's already run this will return immediately. So we don't have to\n        // worry about ending up waiting for mDisableNullDeviceBlock when it isn't queued.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        long timedOut = dispatch_block_wait(disableNullDeviceBlock, kDisableNullDeviceTimeout);\n#pragma clang diagnostic pop\n\n        if(timedOut)\n        {\n            LogWarning(\"BGMDeviceControlsList::~BGMDeviceControlsList: Device toggle timed out\");\n        }\n    }\n\n    mDeviceToggleState = ToggleState::NotToggling;\n\n    DestroyBlock(mDeviceToggleBlock);\n    DestroyBlock(mDeviceToggleBackBlock);\n    DestroyBlock(mDisableNullDeviceBlock);\n\n    if(mListenerBlock)\n    {\n        Block_release(mListenerBlock);\n    }\n\n    if(mListenerQueue)\n    {\n        dispatch_release(BGM_Utils::NN(mListenerQueue));\n    }\n}\n\n#pragma mark Accessors\n\nvoid    BGMDeviceControlsList::SetBGMDevice(AudioObjectID inBGMDeviceID)\n{\n    CAMutex::Locker locker(mMutex);\n\n    mBGMDevice = inBGMDeviceID;\n\n    BGMAssert(mBGMDevice.IsBGMDevice(),\n              \"BGMDeviceControlsList::SetBGMDevice: Given device is not BGMDevice\");\n}\n\n#pragma mark Update Controls List\n\nbool    BGMDeviceControlsList::MatchControlsListOf(AudioObjectID inDeviceID)\n{\n    CAMutex::Locker locker(mMutex);\n\n    if(!mBGMDevice.IsBGMDevice())\n    {\n        LogWarning(\"BGMDeviceControlsList::MatchControlsListOf: BGMDevice ID not set\");\n        return false;\n    }\n\n    // If the output device doesn't have a control that BGMDevice does, disable it on BGMDevice so\n    // the system's audio UI isn't confusing.\n\n    // No need to change input controls.\n    AudioObjectPropertyScope inScope = kAudioObjectPropertyScopeOutput;\n\n    // Check which of BGMDevice's controls are currently enabled. We need to know whether we're\n    // actually enabling/disabling any controls so we know whether we need to call\n    // PropagateControlListChange afterward.\n    CFTypeRef __nullable enabledControlsRef =\n        mBGMDevice.GetPropertyData_CFType(kBGMEnabledOutputControlsAddress);\n\n    ThrowIf(!enabledControlsRef || (CFGetTypeID(enabledControlsRef) != CFArrayGetTypeID()),\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMDeviceControlsList::MatchControlsListOf: Expected a CFArray for \"\n            \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n\n    CACFArray enabledControls(static_cast<CFArrayRef>(enabledControlsRef), true);\n\n    BGMAssert(enabledControls.GetNumberItems() == 2,\n              \"BGMDeviceControlsList::MatchControlsListOf: Expected 2 array elements for \"\n              \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n\n    bool volumeEnabled;\n    bool didGetBool = enabledControls.GetBool(kBGMEnabledOutputControlsIndex_Volume, volumeEnabled);\n    ThrowIf(!didGetBool,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMDeviceControlsList::MatchControlsListOf: Expected volume element of \"\n            \"kAudioDeviceCustomPropertyEnabledOutputControls to be a CFBoolean\");\n\n    bool muteEnabled;\n    didGetBool = enabledControls.GetBool(kBGMEnabledOutputControlsIndex_Mute, muteEnabled);\n    ThrowIf(!didGetBool,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGMDeviceControlsList::MatchControlsListOf: Expected mute element of \"\n            \"kAudioDeviceCustomPropertyEnabledOutputControls to be a CFBoolean\");\n\n    DebugMsg(\"BGMDeviceControlsList::MatchControlsListOf: BGMDevice has volume %s, mute %s\",\n             (volumeEnabled ? \"enabled\" : \"disabled\"),\n             (muteEnabled ? \"enabled\" : \"disabled\"));\n\n    // Check which controls the other device has.\n    BGMAudioDevice device(inDeviceID);\n    bool hasMute = device.HasSettableMasterMute(inScope);\n\n    bool hasVolume =\n        device.HasSettableMasterVolume(inScope) || device.HasSettableVirtualMasterVolume(inScope);\n\n    if(!hasVolume)\n    {\n        // Check for per-channel volume controls.\n        UInt32 numChannels =\n            device.GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);\n\n        for(UInt32 channel = 1; channel <= numChannels; channel++)\n        {\n            BGMLogAndSwallowExceptionsMsg(\"BGMDeviceControlsList::MatchControlsListOf\",\n                                          \"Checking for channel volume controls\",\n                                          ([&] {\n                hasVolume =\n                    (device.HasVolumeControl(inScope, channel)\n                            && device.VolumeControlIsSettable(inScope, channel));\n            }));\n\n            if(hasVolume)\n            {\n                break;\n            }\n        }\n    }\n\n    // Tell BGMDevice to enable/disable its controls to match the output device.\n    bool deviceUpdated = false;\n\n    CACFArray newEnabledControls;\n    newEnabledControls.SetCFMutableArrayFromCopy(enabledControls.GetCFArray());\n\n    // Update volume.\n    if(volumeEnabled != hasVolume)\n    {\n        DebugMsg(\"BGMDeviceControlsList::MatchControlsListOf: %s BGMDevice volume control.\",\n                 hasVolume ? \"Enabling\" : \"Disabling\");\n\n        newEnabledControls.SetBool(kBGMEnabledOutputControlsIndex_Volume, hasVolume);\n        deviceUpdated = true;\n    }\n\n    // Update mute.\n    if(muteEnabled != hasMute)\n    {\n        DebugMsg(\"BGMDeviceControlsList::MatchControlsListOf: %s BGMDevice mute control.\",\n                 hasMute ? \"Enabling\" : \"Disabling\");\n\n        newEnabledControls.SetBool(kBGMEnabledOutputControlsIndex_Mute, hasMute);\n        deviceUpdated = true;\n    }\n\n    if(deviceUpdated)\n    {\n        mBGMDevice.SetPropertyData_CFType(kBGMEnabledOutputControlsAddress,\n                                          newEnabledControls.GetCFMutableArray());\n    }\n\n    return deviceUpdated;\n}\n\nvoid    BGMDeviceControlsList::PropagateControlListChange()\n{\n    CAMutex::Locker locker(mMutex);\n\n    if((mBGMDevice == kAudioObjectUnknown) || !mCanToggleDeviceOnSystem)\n    {\n        return;\n    }\n\n    InitDeviceToggling();\n\n    // Leave the default device alone if the user has changed it since launching BGMApp.\n    bool bgmDeviceIsDefault = true;\n\n    BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::PropagateControlListChange\", ([&] {\n        bgmDeviceIsDefault =\n            (mBGMDevice.GetObjectID() == mAudioSystem.GetDefaultAudioDevice(false, false));\n    }));\n\n    if(bgmDeviceIsDefault)\n    {\n        mDeviceToggleState = ToggleState::SettingNullDeviceAsDefault;\n\n        // We'll get a notification from the HAL after the Null Device is enabled. Then we can\n        // temporarily make it the default device, which gets other programs to notice that\n        // BGMDevice's controls have changed.\n        try\n        {\n            CAMutex::Unlocker unlocker(mMutex);\n            SetNullDeviceEnabled(true);\n        }\n        catch (...)\n        {\n            mDeviceToggleState = ToggleState::NotToggling;\n            LogError(\"BGMDeviceControlsList::PropagateControlListChange: Could not enable the Null \"\n                     \"Device\");\n            throw;\n        }\n    }\n}\n\n#pragma mark Implementation\n\nvoid    BGMDeviceControlsList::InitDeviceToggling()\n{\n    CAMutex::Locker locker(mMutex);\n\n    if(mDeviceTogglingInitialised || !mCanToggleDeviceOnSystem)\n    {\n        return;\n    }\n\n    BGMAssert(mBGMDevice.IsBGMDevice(),\n              \"BGMDeviceControlsList::InitDeviceToggling: mBGMDevice device is not set to \"\n              \"BGMDevice's ID\");\n\n    // Register a listener to find out when the Null Device becomes available/unavailable. See\n    // ToggleDefaultDevice.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    dispatch_queue_attr_t attr =\n        dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);\n#pragma clang diagnostic pop\n    mListenerQueue = dispatch_queue_create(\"com.bearisdriving.BGM.BGMDeviceControlsList\", attr);\n\n    auto listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {\n        // Ignore the notification if we're not toggling the default device, which would just mean\n        // the default device has been changed for an unrelated reason.\n        if(mDeviceToggleState == ToggleState::NotToggling)\n        {\n            return;\n        }\n\n        for(int i = 0; i < inNumberAddresses; i++)\n        {\n            switch(inAddresses[i].mSelector)\n            {\n                case kAudioHardwarePropertyDevices:\n                    {\n                        CAMutex::Locker innerLocker(mMutex);\n\n                        DebugMsg(\"BGMDeviceControlsList::InitDeviceToggling: Got \"\n                                 \"kAudioHardwarePropertyDevices\");\n\n                        // Cancel the previous block in case it hasn't run yet.\n                        DestroyBlock(mDeviceToggleBlock);\n\n                        mDeviceToggleBlock = CreateDeviceToggleBlock();\n\n                        // Changing the default device too quickly after enabling the Null Device\n                        // seems to cause problems with some programs. Not sure why.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n                        if(mDeviceToggleBlock)\n                        {\n                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW,\n                                                         kToggleDeviceInitialDelay),\n                                           dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),\n                                           BGM_Utils::NN(mDeviceToggleBlock));\n                        }\n#pragma clang diagnostic pop\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n    };\n\n    mListenerBlock = Block_copy(listenerBlock);\n\n    BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::InitDeviceToggling\", [&] {\n        mAudioSystem.AddPropertyListenerBlock(CAPropertyAddress(kAudioHardwarePropertyDevices),\n                                              mListenerQueue,\n                                              mListenerBlock);\n    });\n\n    mDeviceTogglingInitialised = true;\n}\n\nvoid    BGMDeviceControlsList::ToggleDefaultDevice()\n{\n    // Set the Null Device as the OS X default device.\n    AudioObjectID nullDeviceID = mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMNullDeviceUID));\n\n    if(nullDeviceID == kAudioObjectUnknown)\n    {\n        // It's unlikely, but we might have been notified about an unrelated device so just log a\n        // warning.\n        LogWarning(\"BGMDeviceControlsList::ToggleDefaultDevice: Null Device not found\");\n        return;\n    }\n\n    DebugMsg(\"BGMDeviceControlsList::ToggleDefaultDevice: Setting Null Device as default. \"\n             \"nullDeviceID = %u\", nullDeviceID);\n    mAudioSystem.SetDefaultAudioDevice(false, false, nullDeviceID);\n\n    mDeviceToggleState = ToggleState::SettingBGMDeviceAsDefault;\n\n    // A small number of apps (e.g. Firefox) seem to have trouble with the default device being\n    // changed back immediately, so for now we insert a short delay here and before disabling the\n    // Null Device.\n\n    // Cancel the previous block in case it hasn't run yet.\n    DestroyBlock(mDeviceToggleBackBlock);\n\n    mDeviceToggleBackBlock = CreateDeviceToggleBackBlock();\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    if(mDeviceToggleBackBlock)\n    {\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kToggleDeviceBackDelay),\n                       dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),\n                       BGM_Utils::NN(mDeviceToggleBackBlock));\n    }\n#pragma clang diagnostic pop\n}\n\nvoid    BGMDeviceControlsList::SetNullDeviceEnabled(bool inEnabled)\n{\n    DebugMsg(\"BGMDeviceControlsList::SetNullDeviceEnabled: %s the null device\",\n             inEnabled ? \"Enabling\" : \"Disabling\");\n\n    // Get the audio object for BGMDriver, which is the object the Null Device belongs to.\n    AudioObjectID bgmDriverID = mAudioSystem.GetAudioPlugInForBundleID(CFSTR(kBGMDriverBundleID));\n\n    if(bgmDriverID == kAudioObjectUnknown)\n    {\n        LogError(\"BGMDeviceControlsList::SetNullDeviceEnabled: BGMDriver plug-in audio object not \"\n                 \"found\");\n        throw CAException(kAudioHardwareUnspecifiedError);\n    }\n\n    CAHALAudioObject bgmDriver(bgmDriverID);\n    bgmDriver.SetPropertyData_CFType(CAPropertyAddress(kAudioPlugInCustomPropertyNullDeviceActive),\n                                     (inEnabled ? kCFBooleanTrue : kCFBooleanFalse));\n}\n\ndispatch_block_t __nullable BGMDeviceControlsList::CreateDeviceToggleBlock()\n{\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    dispatch_block_t __nullable toggleBlock = dispatch_block_create((dispatch_block_flags_t)0, ^{\n#pragma clang diagnostic pop\n        CAMutex::Locker locker(mMutex);\n\n        if(mDeviceToggleState == ToggleState::SettingNullDeviceAsDefault)\n        {\n            BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::CreateDeviceToggleBlock\",\n                                       ([&] {\n                ToggleDefaultDevice();\n            }));\n        }\n    });\n\n    if(!toggleBlock)\n    {\n        // Pretty sure this should never happen, but the docs aren't completely clear.\n        LogError(\"BGMDeviceControlsList::CreateDeviceToggleBlock: !toggleBlock\");\n    }\n\n    return toggleBlock;\n}\n\ndispatch_block_t __nullable BGMDeviceControlsList::CreateDeviceToggleBackBlock()\n{\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    dispatch_block_t __nullable toggleBackBlock =\n            dispatch_block_create((dispatch_block_flags_t)0, ^{\n#pragma clang diagnostic pop\n        CAMutex::Locker locker(mMutex);\n\n        if(mDeviceToggleState != ToggleState::SettingBGMDeviceAsDefault)\n        {\n            return;\n        }\n\n        // Set BGMDevice back as the default device.\n        DebugMsg(\"BGMDeviceControlsList::ToggleDefaultDevice: Setting BGMDevice as default\");\n        BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::CreateDeviceToggleBackBlock\", ([&] {\n            mAudioSystem.SetDefaultAudioDevice(false, false, mBGMDevice.GetObjectID());\n        }));\n\n        mDeviceToggleState = ToggleState::DisablingNullDevice;\n\n        // Cancel the previous block in case it hasn't run yet.\n        DestroyBlock(mDisableNullDeviceBlock);\n\n        mDisableNullDeviceBlock = CreateDisableNullDeviceBlock();\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        if(mDisableNullDeviceBlock)\n        {\n            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kDisableNullDeviceDelay),\n                           dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),\n                           BGM_Utils::NN(mDisableNullDeviceBlock));\n        }\n#pragma clang diagnostic pop\n    });\n\n    if(!toggleBackBlock)\n    {\n        // Pretty sure this should never happen, but the docs aren't completely clear.\n        LogError(\"BGMDeviceControlsList::CreateDeviceToggleBackBlock: !toggleBackBlock\");\n    }\n\n    return toggleBackBlock;\n}\n\ndispatch_block_t __nullable BGMDeviceControlsList::CreateDisableNullDeviceBlock()\n{\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    dispatch_block_t __nullable disableNullDeviceBlock =\n            dispatch_block_create((dispatch_block_flags_t)0, ^{\n#pragma clang diagnostic pop\n        CAMutex::Locker locker(mMutex);\n\n        if(mDeviceToggleState != ToggleState::DisablingNullDevice)\n        {\n            return;\n        }\n\n        mDeviceToggleState = ToggleState::NotToggling;\n\n        BGMLogAndSwallowExceptions(\"BGMDeviceControlsList::CreateDisableNullDeviceBlock\",\n                                   ([&] {\n            CAMutex::Unlocker unlocker(mMutex);\n            // Hide the null device from the user again.\n            SetNullDeviceEnabled(false);\n        }));\n\n        BGMAssert(mBGMDevice.IsBGMDevice(), \"BGMDevice's AudioObjectID changed\");\n    });\n\n    if(!disableNullDeviceBlock)\n    {\n        // Pretty sure this should never happen, but the docs aren't completely clear.\n        LogError(\"BGMDeviceControlsList::CreateDisableNullDeviceBlock: !disableNullDeviceBlock\");\n    }\n\n    return disableNullDeviceBlock;\n}\n\nvoid    BGMDeviceControlsList::DestroyBlock(dispatch_block_t __nullable & block)\n{\n    if(!block)\n    {\n        return;\n    }\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n    dispatch_block_t& blockNN = (dispatch_block_t&)block;\n    if(!dispatch_block_testcancel(blockNN))\n    {\n        // Stop the block from running if it's currently queued.\n        dispatch_block_cancel(blockNN);\n\n        // Make sure the block isn't currently running. That should almost never be the case.\n        while(!dispatch_block_testcancel(blockNN))\n        {\n            CAMutex::Unlocker unlocker(mMutex);\n            usleep(10);\n        }\n        \n        Block_release(block);\n        block = nullptr;\n    }\n#pragma clang diagnostic pop\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlsList.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDeviceControlsList.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n#ifndef BGMApp__BGMDeviceControlsList\n#define BGMApp__BGMDeviceControlsList\n\n// Local Includes\n#include \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#include \"CAHALAudioDevice.h\"\n#include \"CAHALAudioSystemObject.h\"\n#include \"CAMutex.h\"\n\n// System Includes\n#include <dispatch/dispatch.h>\n#include <AudioToolbox/AudioServices.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMDeviceControlsList\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n\n                        BGMDeviceControlsList(AudioObjectID inBGMDevice,\n                                              CAHALAudioSystemObject inAudioSystem\n                                                      = CAHALAudioSystemObject());\n                        ~BGMDeviceControlsList();\n                        // Disallow copying\n                        BGMDeviceControlsList(const BGMDeviceControlsList&) = delete;\n                        BGMDeviceControlsList& operator=(const BGMDeviceControlsList&) = delete;\n\n#pragma mark Accessors\n\n    /*! @param inBGMDeviceID The ID of BGMDevice. */\n    void                SetBGMDevice(AudioObjectID inBGMDeviceID);\n\n#pragma mark Update Controls List\n\n    /*!\n     Enable the BGMDevice controls (volume and mute currently) that can be matched to controls of\n     the given device, and disable the ones that can't.\n\n     @param inDeviceID The ID of the device.\n     @return True if BGMDevice's list of controls was updated.\n     @throws CAException if an error is received from either device.\n     */\n    bool                MatchControlsListOf(AudioObjectID inDeviceID);\n    /*!\n     After updating BGMDevice's controls list, we need to change the default device so programs\n     (including OS X's audio UI) will update themselves. We could just change to the real output\n     device and change back, but that could have side effects the user wouldn't expect. For example,\n     an app the user has muted might be unmuted for a short period.\n\n     Instead we tell BGMDriver to enable the Null Device -- a device that does nothing -- so we can\n     use it to toggle the default device. The Null Device is normally disabled so it can be hidden\n     from the user. OS X won't let us make a hidden device temporarily visible or set a hidden\n     device as the default, so we have to completely remove the Null Device from the system while\n     we're not using it.\n     \n     @throws CAException if it fails to enable the Null Device.\n     */\n    void                PropagateControlListChange();\n\n#pragma mark Implementation\n\nprivate:\n    /*! Lazily initialises the fields used to toggle the default device. */\n    void                InitDeviceToggling();\n    /*! Changes the OS X default audio device to the Null Device and then back to BGMDevice. */\n    void                ToggleDefaultDevice();\n    /*!\n     Enable or disable the Null Device. See PropagateControlListChange and BGM_NullDevice in \n     BGMDriver.\n\n     @throws CAException if we can't get the BGMDriver plug-in audio object from the HAL or the HAL\n                         returns an error when setting kAudioPlugInCustomPropertyNullDeviceActive.\n     */\n    void                SetNullDeviceEnabled(bool inEnabled);\n\n    dispatch_block_t __nullable CreateDeviceToggleBlock();\n    dispatch_block_t __nullable CreateDeviceToggleBackBlock();\n    dispatch_block_t __nullable CreateDisableNullDeviceBlock();\n\n    void                DestroyBlock(dispatch_block_t __nullable & block);\n\nprivate:\n    CAMutex             mMutex { \"Device Controls List\" };\n    bool                mDeviceTogglingInitialised = false;\n    // OS X 10.9 doesn't have the functions we use for PropagateControlListChange.\n    bool                mCanToggleDeviceOnSystem;\n\n    BGMAudioDevice      mBGMDevice;\n    CAHALAudioSystemObject mAudioSystem;  // Not guarded by the mutex.\n\n    enum ToggleState\n    {\n        NotToggling, SettingNullDeviceAsDefault, SettingBGMDeviceAsDefault, DisablingNullDevice\n    };\n    BGMDeviceControlsList::ToggleState mDeviceToggleState = ToggleState::NotToggling;\n\n    dispatch_block_t __nullable mDeviceToggleBlock      = nullptr;\n    dispatch_block_t __nullable mDeviceToggleBackBlock  = nullptr;\n    dispatch_block_t __nullable mDisableNullDeviceBlock = nullptr;\n\n    // These will only ever be null after construction on 10.9, since toggling will be disabled.\n    dispatch_queue_t __nullable                 mListenerQueue = nullptr;\n    AudioObjectPropertyListenerBlock __nullable mListenerBlock = nullptr;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMDeviceControlsList */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputDeviceMenuSection.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMOutputDeviceMenuSection.h\n//  BGMApp\n//\n//  Copyright © 2016, 2018 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMPreferredOutputDevices.h\"\n\n// System Includes\n#import <AppKit/AppKit.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMOutputDeviceMenuSection : NSObject\n\n- (instancetype) initWithBGMMenu:(NSMenu*)inBGMMenu\n                    audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n                preferredDevices:(BGMPreferredOutputDevices*)inPreferredDevices;\n\n// To be called when BGMApp has been set to use a different output device. For example, when a new\n// device is connected and BGMPreferredOutputDevices decides BGMApp should switch to it.\n- (void) outputDeviceDidChange;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputDeviceMenuSection.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMOutputDeviceMenuSection.mm\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMOutputDeviceMenuSection.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGM_Types.h\"\n#import \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#import \"CAAutoDisposer.h\"\n#import \"CAHALAudioDevice.h\"\n#import \"CAHALAudioSystemObject.h\"\n#import \"CAPropertyAddress.h\"\n\n// STL Includes\n#import <set>\n#import <vector>\n\n\n#pragma clang assume_nonnull begin\n\nstatic NSInteger const kOutputDeviceMenuItemTag = 5;\n\n@implementation BGMOutputDeviceMenuSection {\n    NSMenu* bgmMenu;\n    BGMAudioDeviceManager* audioDevices;\n    BGMPreferredOutputDevices* preferredDevices;\n    NSMutableArray<NSMenuItem*>* outputDeviceMenuItems;\n    // Called when a CoreAudio property has changed and we might need to update the menu. For\n    // example, when a device is connected or disconnected.\n    AudioObjectPropertyListenerBlock refreshNeededListener;\n    // The devices we've added refreshNeededListener to. Used to avoid adding it to a device twice\n    // for the same property and to remove it from all devices in dealloc.\n    std::set<BGMAudioDevice> listenedDevices_kAudioDevicePropertyDataSources;\n    std::set<BGMAudioDevice> listenedDevices_kAudioDevicePropertyDataSource;\n}\n\n- (instancetype) initWithBGMMenu:(NSMenu*)inBGMMenu\n                    audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n                preferredDevices:(BGMPreferredOutputDevices*)inPreferredDevices {\n    if ((self = [super init])) {\n        bgmMenu = inBGMMenu;\n        audioDevices = inAudioDevices;\n        preferredDevices = inPreferredDevices;\n        outputDeviceMenuItems = [NSMutableArray new];\n\n        [self listenForDevicesAddedOrRemoved];\n        [self populateBGMMenu];\n    }\n    \n    return self;\n}\n\n- (void) dealloc {\n    // Tell CoreAudio not to call the listener block anymore. This probably isn't necessary.\n    //\n    // I think it's safe to do this without dispatching to the main queue because dealloc and\n    // refreshNeededListener should be essentially mutually exclusive. If refreshNeededListener is\n    // invoked and gets a value for weakSelf, it holds the strong ref until it returns, so dealloc\n    // won't be called. If refreshNeededListener is invoked and deallocation has started, it will\n    // get nil for weakSelf and just return.\n    auto removeListener = [&] (CAHALAudioObject audioObject, AudioObjectPropertySelector prop) {\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            // Check the object still exists first to reduce unnecessary error logs.\n            if (CAHALAudioObject::ObjectExists(audioObject.GetObjectID())) {\n                audioObject.RemovePropertyListenerBlock(CAPropertyAddress(prop),\n                                                        dispatch_get_main_queue(),\n                                                        refreshNeededListener);\n            }\n        });\n    };\n\n    // Remove the listener from each audio object we added it to.\n    removeListener(CAHALAudioSystemObject(), kAudioHardwarePropertyDevices);\n\n    for (auto device : listenedDevices_kAudioDevicePropertyDataSources) {\n        removeListener(device, kAudioDevicePropertyDataSources);\n    }\n\n    for (auto device : listenedDevices_kAudioDevicePropertyDataSource) {\n        removeListener(device, kAudioDevicePropertyDataSource);\n    }\n}\n\n- (void) listenForDevicesAddedOrRemoved {\n    // Create the block that will run when a device is added or removed.\n    BGMOutputDeviceMenuSection* __weak weakSelf = self;\n\n    refreshNeededListener = ^(UInt32 inNumberAddresses,\n                              const AudioObjectPropertyAddress* inAddresses) {\n        #pragma unused (inNumberAddresses, inAddresses)\n\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            [weakSelf populateBGMMenu];\n        });\n    };\n\n    // Register the listener block to be called when devices are connected or disconnected.\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        CAHALAudioSystemObject().AddPropertyListenerBlock(\n            CAPropertyAddress(kAudioHardwarePropertyDevices),\n            dispatch_get_main_queue(),\n            refreshNeededListener);\n    });\n}\n\n- (void) populateBGMMenu {\n    // TODO: Technically, we should assert we're on the main queue rather than just the main thread.\n    BGMAssert([NSThread isMainThread],\n              \"BGMOutputDeviceMenuSection::populateBGMMenu called on non-main thread\");\n\n    // Remove existing menu items\n    for (NSMenuItem* item in outputDeviceMenuItems) {\n        DebugMsg(\"BGMOutputDeviceMenuSection::populateBGMMenu: Removing %s\",\n                 item.description.UTF8String);\n        [bgmMenu removeItem:item];\n    }\n    \n    [outputDeviceMenuItems removeAllObjects];\n    \n    // Add a menu item for each output device\n    CAHALAudioSystemObject audioSystem;\n    UInt32 numDevices = audioSystem.GetNumberAudioDevices();\n    \n    if (numDevices > 0) {\n        CAAutoArrayDelete<AudioObjectID> devices(numDevices);\n        audioSystem.GetAudioDevices(numDevices, devices);\n        \n        for (UInt32 i = 0; i < numDevices; i++) {\n            [self insertMenuItemsForDevice:devices[i]];\n        }\n    }\n}\n\n- (void) insertMenuItemsForDevice:(BGMAudioDevice)device {\n    // Insert menu items after the item for the \"Output Device\" heading.\n    const NSInteger menuItemsIdx = [bgmMenu indexOfItemWithTag:kOutputDeviceMenuItemTag] + 1;\n\n    BOOL canBeOutputDevice = YES;\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        canBeOutputDevice = device.CanBeOutputDeviceInBGMApp();\n    });\n\n    if (canBeOutputDevice) {\n        for (NSMenuItem* item : [self createMenuItemsForDevice:device]) {\n            DebugMsg(\"BGMOutputDeviceMenuSection::insertMenuItemsForDevice: Inserting %s\",\n                     item.description.UTF8String);\n            [bgmMenu insertItem:item atIndex:menuItemsIdx];\n            [outputDeviceMenuItems addObject:item];\n        }\n\n        // Add listeners to update the menu when the device's data source changes or it changes its\n        // list of data sources. We do this so that, for example, when you plug headphones into the\n        // built-in jack, the menu item for the built-in device will change from \"Internal Speakers\"\n        // to \"Headphones\".\n        if (listenedDevices_kAudioDevicePropertyDataSources.count(device) == 0) {\n            BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n                device.AddPropertyListenerBlock(CAPropertyAddress(kAudioDevicePropertyDataSources,\n                                                                  kAudioDevicePropertyScopeOutput),\n                                                dispatch_get_main_queue(),\n                                                refreshNeededListener);\n                listenedDevices_kAudioDevicePropertyDataSources.insert(device);\n            });\n        };\n\n        if (listenedDevices_kAudioDevicePropertyDataSource.count(device) == 0) {\n            BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n                device.AddPropertyListenerBlock(CAPropertyAddress(kAudioDevicePropertyDataSource,\n                                                                  kAudioDevicePropertyScopeOutput),\n                                                dispatch_get_main_queue(),\n                                                refreshNeededListener);\n                listenedDevices_kAudioDevicePropertyDataSource.insert(device);\n            });\n        };\n    }\n}\n\n- (NSArray<NSMenuItem*>*) createMenuItemsForDevice:(CAHALAudioDevice)device {\n    // We fill this array with a menu item for each output device (or each data source for each device) on\n    // the system.\n    NSMutableArray<NSMenuItem*>* items = [NSMutableArray new];\n\n    AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;\n    UInt32 channel = kAudioObjectPropertyElementMaster;\n    \n    // If the device has data sources, create a menu item for each. Otherwise, create a single menu item\n    // for the device. This way the menu items' titles will be, for example, \"Internal Speakers\" rather\n    // than \"Built-in Output\".\n    //\n    // TODO: Handle data destinations as well? I don't have (or know of) any hardware with them.\n    // TODO: Use the current data source's name when the control isn't settable, but only add one menu item.\n    UInt32 numDataSources = 0;\n\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        if (device.HasDataSourceControl(scope, channel) &&\n                device.DataSourceControlIsSettable(scope, channel)) {\n            numDataSources = device.GetNumberAvailableDataSources(scope, channel);\n        }\n    });\n    \n    if (numDataSources > 0) {\n        std::vector<UInt32> dataSourceIDs(numDataSources);\n        // This call updates numDataSources to the real number of IDs it added to our array.\n        device.GetAvailableDataSources(scope, channel, numDataSources, dataSourceIDs.data());\n        \n        for (UInt32 i = 0; i < numDataSources; i++) {\n            DebugMsg(\"BGMOutputDeviceMenuSection::createMenuItemsForDevice: \"\n                     \"Creating item. %s%u %s%u\",\n                     \"Device ID:\", device.GetObjectID(),\n                     \", Data source ID:\", dataSourceIDs[i]);\n\n            BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, \"(DS)\", [&] {\n                NSString* dataSourceName =\n                    CFBridgingRelease(device.CopyDataSourceNameForID(scope, channel, dataSourceIDs[i]));\n                NSString* deviceName = CFBridgingRelease(device.CopyName());\n                \n                [items addObject:[self createMenuItemForDevice:device\n                                                  dataSourceID:@(dataSourceIDs[i])\n                                                         title:dataSourceName\n                                                       toolTip:deviceName]];\n            });\n        }\n    } else {\n        DebugMsg(\"BGMOutputDeviceMenuSection::createMenuItemsForDevice: Creating item. %s%u\",\n                 \"Device ID:\", device.GetObjectID());\n\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            [items addObject:[self createMenuItemForDevice:device\n                                              dataSourceID:nil\n                                                     title:CFBridgingRelease(device.CopyName())\n                                                   toolTip:nil]];\n        });\n    }\n    \n    return items;\n}\n\n- (NSMenuItem*) createMenuItemForDevice:(CAHALAudioDevice)device\n                           dataSourceID:(NSNumber* __nullable)dataSourceID\n                                  title:(NSString* __nullable)title\n                                toolTip:(NSString* __nullable)toolTip {\n    // If we don't have a title, use the tool-tip text instead.\n    if (!title) {\n        title = (toolTip ? toolTip : @\"\");\n    }\n    \n    NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:BGMNN(title)\n                                                  action:@selector(outputDeviceMenuItemSelected:)\n                                           keyEquivalent:@\"\"];\n    \n    // Add the AirPlay icon to the labels of AirPlay devices.\n    //\n    // TODO: Test this with real hardware that supports AirPlay. (I don't have any.)\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        if (device.GetTransportType() == kAudioDeviceTransportTypeAirPlay) {\n            item.image = [NSImage imageNamed:@\"AirPlayIcon\"];\n            \n            // Make the icon a \"template image\" so it gets drawn colour-inverted when it's highlighted or\n            // OS X is in dark mode.\n            [item.image setTemplate:YES];\n        }\n    });\n    \n    // The menu item should be selected if it's the menu item for the current output device. If the device\n    // has data sources, only the menu item for the current data source should be selected.\n    BOOL isSelected =\n        [audioDevices isOutputDevice:device.GetObjectID()] &&\n            (!dataSourceID || [audioDevices isOutputDataSource:[dataSourceID unsignedIntValue]]);\n    \n    item.state = (isSelected ? NSOnState : NSOffState);\n    item.toolTip = toolTip;\n    item.target = self;\n    item.indentationLevel = 1;\n    item.representedObject = @{ @\"deviceID\": @(device.GetObjectID()),\n                                @\"dataSourceID\": dataSourceID ? BGMNN(dataSourceID) : [NSNull null] };\n\n#if __clang_major__ >= 9\n    if (@available(macOS 10.10, *)) {\n        // Used for UI tests.\n        item.accessibilityIdentifier = @\"output-device\";\n    }\n#endif\n    \n    return item;\n}\n\n// Called by BGMAudioDeviceManager to tell us a different device has been set as the output device.\n- (void) outputDeviceDidChange {\n    BGMOutputDeviceMenuSection* __weak weakSelf = self;\n\n    dispatch_async(dispatch_get_main_queue(), ^{\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            [weakSelf populateBGMMenu];\n        });\n    });\n}\n\n- (void) outputDeviceMenuItemSelected:(NSMenuItem*)menuItem {\n    DebugMsg(\"BGMOutputDeviceMenuSection::outputDeviceMenuItemSelected: '%s' menu item selected\",\n             [menuItem.title UTF8String]);\n    \n    // Make sure the menu item is actually for an output device.\n    if (![outputDeviceMenuItems containsObject:menuItem]) {\n        return;\n    }\n    \n    // Change to the new output device.\n    AudioDeviceID newDeviceID = [[menuItem representedObject][@\"deviceID\"] unsignedIntValue];\n    id newDataSourceID = [menuItem representedObject][@\"dataSourceID\"];\n    \n    BOOL changingDevice = ![audioDevices isOutputDevice:newDeviceID];\n    BOOL changingDataSource =\n        (newDataSourceID != [NSNull null]) &&\n            ![audioDevices isOutputDataSource:[newDataSourceID unsignedIntValue]];\n\n    if (changingDevice || changingDataSource) {\n        NSString* deviceName =\n            menuItem.toolTip ?\n                [NSString stringWithFormat:@\"%@ (%@)\", menuItem.title, menuItem.toolTip] :\n                menuItem.title;\n\n        if (changingDevice) {\n            // Add the new output device to the list of preferred devices.\n            [preferredDevices userChangedOutputDeviceTo:newDeviceID];\n        }\n\n        // Dispatched because it usually blocks. (Note that we're using\n        // DISPATCH_QUEUE_PRIORITY_HIGH, which is the second highest priority.)\n        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{\n            [self changeToOutputDevice:newDeviceID\n                         newDataSource:newDataSourceID\n                            deviceName:deviceName];\n        });\n    }\n}\n\n- (void) changeToOutputDevice:(AudioDeviceID)deviceID\n                newDataSource:(id)dataSourceID\n                   deviceName:(NSString*)deviceName {\n    NSError* __nullable error;\n    \n    if (dataSourceID == [NSNull null]) {\n        error = [audioDevices setOutputDeviceWithID:deviceID revertOnFailure:YES];\n    } else {\n        error = [audioDevices setOutputDeviceWithID:deviceID\n                                       dataSourceID:[dataSourceID unsignedIntValue]\n                                    revertOnFailure:YES];\n    }\n    \n    if (error) {\n        // Couldn't change the output device, so show a warning. (No need to change the menu\n        // selection back because it gets repopulated every time it's opened.)\n        \n        // NSAlerts should only be shown on the main thread.\n        dispatch_async(dispatch_get_main_queue(), ^{\n            NSLog(@\"Failed to set output device: %@\", deviceName);\n            \n            NSAlert* alert = [NSAlert new];\n            \n            alert.messageText =\n                [NSString stringWithFormat:@\"Failed to set %@ as the output device.\", deviceName];\n            alert.informativeText = @\"This is probably a bug. Feel free to report it.\";\n            \n            [alert runModal];\n        });\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputVolumeMenuItem.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMOutputVolumeMenuItem.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMOutputVolumeMenuItem : NSMenuItem\n\n// A menu item with a slider for controlling the volume of the output device. Similar to the one in\n// macOS's Volume menu extra.\n//\n// view, slider and deviceLabel are the UI elements from MainMenu.xib.\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                                 view:(NSView*)view\n                               slider:(NSSlider*)slider\n                          deviceLabel:(NSTextField*)label;\n\n- (void) outputDeviceDidChange;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMOutputVolumeMenuItem.mm\n//  BGMApp\n//\n//  Copyright © 2017-2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMOutputVolumeMenuItem.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMAudioDevice.h\"\n#import \"BGMVolumeChangeListener.h\"\n\n// PublicUtility Includes\n#import \"CAException.h\"\n#import \"CAPropertyAddress.h\"\n\n// System Includes\n#import <CoreAudio/AudioHardware.h>\n\n\n#pragma clang assume_nonnull begin\n\nconst float                    kSliderEpsilon           = 1e-10f;\nconst AudioObjectPropertyScope kScope                   = kAudioDevicePropertyScopeOutput;\nNSString* const __nonnull      kGenericOutputDeviceName = @\"Output Device\";\n\n@implementation BGMOutputVolumeMenuItem {\n    BGMAudioDeviceManager* audioDevices;\n    NSTextField* deviceLabel;\n    NSSlider* volumeSlider;\n    BGMAudioDevice outputDevice;\n    BGMVolumeChangeListener* volumeChangeListener;\n    AudioObjectPropertyListenerBlock updateLabelListenerBlock;\n}\n\n// TODO: Show the output device's icon next to its name.\n// TODO: Should the menu (bgmMenu) hide after you change the output volume slider, like the normal\n//       menu bar volume slider does?\n// TODO: Move the output devices from Preferences to the main menu so they're slightly easier to\n//       access?\n// TODO: Update the screenshot in the README at some point.\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                                 view:(NSView*)view\n                               slider:(NSSlider*)slider\n                          deviceLabel:(NSTextField*)label {\n    if ((self = [super initWithTitle:@\"\" action:nil keyEquivalent:@\"\"])) {\n        audioDevices = devices;\n        deviceLabel = label;\n        volumeSlider = slider;\n        outputDevice = audioDevices.outputDevice;\n\n        // volumeChangeListener and updateLabelListenerBlock are initialised in the methods called\n        // below.\n\n        // Apply our custom view from MainMenu.xib.\n        self.view = view;\n\n        // Set up the UI components in the view.\n        [self initSlider];\n        [self updateLabelAndToolTip];\n\n        // Register a listener so we can update if the output device's data source changes.\n        [self addOutputDeviceDataSourceListener];\n    }\n\n    return self;\n}\n\n- (void) dealloc {\n    // Remove the audio property listeners.\n    // TODO: This call isn't thread safe. (But currently this dealloc method is only called if\n    //       there's an error.)\n    [self removeOutputDeviceDataSourceListener];\n}\n\n- (void) initSlider {\n    BGMAssert([NSThread isMainThread],\n              \"initSlider must be called from the main thread because it calls UI functions.\");\n\n    volumeSlider.target = self;\n    volumeSlider.action = @selector(sliderChanged:);\n\n    // Initialise the slider.\n    [self updateVolumeSlider];\n\n    // Register a listener that will update the slider when the user changes the volume or\n    // mutes/unmutes their audio.\n    BGMOutputVolumeMenuItem* __weak weakSelf = self;\n    volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [=] {\n        [weakSelf updateVolumeSlider];\n    });\n}\n\n// Updates the value of the output volume slider. Should only be called on the main thread because\n// it calls UI functions.\n- (void) updateVolumeSlider {\n    BGMAssert([[NSThread currentThread] isMainThread], \"updateVolumeSlider on non-main thread.\");\n\n    BGMAudioDevice bgmDevice = [audioDevices bgmDevice];\n\n    // BGMDevice should never return an error for these calls, so we just swallow any exceptions and\n    // give up. (That said, we do check mute last so that, if it did throw, it wouldn't affect the\n    // more important calls.)\n    BGMLogAndSwallowExceptions(\"BGMOutputVolumeMenuItem::updateVolumeSlider\", ([&] {\n        BOOL hasVolume = bgmDevice.HasSettableMasterVolume(kScope);\n\n        // If the device doesn't have a master volume control, we disable the slider and set it to\n        // full (or to zero, if muted).\n        volumeSlider.enabled = hasVolume;\n\n        if (hasVolume) {\n            // Set the slider to the current output volume. The slider values and volume values are\n            // both from 0 to 1, so we can use the volume as is.\n            volumeSlider.doubleValue =\n                bgmDevice.GetVolumeControlScalarValue(kScope, kMasterChannel);\n        } else {\n            volumeSlider.doubleValue = 1.0;\n        }\n\n        // Set the slider to zero if the device is muted.\n        if (bgmDevice.HasSettableMasterMute(kScope) &&\n            bgmDevice.GetMuteControlValue(kScope, kMasterChannel)) {\n            volumeSlider.doubleValue = 0.0;\n        }\n    }));\n}\n\n- (void) addOutputDeviceDataSourceListener {\n    // Create the block that updates deviceLabel when the output device's data source changes, e.g.\n    // from Internal Speakers to Headphones.\n    if (!updateLabelListenerBlock) {\n        BGMOutputVolumeMenuItem* __weak weakSelf = self;\n\n        updateLabelListenerBlock =\n            ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {\n                // The docs for AudioObjectPropertyListenerBlock say inAddresses will always contain\n                // at least one property the block is listening to, so there's no need to check it.\n                #pragma unused (inNumberAddresses, inAddresses)\n                [weakSelf updateLabelAndToolTip];\n            };\n    }\n\n    // Register the listener.\n    //\n    // Instead of swallowing exceptions, we could try again later, but I doubt it would be worth the\n    // effort. And the documentation doesn't actually explain what could cause this to fail.\n    BGMLogAndSwallowExceptions(\"BGMOutputVolumeMenuItem::addOutputDeviceDataSourceListener\", ([&] {\n        outputDevice.AddPropertyListenerBlock(\n            CAPropertyAddress(kAudioDevicePropertyDataSource, kScope),\n            dispatch_get_main_queue(),\n            updateLabelListenerBlock);\n    }));\n}\n\n- (void) removeOutputDeviceDataSourceListener {\n    BGMLogAndSwallowExceptions(\"BGMOutputVolumeMenuItem::removeOutputDeviceDataSourceListener\",\n                               ([&] {\n        // Technically, there's a race here in that the device could be removed after we check it\n        // exists, but before we try to remove the listener. We could check the error code of the\n        // exception and not log an error message if the code is kAudioHardwareBadObjectError or\n        // kAudioHardwareBadDeviceError, but it probably wouldn't be worth the effort.\n        //\n        // So for now the main reason for checking the device exists here is that it makes debug\n        // builds much less likely to crash here. (They crash/break when an error is logged so it\n        // will be noticed.)\n        if (CAHALAudioObject::ObjectExists(outputDevice)) {\n            outputDevice.RemovePropertyListenerBlock(\n                CAPropertyAddress(kAudioDevicePropertyDataSource, kScope),\n                dispatch_get_main_queue(),\n                updateLabelListenerBlock);\n        }\n    }));\n}\n\n- (void) outputDeviceDidChange {\n    dispatch_async(dispatch_get_main_queue(), ^{\n        // Remove the data source listener from the previous output device.\n        [self removeOutputDeviceDataSourceListener];\n\n        // Add it to the new output device.\n        outputDevice = audioDevices.outputDevice;\n        [self addOutputDeviceDataSourceListener];\n\n        // Update the label to use the name of the new output device.\n        [self updateLabelAndToolTip];\n\n        // Set the slider to the volume of the new device.\n        [self updateVolumeSlider];\n    });\n}\n\n// Sets the label to the output device's name or, if it has one, its current datasource. If it has a\n// datasource, the device's name is set as this menu item's tooltip. Falls back to a generic name if\n// the device returns an error when queried.\n- (void) updateLabelAndToolTip {\n    if (outputDevice.GetObjectID() == kAudioObjectUnknown) {\n        DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Output device unknown. Using the \"\n                 \"generic label.\");\n        self.toolTip = nil;\n        deviceLabel.stringValue = kGenericOutputDeviceName;\n    } else {\n        BOOL didSetLabel = NO;\n\n        DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Output device: %u\",\n                 outputDevice.GetObjectID());\n\n        try {\n            if (outputDevice.HasDataSourceControl(kScope, kMasterChannel)) {\n                DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting data source ID\");\n                // The device has datasources, so use the current datasource's name like macOS does.\n                UInt32 dataSourceID = outputDevice.GetCurrentDataSourceID(kScope, kMasterChannel);\n\n                DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: \"\n                         \"Getting name for data source %u\",\n                         dataSourceID);\n                deviceLabel.stringValue =\n                    (__bridge_transfer NSString*)outputDevice.CopyDataSourceNameForID(\n                        kScope, kMasterChannel, dataSourceID);\n\n                // So we know not to change the text if setting the tooltip fails.\n                didSetLabel = YES;\n\n                DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting device name\");\n                // Set the tooltip of the menu item (the container) rather than the label because\n                // menu items' tooltips will still appear when a different app is focused and, as\n                // far as I know, BGMApp should never be the foreground app.\n                self.toolTip = (__bridge_transfer NSString*)outputDevice.CopyName();\n            } else {\n                DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting device name\");\n                deviceLabel.stringValue = (__bridge_transfer NSString*)outputDevice.CopyName();\n                self.toolTip = nil;\n            }\n        } catch (const CAException& e) {\n            BGMLogException(e);\n\n            // The device returned an error, so set the label to a generic device name, since we\n            // don't want to leave it set to the previous device's name.\n            self.toolTip = nil;\n\n            if (!didSetLabel) {\n                deviceLabel.stringValue = kGenericOutputDeviceName;\n            }\n        }\n    }\n\n    DebugMsg(\"BGMOutputVolumeMenuItem::updateLabelAndToolTip: Label: '%s' Tooltip: '%s'\",\n             deviceLabel.stringValue.UTF8String,\n             self.toolTip.UTF8String);\n\n    // Take the label out of the accessibility hierarchy, which also moves the slider up a level.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000  // MAC_OS_X_VERSION_10_10\n    if ([deviceLabel.cell respondsToSelector:@selector(setAccessibilityElement:)]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        deviceLabel.cell.accessibilityElement = NO;\n#pragma clang diagnostic pop\n    }\n#endif\n}\n\n// Called when the user slides the slider.\n- (IBAction) sliderChanged:(NSSlider*)sender {\n    float newValue = sender.floatValue;\n\n    DebugMsg(\"BGMOutputVolumeMenuItem::sliderChanged: New value: %f\", newValue);\n\n    // Update BGMDevice's volume to the new value selected by the user.\n    try {\n        // The slider values and volume values are both from 0.0f to 1.0f, so we can use the slider\n        // value as is.\n        audioDevices.bgmDevice.SetVolumeControlScalarValue(kScope, kMasterChannel, newValue);\n\n        // Mute BGMDevice if they set the slider to zero, and unmute it for non-zero. Muting makes\n        // sure the audio doesn't play very quietly instead being completely silent. This matches\n        // the behaviour of the Volume menu built-in to macOS.\n        if (audioDevices.bgmDevice.HasMuteControl(kScope, kMasterChannel)) {\n            audioDevices.bgmDevice.SetMuteControlValue(kScope,\n                                                       kMasterChannel,\n                                                       (newValue < kSliderEpsilon));\n        }\n    } catch (const CAException& e) {\n        NSLog(@\"BGMOutputVolumeMenuItem::sliderChanged: Failed to set volume (%d)\", e.GetError());\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThrough.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThrough.cpp\n//  BGMApp\n//\n//  Copyright © 2016, 2017, 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMPlayThrough.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CAHALAudioSystemObject.h\"\n#include \"CAPropertyAddress.h\"\n\n// STL Includes\n#include <algorithm>  // For std::max\n\n// System Includes\n#include <mach/mach_init.h>\n#include <mach/mach_time.h>\n#include <mach/task.h>\n\n\n// The number of IO cycles (roughly) to wait for our IOProcs to stop themselves before assuming something\n// went wrong. If that happens, we try to stop them from a non-IO thread and continue anyway. \nstatic const UInt32 kStopIOProcTimeoutInIOCycles = 600;\n\n#pragma mark Construction/Destruction\n\nBGMPlayThrough::BGMPlayThrough(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)\n:\n    mInputDevice(inInputDevice),\n    mOutputDevice(inOutputDevice)\n{\n    Init(inInputDevice, inOutputDevice);\n}\n\nBGMPlayThrough::~BGMPlayThrough()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n\n    BGMLogAndSwallowExceptionsMsg(\"BGMPlayThrough::~BGMPlayThrough\", \"Deactivate\", [&]() {\n        Deactivate();\n    });\n\n    // If one of the IOProcs failed to stop, CoreAudio could (at least in theory) still call it\n    // after this point. This isn't a solution, but calling DeallocateBuffer instead of letting it\n    // deallocate itself should at least make the error less likely to cause a segfault, since\n    // DeallocateBuffer takes the buffer locks and sets mBuffer to null.\n    //\n    // TODO: It probably wouldn't be too hard to fix this properly by giving the IOProcs weak refs\n    //       to the BGMPlayThrough object instead of raw pointers.\n    DeallocateBuffer();\n    \n    if(mOutputDeviceIOProcSemaphore != SEMAPHORE_NULL)\n    {\n        kern_return_t theError = semaphore_destroy(mach_task_self(), mOutputDeviceIOProcSemaphore);\n        BGM_Utils::LogIfMachError(\"BGMPlayThrough::~BGMPlayThrough\", \"semaphore_destroy\", theError);\n    }\n}\n\nvoid    BGMPlayThrough::Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)\n{\n    BGMAssert(mInputDeviceIOProcState.is_lock_free(),\n              \"BGMPlayThrough::BGMPlayThrough: !mInputDeviceIOProcState.is_lock_free()\");\n    BGMAssert(mOutputDeviceIOProcState.is_lock_free(),\n              \"BGMPlayThrough::BGMPlayThrough: !mOutputDeviceIOProcState.is_lock_free()\");\n    BGMAssert(!mActive, \"BGMPlayThrough::BGMPlayThrough: Can't init while active.\");\n    \n    mInputDevice = inInputDevice;\n    mOutputDevice = inOutputDevice;\n    \n    AllocateBuffer();\n    \n    try\n    {\n        // Init the semaphore for the output IOProc.\n        if(mOutputDeviceIOProcSemaphore == SEMAPHORE_NULL)\n        {\n            kern_return_t theError = semaphore_create(mach_task_self(), &mOutputDeviceIOProcSemaphore, SYNC_POLICY_FIFO, 0);\n            BGM_Utils::ThrowIfMachError(\"BGMPlayThrough::BGMPlayThrough\", \"semaphore_create\", theError);\n            \n            ThrowIf(mOutputDeviceIOProcSemaphore == SEMAPHORE_NULL,\n                    CAException(kAudioHardwareUnspecifiedError),\n                    \"BGMPlayThrough::BGMPlayThrough: Could not create semaphore\");\n        }\n    }\n    catch (...)\n    {\n        // Clean up.\n        DeallocateBuffer();\n        throw;\n    }\n}\n\nvoid    BGMPlayThrough::Activate()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    if(!mActive)\n    {\n        DebugMsg(\"BGMPlayThrough::Activate: Activating playthrough\");\n        \n        CreateIOProcIDs();\n        \n        mActive = true;\n        \n        // TODO: This code (the next two blocks) should be in BGMDeviceControlSync.\n        \n        // Set BGMDevice's sample rate to match the output device.\n        try\n        {\n            Float64 outputSampleRate = mOutputDevice.GetNominalSampleRate();\n            mInputDevice.SetNominalSampleRate(outputSampleRate);\n        }\n        catch (CAException e)\n        {\n            LogWarning(\"BGMPlayThrough::Activate: Failed to sync device sample rates. Error: %d\",\n                       e.GetError());\n        }\n        \n        // Set BGMDevice's IO buffer size to match the output device.\n        try\n        {\n            UInt32 outputBufferSize = mOutputDevice.GetIOBufferSize();\n            mInputDevice.SetIOBufferSize(outputBufferSize);\n        }\n        catch (CAException e)\n        {\n            LogWarning(\"BGMPlayThrough::Activate: Failed to sync device buffer sizes. Error: %d\",\n                       e.GetError());\n        }\n        \n        DebugMsg(\"BGMPlayThrough::Activate: Registering for notifications from BGMDevice.\");\n        \n        mInputDevice.AddPropertyListener(CAPropertyAddress(kAudioDevicePropertyDeviceIsRunning),\n                                         &BGMPlayThrough::BGMDeviceListenerProc,\n                                         this);\n        mInputDevice.AddPropertyListener(CAPropertyAddress(kAudioDeviceProcessorOverload),\n                                         &BGMPlayThrough::BGMDeviceListenerProc,\n                                         this);\n        \n        bool isBGMDevice = true;\n        CATry\n        isBGMDevice = mInputDevice.IsBGMDeviceInstance();\n        CACatch\n        \n        if(isBGMDevice)\n        {\n            mInputDevice.AddPropertyListener(kBGMRunningSomewhereOtherThanBGMAppAddress,\n                                             &BGMPlayThrough::BGMDeviceListenerProc,\n                                             this);\n        }\n        else\n        {\n            LogWarning(\"BGMPlayThrough::Activate: Playthrough activated with an output device other \"\n                       \"than BGMDevice. This hasn't been tested and is almost definitely a bug.\");\n            BGMAssert(false, \"BGMPlayThrough::Activate: !mInputDevice.IsBGMDeviceInstance()\");\n        }\n    }\n}\n\nvoid    BGMPlayThrough::Deactivate()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    if(mActive)\n    {\n        DebugMsg(\"BGMPlayThrough::Deactivate: Deactivating playthrough\");\n        \n        bool inputDeviceIsBGMDevice = true;\n\n        CATry\n        inputDeviceIsBGMDevice = mInputDevice.IsBGMDeviceInstance();\n        CACatch\n        \n        // Unregister notification listeners.\n        if(inputDeviceIsBGMDevice)\n        {\n            // There's not much we can do if these calls throw. The docs for AudioObjectRemovePropertyListener\n            // just say that means it failed.\n            BGMLogAndSwallowExceptions(\"BGMPlayThrough::Deactivate\", [&] {\n                mInputDevice.RemovePropertyListener(CAPropertyAddress(kAudioDevicePropertyDeviceIsRunning),\n                                                    &BGMPlayThrough::BGMDeviceListenerProc,\n                                                    this);\n            });\n            \n            BGMLogAndSwallowExceptions(\"BGMPlayThrough::Deactivate\", [&] {\n                mInputDevice.RemovePropertyListener(CAPropertyAddress(kAudioDeviceProcessorOverload),\n                                                    &BGMPlayThrough::BGMDeviceListenerProc,\n                                                    this);\n            });\n            \n            BGMLogAndSwallowExceptions(\"BGMPlayThrough::Deactivate\", [&] {\n                mInputDevice.RemovePropertyListener(kBGMRunningSomewhereOtherThanBGMAppAddress,\n                                                    &BGMPlayThrough::BGMDeviceListenerProc,\n                                                    this);\n            });\n        }\n\n        BGMLogAndSwallowExceptions(\"BGMPlayThrough::Deactivate\", [&] {\n            Stop();\n        });\n        \n        BGMLogAndSwallowExceptions(\"BGMPlayThrough::Deactivate\", [&] {\n            DestroyIOProcIDs();\n        });\n        \n        mActive = false;\n    }\n}\n\nvoid    BGMPlayThrough::AllocateBuffer()\n{\n    // Allocate the ring buffer that will hold the data passing between the devices\n    UInt32 numberStreams = 1;\n    AudioStreamBasicDescription outputFormat[1];\n    mOutputDevice.GetCurrentVirtualFormats(false, numberStreams, outputFormat);\n    \n    if(numberStreams < 1)\n    {\n        Throw(CAException(kAudioHardwareUnsupportedOperationError));\n    }\n    \n    // Need to lock the buffer mutexes to make sure the IOProcs aren't accessing it. The order is\n    // important here. We always lock them in the same order to prevent deadlocks.\n    CAMutex::Locker lockerInput(mBufferInputMutex);\n    CAMutex::Locker lockerOutput(mBufferOutputMutex);\n\n    mBuffer = std::unique_ptr<CARingBuffer>(new CARingBuffer);\n\n    // The calculation for the size of the buffer is from Apple's CAPlayThrough.cpp sample code\n    //\n    // TODO: Test playthrough with hardware with more than 2 channels per frame, a sample (virtual) format other than\n    //       32-bit floats and/or an IO buffer size other than 512 frames\n    mBuffer->Allocate(outputFormat[0].mChannelsPerFrame,\n                      outputFormat[0].mBytesPerFrame,\n                      mOutputDevice.GetIOBufferSize() * 20);\n}\n\nvoid    BGMPlayThrough::DeallocateBuffer()\n{\n    // Need to lock the buffer mutexes to make sure the IOProcs aren't accessing it. The order is\n    // important here. We always lock them in the same order to prevent deadlocks.\n    CAMutex::Locker lockerInput(mBufferInputMutex);\n    CAMutex::Locker lockerOutput(mBufferOutputMutex);\n    mBuffer = nullptr;  // Note that the buffer's destructor will deallocate it.\n}\n\nvoid    BGMPlayThrough::CreateIOProcIDs()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    BGMAssert(!mPlayingThrough,\n              \"BGMPlayThrough::CreateIOProcIDs: Tried to create IOProcs when playthrough was already running\");\n    BGMAssert(mInputDeviceIOProcID == nullptr,\n              \"BGMPlayThrough::CreateIOProcIDs: mInputDeviceIOProcID must be destroyed first.\");\n    BGMAssert(mOutputDeviceIOProcID == nullptr,\n              \"BGMPlayThrough::CreateIOProcIDs: mOutputDeviceIOProcID must be destroyed first.\");\n    BGMAssert(CheckIOProcsAreStopped(),\n              \"BGMPlayThrough::CreateIOProcIDs: IOProcs not ready.\");\n    \n    const bool inDeviceAlive = mInputDevice.IsAlive();\n    const bool outDeviceAlive = mOutputDevice.IsAlive();\n    \n    if(inDeviceAlive && outDeviceAlive)\n    {\n        DebugMsg(\"BGMPlayThrough::CreateIOProcIDs: Creating IOProcs\");\n    \n        try\n        {\n            mInputDeviceIOProcID = mInputDevice.CreateIOProcID(&BGMPlayThrough::InputDeviceIOProc, this);\n        }\n        catch(CAException e)\n        {\n            LogWarning(\"BGMPlayThrough::CreateIOProcIDs: Failed to create input IOProc ID. mInputDevice = %d\",\n                       mInputDevice.GetObjectID());\n            throw;\n        }\n        \n        try\n        {\n            mOutputDeviceIOProcID = mOutputDevice.CreateIOProcID(&BGMPlayThrough::OutputDeviceIOProc, this);\n        }\n        catch(CAException e)\n        {\n            LogWarning(\"BGMPlayThrough::CreateIOProcIDs: Failed to create output IOProc ID. mOutputDevice = %d\",\n                       mOutputDevice.GetObjectID());\n            DestroyIOProcIDs(); // Clean up.\n            throw;\n        }\n\n        if(mInputDeviceIOProcID == nullptr || mOutputDeviceIOProcID == nullptr)\n        {\n            // Should never happen if CAHALAudioDevice::CreateIOProcID didn't throw.\n            LogError(\"BGMPlayThrough::CreateIOProcIDs: Null IOProc ID returned by CreateIOProcID\");\n            throw new CAException(kAudioHardwareIllegalOperationError);\n        }\n        \n        // TODO: Try using SetIOCycleUsage to reduce latency? Our IOProcs don't really do anything except copy a small\n        //       buffer. According to this, Jack OS X considered it:\n        //       https://lists.apple.com/archives/coreaudio-api/2008/Mar/msg00043.html but from a quick look at their\n        //       code, I don't think they ended up using it.\n        // mInputDevice->SetIOCycleUsage(0.01f);\n        // mOutputDevice->SetIOCycleUsage(0.01f);\n    }\n    else\n    {\n        LogWarning(\"BGMPlayThrough::CreateIOProcIDs: Failed to create IOProcs.%s%s\",\n                   (inDeviceAlive ? \"\" : \" Input device not alive.\"),\n                   (outDeviceAlive ? \"\" : \" Output device not alive.\"));\n        throw new CAException(kAudioHardwareIllegalOperationError);\n    }\n}\n\nvoid    BGMPlayThrough::DestroyIOProcIDs()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n\n    // In release builds, we still try to destroy the IDs if the IOProcs are running, hoping they just haven't been\n    // stopped quite yet. The docs for AudioDeviceDestroyIOProcID don't say not to do that, but it could cause races\n    // if one really is still running so it isn't ideal.\n    BGMAssert(CheckIOProcsAreStopped(), \"BGMPlayThrough::DestroyIOProcIDs: IOProcs not ready.\");\n\n    DebugMsg(\"BGMPlayThrough::DestroyIOProcIDs: Destroying IOProcs\");\n\n    auto destroy = [](BGMAudioDevice& device, const char* deviceName, AudioDeviceIOProcID& ioProcID) {\n#if !DEBUG\n    #pragma unused (deviceName)\n#endif\n        if(ioProcID != nullptr)\n        {\n            try\n            {\n                device.DestroyIOProcID(ioProcID);\n            }\n            catch(CAException e)\n            {\n                if((e.GetError() == kAudioHardwareBadDeviceError) || (e.GetError() == kAudioHardwareBadObjectError))\n                {\n                    // This means the IOProc IDs will have already been destroyed, so there's nothing to do.\n                    DebugMsg(\"BGMPlayThrough::DestroyIOProcIDs: Didn't destroy IOProc ID for %s device because \"\n                             \"it's not connected anymore. deviceID = %d\",\n                             deviceName,\n                             device.GetObjectID());\n                }\n                else\n                {\n                    ioProcID = nullptr;\n                    throw;\n                }\n            }\n            \n            ioProcID = nullptr;\n        }\n    };\n    \n    destroy(mInputDevice, \"input\", mInputDeviceIOProcID);\n    destroy(mOutputDevice, \"output\", mOutputDeviceIOProcID);\n}\n\nbool    BGMPlayThrough::CheckIOProcsAreStopped() const noexcept\n{\n    bool statesOK = true;\n    \n    if(mInputDeviceIOProcState != IOState::Stopped)\n    {\n        LogWarning(\"BGMPlayThrough::CheckIOProcsAreStopped: Input IOProc not stopped. mInputDeviceIOProcState = %d\",\n                   mInputDeviceIOProcState.load());\n        statesOK = false;\n    }\n    \n    if(mOutputDeviceIOProcState != IOState::Stopped)\n    {\n        LogWarning(\"BGMPlayThrough::CheckIOProcsAreStopped: Output IOProc not stopped. mOutputDeviceIOProcState = %d\",\n                   mOutputDeviceIOProcState.load());\n        statesOK = false;\n    }\n    \n    return statesOK;\n}\n\nvoid    BGMPlayThrough::SetDevices(const BGMAudioDevice* __nullable inInputDevice,\n                                   const BGMAudioDevice* __nullable inOutputDevice)\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    bool wasActive = mActive;\n    bool wasPlayingThrough = mPlayingThrough;\n    \n    if(wasPlayingThrough)\n    {\n        BGMAssert(wasActive, \"BGMPlayThrough::SetOutputDevice: wasPlayingThrough && !wasActive\");  // Sanity check.\n    }\n    \n    Deactivate();\n    \n    mInputDevice = inInputDevice ? *inInputDevice : mInputDevice;\n    mOutputDevice = inOutputDevice ? *inOutputDevice : mOutputDevice;\n    \n    // Resize and reallocate the buffer if necessary.\n    Init(mInputDevice, mOutputDevice);\n    \n    if(wasActive)\n    {\n        Activate();\n    }\n    \n    if(wasPlayingThrough)\n    {\n        Start();\n    }\n}\n\n#pragma mark Control Playthrough\n\nvoid    BGMPlayThrough::Start()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    if(mPlayingThrough)\n    {\n        DebugMsg(\"BGMPlayThrough::Start: Already started/starting.\");\n\n        if(mOutputDeviceIOProcState == IOState::Running)\n        {\n            ReleaseThreadsWaitingForOutputToStart();\n        }\n\n        return;\n    }\n    \n    if(!mInputDevice.IsAlive() || !mOutputDevice.IsAlive())\n    {\n        LogError(\"BGMPlayThrough::Start: %s %s\",\n                 mInputDevice.IsAlive() ? \"\" : \"!mInputDevice\",\n                 mOutputDevice.IsAlive() ? \"\" : \"!mOutputDevice\");\n        \n        ReleaseThreadsWaitingForOutputToStart();\n        \n        throw CAException(kAudioHardwareBadDeviceError);\n    }\n    \n    // Set up IOProcs and listeners if they haven't been already.\n    Activate();\n    \n    BGMAssert((mInputDeviceIOProcID != nullptr) && (mOutputDeviceIOProcID != nullptr),\n              \"BGMPlayThrough::Start: Null IOProc ID\");\n    \n    if((mInputDeviceIOProcState != IOState::Stopped) || (mOutputDeviceIOProcState != IOState::Stopped))\n    {\n        LogWarning(\"BGMPlayThrough::Start: IOProc(s) not ready. Trying to start anyway. %s%d %s%d\",\n                   \"mInputDeviceIOProcState = \", mInputDeviceIOProcState.load(),\n                   \"mOutputDeviceIOProcState = \", mOutputDeviceIOProcState.load());\n    }\n    \n    DebugMsg(\"BGMPlayThrough::Start: Starting playthrough\");\n    \n    // Start our IOProcs.\n    try\n    {\n        mInputDeviceIOProcState = IOState::Starting;\n        mInputDevice.StartIOProc(mInputDeviceIOProcID);\n    \n        mOutputDeviceIOProcState = IOState::Starting;\n        mOutputDevice.StartIOProc(mOutputDeviceIOProcID);\n    }\n    catch(CAException e)\n    {\n        ReleaseThreadsWaitingForOutputToStart();\n        \n        // Log an error message.\n        OSStatus err = e.GetError();\n        char err4CC[5] = CA4CCToCString(err);\n        LogError(\"BGMPlayThrough::Start: Failed to start %s device. Error: %d (%s)\",\n                 (mOutputDeviceIOProcState == IOState::Starting ? \"output\" : \"input\"),\n                 err,\n                 err4CC);\n        \n        // Try to stop the IOProcs in case StartIOProc failed because one of our IOProc was already\n        // running. I don't know if it actually does fail in that case, but the documentation\n        // doesn't say so it's safer to assume it could.\n        CATry\n        mInputDevice.StopIOProc(mInputDeviceIOProcID);\n        CACatch\n        CATry\n        mOutputDevice.StopIOProc(mOutputDeviceIOProcID);\n        CACatch\n        \n        mInputDeviceIOProcState = IOState::Stopped;\n        mOutputDeviceIOProcState = IOState::Stopped;\n        \n        throw;\n    }\n    \n    mPlayingThrough = true;\n}\n\nOSStatus    BGMPlayThrough::WaitForOutputDeviceToStart() noexcept\n{\n    // Check for errors.\n    //\n    // Technically we should take the state mutex here, but that could cause deadlocks because\n    // BGM_Device::StartIO (in BGMDriver) blocks on this function (via XPC). Other BGMPlayThrough\n    // functions make requests to BGMDriver while holding the state mutex, usually to get/set\n    // properties, but the HAL will block those requests until BGM_Device::StartIO returns.\n    try\n    {\n        if(!mActive)\n        {\n            LogError(\"BGMPlayThrough::WaitForOutputDeviceToStart: !mActive\");\n            return kAudioHardwareNotRunningError;\n        }\n        \n        if(!mOutputDevice.IsAlive())\n        {\n            LogError(\"BGMPlayThrough::WaitForOutputDeviceToStart: Device not alive\");\n            return kAudioHardwareBadDeviceError;\n        }\n    }\n    catch(const CAException& e)\n    {\n        BGMLogException(e);\n        return e.GetError();\n    }\n    \n    const IOState initialState = mOutputDeviceIOProcState;\n    const UInt64 startedAt = mach_absolute_time();\n\n    if(initialState == IOState::Running)\n    {\n        // Return early because the output device is already running.\n        return kAudioHardwareNoError;\n    }\n    else if(initialState != IOState::Starting)\n    {\n        // Warn if we haven't been told to start the output device yet. Usually means we\n        // haven't received a kAudioDevicePropertyDeviceIsRunning notification yet, which can\n        // happen. It's most common when the user changes the output device while IO is\n        // running.\n        LogWarning(\"BGMPlayThrough::WaitForOutputDeviceToStart: Device not starting\");\n        \n        return kDeviceNotStarting;\n    }\n\n    // Wait for our output IOProc to start. mOutputDeviceIOProcSemaphore is reset to 0\n    // (semaphore_signal_all) when our IOProc is running on the output device.\n    //\n    // This does mean that we won't have any data the first time our IOProc is called, but I\n    // don't know any way to wait until just before that point. (The device's IsRunning property\n    // changes immediately after we call StartIOProc.)\n    //\n    // We check mOutputDeviceIOProcState every 200ms as a fault tolerance mechanism. (Though,\n    // I'm not completely sure it's impossible to miss the signal from the IOProc because of a\n    // spurious wake up, so it might actually be necessary.)\n    DebugMsg(\"BGMPlayThrough::WaitForOutputDeviceToStart: Waiting.\");\n    \n    kern_return_t theError;\n    IOState state;\n    UInt64 waitedNsec = 0;\n    mach_timebase_info_data_t info;\n    mach_timebase_info(&info);\n    \n    do\n    {\n        BGMAssert(mOutputDeviceIOProcSemaphore != SEMAPHORE_NULL,\n                  \"BGMPlayThrough::WaitForOutputDeviceToStart: !mOutputDeviceIOProcSemaphore\");\n        \n        theError = semaphore_timedwait(mOutputDeviceIOProcSemaphore,\n                                       (mach_timespec_t){ 0, 200 * NSEC_PER_MSEC });\n        \n        // Update the total time we've been waiting and the output device's state.\n        waitedNsec = (mach_absolute_time() - startedAt) * info.numer / info.denom;\n        state = mOutputDeviceIOProcState;\n    }\n    while((theError != KERN_SUCCESS) &&         // Signalled from the IOProc.\n          (state == IOState::Starting) &&       // IO state changed.\n          (waitedNsec < kStartIOTimeoutNsec));  // Timed out.\n\n    if(BGMDebugLoggingIsEnabled())\n    {\n        UInt64 startedBy = mach_absolute_time();\n\n        struct mach_timebase_info baseInfo = { 0, 0 };\n        mach_timebase_info(&baseInfo);\n        UInt64 base = baseInfo.numer / baseInfo.denom;\n\n        DebugMsg(\"BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f \"\n                 \"ms after entering WaitForOutputDeviceToStart.\",\n                 static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,\n                 static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);\n    }\n\n    // Figure out which error code to return.\n    switch (theError)\n    {\n        case KERN_SUCCESS:              // Signalled from the IOProc.\n            return kAudioHardwareNoError;\n            \n                                        // IO state changed or we timed out after\n        case KERN_OPERATION_TIMED_OUT:  //  - semaphore_timedwait timed out, or\n        case KERN_ABORTED:              //  - a spurious wake-up.\n            return (state == IOState::Running) ? kAudioHardwareNoError : kAudioHardwareNotRunningError;\n            \n        default:\n            BGM_Utils::LogIfMachError(\"BGMPlayThrough::WaitForOutputDeviceToStart\",\n                                      \"semaphore_timedwait\",\n                                      theError);\n            return kAudioHardwareUnspecifiedError;\n    }\n}\n\n// Release any threads waiting for the output device to start. This function doesn't take mStateMutex\n// because it gets called on the IO thread, which is realtime priority.\nvoid    BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart()\n{\n    if(mActive)\n    {\n        semaphore_t semaphore = mOutputDeviceIOProcSemaphore;\n        \n        if(semaphore != SEMAPHORE_NULL)\n        {\n            mRTLogger.LogReleasingWaitingThreads();\n\n            kern_return_t theError = semaphore_signal_all(semaphore);\n            mRTLogger.LogIfMachError_ReleaseWaitingThreadsSignal(theError);\n        }\n    }\n}\n\nOSStatus    BGMPlayThrough::Stop()\n{\n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    // TODO: Tell the waiting threads what happened so they can return an error?\n    ReleaseThreadsWaitingForOutputToStart();\n    \n    if(mActive && mPlayingThrough)\n    {\n        DebugMsg(\"BGMPlayThrough::Stop: Stopping playthrough\");\n        \n        bool inputDeviceAlive = false;\n        bool outputDeviceAlive = false;\n        \n        CATry\n        inputDeviceAlive = CAHALAudioObject::ObjectExists(mInputDevice) && mInputDevice.IsAlive();\n        CACatch\n        \n        CATry\n        outputDeviceAlive =\n            CAHALAudioObject::ObjectExists(mOutputDevice) && mOutputDevice.IsAlive();\n        CACatch\n\n        mInputDeviceIOProcState = inputDeviceAlive ? IOState::Stopping : IOState::Stopped;\n        mOutputDeviceIOProcState = outputDeviceAlive ? IOState::Stopping : IOState::Stopped;\n        \n        // Wait for the IOProcs to stop themselves. This is so the IOProcs don't get called after the BGMPlayThrough instance\n        // (pointed to by the client data they get from the HAL) is deallocated.\n        //\n        // From Jeff Moore on the Core Audio mailing list:\n        //     Note that there is no guarantee about how many times your IOProc might get called after AudioDeviceStop() returns\n        //     when you make the call from outside of your IOProc. However, if you call AudioDeviceStop() from inside your IOProc,\n        //     you do get the guarantee that your IOProc will not get called again after the IOProc has returned.\n        UInt64 totalWaitNs = 0;\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&]() {\n            Float64 expectedInputCycleNs = 0;\n\n            if(inputDeviceAlive)\n            {\n                expectedInputCycleNs =\n                    mInputDevice.GetIOBufferSize() * (1 / mInputDevice.GetNominalSampleRate()) *\n                            NSEC_PER_SEC;\n            }\n\n            Float64 expectedOutputCycleNs = 0;\n\n            if(outputDeviceAlive)\n            {\n                expectedOutputCycleNs =\n                    mOutputDevice.GetIOBufferSize() * (1 / mOutputDevice.GetNominalSampleRate()) *\n                            NSEC_PER_SEC;\n            }\n\n            UInt64 expectedMaxCycleNs =\n                static_cast<UInt64>(std::max(expectedInputCycleNs, expectedOutputCycleNs));\n\n            while((mInputDeviceIOProcState == IOState::Stopping || mOutputDeviceIOProcState == IOState::Stopping)\n                  && (totalWaitNs < kStopIOProcTimeoutInIOCycles * expectedMaxCycleNs))\n            {\n                // TODO: If playthrough is started again while we're waiting in this loop we could drop frames. Wait on a\n                //       semaphore instead of sleeping? That way Start() could also signal it, before waiting on the state mutex,\n                //       as a way of cancelling the stop operation.\n                struct timespec rmtp;\n                int err = nanosleep((const struct timespec[]){{0, NSEC_PER_MSEC}}, &rmtp);\n                totalWaitNs += NSEC_PER_MSEC - (err == -1 ? rmtp.tv_nsec : 0);\n            }\n        });\n        \n        // Clean up if the IOProcs didn't stop themselves\n        if(mInputDeviceIOProcState == IOState::Stopping && mInputDeviceIOProcID != nullptr)\n        {\n            LogError(\"BGMPlayThrough::Stop: The input IOProc didn't stop itself in time. Stopping \"\n                     \"it from outside of the IO thread.\");\n            \n            BGMLogUnexpectedExceptions(\"BGMPlayThrough::Stop\", [&]() {\n                mInputDevice.StopIOProc(mInputDeviceIOProcID);\n            });\n\n            mInputDeviceIOProcState = IOState::Stopped;\n        }\n        \n        if(mOutputDeviceIOProcState == IOState::Stopping && mOutputDeviceIOProcID != nullptr)\n        {\n            LogError(\"BGMPlayThrough::Stop: The output IOProc didn't stop itself in time. Stopping \"\n                     \"it from outside of the IO thread.\");\n            \n            BGMLogUnexpectedExceptions(\"BGMPlayThrough::Stop\", [&]() {\n                mOutputDevice.StopIOProc(mOutputDeviceIOProcID);\n            });\n\n            mOutputDeviceIOProcState = IOState::Stopped;\n        }\n        \n        mPlayingThrough = false;\n    }\n    \n    mFirstInputSampleTime = -1;\n    mLastInputSampleTime = -1;\n    mLastOutputSampleTime = -1;\n    \n    return noErr; // TODO: Why does this return anything and why always noErr?\n}\n\nvoid    BGMPlayThrough::StopIfIdle()\n{\n    // To save CPU time, we stop playthrough when no clients are doing IO. This should reduce the coreaudiod and BGMApp\n    // processes' idle CPU use to virtually none. If this isn't working for you, a client might be running IO without\n    // being audible. VLC does that when you have a file paused, for example.\n    \n    CAMutex::Locker stateLocker(mStateMutex);\n    \n    BGMAssert(mInputDevice.IsBGMDeviceInstance(),\n              \"BGMDevice not set as input device. StopIfIdle can't tell if other devices are idle.\");\n    \n    if(!IsRunningSomewhereOtherThanBGMApp(mInputDevice))\n    {\n        mLastNotifiedIOStoppedOnBGMDevice = mach_absolute_time();\n        \n        // Wait a bit before stopping playthrough.\n        //\n        // This keeps us from starting and stopping IO too rapidly, which wastes CPU, and gives BGMDriver time to update\n        // kAudioDeviceCustomPropertyDeviceAudibleState, which it can only do while IO is running. (The wait duration is\n        // more or less arbitrary, except that it has to be longer than kDeviceAudibleStateMinChangedFramesForUpdate.)\n\n        // 1 / sample rate = seconds per frame\n        Float64 nsecPerFrame = (1.0 / mInputDevice.GetNominalSampleRate()) * NSEC_PER_SEC;\n        UInt64 waitNsec = static_cast<UInt64>(20 * kDeviceAudibleStateMinChangedFramesForUpdate * nsecPerFrame);\n        UInt64 queuedAt = mLastNotifiedIOStoppedOnBGMDevice;\n        \n        DebugMsg(\"BGMPlayThrough::StopIfIdle: Will dispatch stop-if-idle block in %llu ns. %s%llu\",\n                 waitNsec,\n                 \"queuedAt=\", queuedAt);\n        \n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, waitNsec),\n                       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),\n                       ^{\n                           // Check the BGMPlayThrough instance hasn't been destructed since it queued this block\n                           if(mActive)\n                           {\n                               // The \"2\" is just to avoid shadowing the other locker\n                               CAMutex::Locker stateLocker2(mStateMutex);\n                               \n                               // Don't stop playthrough if IO has started running again or if\n                               // kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp has changed since\n                               // this block was queued\n                               if(mPlayingThrough\n                                  && !IsRunningSomewhereOtherThanBGMApp(mInputDevice)\n                                  && queuedAt == mLastNotifiedIOStoppedOnBGMDevice)\n                               {\n                                   DebugMsg(\"BGMPlayThrough::StopIfIdle: BGMDevice is only running IO for BGMApp. \"\n                                            \"Stopping playthrough.\");\n                                   Stop();\n                               }\n                           }\n                       });\n    }\n}\n\n#pragma mark BGMDevice Listener\n\n// TODO: Listen for changes to the sample rate and IO buffer size of the output device and update the input device to match\n\n// static\nOSStatus    BGMPlayThrough::BGMDeviceListenerProc(AudioObjectID inObjectID,\n                                                  UInt32 inNumberAddresses,\n                                                  const AudioObjectPropertyAddress* __nonnull inAddresses,\n                                                  void* __nullable inClientData)\n{\n    // refCon (reference context) is the instance that registered the listener proc\n    BGMPlayThrough* refCon = static_cast<BGMPlayThrough*>(inClientData);\n    \n    // If the input device isn't BGMDevice, this listener proc shouldn't be registered\n    ThrowIf(inObjectID != refCon->mInputDevice.GetObjectID(),\n            CAException(kAudioHardwareBadObjectError),\n            \"BGMPlayThrough::BGMDeviceListenerProc: notified about audio object other than BGMDevice\");\n    \n    for(int i = 0; i < inNumberAddresses; i++)\n    {\n        switch(inAddresses[i].mSelector)\n        {\n            case kAudioDeviceProcessorOverload:\n                // These warnings are common when you use the UI if you're running a debug build or have \"Debug executable\"\n                // checked. You shouldn't be seeing them otherwise.\n                DebugMsg(\"BGMPlayThrough::BGMDeviceListenerProc: WARNING! Got kAudioDeviceProcessorOverload notification\");\n                LogWarning(\"Background Music: CPU overload reported\\n\");\n                break;\n                \n            // Start playthrough when a client starts IO on BGMDevice and stop when BGMApp (i.e. playthrough itself) is\n            // the only client left doing IO.\n            //\n            // These cases are dispatched to avoid causing deadlocks by triggering one of the following notifications in\n            // the process of handling one. Deadlocks could happen if these were handled synchronously when:\n            //     - the first BGMDeviceListenerProc call takes the state mutex, then requests some data from the HAL and\n            //       waits for it to return,\n            //     - the request triggers the HAL to send notifications, which it sends on a different thread,\n            //     - the HAL waits for the second BGMDeviceListenerProc call to return before it returns the data\n            //       requested by the first BGMDeviceListenerProc call, and\n            //     - the second BGMDeviceListenerProc call waits for the first to unlock the state mutex.\n                \n            case kAudioDevicePropertyDeviceIsRunning:  // Received on the IO thread before our IOProc is called\n                HandleBGMDeviceIsRunning(refCon);\n                break;\n                \n            case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:\n                HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(refCon);\n                break;\n                \n            default:\n                // We might get properties we didn't ask for, so we just ignore them.\n                break;\n        }\n    }\n    \n    // From AudioHardware.h: \"The return value is currently unused and should always be 0.\"\n    return 0;\n}\n\n// static\nvoid    BGMPlayThrough::HandleBGMDeviceIsRunning(BGMPlayThrough* refCon)\n{\n    DebugMsg(\"BGMPlayThrough::HandleBGMDeviceIsRunning: Got notification\");\n    \n    // This is dispatched because it can block and\n    //   - we might be on a real-time thread, or\n    //   - BGMXPCListener::startPlayThroughSyncWithReply might get called on the same thread just\n    //     before this and time out waiting for this to run.\n    //\n    // TODO: We should find a way to do this without dispatching because dispatching isn't actually\n    //       real-time safe.\n    dispatch_async(BGMGetDispatchQueue_PriorityUserInteractive(), ^{\n        if(refCon->mActive)\n        {\n            CAMutex::Locker stateLocker(refCon->mStateMutex);\n            \n            // Set to true initially because if we fail to get this property from BGMDevice we want to\n            // try to start playthrough anyway.\n            bool isRunningSomewhereOtherThanBGMApp = true;\n            \n            BGMLogAndSwallowExceptions(\"HandleBGMDeviceIsRunning\", [&]() {\n                // IsRunning doesn't always return true when IO is starting. Using\n                // RunningSomewhereOtherThanBGMApp instead seems to be working so far.\n                isRunningSomewhereOtherThanBGMApp =\n                    IsRunningSomewhereOtherThanBGMApp(refCon->mInputDevice);\n            });\n\n            DebugMsg(\"BGMPlayThrough::HandleBGMDeviceIsRunning: \"\n                     \"BGMDevice is %srunning somewhere other than BGMApp\",\n                     isRunningSomewhereOtherThanBGMApp ? \"\" : \"not \");\n            \n            if(isRunningSomewhereOtherThanBGMApp)\n            {\n                refCon->mToldOutputDeviceToStartAt = mach_absolute_time();\n\n                // TODO: Handle expected exceptions (mostly CAExceptions from PublicUtility classes) in Start.\n                //       For any that can't be handled sensibly in Start, catch them here and retry a few\n                //       times (with a very short delay) before handling them by showing an unobtrusive error\n                //       message or something. Then try a different device or just set the system device back\n                //       to the real device.\n                BGMLogAndSwallowExceptions(\"HandleBGMDeviceIsRunning\", [&refCon]() {\n                    refCon->Start();\n                });\n            }\n        }\n    });\n}\n\n// static\nvoid    BGMPlayThrough::HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlayThrough* refCon)\n{\n    DebugMsg(\"BGMPlayThrough::HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp: Got notification\");\n    \n    // These notifications don't need to be handled quickly, so we can always dispatch.\n    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{\n        // TODO: Handle expected exceptions (mostly CAExceptions from PublicUtility classes) in StopIfIdle.\n        BGMLogUnexpectedExceptions(\"HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp\", [&refCon]() {\n            if(refCon->mActive)\n            {\n                refCon->StopIfIdle();\n            }\n        });\n    });\n}\n\n// static\nbool    BGMPlayThrough::IsRunningSomewhereOtherThanBGMApp(const BGMAudioDevice& inBGMDevice)\n{\n    auto type = inBGMDevice.GetPropertyData_CFType(kBGMRunningSomewhereOtherThanBGMAppAddress);\n    return type && CFBooleanGetValue(static_cast<CFBooleanRef>(type));\n}\n\n#pragma mark IOProcs\n\n// Note that the IOProcs will very likely not run on the same thread and that they intentionally\n// only lock mutexes around their use of mBuffer.\n\n// static\nOSStatus    BGMPlayThrough::InputDeviceIOProc(AudioObjectID           inDevice,\n                                              const AudioTimeStamp*   inNow,\n                                              const AudioBufferList*  inInputData,\n                                              const AudioTimeStamp*   inInputTime,\n                                              AudioBufferList*        outOutputData,\n                                              const AudioTimeStamp*   inOutputTime,\n                                              void* __nullable        inClientData)\n{\n    #pragma unused (inDevice, inNow, outOutputData, inOutputTime)\n    \n    // refCon (reference context) is the instance that created the IOProc\n    BGMPlayThrough* const refCon = static_cast<BGMPlayThrough*>(inClientData);\n    \n    IOState state;\n    UpdateIOProcState(\"InputDeviceIOProc\",\n                      refCon->mRTLogger,\n                      refCon->mInputDeviceIOProcState,\n                      refCon->mInputDeviceIOProcID,\n                      refCon->mInputDevice,\n                      state);\n    \n    if(state == IOState::Stopped || state == IOState::Stopping)\n    {\n        // Return early, since we just asked to stop. (Or something really weird is going on.)\n        return noErr;\n    }\n    \n    BGMAssert(state == IOState::Running, \"BGMPlayThrough::InputDeviceIOProc: Unexpected state\");\n    \n    if(refCon->mFirstInputSampleTime == -1)\n    {\n        refCon->mFirstInputSampleTime = inInputTime->mSampleTime;\n    }\n    \n    UInt32 framesToStore = inInputData->mBuffers[0].mDataByteSize / (SizeOf32(Float32) * 2);\n\n    // See the comments in OutputDeviceIOProc where it locks mBufferOutputMutex.\n    CAMutex::Tryer tryer(refCon->mBufferInputMutex);\n\n    // Disable a warning about accessing mBuffer without holding both mBufferInputMutex and\n    // mBufferOutputMutex. Explained further in OutputDeviceIOProc.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wthread-safety\"\n    if(tryer.HasLock() && refCon->mBuffer)\n    {\n        CARingBufferError err =\n                refCon->mBuffer->Store(inInputData,\n                                       framesToStore,\n                                       static_cast<CARingBuffer::SampleTime>(\n                                               inInputTime->mSampleTime));\n#pragma clang diagnostic pop\n        refCon->mRTLogger.LogIfRingBufferError_Store(err);\n\n        refCon->mLastInputSampleTime = inInputTime->mSampleTime;\n    }\n    else\n    {\n        refCon->mRTLogger.LogRingBufferUnavailable(\"InputDeviceIOProc\", tryer.HasLock());\n    }\n\n    return noErr;\n}\n\n// static\nOSStatus    BGMPlayThrough::OutputDeviceIOProc(AudioObjectID           inDevice,\n                                               const AudioTimeStamp*   inNow,\n                                               const AudioBufferList*  inInputData,\n                                               const AudioTimeStamp*   inInputTime,\n                                               AudioBufferList*        outOutputData,\n                                               const AudioTimeStamp*   inOutputTime,\n                                               void* __nullable        inClientData)\n{\n    #pragma unused (inDevice, inNow, inInputData, inInputTime)\n    \n    // refCon (reference context) is the instance that created the IOProc\n    BGMPlayThrough* const refCon = static_cast<BGMPlayThrough*>(inClientData);\n    \n    IOState state;\n    const bool didChangeState = UpdateIOProcState(\"OutputDeviceIOProc\",\n                                                  refCon->mRTLogger,\n                                                  refCon->mOutputDeviceIOProcState,\n                                                  refCon->mOutputDeviceIOProcID,\n                                                  refCon->mOutputDevice,\n                                                  state);\n    \n    if(state == IOState::Stopped || state == IOState::Stopping)\n    {\n        // Return early, since we just asked to stop. (Or something really weird is going on.)\n        FillWithSilence(outOutputData);\n        return noErr;\n    }\n    \n    BGMAssert(state == IOState::Running, \"BGMPlayThrough::OutputDeviceIOProc: Unexpected state\");\n    \n    if(didChangeState)\n    {\n        // We just changed state from Starting to Running, which means this is the first time this IOProc\n        // has been called since the output device finished starting up, so now we can wake any threads\n        // waiting in WaitForOutputDeviceToStart.\n        BGMAssert(refCon->mLastOutputSampleTime == -1,\n                  \"BGMPlayThrough::OutputDeviceIOProc: mLastOutputSampleTime not reset\");\n        \n        refCon->ReleaseThreadsWaitingForOutputToStart();\n    }\n    \n    if(refCon->mLastInputSampleTime == -1)\n    {\n        // Return early, since we don't have any data to output yet.\n        FillWithSilence(outOutputData);\n        return noErr;\n    }\n    \n    // If this is the first time this IOProc has been called since starting playthrough...\n    if(refCon->mLastOutputSampleTime == -1)\n    {\n        // Calculate the number of frames between the read and write heads\n        refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - refCon->mLastInputSampleTime;\n        \n        // Log if we dropped frames\n        refCon->mRTLogger.LogIfDroppedFrames(refCon->mFirstInputSampleTime,\n                                             refCon->mLastInputSampleTime);\n    }\n    \n    CARingBuffer::SampleTime readHeadSampleTime =\n        static_cast<CARingBuffer::SampleTime>(inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);\n    CARingBuffer::SampleTime lastInputSampleTime =\n        static_cast<CARingBuffer::SampleTime>(refCon->mLastInputSampleTime);\n    \n    UInt32 framesToOutput = outOutputData->mBuffers[0].mDataByteSize / (SizeOf32(Float32) * 2);\n\n    // When the input and output devices are set, during start up or because the user changed the\n    // output device, this class (re)allocates the ring buffer (mBuffer). We try to take this\n    // lock before accessing the buffer to make sure it's allocated.\n    //\n    // If we don't get the lock, another thread must be allocating or deallocating it, so we just\n    // give up. We can't avoid audio glitches while changing devices anyway. This class tries to\n    // make sure the IOProcs aren't running when it allocates the buffer, but it can't guarantee\n    // that.\n    //\n    // Note that this is only realtime safe because we only try to lock the mutex. If another\n    // thread has the mutex, it will be a non-realtime thread, so we can't wait for it.\n    CAMutex::Tryer tryer(refCon->mBufferOutputMutex);\n\n    // Disable a warning about accessing mBuffer without holding both mBufferInputMutex and\n    // mBufferOutputMutex. The input IOProc always writes ahead of where the output IOProc will read\n    // in a given IO cycle, so it's safe for them to read and write at the same time.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wthread-safety\"\n    if(tryer.HasLock() && refCon->mBuffer)\n    {\n        // Very occasionally (at least for me) our read head gets ahead of input, i.e. we haven't\n        // received any new input since this IOProc was last called, and we have to recalculate its\n        // position. I figure this might be caused by clock drift but I'm really not sure. It also\n        // happens if the input or output sample times are restarted from zero.\n        //\n        // We also recalculate the offset if the read head is outside of the ring buffer. This\n        // happens for example when you plug in or unplug headphones, which causes the output sample\n        // times to be restarted from zero.\n        //\n        // The vast majority of the time, just using lastInputSampleTime as the read head time\n        // instead of the one we calculate would work fine (and would also account for the above).\n        SInt64 bufferStartTime, bufferEndTime;\n        CARingBufferError err = refCon->mBuffer->GetTimeBounds(bufferStartTime, bufferEndTime);\n        bool outOfBounds = false;\n\n        if(err == kCARingBufferError_OK)\n        {\n            outOfBounds = (readHeadSampleTime < bufferStartTime)\n                    || (readHeadSampleTime - framesToOutput > bufferEndTime);\n        }\n\n        if(lastInputSampleTime < readHeadSampleTime || outOfBounds)\n        {\n            refCon->mRTLogger.LogNoSamplesReady(lastInputSampleTime,\n                                                readHeadSampleTime,\n                                                refCon->mInToOutSampleOffset);\n\n            // Recalculate the in-to-out offset and read head.\n            refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - lastInputSampleTime;\n            readHeadSampleTime = static_cast<CARingBuffer::SampleTime>(\n                    inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);\n        }\n\n        // Copy the frames from the ring buffer.\n        err = refCon->mBuffer->Fetch(outOutputData, framesToOutput, readHeadSampleTime);\n        refCon->mRTLogger.LogIfRingBufferError_Fetch(err);\n\n        if(err != kCARingBufferError_OK)\n        {\n            FillWithSilence(outOutputData);\n        }\n    }\n    else\n    {\n        refCon->mRTLogger.LogRingBufferUnavailable(\"OutputDeviceIOProc\", tryer.HasLock());\n        FillWithSilence(outOutputData);\n    }\n#pragma clang diagnostic pop\n\n    refCon->mLastOutputSampleTime = inOutputTime->mSampleTime;\n    \n    return noErr;\n}\n\n// static\ninline void BGMPlayThrough::FillWithSilence(AudioBufferList* ioBuffer)\n{\n    for(UInt32 i = 0; i < ioBuffer->mNumberBuffers; i++)\n    {\n        memset(ioBuffer->mBuffers[i].mData, 0, ioBuffer->mBuffers[i].mDataByteSize);\n    }\n}\n\n// static\nbool    BGMPlayThrough::UpdateIOProcState(const char* inCallerName,\n                                          BGMPlayThroughRTLogger& inRTLogger,\n                                          std::atomic<IOState>& inState,\n                                          AudioDeviceIOProcID __nullable inIOProcID,\n                                          BGMAudioDevice& inDevice,\n                                          IOState& outNewState)\n{\n    BGMAssert(inIOProcID != nullptr, \"BGMPlayThrough::UpdateIOProcState: !inIOProcID\");\n\n    // Change this IOProc's state to Running if this is the first time it's been called since we\n    // started playthrough.\n    //\n    // compare_exchange_strong will return true iff it changed inState from Starting to Running.\n    // Otherwise it will set prevState to the current value of inState.\n    //\n    // TODO: We probably don't actually need memory_order_seq_cst (the default). Would it be worth\n    //       changing? Might be worth checking for the other atomics/barriers in this class, too.\n    IOState prevState = IOState::Starting;\n    bool didChangeState = inState.compare_exchange_strong(prevState, IOState::Running);\n\n    if(didChangeState)\n    {\n        BGMAssert(prevState == IOState::Starting, \"BGMPlayThrough::UpdateIOProcState: ?!\");\n        outNewState = IOState::Running;\n    }\n    else\n    {\n        // Return the current value of inState to the caller.\n        outNewState = prevState;\n        \n        if(outNewState != IOState::Running)\n        {\n            // The IOProc isn't Starting or Running, so it must be Stopping. That is, it's been\n            // told to stop itself.\n            BGMAssert(outNewState == IOState::Stopping,\n                      \"BGMPlayThrough::UpdateIOProcState: Unexpected state: %d\",\n                      outNewState);\n            \n            bool stoppedSuccessfully = false;\n\n            try\n            {\n                inDevice.StopIOProc(inIOProcID);\n\n                // StopIOProc didn't throw, so the IOProc won't be called again until the next\n                // time playthrough is started.\n                stoppedSuccessfully = true;\n            }\n            catch(CAException e)\n            {\n                inRTLogger.LogExceptionStoppingIOProc(inCallerName, e.GetError());\n            }\n            catch(...)\n            {\n                inRTLogger.LogExceptionStoppingIOProc(inCallerName);\n            }\n\n            if(stoppedSuccessfully)\n            {\n                // Change inState to Stopped.\n                //\n                // If inState has been changed since we last read it, we don't know if we called\n                // StopIOProc before or after the thread that changed it called StartIOProc (if it\n                // did). However, inState is only changed here (in the IOProc), in Start and in\n                // Stop.\n                //\n                // Stop won't return until the IOProc has changed inState to Stopped, unless it\n                // times out, so Stop should still be waiting. And since Start and Stop are\n                // mutually exclusive, this should be safe.\n                //\n                // But if Stop has timed out and inState has changed, we leave it in its new\n                // state (unless there's some ABA problem thing happening), which I suspect is\n                // the safest option.\n                didChangeState = inState.compare_exchange_strong(outNewState, IOState::Stopped);\n                \n                if(didChangeState)\n                {\n                    outNewState = IOState::Stopped;\n                }\n                else\n                {\n                    inRTLogger.LogUnexpectedIOStateAfterStopping(inCallerName,\n                                                                 static_cast<int>(outNewState));\n                }\n            }\n        }\n    }\n\n    return didChangeState;\n}\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThrough.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThrough.h\n//  BGMApp\n//\n//  Copyright © 2016, 2017, 2020 Kyle Neideck\n//\n//  Reads audio from an input device and immediately writes it to an output device. We currently use this class with the input\n//  device always set to BGMDevice and the output device set to the one selected in the preferences menu.\n//\n//  Apple's CAPlayThrough sample code (https://developer.apple.com/library/mac/samplecode/CAPlayThrough/Introduction/Intro.html)\n//  has a similar class, but I couldn't get it fast enough to use here. Soundflower also has a similar class\n//  (https://github.com/mattingalls/Soundflower/blob/master/SoundflowerBed/AudioThruEngine.h) that seems to be based on Apple\n//  sample code from 2004. This class's main addition is pausing playthrough when idle to save CPU.\n//\n//  Playing audio with this class uses more CPU, mostly in the coreaudiod process, than playing audio normally because we need\n//  an input IOProc as well as an output one, and BGMDriver is running in addition to the output device's driver. For me, it\n//  usually adds around 1-2% (as a percentage of total usage -- it doesn't seem to be relative to the CPU used when playing\n//  audio normally).\n//\n//  This class will hopefully not be needed after CoreAudio's aggregate devices get support for controls, which is planned for\n//  a future release.\n//\n\n#ifndef BGMApp__BGMPlayThrough\n#define BGMApp__BGMPlayThrough\n\n// Local Includes\n#include \"BGMAudioDevice.h\"\n#include \"BGMPlayThroughRTLogger.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n#include \"CARingBuffer.h\"\n#include \"BGMThreadSafetyAnalysis.h\"\n\n// STL Includes\n#include <atomic>\n#include <algorithm>\n#include <memory>\n\n// System Includes\n#include <mach/semaphore.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMPlayThrough\n{\n    \npublic:\n    // Error codes\n    static const OSStatus kDeviceNotStarting = 100;\n\npublic:\n                        BGMPlayThrough(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice);\n                        ~BGMPlayThrough();\n                        // Disallow copying\n                        BGMPlayThrough(const BGMPlayThrough&) = delete;\n                        BGMPlayThrough& operator=(const BGMPlayThrough&) = delete;\n\n#ifdef __OBJC__\n                        // Only intended as a convenience (hack) for Objective-C instance vars. Call\n                        // SetDevices to initialise the instance before using it.\n                        BGMPlayThrough() { }\n#endif\n    \nprivate:\n    /*! @throws CAException */\n    void                Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)\n                            REQUIRES(mStateMutex);\n\npublic:\n    /*! @throws CAException */\n    void                Activate();\n    /*! @throws CAException */\n    void                Deactivate();\n\nprivate:\n    void                AllocateBuffer() REQUIRES(mStateMutex);\n    void                DeallocateBuffer();\n\n    /*! @throws CAException */\n    void                CreateIOProcIDs();\n    /*! @throws CAException */\n    void                DestroyIOProcIDs();\n    /*!\n        @return True if both IOProcs are stopped.\n        @nonthreadsafe\n     */\n    bool                CheckIOProcsAreStopped() const noexcept REQUIRES(mStateMutex);\n\t\npublic:\n    /*!\n     Pass null for either param to only change one of the devices.\n     @throws CAException\n     */\n    void                SetDevices(const BGMAudioDevice* __nullable inInputDevice,\n                                   const BGMAudioDevice* __nullable inOutputDevice);\n    \n    /*! @throws CAException */\n    void                Start();\n    \n    // Blocks until the output device has started our IOProc. Returns one of the error constants\n    // from AudioHardwareBase.h (e.g. kAudioHardwareNoError).\n    OSStatus            WaitForOutputDeviceToStart() noexcept;\n    \nprivate:\n    /*! Real-time safe. */\n    void                ReleaseThreadsWaitingForOutputToStart();\n    \npublic:\n    OSStatus            Stop();\n    void                StopIfIdle();\n    \nprivate:\n    \n    static OSStatus     BGMDeviceListenerProc(AudioObjectID inObjectID,\n                                              UInt32 inNumberAddresses,\n                                              const AudioObjectPropertyAddress* inAddresses,\n                                              void* __nullable inClientData);\n    static void         HandleBGMDeviceIsRunning(BGMPlayThrough* refCon);\n    static void         HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlayThrough* refCon);\n    \n    static bool         IsRunningSomewhereOtherThanBGMApp(const BGMAudioDevice& inBGMDevice);\n\n    static OSStatus     InputDeviceIOProc(AudioObjectID           inDevice,\n                                          const AudioTimeStamp*   inNow,\n                                          const AudioBufferList*  inInputData,\n                                          const AudioTimeStamp*   inInputTime,\n                                          AudioBufferList*        outOutputData,\n                                          const AudioTimeStamp*   inOutputTime,\n                                          void* __nullable        inClientData);\n    static OSStatus     OutputDeviceIOProc(AudioObjectID           inDevice,\n                                           const AudioTimeStamp*   inNow,\n                                           const AudioBufferList*  inInputData,\n                                           const AudioTimeStamp*   inInputTime,\n                                           AudioBufferList*        outOutputData,\n                                           const AudioTimeStamp*   inOutputTime,\n                                           void* __nullable        inClientData);\n\n    /*! Fills the given ABL with zeroes to make it silent. */\n    static inline void  FillWithSilence(AudioBufferList* ioBuffer);\n\n    // The state of an IOProc. Used by the IOProc to tell other threads when it's finished starting. Used by other\n    // threads to tell the IOProc to stop itself. (Probably used for other things as well.)\n    enum class          IOState\n                        {\n                            Stopped, Starting, Running, Stopping\n                        };\n    \n    // The IOProcs call this to update their IOState member. Also stops the IOProc if its state has been set to Stopping.\n    // Returns true if it changes the state.\n    static bool         UpdateIOProcState(const char* inCallerName,\n                                          BGMPlayThroughRTLogger& inRTLogger,\n                                          std::atomic<IOState>& inState,\n                                          AudioDeviceIOProcID __nullable inIOProcID,\n                                          BGMAudioDevice& inDevice,\n                                          IOState& outNewState);\n    \nprivate:\n    std::unique_ptr<CARingBuffer>    mBuffer PT_GUARDED_BY(mBufferInputMutex)\n                                        PT_GUARDED_BY(mBufferOutputMutex) { nullptr };\n    \n    AudioDeviceIOProcID __nullable mInputDeviceIOProcID { nullptr };\n    AudioDeviceIOProcID __nullable mOutputDeviceIOProcID { nullptr };\n    \n    BGMAudioDevice      mInputDevice { kAudioObjectUnknown };\n    BGMAudioDevice      mOutputDevice { kAudioObjectUnknown };\n\n    // mStateMutex is the general purpose mutex. mBufferInputMutex and mBufferOutputMutex are\n    // just used to make sure mBuffer, the ring buffer, is allocated when the IOProcs access it. See\n    // the comments in the IOProcs for details.\n    //\n    // If a thread might lock more than one of these mutexes, it *must* take them in this order:\n    //     1. mStateMutex\n    //     2. mBufferInputMutex\n    //     3. mBufferOutputMutex\n    //\n    // The ACQUIRED_BEFORE annotations don't do anything yet. From clang's docs: \"ACQUIRED_BEFORE(…)\n    // and ACQUIRED_AFTER(…) are currently unimplemented. To be fixed in a future update.\" After\n    // they've fixed that, the compiler will enforce the ordering statically.\n    //\n    // TODO: We can't use std::shared_lock because we're still on C++11, but we could use std::lock\n    //       to help ensure the locks are always taken in the right order.\n    // TODO: It would be better to have a separate class for the buffer and its mutexes.\n    CAMutex             mStateMutex ACQUIRED_BEFORE(mBufferInputMutex)\n                            ACQUIRED_BEFORE(mBufferOutputMutex) { \"Playthrough state\" };\n    CAMutex             mBufferInputMutex ACQUIRED_BEFORE(mBufferOutputMutex)\n                            { \"Playthrough ring buffer input\" };\n    CAMutex             mBufferOutputMutex { \"Playthrough ring buffer output\" };\n\n    // Signalled when the output IOProc runs. We use it to tell BGMDriver when the output device is ready to receive audio data.\n    semaphore_t         mOutputDeviceIOProcSemaphore { SEMAPHORE_NULL };\n    \n    bool                mActive = false;\n    bool                mPlayingThrough = false;\n\n    UInt64              mLastNotifiedIOStoppedOnBGMDevice { 0 };\n\n    std::atomic<IOState>    mInputDeviceIOProcState { IOState::Stopped };\n    std::atomic<IOState>    mOutputDeviceIOProcState { IOState::Stopped };\n    \n    // For debug logging.\n    UInt64              mToldOutputDeviceToStartAt { 0 };\n\n    // IOProc vars. (Should only be used inside IOProcs.)\n    \n    // The earliest/latest sample times seen by the IOProcs since starting playthrough. -1 for unset.\n    Float64             mFirstInputSampleTime = -1;\n    Float64             mLastInputSampleTime = -1;\n    Float64             mLastOutputSampleTime = -1;\n    \n    // Subtract this from the output time to get the input time.\n    Float64             mInToOutSampleOffset { 0.0 };\n\n    BGMPlayThroughRTLogger mRTLogger;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMPlayThrough */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThroughRTLogger.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThroughRTLogger.cpp\n//  BGMApp\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMPlayThroughRTLogger.h\"\n\n// Local Includes\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n// STL Includes\n#include <atomic>\n\n// System Includes\n#include <CoreAudio/CoreAudio.h>\n#include <mach/mach_init.h>\n#include <mach/task.h>\n#include <unistd.h>\n\n\n#pragma clang assume_nonnull begin\n\n// Track the number of messages logged when built for the unit tests.\n#if BGM_UnitTest\n    #define LogSync_Debug(inFormat, ...) do { \\\n                mNumDebugMessagesLogged++; \\\n                DebugMsg(inFormat, ## __VA_ARGS__); \\\n            } while (0)\n#else\n    #define LogSync_Debug(inFormat, ...) DebugMsg(inFormat, ## __VA_ARGS__)\n#endif\n\n#pragma mark Construction/Destruction\n\nBGMPlayThroughRTLogger::BGMPlayThroughRTLogger()\n{\n    // Create the semaphore we use to wake up the logging thread when it has messages to log.\n    mWakeUpLoggingThreadSemaphore = CreateSemaphore();\n\n    // Create the logging thread last because it starts immediately and expects the other member\n    // variables to be initialised.\n    mLoggingThread = std::thread(&BGMPlayThroughRTLogger::LoggingThreadEntry, this);\n}\n\n// static\nsemaphore_t BGMPlayThroughRTLogger::CreateSemaphore()\n{\n    // TODO: Make a BGMMachSemaphore class to reduce some of this repetitive semaphore code.\n\n    // Create the semaphore.\n    semaphore_t semaphore;\n    kern_return_t error =\n            semaphore_create(mach_task_self(), &semaphore, SYNC_POLICY_FIFO, 0);\n\n    // Check the error code.\n    BGM_Utils::ThrowIfMachError(\"BGMPlayThroughRTLogger::CreateSemaphore\",\n                                \"semaphore_create\",\n                                error);\n    ThrowIf(semaphore == SEMAPHORE_NULL,\n            CAException(kAudioHardwareUnspecifiedError),\n            \"BGMPlayThroughRTLogger::CreateSemaphore: Failed to create semaphore\");\n\n    return semaphore;\n}\n\nBGMPlayThroughRTLogger::~BGMPlayThroughRTLogger()\n{\n    // Stop the logging thread.\n    mLoggingThreadShouldExit = true;\n    kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);\n\n    BGM_Utils::LogIfMachError(\"BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger\",\n                              \"semaphore_signal\",\n                              error);\n\n    if(error == KERN_SUCCESS)\n    {\n        // Wait for it to stop.\n        mLoggingThread.join();\n\n        // Destroy the semaphore.\n        error = semaphore_destroy(mach_task_self(), mWakeUpLoggingThreadSemaphore);\n        BGM_Utils::LogIfMachError(\"BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger\",\n                                  \"semaphore_destroy\",\n                                  error);\n    }\n    else\n    {\n        // If we couldn't tell it to wake up, it's not safe to wait for it to stop or to destroy the\n        // semaphore. We have to detach it so its destructor doesn't cause a crash.\n        mLoggingThread.detach();\n    }\n}\n\n#pragma mark Log Messages\n\nvoid BGMPlayThroughRTLogger::LogReleasingWaitingThreads()\n{\n    if(!BGMDebugLoggingIsEnabled())\n    {\n        return;\n    }\n\n    if(!mLogReleasingWaitingThreadsMsg.is_lock_free())\n    {\n        // Modifying mLogReleasingWaitingThreadsMsg might cause the thread to lock a mutex that\n        // isn't safe to lock on a realtime thread, so just give up.\n        return;\n    }\n\n    // Set the flag that tells the logging thread to log the message.\n    mLogReleasingWaitingThreadsMsg = true;\n\n    // Wake the logging thread so it can log the message.\n    WakeLoggingThread();\n}\n\nvoid BGMPlayThroughRTLogger::LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError)\n{\n    if(inError == KERN_SUCCESS)\n    {\n        // No error.\n        return;\n    }\n\n    if(!mReleaseWaitingThreadsSignalError.is_lock_free())\n    {\n        // Modifying mReleaseWaitingThreadsSignalError might cause the thread to lock a mutex that\n        // isn't safe to lock on a realtime thread, so just give up.\n        return;\n    }\n\n    mReleaseWaitingThreadsSignalError = inError;\n    WakeLoggingThread();\n}\n\nvoid BGMPlayThroughRTLogger::LogIfDroppedFrames(Float64 inFirstInputSampleTime,\n                                                Float64 inLastInputSampleTime)\n{\n    if(inFirstInputSampleTime == inLastInputSampleTime || !BGMDebugLoggingIsEnabled())\n    {\n        // Either we didn't drop any initial frames or we don't need to log a message about it.\n        return;\n    }\n\n    LogAsync(mDroppedFrames, [&]()\n    {\n        // Store the data to include in the log message.\n        mDroppedFrames.firstInputSampleTime = inFirstInputSampleTime;\n        mDroppedFrames.lastInputSampleTime = inLastInputSampleTime;\n    });\n}\n\nvoid BGMPlayThroughRTLogger::LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,\n                                               CARingBuffer::SampleTime inReadHeadSampleTime,\n                                               Float64 inInToOutSampleOffset)\n{\n    if(!BGMDebugLoggingIsEnabled())\n    {\n        return;\n    }\n\n    LogAsync(mNoSamplesReady, [&]()\n    {\n        // Store the data to include in the log message.\n        mNoSamplesReady.lastInputSampleTime = inLastInputSampleTime;\n        mNoSamplesReady.readHeadSampleTime = inReadHeadSampleTime;\n        mNoSamplesReady.inToOutSampleOffset = inInToOutSampleOffset;\n    });\n}\n\nvoid BGMPlayThroughRTLogger::LogExceptionStoppingIOProc(const char* inCallerName,\n                                                        OSStatus inError,\n                                                        bool inErrorKnown)\n{\n    LogAsync(mExceptionStoppingIOProc, [&]()\n    {\n        // Store the data to include in the log message.\n        mExceptionStoppingIOProc.callerName = inCallerName;\n        mExceptionStoppingIOProc.error = inError;\n        mExceptionStoppingIOProc.errorKnown = inErrorKnown;\n    });\n}\n\nvoid BGMPlayThroughRTLogger::LogUnexpectedIOStateAfterStopping(const char* inCallerName,\n                                                               int inIOState)\n{\n    LogAsync(mUnexpectedIOStateAfterStopping, [&]()\n    {\n        // Store the data to include in the log message.\n        mUnexpectedIOStateAfterStopping.callerName = inCallerName;\n        mUnexpectedIOStateAfterStopping.ioState = inIOState;\n    });\n}\n\nvoid BGMPlayThroughRTLogger::LogRingBufferUnavailable(const char* inCallerName, bool inGotLock)\n{\n    LogAsync(mRingBufferUnavailable, [&]()\n    {\n        // Store the data to include in the log message.\n        mRingBufferUnavailable.callerName = inCallerName;\n        mRingBufferUnavailable.gotLock = inGotLock;\n    });\n}\n\nvoid BGMPlayThroughRTLogger::LogIfRingBufferError(CARingBufferError inError,\n                                                  std::atomic<CARingBufferError>& outError)\n{\n    if(inError == kCARingBufferError_OK)\n    {\n        // No error.\n        return;\n    }\n\n    if(!outError.is_lock_free())\n    {\n        // Modifying outError might cause the thread to lock a mutex that isn't safe to lock on\n        // a realtime thread, so just give up.\n        return;\n    }\n\n    // Store the error.\n    outError = inError;\n\n    // Wake the logging thread so it can log the error.\n    WakeLoggingThread();\n}\n\ntemplate <typename T, typename F>\nvoid BGMPlayThroughRTLogger::LogAsync(T& inMessageData, F&& inStoreMessageData)\n{\n    if(!inMessageData.shouldLogMessage.is_lock_free())\n    {\n        // Modifying shouldLogMessage might cause the thread to lock a mutex that isn't safe to\n        // lock on a realtime thread, so just give up.\n        return;\n    }\n\n    if(inMessageData.shouldLogMessage)\n    {\n        // The logging thread could be reading inMessageData.\n        return;\n    }\n\n    // Store the data to include in the log message.\n    //\n    // std::forward lets the compiler treat inStoreMessageData as an rvalue if the caller gave it as\n    // an rvalue. No idea if that actually does anything.\n    std::forward<F>(inStoreMessageData)();\n\n    // shouldLogMessage is a std::atomic, so this store also makes sure that the non-atomic stores\n    // in inStoreMessageData will be visible to the logger thread (since the default memory order is\n    // memory_order_seq_cst).\n    inMessageData.shouldLogMessage = true;\n\n    WakeLoggingThread();\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_Warning(const char* inFormat, ...)\n{\n    va_list args;\n    va_start(args, inFormat);\n\n#if BGM_UnitTest\n    mNumWarningMessagesLogged++;\n#endif\n\n    vLogWarning(inFormat, args);\n\n    va_end(args);\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_Error(const char* inFormat, ...)\n{\n    va_list args;\n    va_start(args, inFormat);\n\n#if BGM_UnitTest\n    mNumErrorMessagesLogged++;\n\n    if(!mContinueOnErrorLogged)\n    {\n        vLogError(inFormat, args);\n    }\n#else\n    vLogError(inFormat, args);\n#endif\n\n    va_end(args);\n}\n\n#pragma mark Logging Thread\n\nvoid BGMPlayThroughRTLogger::WakeLoggingThread()\n{\n    kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);\n\n    BGMAssert(error == KERN_SUCCESS, \"semaphore_signal (%d)\", error);\n\n    // We can't do anything useful with the error in release builds. At least, not easily.\n    (void)error;\n}\n\nvoid BGMPlayThroughRTLogger::LogMessages()\n{\n    // Log the messages/errors from the realtime threads (if any).\n    LogSync_ReleasingWaitingThreads();\n    LogSync_ReleaseWaitingThreadsSignalError();\n    LogSync_DroppedFrames();\n    LogSync_NoSamplesReady();\n    LogSync_ExceptionStoppingIOProc();\n    LogSync_UnexpectedIOStateAfterStopping();\n    LogSync_RingBufferUnavailable();\n    LogSync_RingBufferError(mRingBufferStoreError, \"InputDeviceIOProc\");\n    LogSync_RingBufferError(mRingBufferFetchError, \"OutputDeviceIOProc\");\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_ReleasingWaitingThreads()\n{\n    if(mLogReleasingWaitingThreadsMsg)\n    {\n        LogSync_Debug(\"BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart: \"\n                      \"Releasing waiting threads\");\n        // Reset it.\n        mLogReleasingWaitingThreadsMsg = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_ReleaseWaitingThreadsSignalError()\n{\n    if(mReleaseWaitingThreadsSignalError != KERN_SUCCESS)\n    {\n        BGM_Utils::LogIfMachError(\"BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart\",\n                                  \"semaphore_signal_all\",\n                                  mReleaseWaitingThreadsSignalError);\n        // Reset it.\n        mReleaseWaitingThreadsSignalError = KERN_SUCCESS;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_DroppedFrames()\n{\n    if(mDroppedFrames.shouldLogMessage)\n    {\n        LogSync_Debug(\"BGMPlayThrough::OutputDeviceIOProc: \"\n                      \"Dropped %f frames before output started. %s%f %s%f\",\n                      (mDroppedFrames.lastInputSampleTime - mDroppedFrames.firstInputSampleTime),\n                      \"mFirstInputSampleTime=\",\n                      mDroppedFrames.firstInputSampleTime,\n                      \"mLastInputSampleTime=\",\n                      mDroppedFrames.lastInputSampleTime);\n        mDroppedFrames.shouldLogMessage = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_NoSamplesReady()\n{\n    if(mNoSamplesReady.shouldLogMessage)\n    {\n        LogSync_Debug(\"BGMPlayThrough::OutputDeviceIOProc: \"\n                      \"No input samples ready at output sample time. %s%lld %s%lld %s%f\",\n                      \"lastInputSampleTime=\", mNoSamplesReady.lastInputSampleTime,\n                      \"readHeadSampleTime=\", mNoSamplesReady.readHeadSampleTime,\n                      \"mInToOutSampleOffset=\", mNoSamplesReady.inToOutSampleOffset);\n        mNoSamplesReady.shouldLogMessage = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_ExceptionStoppingIOProc()\n{\n    if(mExceptionStoppingIOProc.shouldLogMessage)\n    {\n        const char error4CC[5] = CA4CCToCString(mExceptionStoppingIOProc.error);\n        LogSync_Error(\"BGMPlayThrough::UpdateIOProcState: \"\n                      \"Exception while stopping IOProc %s: %s (%d)\",\n                      mExceptionStoppingIOProc.callerName,\n                      mExceptionStoppingIOProc.errorKnown ? error4CC : \"unknown\",\n                      mExceptionStoppingIOProc.error);\n        mExceptionStoppingIOProc.shouldLogMessage = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_UnexpectedIOStateAfterStopping()\n{\n    if(mUnexpectedIOStateAfterStopping.shouldLogMessage)\n    {\n        LogSync_Warning(\"BGMPlayThrough::UpdateIOProcState: \"\n                        \"%s IO state changed since last read. state = %d\",\n                        mUnexpectedIOStateAfterStopping.callerName,\n                        mUnexpectedIOStateAfterStopping.ioState);\n        mUnexpectedIOStateAfterStopping.shouldLogMessage = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_RingBufferUnavailable()\n{\n    if(mRingBufferUnavailable.shouldLogMessage)\n    {\n        LogSync_Warning(\"BGMPlayThrough::%s: Ring buffer unavailable. %s\",\n                        mRingBufferUnavailable.callerName,\n                        mRingBufferUnavailable.gotLock ?\n                            \"No buffer currently allocated.\" :\n                            \"Buffer locked for allocation/deallocation by another thread.\");\n        mRingBufferUnavailable.shouldLogMessage = false;\n    }\n}\n\nvoid BGMPlayThroughRTLogger::LogSync_RingBufferError(\n        std::atomic<CARingBufferError>& ioRingBufferError,\n        const char* inMethodName)\n{\n    CARingBufferError error = ioRingBufferError;\n\n    switch(error)\n    {\n        case kCARingBufferError_OK:\n            // No error.\n            return;\n        case kCARingBufferError_CPUOverload:\n            // kCARingBufferError_CPUOverload might not be our fault, so just log a warning.\n            LogSync_Warning(\"BGMPlayThrough::%s: Ring buffer error: \"\n                            \"kCARingBufferError_CPUOverload (%d)\",\n                            inMethodName,\n                            error);\n            break;\n        default:\n            // Other types of CARingBuffer errors should never occur. This will crash debug builds.\n            LogSync_Error(\"BGMPlayThrough::%s: Ring buffer error: %s (%d)\",\n                          inMethodName,\n                          (error == kCARingBufferError_TooMuch ?\n                                  \"kCARingBufferError_TooMuch\" :\n                                  \"unknown error\"),\n                          error);\n            break;\n    };\n\n    // Reset it.\n    ioRingBufferError = kCARingBufferError_OK;\n}\n\n// static\nvoid* __nullable BGMPlayThroughRTLogger::LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon)\n{\n    DebugMsg(\"BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: \"\n             \"Starting the IOProc logging thread\");\n\n    while(!inRefCon->mLoggingThreadShouldExit)\n    {\n        // Log the messages, if there are any to log.\n        inRefCon->LogMessages();\n\n        // Wait until woken up.\n        kern_return_t error = semaphore_wait(inRefCon->mWakeUpLoggingThreadSemaphore);\n        BGM_Utils::LogIfMachError(\"BGMPlayThroughRTLogger::IOProcLoggingThreadEntry\",\n                                  \"semaphore_wait\",\n                                  error);\n    }\n\n    DebugMsg(\"BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: IOProc logging thread exiting\");\n\n    return nullptr;\n}\n\n#if BGM_UnitTest\n\n#pragma mark Test Helpers\n\nbool BGMPlayThroughRTLogger::WaitUntilLoggerThreadIdle()\n{\n    int msWaited = 0;\n\n    while(mLogReleasingWaitingThreadsMsg ||\n            mReleaseWaitingThreadsSignalError != KERN_SUCCESS ||\n            mDroppedFrames.shouldLogMessage ||\n            mNoSamplesReady.shouldLogMessage ||\n            mUnexpectedIOStateAfterStopping.shouldLogMessage ||\n            mRingBufferUnavailable.shouldLogMessage ||\n            mExceptionStoppingIOProc.shouldLogMessage ||\n            mRingBufferStoreError != kCARingBufferError_OK ||\n            mRingBufferFetchError != kCARingBufferError_OK)\n    {\n        // Poll until the logger thread has nothing left to log. (Ideally we'd use a semaphore\n        // instead of polling, but it isn't worth the effort at this point.)\n        usleep(10 * 1000);\n        msWaited += 10;\n\n        // Time out after 5 seconds.\n        if(msWaited > 5000)\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n#endif /* BGM_UnitTest */\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThroughRTLogger.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThroughRTLogger.h\n//  BGMApp\n//\n//  Copyright © 2020 Kyle Neideck\n//\n//  A real-time safe logger for BGMPlayThrough. The messages are logged asynchronously by a\n//  non-realtime thread.\n//\n//  For the sake of simplicity, this class is very closely coupled with BGMPlayThrough and its\n//  methods make assumptions about where they will be called. Also, if the same logging method is\n//  called multiple times before the logging thread next checks for messages, it will only log the\n//  message for one of those calls and ignore the others.\n//\n//  This class's methods are real-time safe in that they return in a bounded amount of time and we\n//  think they're probably fast enough that the callers won't miss their deadlines, but we don't try\n//  to guarantee it. Some of them should only be called in unusual cases where it's worth increasing\n//  the risk of a thread missing its deadline.\n//\n\n#ifndef BGMApp__BGMPlayThroughRTLogger\n#define BGMApp__BGMPlayThroughRTLogger\n\n// PublicUtility Includes\n#include \"CARingBuffer.h\"\n\n// STL Includes\n#include <thread>\n\n// System Includes\n#include <mach/error.h>\n#include <mach/semaphore.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMPlayThroughRTLogger\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                            BGMPlayThroughRTLogger();\n                            ~BGMPlayThroughRTLogger();\n                            BGMPlayThroughRTLogger(const BGMPlayThroughRTLogger&) = delete;\n                            BGMPlayThroughRTLogger& operator=(\n                                    const BGMPlayThroughRTLogger&) = delete;\nprivate:\n    static semaphore_t      CreateSemaphore();\n\n#pragma mark Log Messages\n\npublic:\n    /*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */\n    void                    LogReleasingWaitingThreads();\n    /*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */\n    void                    LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError);\n\n    /*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */\n    void                    LogIfDroppedFrames(Float64 inFirstInputSampleTime,\n                                               Float64 inLastInputSampleTime);\n    /*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */\n    void                    LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,\n                                              CARingBuffer::SampleTime inReadHeadSampleTime,\n                                              Float64 inInToOutSampleOffset);\n\n    /*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */\n    void                    LogExceptionStoppingIOProc(const char* inCallerName)\n                            {\n                                LogExceptionStoppingIOProc(inCallerName, noErr, false);\n                            }\n    /*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */\n    void                    LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError)\n                            {\n                                LogExceptionStoppingIOProc(inCallerName, inError, true);\n                            }\n\nprivate:\n    void                    LogExceptionStoppingIOProc(const char* inCallerName,\n                                                       OSStatus inError,\n                                                       bool inErrorKnown);\n\npublic:\n    /*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */\n    void                    LogUnexpectedIOStateAfterStopping(const char* inCallerName,\n                                                              int inIOState);\n    /*! For BGMPlayThrough::InputDeviceIOProc and BGMPlayThrough::OutputDeviceIOProc. */\n    void                    LogRingBufferUnavailable(const char* inCallerName, bool inGotLock);\n    /*! For BGMPlayThrough::OutputDeviceIOProc. */\n    void                    LogIfRingBufferError_Fetch(CARingBufferError inError)\n                            {\n                                LogIfRingBufferError(inError, mRingBufferFetchError);\n                            }\n    /*! For BGMPlayThrough::InputDeviceIOProc. */\n    void                    LogIfRingBufferError_Store(CARingBufferError inError)\n                            {\n                                LogIfRingBufferError(inError, mRingBufferStoreError);\n                            }\n\nprivate:\n    void                    LogIfRingBufferError(CARingBufferError inError,\n                                                 std::atomic<CARingBufferError>& outError);\n\n    template <typename T, typename F>\n    void                    LogAsync(T& inMessageData, F&& inStoreMessageData);\n\n    // Wrapper methods used to mock out the logging for unit tests.\n    void                    LogSync_Warning(const char* inFormat, ...) __printflike(2, 3);\n    void                    LogSync_Error(const char* inFormat, ...) __printflike(2, 3);\n\n#pragma mark Logging Thread\n\nprivate:\n    void                    WakeLoggingThread();\n\n    void                    LogMessages();\n    void                    LogSync_ReleasingWaitingThreads();\n    void                    LogSync_ReleaseWaitingThreadsSignalError();\n    void                    LogSync_DroppedFrames();\n    void                    LogSync_NoSamplesReady();\n    void                    LogSync_ExceptionStoppingIOProc();\n    void                    LogSync_UnexpectedIOStateAfterStopping();\n    void                    LogSync_RingBufferUnavailable();\n    void                    LogSync_RingBufferError(\n                                std::atomic<CARingBufferError>& ioRingBufferError,\n                                const char* inMethodName);\n\n    // The entry point of the logging thread (mLoggingThread).\n    static void* __nullable LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon);\n\n#if BGM_UnitTest\n\n#pragma mark Test Helpers\n\npublic:\n    /*!\n     * @return True if the logger thread finished logging the requested messages. False if it still\n     *         had messages to log after 5 seconds.\n     */\n    bool                    WaitUntilLoggerThreadIdle();\n\n#endif /* BGM_UnitTest */\n\nprivate:\n    // For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart\n    std::atomic<bool>       mLogReleasingWaitingThreadsMsg { false };\n    std::atomic<kern_return_t> mReleaseWaitingThreadsSignalError { KERN_SUCCESS };\n\n    // For BGMPlayThrough::InputDeviceIOProc and BGMPlayThrough::OutputDeviceIOProc\n    struct {\n        Float64 firstInputSampleTime;\n        Float64 lastInputSampleTime;\n        std::atomic<bool> shouldLogMessage { false };\n    } mDroppedFrames;\n\n    struct {\n        CARingBuffer::SampleTime lastInputSampleTime;\n        CARingBuffer::SampleTime readHeadSampleTime;\n        Float64 inToOutSampleOffset;\n        std::atomic<bool> shouldLogMessage { false };\n    } mNoSamplesReady;\n\n    struct {\n        const char* callerName;\n        bool gotLock;\n        std::atomic<bool> shouldLogMessage { false };\n    } mRingBufferUnavailable;\n\n    // For BGMPlayThrough::UpdateIOProcState\n    struct {\n        const char* callerName;\n        int ioState;\n        std::atomic<bool> shouldLogMessage { false };\n    } mUnexpectedIOStateAfterStopping;\n\n    struct {\n        const char* callerName;\n        OSStatus error;\n        bool errorKnown;  // If false, we didn't get an error code from the exception.\n        std::atomic<bool> shouldLogMessage { false };\n    } mExceptionStoppingIOProc;\n\n    // For BGMPlayThrough::OutputDeviceIOProc\n    std::atomic<CARingBufferError> mRingBufferStoreError { kCARingBufferError_OK };\n    // For BGMPlayThrough::InputDeviceIOProc.\n    std::atomic<CARingBufferError> mRingBufferFetchError { kCARingBufferError_OK };\n\n    // Signalled to wake up the mLoggingThread when it has messages to log.\n    semaphore_t             mWakeUpLoggingThreadSemaphore;\n    std::atomic<bool>       mLoggingThreadShouldExit { false };\n    // The thread that actually logs the messages.\n    std::thread             mLoggingThread;\n\n#if BGM_UnitTest\n\npublic:\n    // Tests normally crash (abort) if LogError is called. This flag lets us test the code that\n    // would otherwise call LogError.\n    bool                    mContinueOnErrorLogged { false };\n\n    int                     mNumDebugMessagesLogged { 0 };\n    int                     mNumWarningMessagesLogged { 0 };\n    int                     mNumErrorMessagesLogged { 0 };\n\n#endif /* BGM_UnitTest */\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMPlayThroughRTLogger */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPreferredOutputDevices.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPreferredOutputDevices.h\n//  BGMApp\n//\n//  Copyright © 2018 Kyle Neideck\n//\n//  Tries to change BGMApp's output device when the user plugs in or unplugs an audio device, in the\n//  same way macOS would change its default device if Background Music wasn't running.\n//\n//  For example, if you plug in some USB headphones, make them your default device and then unplug\n//  them, macOS will change its default device to the previous default device. Then, if you plug\n//  them back in, macOS will make them the default device again.\n//\n//  This class isn't always able to figure out what macOS would do, in which case it leaves BGMApp's\n//  output device as it is.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <CoreAudio/AudioHardwareBase.h>\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMPreferredOutputDevices : NSObject\n\n// Starts responding to device connections/disconnections immediately. Stops if/when the instance is\n// deallocated.\n- (instancetype) initWithDevices:(BGMAudioDeviceManager*)devices\n                    userDefaults:(BGMUserDefaults*)userDefaults;\n\n// Returns the most-preferred device that's currently connected. If no preferred devices are\n// connected, returns the current output device. If the current output device has been disconnected,\n// returns an arbitrary device.\n//\n// If none of the connected devices can be used as the output device, or if it can't find a device\n// to use because the HAL returned errors when queried, returns kAudioObjectUnknown.\n- (AudioObjectID) findPreferredDevice;\n\n- (void) userChangedOutputDeviceTo:(AudioObjectID)device;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMPreferredOutputDevices.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPreferredOutputDevices.m\n//  BGMApp\n//\n//  Copyright © 2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMPreferredOutputDevices.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n#import \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#import \"CAAutoDisposer.h\"\n#import \"CAHALAudioSystemObject.h\"\n#import \"CAPropertyAddress.h\"\n\n\n#pragma clang assume_nonnull begin\n\n// The Plist file CoreAudio stores the preferred devices list in. It isn't part of the public API,\n// but if this class fails to read/parse it, BGMApp will continue without it.\nNSString* const kAudioSystemSettingsPlist =\n    @\"/Library/Preferences/Audio/com.apple.audio.SystemSettings.plist\";\n\n@implementation BGMPreferredOutputDevices {\n    NSRecursiveLock* _stateLock;\n\n    // Used to change BGMApp's output device.\n    BGMAudioDeviceManager* _devices;\n\n    // User settings and data.\n    BGMUserDefaults* _userDefaults;\n\n    // The UIDs of the preferred devices, in order of preference. The most-preferred device is at\n    // index 0. This list is derived from several sources.\n    NSArray<NSString*>* _preferredDeviceUIDs;\n\n    // Called when a device is connected or disconnected.\n    AudioObjectPropertyListenerBlock _deviceListListener;\n}\n\n- (instancetype) initWithDevices:(BGMAudioDeviceManager*)devices\n                    userDefaults:(BGMUserDefaults*)userDefaults {\n    if ((self = [super init])) {\n        _stateLock = [NSRecursiveLock new];\n        _devices = devices;\n        _userDefaults = userDefaults;\n        _preferredDeviceUIDs = [self readPreferredDevices];\n\n        DebugMsg(\"BGMPreferredOutputDevices::initWithDevices: Preferred devices: %s\",\n                 _preferredDeviceUIDs.debugDescription.UTF8String);\n\n        [self listenForDevicesAddedOrRemoved];\n    }\n\n    return self;\n}\n\n- (void) dealloc {\n    @try {\n        [_stateLock lock];\n\n        // Tell CoreAudio not to call the listener block anymore.\n        CAHALAudioSystemObject().RemovePropertyListenerBlock(\n            CAPropertyAddress(kAudioHardwarePropertyDevices),\n            dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),\n            _deviceListListener);\n    } @finally {\n        [_stateLock unlock];\n    }\n}\n\n// Reads the preferred devices list from CoreAudio's Plist file. Uses BGMApp's stored list to fill\n// in the blanks, if necessary.\n- (NSArray<NSString*>*) readPreferredDevices {\n    // Read the Plist file into a dictionary.\n    //\n    // TODO: If this file doesn't exist, try paths used by older versions of macOS. (If there are\n    //       any, that is. I haven't checked.)\n    NSURL* url = [NSURL fileURLWithPath:kAudioSystemSettingsPlist];\n    NSError* error = nil;\n    NSData* data = [NSData dataWithContentsOfURL:url options:0 error:&error];\n    NSDictionary* settings = nil;\n\n    if (data && !error) {\n        settings = [NSPropertyListSerialization propertyListWithData:data\n                                                             options:NSPropertyListImmutable\n                                                              format:nil\n                                                               error:&error];\n    }\n\n    // Default to a list with just the systemwide default device (or an empty list if that fails) if\n    // we can't read the preferred devices from the Plist because preferredDeviceUIDsFrom will use\n    // BGMApp's stored preferred devices to fill in the rest optimistically. This doesn't help us\n    // tell when to switch to a newly connected device, but it should improve our chances of\n    // switching to the best device if the current output device is disconnected.\n    NSArray<NSDictionary*>* _Nonnull preferredOutputDeviceInfos = @[];\n\n    // If we can't read the Plist, we only know that the current systemwide default device is the\n    // most-preferred device that's currently connected.\n    //\n    // TODO: If we are able to read the Plist, check that the systemwide default device is the\n    //       most-preferred device in the list from the Plist that's also connected. If it isn't,\n    //       the format of the Plist has probably changed, so we should ignore its data and log a\n    //       warning.\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        BGMAudioDevice defaultDevice = CAHALAudioSystemObject().GetDefaultAudioDevice(false, false);\n        NSString* __nullable defaultDeviceUID =\n            (__bridge_transfer NSString* __nullable)defaultDevice.CopyDeviceUID();\n\n        if (defaultDeviceUID) {\n            preferredOutputDeviceInfos = @[ @{ @\"uid\": BGMNN(defaultDeviceUID) } ];\n        }\n    });\n\n    if (error || !data || !settings) {\n        // The Plist file either doesn't exist or we weren't able to parse it.\n        LogWarning(\"BGMPreferredOutputDevices::readPreferredDevices: Couldn't read %s. \"\n                   \"(data = %s, settings = %s) Error: %s\",\n                   kAudioSystemSettingsPlist.UTF8String,\n                   data.debugDescription.UTF8String,\n                   settings.debugDescription.UTF8String,\n                   error.debugDescription.UTF8String);\n    } else if (!settings[@\"preferred devices\"]) {\n        // The Plist doesn't include the lists of preferred devices (for input, output and system\n        // output).\n        LogWarning(\"BGMPreferredOutputDevices::readPreferredDevices: No preferred devices in %s\",\n                   settings.debugDescription.UTF8String);\n    } else if (!settings[@\"preferred devices\"][@\"output\"]) {\n        // The Plist doesn't include the list of preferred output devices.\n        LogWarning(\"BGMPreferredOutputDevices::readPreferredDevices: \"\n                   \"No preferred output devices in %s\",\n                   settings.debugDescription.UTF8String);\n    } else {\n        // Copy the preferred devices out of the Plist.\n        preferredOutputDeviceInfos = BGMNN(settings[@\"preferred devices\"][@\"output\"]);\n    }\n\n    return [self preferredDeviceUIDsFrom:preferredOutputDeviceInfos\n               storedPreferredDeviceUIDs:_userDefaults.preferredDeviceUIDs];\n}\n\n- (NSArray<NSString*>*) preferredDeviceUIDsFrom:(NSArray<NSDictionary*>*)deviceInfos\n                      storedPreferredDeviceUIDs:(NSArray<NSString*>*)storedUIDs {\n    NSArray<NSString*>* deviceUIDs = @[];\n    int storedPreferredDeviceIdx = 0;\n\n    for (NSDictionary* deviceInfo in deviceInfos) {\n        // Check the Plist actually has a UID for this device.\n        if (![deviceInfo[@\"uid\"] isKindOfClass:NSString.class]) {\n            LogWarning(\"BGMPreferredOutputDevices::preferredDeviceUIDsFrom: No UID in %s\",\n                       deviceInfo.debugDescription.UTF8String);\n            continue;\n        }\n\n        NSString* uid = deviceInfo[@\"uid\"];\n\n        if ([uid isEqualToString:@kBGMDeviceUID] ||\n            [uid isEqualToString:@kBGMDeviceUID_UISounds] ||\n            [uid isEqualToString:@kBGMNullDeviceUID]) {\n            // This is one of the Background Music devices, so look for a preferred device saved\n            // from a previous run of BGMApp and add it instead.\n            //\n            // BGMApp has to set BGMDevice, and often also the Null Device for a short time, as the\n            // systemwide default audio device, which makes CoreAudio put them in its Plist. And\n            // since the Plist is limited to three devices, it only gives us one or two usable ones.\n            // Ideally, CoreAudio just wouldn't add our devices to its list, but I don't think we\n            // can prevent that. And we can't be sure that editing its Plist file ourselves would be\n            // safe.\n            //\n            // TODO: This doesn't work if the user has made BGMDevice the systemwide default device\n            //       themselves since the last time they closed BGMApp. We might be able to fix that\n            //       by having a background process watch the Plist for changes while BGMApp is\n            //       closed or something like that, but I doubt there's a nice or simple solution.\n            deviceUIDs = [self addNextStoredPreferredDevice:&storedPreferredDeviceIdx\n                                        preferredDeviceUIDs:deviceUIDs\n                                  storedPreferredDeviceUIDs:storedUIDs];\n        } else if (![deviceUIDs containsObject:uid]) {\n            // Add this preferred device's UID to the list.\n            deviceUIDs = [deviceUIDs arrayByAddingObject:uid];\n        }\n    }\n\n    // Fill in any remaining places in the list with stored devices. Limit the list to three devices\n    // just to match CoreAudio's behaviour.\n    while ((storedPreferredDeviceIdx < storedUIDs.count) && (deviceUIDs.count < 3)) {\n        deviceUIDs = [self addNextStoredPreferredDevice:&storedPreferredDeviceIdx\n                                    preferredDeviceUIDs:deviceUIDs\n                              storedPreferredDeviceUIDs:storedUIDs];\n    }\n\n    return deviceUIDs;\n}\n\n- (NSArray<NSString*>*) addNextStoredPreferredDevice:(int*)storedPreferredDeviceIdx\n                                 preferredDeviceUIDs:(NSArray<NSString*>*)deviceUIDs\n                           storedPreferredDeviceUIDs:(NSArray<NSString*>*)storedUIDs {\n    NSString* __nullable storedPreferredDevice;\n\n    // Try to find a stored UID that isn't already in the list.\n    do {\n        storedPreferredDevice = (*storedPreferredDeviceIdx < storedUIDs.count) ?\n                                    storedUIDs[*storedPreferredDeviceIdx] : nil;\n        (*storedPreferredDeviceIdx)++;\n    } while (storedPreferredDevice && [deviceUIDs containsObject:BGMNN(storedPreferredDevice)]);\n\n    // If we found a stored UID, add it to the list.\n    if (storedPreferredDevice) {\n        DebugMsg(\"BGMPreferredOutputDevices::addNextStoredPreferredDevice: \"\n                 \"Adding stored preferred device: %s\",\n                 storedPreferredDevice.UTF8String);\n        deviceUIDs = [deviceUIDs arrayByAddingObject:BGMNN(storedPreferredDevice)];\n    }\n\n    return deviceUIDs;\n}\n\n- (void) listenForDevicesAddedOrRemoved {\n    // Create the block that will run when a device is added or removed.\n    BGMPreferredOutputDevices* __weak weakSelf = self;\n\n    _deviceListListener = ^(UInt32 inNumberAddresses,\n                            const AudioObjectPropertyAddress* inAddresses) {\n        #pragma unused (inNumberAddresses, inAddresses)\n\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            [weakSelf connectedDeviceListChanged];\n        });\n    };\n\n    // Register the listener block with CoreAudio.\n    CAHALAudioSystemObject().AddPropertyListenerBlock(\n        CAPropertyAddress(kAudioHardwarePropertyDevices),\n        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),\n        _deviceListListener);\n}\n\n- (void) connectedDeviceListChanged {\n    @try {\n        [_stateLock lock];\n\n        // Decide which device should be the output device now. If a device has been connected and\n        // it's preferred over the current output device, we'll change to that device. If the\n        // current output device has been removed, we'll change to the next most-preferred device.\n        AudioObjectID preferredDevice = [self findPreferredDevice];\n\n        if (preferredDevice == kAudioObjectUnknown) {\n            LogWarning(\"BGMPreferredOutputDevices::connectedDeviceListChanged: \"\n                       \"No preferred device found.\");\n        } else if (_devices.outputDevice.GetObjectID() == preferredDevice) {\n            DebugMsg(\"BGMPreferredOutputDevices::connectedDeviceListChanged: \"\n                     \"The preferred device is already set as the output device.\");\n        } else {\n            // Change to the preferred device.\n            DebugMsg(\"BGMPreferredOutputDevices::connectedDeviceListChanged: \"\n                     \"Changing output device to %d.\",\n                     preferredDevice);\n            NSError* __nullable error = [_devices setOutputDeviceWithID:preferredDevice\n                                                        revertOnFailure:YES];\n            if (error) {\n                // There's not much we can do if this happens.\n                LogError(\"BGMPreferredOutputDevices::connectedDeviceListChanged: \"\n                         \"Failed to change to preferred device. Error: %s\",\n                         error.debugDescription.UTF8String);\n            }\n        }\n    } @finally {\n        [_stateLock unlock];\n    }\n}\n\n- (AudioObjectID) findPreferredDevice {\n    AudioObjectID preferredDevice = kAudioObjectUnknown;\n    CAHALAudioSystemObject audioSystem;\n\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        BGMAudioDevice defaultDevice = audioSystem.GetDefaultAudioDevice(false, false);\n\n        if (!defaultDevice.IsBGMDeviceInstance()) {\n            // BGMDevice isn't the systemwide default device, so we know the default device is the\n            // most-preferred device that's currently connected.\n            preferredDevice = defaultDevice;\n        }\n    });\n\n    if (preferredDevice == kAudioObjectUnknown) {\n        // BGMDevice is the systemwide default device, so this method is probably being called after\n        // launch, since we set BGMDevice as the default device then. It could also be that the user\n        // set it manually or that BGMApp failed to change it back the last time it closed. Either\n        // way, we'll try to find a device to use in the Plist or stored list instead.\n        DebugMsg(\"BGMPreferredOutputDevices::findPreferredDevice: Checking Plist and stored list.\");\n\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            preferredDevice = [self findPreferredDeviceInDerivedList];\n        });\n    }\n\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        if (preferredDevice == kAudioObjectUnknown && [self isCurrentOutputDeviceConnected]) {\n            // The output device (for BGMApp) has been set and is still connected. We haven't found\n            // a better device to use, so prefer leaving the output device as it is.\n            DebugMsg(\"BGMPreferredOutputDevices::findPreferredDevice: \"\n                     \"Choosing the current output device as the preferred device.\");\n            preferredDevice = _devices.outputDevice.GetObjectID();\n        }\n    });\n\n    if (preferredDevice == kAudioObjectUnknown) {\n        // The current output device has been disconnected or hasn't been set yet and there are no\n        // preferred devices connected, so pick one arbitrarily.\n        DebugMsg(\"BGMPreferredOutputDevices::findPreferredDevice: Choosing an arbitrary device.\");\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            preferredDevice = [self findPreferredDeviceByLatency];\n        });\n    }\n\n    return preferredDevice;\n}\n\n// Looks for a suitable device in _preferredDeviceUIDs, the list of preferred devices derived from\n// CoreAudio's Plist and BGMApp's stored list.\n- (AudioObjectID) findPreferredDeviceInDerivedList {\n    CAHALAudioSystemObject audioSystem;\n    UInt32 numDevices = audioSystem.GetNumberAudioDevices();\n\n    // Get the list of currently connected audio devices.\n    CAAutoArrayDelete<AudioObjectID> devices(numDevices);\n    audioSystem.GetAudioDevices(numDevices, devices);\n\n    // Look through the preferred devices list to see if one's connected. Return the first one we\n    // find because they're stored in order of preference.\n    for (int position = 0; position < _preferredDeviceUIDs.count; position++) {\n        // Compare the current preferred device to each connected device by UID.\n        for (UInt32 i = 0; i < numDevices; i++) {\n            NSString* __nullable connectedDeviceUID = nil;\n\n            BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n                // Skip devices we can't use, e.g. BGMDevice.\n                if (BGMAudioDevice(devices[i]).CanBeOutputDeviceInBGMApp()) {\n                    // Get the connected device's UID.\n                    connectedDeviceUID =\n                        (__bridge NSString* __nullable)CAHALAudioDevice(devices[i]).CopyDeviceUID();\n                }\n            });\n\n            // If the UIDs match, the current preferred device is connected.\n            //\n            // If you plug a USB device in to different USB port, macOS might assign it a different\n            // UID and make it fail to match its old UID here. CoreAudio/macOS doesn't seem to\n            // handle that case either, though, so it's probably not worth worrying about.\n            if ([connectedDeviceUID isEqualToString:_preferredDeviceUIDs[position]]) {\n                // We're iterating through the preferred devices from most to least-preferred, so\n                // we've found the device to use.\n                DebugMsg(\"BGMPreferredOutputDevices::findPreferredDeviceInDerivedList: \"\n                         \"Found preferred device '%s' at position %d\",\n                         _preferredDeviceUIDs[position].UTF8String,\n                         position);\n                return devices[i];\n            }\n        }\n\n        DebugMsg(\"BGMPreferredOutputDevices::findPreferredDeviceInDerivedList: \"\n                 \"Preferred device not connected: %s\",\n                 _preferredDeviceUIDs[position].UTF8String);\n    }\n\n    return kAudioObjectUnknown;\n}\n\n- (bool) isCurrentOutputDeviceConnected {\n    if (_devices.outputDevice.GetObjectID() == kAudioObjectUnknown) {\n        DebugMsg(\"BGMPreferredOutputDevices::isCurrentOutputDeviceConnected: \"\n                 \"The output device hasn't been set yet.\");\n        return false;\n    }\n\n    CAHALAudioSystemObject audioSystem;\n    UInt32 numDevices = audioSystem.GetNumberAudioDevices();\n\n    // Get the list of currently connected audio devices.\n    CAAutoArrayDelete<AudioObjectID> devices(numDevices);\n    audioSystem.GetAudioDevices(numDevices, devices);\n\n    // Look for the current output device in the list of connected devices.\n    for (UInt32 i = 0; i < numDevices; i++) {\n        // TODO: Are AudioObjectIDs reused? If they are, could that cause a collision here?\n        if (_devices.outputDevice.GetObjectID() == devices[i]) {\n            DebugMsg(\"BGMPreferredOutputDevices::isCurrentOutputDeviceConnected: \"\n                     \"The output device is connected.\");\n            return true;\n        }\n    }\n\n    DebugMsg(\"BGMPreferredOutputDevices::isCurrentOutputDeviceConnected: \"\n             \"The output device is not connected.\");\n    return false;\n}\n\n// Returns the audio output device with the lowest latency. Used when we have no better way to\n// choose the output device for BGMApp to use.\n- (AudioObjectID) findPreferredDeviceByLatency {\n    CAHALAudioSystemObject audioSystem;\n    UInt32 numDevices = audioSystem.GetNumberAudioDevices();\n    AudioObjectID minLatencyDevice = kAudioObjectUnknown;\n    UInt32 minLatency = UINT32_MAX;\n\n    CAAutoArrayDelete<AudioObjectID> devices(numDevices);\n    audioSystem.GetAudioDevices(numDevices, devices);\n\n    for (UInt32 i = 0; i < numDevices; i++) {\n        BGMAudioDevice device(devices[i]);\n\n        if (!device.IsBGMDeviceInstance()) {\n            BOOL hasOutputChannels = NO;\n\n            BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, \"GetTotalNumberChannels\", [&] {\n                hasOutputChannels = device.GetTotalNumberChannels(/* inIsInput = */ false) > 0;\n            });\n\n            if (hasOutputChannels) {\n                BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, \"GetLatency\", [&] {\n                    UInt32 latency = device.GetLatency(false);\n\n                    if (latency < minLatency) {\n                        minLatencyDevice = devices[i];\n                        minLatency = latency;\n                    }\n                });\n            }\n        }\n    }\n\n    return minLatencyDevice;\n}\n\n- (void) userChangedOutputDeviceTo:(AudioObjectID)device {\n    @try {\n        [_stateLock lock];\n\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            // Add the new output device to the list.\n            NSString* __nullable outputDeviceUID =\n                (__bridge_transfer NSString* __nullable)CAHALAudioDevice(device).CopyDeviceUID();\n\n            if (outputDeviceUID) {\n                // Limit the list to three devices because that's what macOS does.\n                if (_preferredDeviceUIDs.count >= 2) {\n                    _preferredDeviceUIDs = @[BGMNN(outputDeviceUID),\n                                          _preferredDeviceUIDs[0],\n                                          _preferredDeviceUIDs[1]];\n                } else if (_preferredDeviceUIDs.count >= 1) {\n                    _preferredDeviceUIDs = @[BGMNN(outputDeviceUID), _preferredDeviceUIDs[0]];\n                } else {\n                    _preferredDeviceUIDs = @[BGMNN(outputDeviceUID)];\n                }\n\n                DebugMsg(\"BGMPreferredOutputDevices::userChangedOutputDeviceTo: \"\n                         \"Preferred devices: %s\",\n                         _preferredDeviceUIDs.debugDescription.UTF8String);\n\n                // Save the list.\n                _userDefaults.preferredDeviceUIDs = _preferredDeviceUIDs;\n            } else {\n                LogWarning(\"BGMPreferredOutputDevices::userChangedOutputDeviceTo: \"\n                           \"Output device has no UID\");\n            }\n        });\n    } @finally {\n        [_stateLock unlock];\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMStatusBarItem.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMStatusBarItem.h\n//  BGMApp\n//\n//  Copyright © 2019, 2020 Kyle Neideck\n//\n//  The button in the system status bar (the bar with volume, battery, clock, etc.) to show the main\n//  menu for the app. These are called \"menu bar extras\" in the Human Interface Guidelines.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMDebugLoggingMenuItem.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n// Forward Declarations\n@class BGMUserDefaults;\n\n\n#pragma clang assume_nonnull begin\n\ntypedef NS_ENUM(NSInteger, BGMStatusBarIcon) {\n    BGMFermataStatusBarIcon = 0,\n    BGMVolumeStatusBarIcon\n};\n\nstatic BGMStatusBarIcon const kBGMStatusBarIconMinValue     = BGMFermataStatusBarIcon;\nstatic BGMStatusBarIcon const kBGMStatusBarIconMaxValue     = BGMVolumeStatusBarIcon;\nstatic BGMStatusBarIcon const kBGMStatusBarIconDefaultValue = BGMFermataStatusBarIcon;\n\n@interface BGMStatusBarItem : NSObject\n\n- (instancetype) initWithMenu:(NSMenu*)bgmMenu\n                 audioDevices:(BGMAudioDeviceManager*)devices\n                 userDefaults:(BGMUserDefaults*)defaults;\n\n// Set this to BGMFermataStatusBarIcon to change the icon to the Background Music logo.\n//\n// Set this to BGMFermataStatusBarIcon to change the icon to a volume icon. This icon has the\n// advantage of indicating the volume level, but we can't make it the default because it looks the\n// same as the icon for the macOS volume status bar item.\n@property BGMStatusBarIcon icon;\n\n// If the user holds down the option key when they click the status bar icon, this menu item will be\n// shown in the main menu.\n- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMStatusBarItem.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMStatusBarItem.m\n//  BGMApp\n//\n//  Copyright © 2019, 2020 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMStatusBarItem.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMUserDefaults.h\"\n#import \"BGMVolumeChangeListener.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic CGFloat const kStatusBarIconPadding                = 0.25;\nstatic CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;\n\n@implementation BGMStatusBarItem\n{\n    BGMAudioDeviceManager* audioDevices;\n\n    // User settings and data.\n    BGMUserDefaults* userDefaults;\n\n    NSImage* fermataIcon;\n    NSImage* volumeIcon0SoundWaves;\n    NSImage* volumeIcon1SoundWave;\n    NSImage* volumeIcon2SoundWaves;\n    NSImage* volumeIcon3SoundWaves;\n\n    NSStatusItem* statusBarItem;\n    BGMDebugLoggingMenuItem* debugLoggingMenuItem;\n\n    BGMVolumeChangeListener* volumeChangeListener;\n    id __nullable clickEventHandler;\n\n    BGMStatusBarIcon _icon;\n}\n\n#pragma mark Initialisation\n\n- (instancetype) initWithMenu:(NSMenu*)bgmMenu\n                 audioDevices:(BGMAudioDeviceManager*)devices\n                 userDefaults:(BGMUserDefaults*)defaults {\n    if ((self = [super init])) {\n        statusBarItem =\n                [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];\n\n        audioDevices = devices;\n        userDefaults = defaults;\n\n        // Initialise the icons.\n        [self initIcons];\n\n        // Set the initial icon.\n        self.icon = userDefaults.statusBarIcon;\n\n        // Set the menu item to open the main menu.\n        statusBarItem.menu = bgmMenu;\n\n        // Monitor click events so we can show extra options in the menu if the user was holding the\n        // option key.\n        clickEventHandler = [self addClickMonitor];\n\n        // Set the accessibility label to \"Background Music\". (We intentionally don't set a title or\n        // a tooltip.)\n        if ([BGMStatusBarItem buttonAvailable]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n            statusBarItem.button.accessibilityLabel =\n                    [NSRunningApplication currentApplication].localizedName;\n#pragma clang diagnostic pop\n        }\n\n        // Update the icon when BGMDevice's volume changes.\n        BGMStatusBarItem* __weak weakSelf = self;\n        volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [=] {\n            [weakSelf bgmDeviceVolumeDidChange];\n        });\n    }\n\n    return self;\n}\n\n- (id __nullable) addClickMonitor {\n    NSEvent* __nullable (^handlerBlock)(NSEvent*) =\n        ^NSEvent* __nullable (NSEvent* event) {\n            [self statusBarItemWasClicked:event];\n            return event;\n        };\n\n    // TODO: I doubt this works well with VoiceOver.\n    return [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown\n                                                 handler:handlerBlock];\n}\n\n- (void) dealloc {\n    delete volumeChangeListener;\n\n    if (clickEventHandler) {\n        [NSEvent removeMonitor:(id)clickEventHandler];\n        clickEventHandler = nil;\n    }\n}\n\n- (void) initIcons {\n    // Load the icons.\n    fermataIcon = [NSImage imageNamed:@\"FermataIcon\"];\n    if (@available(macOS 11.0, *)) {\n        volumeIcon0SoundWaves = [NSImage imageWithSystemSymbolName:@\"speaker.fill\" accessibilityDescription:nil];\n        volumeIcon1SoundWave = [NSImage imageWithSystemSymbolName:@\"speaker.wave.1.fill\" accessibilityDescription:nil];\n        volumeIcon2SoundWaves = [NSImage imageWithSystemSymbolName:@\"speaker.wave.2.fill\" accessibilityDescription:nil];\n        volumeIcon3SoundWaves = [NSImage imageWithSystemSymbolName:@\"speaker.wave.3.fill\" accessibilityDescription:nil];\n    } else {\n        volumeIcon0SoundWaves = [NSImage imageNamed:@\"Volume0\"];\n        volumeIcon1SoundWave = [NSImage imageNamed:@\"Volume1\"];\n        volumeIcon2SoundWaves = [NSImage imageNamed:@\"Volume2\"];\n        volumeIcon3SoundWaves = [NSImage imageNamed:@\"Volume3\"];\n    }\n\n    // Set the icons' sizes.\n    NSRect statusBarItemFrame;\n\n    if ([BGMStatusBarItem buttonAvailable]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        statusBarItemFrame = statusBarItem.button.frame;\n#pragma clang diagnostic pop\n    } else {\n        // OS X 10.9 fallback. I haven't tested this (or anything else on 10.9).\n        statusBarItemFrame = statusBarItem.view.frame;\n    }\n\n    CGFloat heightMinusPadding = statusBarItemFrame.size.height * (1 - kStatusBarIconPadding);\n\n    // The fermata icon has equal width and height.\n    [fermataIcon setSize:NSMakeSize(heightMinusPadding, heightMinusPadding)];\n\n    // The volume icons are all the same width and height.\n    CGFloat volumeIconWidthToHeightRatio =\n            volumeIcon0SoundWaves.size.width / volumeIcon0SoundWaves.size.height;\n    CGFloat volumeIconWidth = heightMinusPadding * volumeIconWidthToHeightRatio;\n    CGFloat volumeIconHeight = heightMinusPadding * (1 - kVolumeIconAdditionalVerticalPadding);\n\n    [volumeIcon0SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];\n    [volumeIcon1SoundWave setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];\n    [volumeIcon2SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];\n    [volumeIcon3SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];\n\n    // Make the icons \"template images\" so they get drawn colour-inverted when they're highlighted\n    // or the system is in dark mode.\n    [fermataIcon setTemplate:YES];\n    [volumeIcon0SoundWaves setTemplate:YES];\n    [volumeIcon1SoundWave setTemplate:YES];\n    [volumeIcon2SoundWaves setTemplate:YES];\n    [volumeIcon3SoundWaves setTemplate:YES];\n}\n\n#pragma mark Accessors\n\n+ (BOOL) buttonAvailable {\n    // NSStatusItem doesn't have the \"button\" property on OS X 10.9.\n    return (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10);\n}\n\n- (void) setImage:(NSImage*)image {\n    if ([BGMStatusBarItem buttonAvailable]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        statusBarItem.button.image = image;\n#pragma clang diagnostic pop\n    } else {\n        statusBarItem.image = image;\n    }\n}\n\n- (BGMStatusBarIcon) icon {\n    return _icon;\n}\n\n- (void) setIcon:(BGMStatusBarIcon)icon {\n    _icon = icon;\n\n    // Save the setting.\n    userDefaults.statusBarIcon = self.icon;\n\n    // Change the icon (i.e. the image). Dispatch this to the main thread because it changes the UI.\n    dispatch_async(dispatch_get_main_queue(), ^{\n        if (_icon == BGMFermataStatusBarIcon) {\n            [self setImage:fermataIcon];\n\n            // If the icon was greyed out, change it back.\n            if ([BGMStatusBarItem buttonAvailable]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n                statusBarItem.button.appearsDisabled = NO;\n#pragma clang diagnostic pop\n            }\n        } else {\n            BGMAssert((_icon == BGMVolumeStatusBarIcon), \"Unknown icon in enum\");\n\n            [self updateVolumeStatusBarIcon];\n        }\n    });\n}\n\n#pragma mark Volume Icon\n\n- (void) bgmDeviceVolumeDidChange {\n    if (self.icon == BGMVolumeStatusBarIcon) {\n        [self updateVolumeStatusBarIcon];\n    }\n}\n\n// Should only be called on the main thread because it calls UI functions.\n- (void) updateVolumeStatusBarIcon {\n    BGMAssert([[NSThread currentThread] isMainThread],\n              \"updateVolumeStatusBarIcon called on non-main thread.\");\n    BGMAssert((self.icon == BGMVolumeStatusBarIcon), \"Volume status bar icon not enabled\");\n\n    BGMAudioDevice bgmDevice = [audioDevices bgmDevice];\n\n    // BGMDevice should never return an error for these calls, so we just swallow any exceptions and\n    // give up.\n    BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n        AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;\n        AudioObjectPropertyScope element = kAudioObjectPropertyElementMaster;\n\n        BOOL hasVolume = bgmDevice.HasVolumeControl(scope, element);\n\n        // Show the button as greyed out if BGMDevice doesn't have a volume control (which means the\n        // output device doesn't have one).\n        if ([BGMStatusBarItem buttonAvailable]) {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n            statusBarItem.button.appearsDisabled = !hasVolume;\n#pragma clang diagnostic pop\n        }\n\n        if (hasVolume) {\n            if (bgmDevice.HasMuteControl(scope, element) &&\n                    bgmDevice.GetMuteControlValue(scope, element)) {\n                // The device is muted, so use the zero waves icon.\n                [self setImage:volumeIcon0SoundWaves];\n            } else {\n                // Set the icon to reflect the device's volume.\n                double volume = bgmDevice.GetVolumeControlScalarValue(scope, element);\n\n                // These values match the macOS volume status bar item, except for the first one. I\n                // don't know why, but at a very low volume macOS will show the zero waves icon even\n                // though the sound is still audible.\n                if (volume == 0.05) {\n                    [self setImage:volumeIcon0SoundWaves];\n                } else if (volume < 0.33) {\n                    [self setImage:volumeIcon1SoundWave];\n                } else if (volume < 0.66) {\n                    [self setImage:volumeIcon2SoundWaves];\n                } else {\n                    [self setImage:volumeIcon3SoundWaves];\n                }\n            }\n        } else {\n            // Always use the full-volume icon when the device has no volume control.\n            [self setImage:volumeIcon3SoundWaves];\n        }\n    });\n\n    DebugMsg(\"BGMStatusBarItem::updateVolumeStatusBarIcon: Set icon to %s\",\n             statusBarItem.image.name.UTF8String);\n}\n\n#pragma mark Debug Logging Menu Item\n\n- (void) statusBarItemWasClicked:(NSEvent* __nonnull)event {\n    if ((event.modifierFlags & NSEventModifierFlagOption) != 0) {\n        DebugMsg(\"BGMStatusBarItem::statusBarItemWasClicked: Option key held\");\n        [debugLoggingMenuItem setMenuShowingExtraOptions:YES];\n    } else {\n        [debugLoggingMenuItem setMenuShowingExtraOptions:NO];\n    }\n}\n\n- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem {\n    debugLoggingMenuItem = menuItem;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMSystemSoundsVolume.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSystemSoundsVolume.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  The menu item with the volume slider that controls system-related sounds. The slider is used to\n//  set the volume of the instance of BGMDevice that system sounds are played on, i.e. the audio\n//  device returned by BGMBackgroundMusicDevice::GetUISoundsBGMDeviceInstance.\n//\n//  System sounds are any sounds played using the audio device macOS is set to use as the device\n//  \"for system related sound from the alert sound to digital call progress\". See\n//  kAudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h. They can be played by any\n//  app, though most apps use systemsoundserverd to play their system sounds, which means BGMDriver\n//  can't tell which app is actually playing the sounds.\n//\n\n// Local Includes\n#import \"BGMAudioDevice.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMSystemSoundsVolume : NSObject\n\n// The volume level of uiSoundsDevice will be used to set the slider's initial position and will be\n// updated when the user moves the slider. view and slider are the UI elements from MainMenu.xib.\n- (instancetype) initWithUISoundsDevice:(BGMAudioDevice)uiSoundsDevice\n                                   view:(NSView*)view\n                                 slider:(NSSlider*)slider;\n\n// The menu item with the volume slider for system sounds.\n@property (readonly) NSMenuItem* menuItem;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMSystemSoundsVolume.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSystemSoundsVolume.mm\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMSystemSoundsVolume.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n\n\n#pragma clang assume_nonnull begin\n\n// TODO: It's a bit confusing that this slider's default position is all the way right, but the App\n//       Volumes sliders default to 50%. After you move the slider there's no way to tell how to put\n//       it back to its normal position.\n\nNSString* const kMenuItemToolTip =\n    @\"Alerts, notification sounds, etc. Usually short. Can be played by any app.\";\n\n@implementation BGMSystemSoundsVolume {\n    BGMAudioDevice uiSoundsDevice;\n    NSSlider* volumeSlider;\n}\n\n- (instancetype) initWithUISoundsDevice:(BGMAudioDevice)inUISoundsDevice\n                                   view:(NSView*)inView\n                                 slider:(NSSlider*)inSlider {\n    if ((self = [super init])) {\n        uiSoundsDevice = inUISoundsDevice;\n        volumeSlider = inSlider;\n\n        _menuItem = [[NSMenuItem alloc] initWithTitle:@\"\" action:nil keyEquivalent:@\"\"];\n        _menuItem.toolTip = kMenuItemToolTip;\n\n        // Apply our custom view from MainMenu.xib. It's very similar to the one for app volumes.\n        _menuItem.view = inView;\n\n        try {\n            volumeSlider.floatValue =\n                uiSoundsDevice.GetVolumeControlScalarValue(kAudioObjectPropertyScopeOutput,\n                                                           kMasterChannel);\n        } catch (const CAException& e) {\n            BGMLogException(e);\n            volumeSlider.floatValue = 1.0f;  // Full volume\n        }\n\n        volumeSlider.target = self;\n        volumeSlider.action = @selector(systemSoundsSliderChanged:);\n    }\n\n    return self;\n}\n\n- (void) systemSoundsSliderChanged:(id)sender {\n    #pragma unused(sender)\n\n    float sliderLevel = volumeSlider.floatValue;\n\n    BGMAssert((sliderLevel >= 0.0f) && (sliderLevel <= 1.0f), \"Invalid value from slider\");\n    DebugMsg(\"BGMSystemSoundsVolume::systemSoundsSliderChanged: UI sounds volume: %f\", sliderLevel);\n\n    BGMLogAndSwallowExceptions(\"BGMSystemSoundsVolume::systemSoundsSliderChanged\", ([&] {\n        uiSoundsDevice.SetVolumeControlScalarValue(kAudioObjectPropertyScopeOutput,\n                                                   kMasterChannel,\n                                                   sliderLevel);\n    }));\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMTermination.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMTermination.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  Cleans up if BGMApp crashes because of an uncaught C++ or Objective-C exception, or is sent\n//  SIGINT/SIGTERM/SIGQUIT. Currently, it just changes the default output device from BGMDevice to\n//  the real output device and records debug info for some types of crashes.\n//\n//  BGMXPCHelper also changes the default device if BGMApp disconnects and leaves BGMDevice as the\n//  default. This handles cases like segfaults where it wouldn't be safe to clean up from the\n//  crashing process.\n//\n\n#ifndef BGMApp__BGMTermination\n#define BGMApp__BGMTermination\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n\n// PublicUtility Includes\n#import \"CAPThread.h\"\n\n// STL Includes\n#import <exception>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMTermination\n{\n\npublic:\n    /*!\n     Starts a thread that will clean up before exiting if BGMApp receives SIGINT, SIGTERM or\n     SIGQUIT. Sets a similar clean up function to run if BGMApp terminates due to an uncaught\n     exception.\n     */\n    static void                      SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevices);\n\n    /*! Some commented out ways to have BGMApp crash for testing. Does nothing if unmodified. */\n    static void                      TestCrash() __attribute__((noinline));\n\nprivate:\n    static void                      StartExitSignalsThread();\n\n    static void                      CleanUpAudioDevices();\n\n    /*! Adds some info about the uncaught exception that caused a crash to the crash report. */\n    static void                      AddCurrentExceptionToCrashReport();\n\n    /*! The entry point for sExitSignalsThread. */\n    static void* __nullable          ExitSignalsProc(void* __nullable ignored);\n\n    /*! The thread that handles SIGQUIT, SIGTERM and SIGINT. Never destroyed. */\n    static CAPThread* const          sExitSignalsThread;\n    static sigset_t                  sExitSignals;\n\n    /*! The function that handles std::terminate by default. */\n    static std::terminate_handler    sOriginalTerminateHandler;\n\n    /*! The audio device manager. (Must be static to be accessed in our std::terminate_handler.) */\n    static BGMAudioDeviceManager* __nullable sAudioDevices;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMApp__BGMTermination */\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMTermination.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMTermination.mm\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMTermination.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n// STL Includes\n#import <string>\n\n// System Includes\n#import <signal.h>\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\nstd::terminate_handler BGMTermination::sOriginalTerminateHandler = std::get_terminate();\n\nCAPThread* const       BGMTermination::sExitSignalsThread = new CAPThread(ExitSignalsProc, nullptr);\nsigset_t               BGMTermination::sExitSignals;\n\nBGMAudioDeviceManager* __nullable BGMTermination::sAudioDevices = nullptr;\n\n// If BGMApp crashes, CrashReporter will read this string from our process' memory and include it in\n// the crash report.\nconst char* __nullable __crashreporter_info__ = nullptr;\n// Set the REFERENCED_DYNAMICALLY bit so the symbol doesn't get stripped from the binary. See\n// <https://developer.apple.com/library/content/documentation/DeveloperTools/Reference/Assembler/040-Assembler_Directives/asm_directives.html>\n// and\n// <https://github.com/aidansteele/osx-abi-macho-file-format-reference#symbol-table-and-related-data-structures>\n// (Ctrl+F \"REFERENCED_DYNAMICALLY\").\nasm(\".desc ___crashreporter_info__, 0x10\");\n\n\n// static\nvoid BGMTermination::TestCrash()\n{\n    // To give BGMApp a few seconds to finish launching and then crash:\n    //\n    // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),\n    //                dispatch_get_main_queue(),\n    //                ^{\n    //                    BGMTermination::TestCrash();\n    //                });\n\n    // throw CAException(kAudioHardwareBadDeviceError);\n    // throw BGM_InvalidClientRelativeVolumeException();\n    // std::string().at(1);\n    // *reinterpret_cast<int*>(0x1234) = 9;\n    // [NSException raise:@\"ObjC Test Exception\" format:@\"The description of the test exception.\"];\n}\n\n// static\nvoid BGMTermination::SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevices)\n{\n    sAudioDevices = inAudioDevices;\n\n    StartExitSignalsThread();\n\n    // Wrap the default handler for std::terminate, which is called if BGMApp crashes because of an\n    // uncaught C++ or Objective-C exception, so we can clean up first.\n    sOriginalTerminateHandler = std::get_terminate();\n\n    std::set_terminate([] {\n        CleanUpAudioDevices();\n\n        AddCurrentExceptionToCrashReport();\n\n        // Call the default terminate handler to finish crashing normally.\n        sOriginalTerminateHandler();\n    });\n}\n\n// static\nvoid    BGMTermination::StartExitSignalsThread()\n{\n    // Block the signals the thread will handle, so they can be unblocked for just that thread.\n    sigemptyset(&sExitSignals);\n    sigaddset(&sExitSignals, SIGQUIT);\n    sigaddset(&sExitSignals, SIGTERM);\n    sigaddset(&sExitSignals, SIGINT);\n\n    if(pthread_sigmask(SIG_BLOCK, &sExitSignals, nullptr) != 0)\n    {\n        perror(\"pthread_sigmask\");\n        return;  // This would just mean the signals would be handled by the default handlers.\n    }\n\n    // Start the thread.\n    sExitSignalsThread->Start();\n}\n\n// static\nvoid    BGMTermination::CleanUpAudioDevices()\n{\n    // BGMXPCHelper would set the output device back if we didn't do it here, but in general\n    // it's better for things to work even if BGMXPCHelper isn't installed.\n    if(sAudioDevices)\n    {\n        [sAudioDevices unsetBGMDeviceAsOSDefault];\n    }\n}\n\n// static\nvoid    BGMTermination::AddCurrentExceptionToCrashReport()\n{\n    std::exception_ptr exceptionPtr = std::current_exception();\n\n    if(exceptionPtr)\n    {\n        // The message to add to the crash report (and log).\n        std::string* msg = new std::string(\"\");\n\n        // Throw the exception again and catch it so we can get some info if it's a CAException. If\n        // it's a std::exception, the default terminate handler will do the same thing, so we can\n        // just ignore it here.\n        try\n        {\n            std::rethrow_exception(exceptionPtr);\n        }\n        catch(const CAException& e)\n        {\n            OSStatus err = e.GetError();\n            const char err4CC[5] = CA4CCToCString(err);\n\n            msg = new std::string(\"Uncaught CAException. Error code: '\");\n            msg->append(err4CC);\n            msg->append(\"' (\");\n            msg->append(std::to_string(err));\n            msg->append(\").\");\n        }\n        catch(...)\n        {\n        }\n\n        // CrashReporter will read the contents of __crashreporter_info__.\n        __crashreporter_info__ = msg->c_str();\n        NSLog(@\"%s\", msg->c_str());\n    }\n}\n\n// The entry point for the thread that handles SIGQUIT, SIGTERM and SIGINT.\n// static\nvoid* __nullable BGMTermination::ExitSignalsProc(void* __nullable ignored)\n{\n    #pragma unused (ignored)\n\n    DebugMsg(\"BGMTermination::ExitSignalsProc: Thread started.\");\n\n    int signal = -1;\n\n    // Wait until we receive a signal.\n    while((signal != SIGINT) && (signal != SIGTERM) && (signal != SIGQUIT))\n    {\n        if(sigwait(&sExitSignals, &signal) != 0)\n        {\n            perror(\"sigwait\");\n            return nullptr;\n        }\n    }\n\n    if(signal == SIGINT)\n    {\n        NSLog(@\"Interrupted.\");\n    }\n    else if(signal == SIGTERM)\n    {\n        NSLog(@\"Exiting.\");\n    }\n\n    CleanUpAudioDevices();\n\n    // Unblock the signal and resend it to ourselves so it will be handled by the default handler\n    // and exit BGMApp.\n    if(pthread_sigmask(SIG_UNBLOCK, &sExitSignals, nullptr) != 0)\n    {\n        perror(\"pthread_sigmask\");\n        abort();\n    }\n\n    raise(signal);\n    return nullptr;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMUserDefaults.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMUserDefaults.h\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//\n//  A simple wrapper around our use of NSUserDefaults. Used to store the preferences/state that only\n//  apply to BGMApp. The others are stored by BGMDriver.\n//\n//  Private data will be stored in the user's keychain instead of user defaults.\n//\n\n// Local Includes\n#import \"BGMStatusBarItem.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMUserDefaults : NSObject\n\n// If inDefaults is nil, settings are not loaded from or saved to disk, which is useful for testing.\n- (instancetype) initWithDefaults:(NSUserDefaults* __nullable)inDefaults;\n\n// The musicPlayerID (see BGMMusicPlayer.h), as a string, of the music player selected by the user.\n// Must be either null or a string that can be parsed by NSUUID.\n@property NSString* __nullable selectedMusicPlayerID;\n\n@property BOOL autoPauseMusicEnabled;\n\n// The UIDs of the output devices most recently selected by the user. The most-recently selected\n// device is at index 0. See BGMPreferredOutputDevices.\n@property NSArray<NSString*>* preferredDeviceUIDs;\n\n// The (type of) icon to show in the button in the status bar. (The button the user clicks to open\n// BGMApp's main menu.)\n@property BGMStatusBarIcon statusBarIcon;\n\n// The auth code we're required to send when connecting to GPMDP. Stored in the keychain. Reading\n// this property is thread-safe, but writing it isn't.\n//\n// Returns nil if no code is found or if reading fails. If writing fails, an error is logged, but no\n// exception is thrown.\n@property NSString* __nullable googlePlayMusicDesktopPlayerPermanentAuthCode;\n\n// Auto-pause delay settings in milliseconds. These control how long to wait before pausing/unpausing\n// music when other audio starts/stops playing.\n@property NSUInteger pauseDelayMS;\n@property NSUInteger maxUnpauseDelayMS;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMUserDefaults.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMUserDefaults.m\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMUserDefaults.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n\n\n#pragma clang assume_nonnull begin\n\n// Keys\nstatic NSString* const kDefaultKeyAutoPauseMusicEnabled = @\"AutoPauseMusicEnabled\";\nstatic NSString* const kDefaultKeySelectedMusicPlayerID = @\"SelectedMusicPlayerID\";\nstatic NSString* const kDefaultKeyPreferredDeviceUIDs   = @\"PreferredDeviceUIDs\";\nstatic NSString* const kDefaultKeyStatusBarIcon         = @\"StatusBarIcon\";\nstatic NSString* const kDefaultKeyPauseDelayMS          = @\"PauseDelayMS\";\nstatic NSString* const kDefaultKeyMaxUnpauseDelayMS     = @\"MaxUnpauseDelayMS\";\n\n// Labels for Keychain Data\nstatic NSString* const kKeychainLabelGPMDPAuthCode =\n    @\"app.backgroundmusic: Google Play Music Desktop Player permanent auth code\";\n\n@implementation BGMUserDefaults {\n    // The defaults object wrapped by this object.\n    NSUserDefaults* defaults;\n    // When we're not persisting defaults, settings are stored in this dictionary instead. This\n    // var should only be accessed if 'defaults' is nil.\n    NSMutableDictionary<NSString*,id>* transientDefaults;\n}\n\n- (instancetype) initWithDefaults:(NSUserDefaults* __nullable)inDefaults {\n    if ((self = [super init])) {\n        defaults = inDefaults;\n\n        // Register the settings defaults.\n        //\n        // iTunes is the default music player, but we don't set kDefaultKeySelectedMusicPlayerID\n        // here so we know when it's never been set. (If it hasn't, we try using BGMDevice's\n        // kAudioDeviceCustomPropertyMusicPlayerBundleID property to tell which music player should\n        // be selected. See BGMMusicPlayers.)\n        NSDictionary* defaultsDict = @{ \n            kDefaultKeyAutoPauseMusicEnabled: @YES,\n            kDefaultKeyPauseDelayMS: @1500,\n            kDefaultKeyMaxUnpauseDelayMS: @3500\n        };\n\n        if (defaults) {\n            [defaults registerDefaults:defaultsDict];\n        } else {\n            transientDefaults = [defaultsDict mutableCopy];\n        }\n    }\n\n    return self;\n}\n\n#pragma mark Selected Music Player\n\n- (NSString* __nullable) selectedMusicPlayerID {\n    return [self get:kDefaultKeySelectedMusicPlayerID];\n}\n\n- (void) setSelectedMusicPlayerID:(NSString* __nullable)selectedMusicPlayerID {\n    [self set:kDefaultKeySelectedMusicPlayerID to:selectedMusicPlayerID];\n}\n\n#pragma mark Auto-pause\n\n- (BOOL) autoPauseMusicEnabled {\n    return [self getBool:kDefaultKeyAutoPauseMusicEnabled];\n}\n\n- (void) setAutoPauseMusicEnabled:(BOOL)autoPauseMusicEnabled {\n    [self setBool:kDefaultKeyAutoPauseMusicEnabled to:autoPauseMusicEnabled];\n}\n\n#pragma mark Auto-pause Delays\n\n- (NSUInteger) pauseDelayMS {\n    NSInteger delay = [self getInt:kDefaultKeyPauseDelayMS or:1500];\n    // Clamp to reasonable range: 0ms to 10000ms\n    delay = MAX(0, MIN(10000, delay));\n    return (NSUInteger)delay;\n}\n\n- (void) setPauseDelayMS:(NSUInteger)pauseDelayMS {\n    // Clamp to reasonable range: 0ms to 10000ms\n    NSUInteger clampedDelay = MAX(0, MIN(10000, pauseDelayMS));\n    [self setInt:kDefaultKeyPauseDelayMS to:(NSInteger)clampedDelay];\n}\n\n- (NSUInteger) maxUnpauseDelayMS {\n    NSInteger delay = [self getInt:kDefaultKeyMaxUnpauseDelayMS or:3500];\n    // Clamp to reasonable range: 0ms to 10000ms\n    delay = MAX(0, MIN(10000, delay));\n    return (NSUInteger)delay;\n}\n\n- (void) setMaxUnpauseDelayMS:(NSUInteger)maxUnpauseDelayMS {\n    // Clamp to reasonable range: 0ms to 10000ms\n    NSUInteger clampedDelay = MAX(0, MIN(10000, maxUnpauseDelayMS));\n    [self setInt:kDefaultKeyMaxUnpauseDelayMS to:(NSInteger)clampedDelay];\n}\n\n- (NSArray<NSString*>*) preferredDeviceUIDs {\n    NSArray<NSString*>* __nullable uids = [self get:kDefaultKeyPreferredDeviceUIDs];\n    return uids ? BGMNN(uids) : @[];\n}\n\n- (void) setPreferredDeviceUIDs:(NSArray<NSString*>*)devices {\n    [self set:kDefaultKeyPreferredDeviceUIDs to:devices];\n}\n\n- (BGMStatusBarIcon) statusBarIcon {\n    NSInteger icon = [self getInt:kDefaultKeyStatusBarIcon or:kBGMStatusBarIconDefaultValue];\n\n    // Just in case we get an invalid value somehow.\n    if ((icon < kBGMStatusBarIconMinValue) || (icon > kBGMStatusBarIconMaxValue)) {\n        NSLog(@\"BGMUserDefaults::statusBarIcon: Unknown BGMStatusBarIcon: %ld\", (long)icon);\n        icon = kBGMStatusBarIconDefaultValue;\n    }\n\n    return (BGMStatusBarIcon)icon;\n}\n\n- (void) setStatusBarIcon:(BGMStatusBarIcon)icon {\n    [self setInt:kDefaultKeyStatusBarIcon to:icon];\n}\n\n#pragma mark Google Play Music Desktop Player\n\n- (NSString* __nullable) googlePlayMusicDesktopPlayerPermanentAuthCode {\n    // Try to read the permanent auth code from the user's keychain.\n    NSDictionary<NSString*, NSObject*>* query = @{\n        (__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,\n        (__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode,\n        (__bridge NSString*)kSecMatchLimit: (__bridge NSString*)kSecMatchLimitOne,\n        (__bridge NSString*)kSecReturnData: @YES\n    };\n\n    CFTypeRef result = nil;\n    OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);\n\n    NSString* __nullable authCode = nil;\n\n    // Check the return status, null check and check the type.\n    if ((err == errSecSuccess) && result && (CFGetTypeID(result) == CFDataGetTypeID())) {\n        // Convert it to a string.\n        CFStringRef __nullable code =\n                CFStringCreateFromExternalRepresentation(kCFAllocatorDefault,\n                                                         result,\n                                                         kCFStringEncodingUTF8);\n        authCode = (__bridge_transfer NSString* __nullable)code;\n    } else if (err != errSecItemNotFound) {\n        NSString* __nullable errMsg =\n                (__bridge_transfer NSString* __nullable)SecCopyErrorMessageString(err, nil);\n        NSLog(@\"Failed to read GPMDP auth code from keychain: %d, %@\", err, errMsg);\n    }\n\n    // Release the data we read.\n    if (result) {\n        CFRelease(result);\n    }\n\n    return authCode;\n}\n\n- (void) setGooglePlayMusicDesktopPlayerPermanentAuthCode:(NSString* __nullable)authCode {\n    if (authCode) {\n        // Convert it to an NSData so we can store it in the user's keychain.\n        NSData* authCodeData = [authCode dataUsingEncoding:NSUTF8StringEncoding];\n\n        // Delete the old code if necessary. (There's an update function, but this takes less code.)\n        if (self.googlePlayMusicDesktopPlayerPermanentAuthCode) {\n            [self deleteGPMDPPermanentAuthCode];\n        }\n\n        // Store the code.\n        [self addGPMDPPermanentAuthCode:authCodeData];\n    } else {\n        [self deleteGPMDPPermanentAuthCode];\n    }\n}\n\n- (void) addGPMDPPermanentAuthCode:(NSData*)authCodeData {\n    NSDictionary<NSString*, NSObject*>* attributes = @{\n        (__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,\n        (__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode,\n        (__bridge NSString*)kSecValueData: authCodeData\n    };\n\n    OSStatus err = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);\n\n    // Just log an error if it failed.\n    if (err != errSecSuccess) {\n        NSString* errMsg = (__bridge_transfer NSString*)SecCopyErrorMessageString(err, nil);\n        NSLog(@\"Failed to store GPMDP auth code in keychain: %d, %@\", err, errMsg);\n    }\n}\n\n- (void) deleteGPMDPPermanentAuthCode {\n    NSDictionary<NSString*, NSObject*>* query = @{\n        (__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,\n        (__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode\n    };\n\n    OSStatus err = SecItemDelete((__bridge CFDictionaryRef)query);\n\n    // Just log an error if it failed.\n    if (err != errSecSuccess) {\n        NSString* errMsg = (__bridge_transfer NSString*)SecCopyErrorMessageString(err, nil);\n        NSLog(@\"Failed to delete GPMDP auth code from keychain: %d, %@\", err, errMsg);\n    }\n}\n\n#pragma mark General Accessors\n\n- (id __nullable) get:(NSString*)key {\n    return defaults ? [defaults objectForKey:key] : transientDefaults[key];\n}\n\n- (void) set:(NSString*)key to:(NSObject<NSCopying,NSSecureCoding>* __nullable)value {\n    if (defaults) {\n        [defaults setObject:value forKey:key];\n    } else {\n        transientDefaults[key] = value;\n    }\n}\n\n// TODO: This method should have a default value param.\n- (BOOL) getBool:(NSString*)key {\n    return defaults ? [defaults boolForKey:key] : [transientDefaults[key] boolValue];\n}\n\n- (void) setBool:(NSString*)key to:(BOOL)value {\n    if (defaults) {\n        [defaults setBool:value forKey:key];\n    } else {\n        transientDefaults[key] = @(value);\n    }\n}\n\n- (NSInteger) getInt:(NSString*)key or:(NSInteger)valueIfNil\n{\n    if (defaults) {\n        if ([defaults objectForKey:key]) {\n            return [defaults integerForKey:key];\n        } else {\n            return valueIfNil;\n        }\n    } else {\n        if (transientDefaults[key]) {\n            return [transientDefaults[key] intValue];\n        } else {\n            return valueIfNil;\n        }\n    }\n}\n\n- (void) setInt:(NSString*)key to:(NSInteger)value {\n    if (defaults) {\n        [defaults setInteger:value forKey:key];\n    } else {\n        transientDefaults[key] = @(value);\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMVolumeChangeListener.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVolumeChangeListener.cpp\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMVolumeChangeListener.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#import \"CAException.h\"\n#import \"CAPropertyAddress.h\"\n\n\n#pragma clang assume_nonnull begin\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\nconst static std::vector<CAPropertyAddress> kVolumeChangeProperties = {\n        // Output volume changes\n        CAPropertyAddress(kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput),\n        // Mute/unmute\n        CAPropertyAddress(kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput),\n        // Received when controls are added to or removed from the device.\n        CAPropertyAddress(kAudioObjectPropertyControlList),\n        // Received when the device has changed and \"clients should re-evaluate everything they need\n        // to know about the device, particularly the layout and values of the controls\".\n        CAPropertyAddress(kAudioDevicePropertyDeviceHasChanged)\n};\n#pragma clang diagnostic pop\n\nBGMVolumeChangeListener::BGMVolumeChangeListener(BGMAudioDevice device,\n                                                 std::function<void(void)> handler)\n:\n    mDevice(device)\n{\n    // Register a listener that will update the slider when the user changes the volume or\n    // mutes/unmutes their audio.\n    mListenerBlock =\n            Block_copy(^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {\n                // The docs for AudioObjectPropertyListenerBlock say inAddresses will always contain\n                // at least one property the block is listening to, so there's no need to check it.\n                (void)inNumberAddresses;\n                (void)inAddresses;\n\n                // Call the callback.\n                handler();\n            });\n\n    // Register for a number of properties that might indicate that clients need to update. For\n    // example, the mute property changing means UI elements that display the volume will need to be\n    // updated, even though it's not strictly a change in volume.\n    for(CAPropertyAddress property : kVolumeChangeProperties)\n    {\n        // Instead of swallowing exceptions here, we could try again later, but I doubt it would be\n        // worth the effort. And the documentation doesn't actually explain what could cause this\n        // call to fail.\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            mDevice.AddPropertyListenerBlock(property, dispatch_get_main_queue(), mListenerBlock);\n        });\n    }\n}\n\nBGMVolumeChangeListener::~BGMVolumeChangeListener()\n{\n    // Deregister and release the listener block.\n    for(CAPropertyAddress property : kVolumeChangeProperties)\n    {\n        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {\n            mDevice.RemovePropertyListenerBlock(property,\n                                                dispatch_get_main_queue(),\n                                                mListenerBlock);\n        });\n    }\n\n    Block_release(mListenerBlock);\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMVolumeChangeListener.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVolumeChangeListener.h\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Local Includes\n#include \"BGMBackgroundMusicDevice.h\"\n\n// PublicUtility Includes\n#import \"CAPropertyAddress.h\"\n\n// STL Includes\n#include <functional>\n\n// System Includes\n#include <CoreAudio/CoreAudio.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGMVolumeChangeListener\n{\n\npublic:\n    /*!\n     * @param device Listens for notifications about this device.\n     * @param handler The function to call when the device's volume (or mute) changes. Called on the\n     *                main queue.\n     */\n    BGMVolumeChangeListener(BGMAudioDevice device, std::function<void(void)> handler);\n    virtual ~BGMVolumeChangeListener();\n    BGMVolumeChangeListener(const BGMVolumeChangeListener&) = delete;\n    BGMVolumeChangeListener& operator=(const BGMVolumeChangeListener&) = delete;\n\nprivate:\n    AudioObjectPropertyListenerBlock     mListenerBlock;\n    BGMAudioDevice                       mDevice;\n\n};\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMXPCListener.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCListener.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  Connects to BGMXPCHelper via XPC. When BGMDriver wants BGMApp to do something it can call one of BGMHelper's\n//  XPC methods, which passes the request along to this class.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMXPCProtocols.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMXPCListener : NSObject <BGMAppXPCProtocol, NSXPCListenerDelegate>\n\n- (id) initWithAudioDevices:(BGMAudioDeviceManager*)devices helperConnectionErrorHandler:(void (^)(NSError* error))errorHandler;\n\n- (void) initHelperConnection;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/BGMXPCListener.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCListener.mm\n//  BGMApp\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMXPCListener.h\"\n\n// Local Includes\n#import \"BGMPlayThrough.h\"  // For kDeviceNotStarting.\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMXPCListener {\n    NSXPCListener* listener;\n    // The connection to BGMXPCHelper. We keep the connection alive so if BGMXPCHelper is killed or crashes our interruptionHandler\n    // runs and we can re-register our listener endpoint. Otherwise BGMXPCHelper would have no way to initiate connections to BGMApp.\n    // (That's because BGMXPCHelper is in the global bootstrap context, so it can't look BGMApp up, and XPC services are supposed to\n    // be as stateless as possible, so when it restarts it doesn't restore its copy of BGMApp's listener endpoint.)\n    NSXPCConnection* __nullable helperConnection;\n    BGMAudioDeviceManager* audioDevices;\n    // Used to regularly try reconnecting to BGMXPCHelper if the connection has failed.\n    NSTimer* __nullable retryTimer;\n}\n\n- (id) initWithAudioDevices:(BGMAudioDeviceManager*)devices helperConnectionErrorHandler:(void (^)(NSError* error))errorHandler {\n    if ((self = [super init])) {\n        audioDevices = devices;\n        \n        // Create BGMApp's local listener.\n        listener = [NSXPCListener anonymousListener];\n        \n        // Set this class as the listener's delegate so our listener:shouldAcceptNewConnection method handles incoming connections.\n        listener.delegate = self;\n        [listener resume];\n        \n        // Set up the connection to BGMXPCHelper.\n        [self initHelperConnectionWithErrorHandler:errorHandler];\n\n        // Pass the connection to the audio device manager so it can tell BGMXPCHelper the output device's ID.\n        [audioDevices setBGMXPCHelperConnection:helperConnection];\n    }\n    \n    return self;\n}\n\n- (void) initHelperConnection {\n    // Note that this is called when the helper connection's interruption handler is retrying the connection after a timeout.\n    [self initHelperConnectionWithErrorHandler:^(NSError* error) {\n#if !DEBUG\n    #pragma unused (error)\n#endif\n        \n        DebugMsg(\"BGMXPCListener::initHelperConnection: Connection error: %s\", [[error description] UTF8String]);\n    }];\n}\n\n- (void) initHelperConnectionWithErrorHandler:(void (^)(NSError* error))errorHandler {\n    // Connect to the helper service so we can call its remote methods (and vice versa).\n    \n    // Clean up if there's an existing connection.\n    if (helperConnection) {\n        [helperConnection invalidate];\n        helperConnection = nil;\n    }\n    \n    // Uses the NSXPCConnectionPrivileged option because BGMXPCHelper has to run in the privileged/global bootstrap context for\n    // BGMDriver to be able to look it up. BGMDriver runs in the coreaudiod process, which runs in the global context, and services\n    // in the global context are only able to look up other services in that context.\n    helperConnection = [[NSXPCConnection alloc] initWithMachServiceName:kBGMXPCHelperMachServiceName\n                                                                options:NSXPCConnectionPrivileged];\n    \n    helperConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMXPCHelperXPCProtocol)];\n    \n    // Export this object so BGMXPCHelper can send messages through helperConnection if it wants to.\n    helperConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMAppXPCProtocol)];\n    helperConnection.exportedObject = self;\n    \n    // If we lose the connection to BGMXPCHelper, we try to reconnect every 10 seconds.\n    void (^retryAfterTimeout)(NSError*) = ^(NSError* error) {\n        dispatch_async(dispatch_get_main_queue(), ^{\n            NSLog(@\"BGMXPCListener::initHelperConnectionWithErrorHandler: Lost connection to BGMXPCHelper. Will try to reconnect \"\n                  \"every 10 seconds. Details: %@\",\n                  error);\n            \n            if (!retryTimer) {\n                retryTimer = [NSTimer scheduledTimerWithTimeInterval:10.0\n                                                              target:self\n                                                            selector:@selector(initHelperConnection)\n                                                            userInfo:nil\n                                                             repeats:YES];\n            }\n        });\n    };\n    \n    // If the XPC helper crashes or is killed, this handler gets invoked and attempts to reestablish the connection by sending\n    // a message.\n    NSXPCConnection* __weak helperConnectionWeakRef = helperConnection;\n    NSXPCListener* __weak listenerWeakRef = listener;\n    helperConnection.interruptionHandler = ^{\n        DebugMsg(\"BGMXPCListener::initHelperConnectionWithErrorHandler: Connection to BGMXPCHelper interrupted. Trying to reconnect.\");\n        [[helperConnectionWeakRef remoteObjectProxyWithErrorHandler:retryAfterTimeout]\n         registerAsBGMAppWithListenerEndpoint:[listenerWeakRef endpoint] reply:^{}];\n    };\n    \n    // Use the initialization error handler while we send the initial message. This is so we can warn the user, since failing at\n    // start up probably means we won't be able to recover.\n    helperConnection.invalidationHandler = ^{\n        errorHandler([NSError errorWithDomain:@kBGMAppBundleID\n                                         code:kBGMXPC_MessageFailure\n                                     userInfo:@{ NSLocalizedDescriptionKey: @\"Failed to send initial message to BGMXPCHelper.\" }]);\n    };\n    \n    [helperConnection resume];\n        \n    // Send the listener's endpoint to BGMXPCHelper so it can initiate connections to BGMApp.\n    [[helperConnection remoteObjectProxyWithErrorHandler:errorHandler] registerAsBGMAppWithListenerEndpoint:[listener endpoint] reply:^{\n        // If we were retrying the connection, we can stop now.\n        if (retryTimer) {\n            NSLog(@\"BGMXPCListener::initHelperConnectionWithErrorHandler: Reconnected to BGMXPCHelper.\");\n            [retryTimer invalidate];\n            retryTimer = nil;\n        }\n        \n        // Silently retry the connection, after a timeout, if it fails after having worked initially.\n        helperConnection.invalidationHandler = ^{\n            DebugMsg(\"BGMXPCListener::initHelperConnectionWithErrorHandler: Connection to BGMXPCHelper invalidated\");\n            helperConnection = nil;\n            retryAfterTimeout(nil);\n        };\n    }];\n\n    // Pass the new connection to the audio device manager.\n    [audioDevices setBGMXPCHelperConnection:helperConnection];\n}\n\n- (void) dealloc {\n    if (retryTimer) {\n        [retryTimer invalidate];\n    }\n    \n    [[helperConnection remoteObjectProxy] unregisterAsBGMApp];\n}\n\n- (BOOL) listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection {\n    #pragma unused (listener)\n    \n    // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.\n    \n    DebugMsg(\"BGMXPCListener::listener: Received new connection\");\n    \n    // Configure the connection.\n    // First, set the interface that the exported object implements.\n    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMAppXPCProtocol)];\n    \n    // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to\n    // the exported object to handle. The connection retains the exported object.\n    newConnection.exportedObject = self;\n    \n    // Resuming the connection allows the system to deliver more incoming messages.\n    [newConnection resume];\n    \n    // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the\n    // connection for some reason, call -invalidate on the connection and return NO.\n    return YES;\n}\n\n- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI {\n    NSString* description;\n    OSStatus err;\n    \n    try {\n        err = [audioDevices startPlayThroughSync:isUI];\n    } catch (CAException e) {\n        // startPlayThroughSync should never throw a CAException, but check anyway in case we change that at some point.\n        LogError(\"BGMXPCListener::startPlayThroughSyncWithReply: Caught CAException (%d). Replying kBGMXPC_HardwareError.\",\n                 e.GetError());\n        err = kBGMXPC_HardwareError;\n    } catch (...) {\n        LogError(\"BGMXPCListener::startPlayThroughSyncWithReply: Caught unknown exception. Replying kBGMXPC_InternalError.\");\n        err = kBGMXPC_InternalError;\n#if DEBUG\n        throw;\n#endif\n    }\n    \n    switch (err) {\n        case kAudioHardwareNoError:\n            description = @\"BGMApp started the output device.\";\n            err = kBGMXPC_Success;\n            break;\n            \n        case kAudioHardwareNotRunningError:\n            description = @\"BGMApp is not ready for audio play-through.\";\n            err = kBGMXPC_BGMAppStateError;\n            break;\n            \n        case kAudioHardwareIllegalOperationError:\n            description = @\"The output device is not available.\";\n            err = kBGMXPC_HardwareError;\n            break;\n            \n        case kBGMErrorCode_ReturningEarly:\n            // We have to send a more specific error in this case because BGMDevice handles this case differently.\n            description = @\"BGMApp could not wait for the output device to be ready for IO.\";\n            err = kBGMXPC_ReturningEarlyError;\n            break;\n            \n        default:\n            description = @\"Unknown error while waiting for the output device.\";\n            err = kBGMXPC_InternalError;\n            break;\n    }\n    \n    reply([NSError errorWithDomain:@kBGMAppBundleID\n                              code:err\n                          userInfo:@{ NSLocalizedDescriptionKey: description }]);\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Base.lproj/MainMenu.xib",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion=\"24506\" targetRuntime=\"MacOSX.Cocoa\" propertyAccessControl=\"none\" useAutolayout=\"YES\" customObjectInstantitationMethod=\"direct\">\n    <dependencies>\n        <deployment identifier=\"macosx\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.CocoaPlugin\" version=\"24506\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <objects>\n        <customObject id=\"-2\" userLabel=\"File's Owner\" customClass=\"NSApplication\">\n            <connections>\n                <outlet property=\"delegate\" destination=\"Voe-Tx-rLC\" id=\"GzC-gU-4Uq\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"-1\" userLabel=\"First Responder\" customClass=\"FirstResponder\"/>\n        <customObject id=\"-3\" userLabel=\"Application\" customClass=\"NSObject\"/>\n        <customObject id=\"Voe-Tx-rLC\" customClass=\"BGMAppDelegate\">\n            <connections>\n                <outlet property=\"aboutPanel\" destination=\"Cf4-3V-gl1\" id=\"cgo-Hw-rE2\"/>\n                <outlet property=\"aboutPanelLicenseView\" destination=\"LSG-PF-cl8\" id=\"mbu-kv-Jfc\"/>\n                <outlet property=\"appVolumeView\" destination=\"MWB-XH-kFI\" id=\"eFA-RN-VMC\"/>\n                <outlet property=\"autoPauseMenuItemUnwrapped\" destination=\"nHv-T8-1nb\" id=\"Lie-Cx-jw6\"/>\n                <outlet property=\"bgmMenu\" destination=\"8AN-nh-rEe\" id=\"UWn-BX-eLy\"/>\n                <outlet property=\"debugLoggingMenuItemUnwrapped\" destination=\"sc9-vO-KyP\" id=\"Zyd-0v-0RN\"/>\n                <outlet property=\"outputVolumeLabel\" destination=\"wfC-C6-SLv\" id=\"Nuf-mo-osG\"/>\n                <outlet property=\"outputVolumeSlider\" destination=\"9Ru-Sc-dqC\" id=\"wv0-Md-BwF\"/>\n                <outlet property=\"outputVolumeView\" destination=\"JOz-H1-mj9\" id=\"xeJ-fk-NMI\"/>\n                <outlet property=\"systemSoundsSlider\" destination=\"gyd-WV-2ju\" id=\"NEe-5W-EI5\"/>\n                <outlet property=\"systemSoundsView\" destination=\"dBD-CE-4dw\" id=\"4SD-Z1-akp\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"YLy-65-1bz\" customClass=\"NSFontManager\"/>\n        <menu id=\"8AN-nh-rEe\" userLabel=\"BGM Menu\">\n            <items>\n                <menuItem title=\"Auto-pause Music\" tag=\"2\" id=\"nHv-T8-1nb\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <accessibility help=\"Enable to automatically pause your selected music player when a different app starts playing audio.\" identifier=\"Auto-pause enabled\"/>\n                </menuItem>\n                <menuItem isSeparatorItem=\"YES\" id=\"ZGd-Pq-YeA\"/>\n                <menuItem title=\"Volumes\" tag=\"3\" enabled=\"NO\" id=\"8PP-wA-Pae\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                </menuItem>\n                <menuItem isSeparatorItem=\"YES\" tag=\"4\" id=\"rkf-nx-H2J\"/>\n                <menuItem title=\"Output Device\" tag=\"5\" enabled=\"NO\" id=\"chk-9C-pab\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                </menuItem>\n                <menuItem isSeparatorItem=\"YES\" id=\"D3z-zv-JrJ\"/>\n                <menuItem title=\"Preferences\" tag=\"1\" id=\"BHb-uh-9Zm\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Preferences\" id=\"Img-Dh-cpU\">\n                        <items>\n                            <menuItem title=\"Auto-pause\" tag=\"1\" enabled=\"NO\" id=\"2aT-t7-HGW\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"nb1-jq-97L\"/>\n                            <menuItem title=\"Status Bar Icon\" enabled=\"NO\" id=\"CmD-ot-1wE\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                            </menuItem>\n                            <menuItem title=\"Background Music Logo\" state=\"on\" tag=\"2\" id=\"9VF-qy-6fh\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                            </menuItem>\n                            <menuItem title=\"Volume Icon\" tag=\"3\" id=\"B47-O2-wd0\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"pYP-Fy-nKA\"/>\n                            <menuItem title=\"About Background Music\" tag=\"4\" id=\"R45-Vo-Eto\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <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\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                </menuItem>\n                <menuItem title=\"Quit Background Music\" id=\"Nj2-gJ-DhW\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <connections>\n                        <action selector=\"terminate:\" target=\"-1\" id=\"seH-Fc-tXb\"/>\n                    </connections>\n                </menuItem>\n            </items>\n            <accessibility description=\"Background Music Main Menu\" identifier=\"MainMenu\"/>\n            <point key=\"canvasLocation\" x=\"-184\" y=\"-69.5\"/>\n        </menu>\n        <customView wantsLayer=\"YES\" id=\"MWB-XH-kFI\">\n            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"269\" height=\"47\"/>\n            <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n            <subviews>\n                <imageView identifier=\"AppIcon\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"W04-iT-IUw\" customClass=\"BGMAVM_AppIcon\">\n                    <rect key=\"frame\" x=\"20\" y=\"27\" width=\"16\" height=\"16\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <imageCell key=\"cell\" refusesFirstResponder=\"YES\" alignment=\"left\" imageScaling=\"proportionallyDown\" id=\"6QQ-oO-HxF\"/>\n                </imageView>\n                <button springLoaded=\"YES\" verticalHuggingPriority=\"750\" id=\"3qR-sO-pXN\" customClass=\"BGMAVM_VolumeMute\">\n                    <rect key=\"frame\" x=\"139\" y=\"24.5\" width=\"21.5\" height=\"21\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <buttonCell key=\"cell\" type=\"smallSquare\" bezelStyle=\"smallSquare\" image=\"NSAddTemplate\" imagePosition=\"overlaps\" alignment=\"center\" lineBreakMode=\"truncatingTail\" state=\"on\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"xzH-YY-5On\">\n                        <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                        <font key=\"font\" metaFont=\"system\"/>\n                    </buttonCell>\n                </button>\n                <slider verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"I1l-Ci-4md\" customClass=\"BGMAVM_VolumeSlider\">\n                    <rect key=\"frame\" x=\"163\" y=\"27\" width=\"74\" height=\"15\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <sliderCell key=\"cell\" controlSize=\"mini\" continuous=\"YES\" state=\"on\" alignment=\"left\" maxValue=\"100\" doubleValue=\"50\" tickMarkPosition=\"above\" sliderType=\"linear\" id=\"Jmg-df-9Xl\"/>\n                    <accessibility description=\"Volume\"/>\n                </slider>\n                <slider toolTip=\"Pan\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"2mh-uO-kOV\" customClass=\"BGMAVM_PanSlider\">\n                    <rect key=\"frame\" x=\"163\" y=\"7\" width=\"74\" height=\"15\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <sliderCell key=\"cell\" controlSize=\"mini\" continuous=\"YES\" state=\"on\" alignment=\"left\" minValue=\"-100\" maxValue=\"100\" tickMarkPosition=\"below\" numberOfTickMarks=\"1\" sliderType=\"linear\" id=\"ccM-Mt-93g\"/>\n                    <accessibility description=\"Pan\"/>\n                </slider>\n                <button tag=\"1\" springLoaded=\"YES\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"vTG-n6-GxY\" customClass=\"BGMAVM_ShowMoreControlsButton\">\n                    <rect key=\"frame\" x=\"243\" y=\"27\" width=\"16\" height=\"17\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <contentFilters>\n                        <ciFilter name=\"CIAffineTransform\">\n                            <configuration>\n                                <null key=\"inputImage\"/>\n                                <affineTransform key=\"inputTransform\" m11=\"1\" m12=\"0.0\" m21=\"0.0\" m22=\"1\" tX=\"0.0\" tY=\"2\"/>\n                            </configuration>\n                        </ciFilter>\n                    </contentFilters>\n                    <buttonCell key=\"cell\" type=\"square\" title=\"⌃\" bezelStyle=\"shadowlessSquare\" image=\"buttonCell:IXo-C7-3uE:image\" alignment=\"center\" lineBreakMode=\"truncatingTail\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"IXo-C7-3uE\">\n                        <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                        <font key=\"font\" metaFont=\"system\"/>\n                    </buttonCell>\n                </button>\n                <textField identifier=\"PanLeft\" toolTip=\"Pan\" focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"9jc-9i-jw2\">\n                    <rect key=\"frame\" x=\"162\" y=\"-1\" width=\"12\" height=\"17\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" title=\"L\" id=\"hgE-7A-bez\">\n                        <font key=\"font\" metaFont=\"menu\" size=\"9\"/>\n                        <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                    </textFieldCell>\n                </textField>\n                <textField identifier=\"PanRight\" toolTip=\"Pan\" focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"1lZ-hX-6Kl\">\n                    <rect key=\"frame\" x=\"228\" y=\"-1\" width=\"12\" height=\"17\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" title=\"R\" id=\"lzr-NO-0Na\">\n                        <font key=\"font\" metaFont=\"menu\" size=\"9\"/>\n                        <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                    </textFieldCell>\n                </textField>\n                <textField identifier=\"AppName\" focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"Xmd-bg-huG\" customClass=\"BGMAVM_AppNameLabel\">\n                    <rect key=\"frame\" x=\"44\" y=\"27\" width=\"87\" height=\"14\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <textFieldCell key=\"cell\" controlSize=\"small\" lineBreakMode=\"truncatingTail\" allowsUndo=\"NO\" sendsActionOnEndEditing=\"YES\" alignment=\"left\" title=\"App name here\" usesSingleLineMode=\"YES\" id=\"ZHF-ZW-Oqg\">\n                        <font key=\"font\" metaFont=\"message\" size=\"11\"/>\n                        <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                    </textFieldCell>\n                </textField>\n            </subviews>\n            <point key=\"canvasLocation\" x=\"116.5\" y=\"-39\"/>\n        </customView>\n        <window allowsToolTipsWhenApplicationIsInactive=\"NO\" autorecalculatesKeyViewLoop=\"NO\" restorable=\"NO\" hidesOnDeactivate=\"YES\" releasedWhenClosed=\"NO\" visibleAtLaunch=\"NO\" animationBehavior=\"default\" id=\"Cf4-3V-gl1\" customClass=\"NSPanel\">\n            <windowStyleMask key=\"styleMask\" titled=\"YES\" closable=\"YES\" utility=\"YES\" nonactivatingPanel=\"YES\"/>\n            <windowPositionMask key=\"initialPositionMask\" topStrut=\"YES\"/>\n            <rect key=\"contentRect\" x=\"248\" y=\"350\" width=\"1002\" height=\"335\"/>\n            <rect key=\"screenRect\" x=\"0.0\" y=\"0.0\" width=\"1710\" height=\"1068\"/>\n            <view key=\"contentView\" id=\"HlB-hX-Y0Y\">\n                <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"1002\" height=\"335\"/>\n                <autoresizingMask key=\"autoresizingMask\"/>\n                <subviews>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"r51-dd-LGP\">\n                        <rect key=\"frame\" x=\"71\" y=\"125\" width=\"240\" height=\"22\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" alignment=\"center\" title=\"Background Music\" id=\"Dw2-nu-eBQ\">\n                            <font key=\"font\" metaFont=\"system\" size=\"18\"/>\n                            <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" tag=\"1\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"ekc-h0-I43\">\n                        <rect key=\"frame\" x=\"71\" y=\"100\" width=\"240\" height=\"17\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" alignment=\"center\" title=\"Version 0.4.3\" id=\"FDH-7l-wFf\">\n                            <font key=\"font\" metaFont=\"system\"/>\n                            <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"L5P-Lw-aCd\">\n                        <rect key=\"frame\" x=\"413\" y=\"298\" width=\"270\" height=\"17\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" alignment=\"left\" title=\"Licensed under GPL v2 or any later version.\" id=\"ETh-En-bzX\">\n                            <font key=\"font\" metaFont=\"system\"/>\n                            <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <box horizontalHuggingPriority=\"750\" fixedFrame=\"YES\" boxType=\"separator\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"Zc9-gs-X8C\">\n                        <rect key=\"frame\" x=\"383\" y=\"93\" width=\"5\" height=\"150\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    </box>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" tag=\"3\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"nx6-kQ-N8Z\" customClass=\"BGMLinkField\">\n                        <rect key=\"frame\" x=\"36\" y=\"50\" width=\"310\" height=\"17\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" alignment=\"center\" tag=\"3\" title=\"https://github.com/kyleneideck/BackgroundMusic\" placeholderString=\"\" id=\"VOb-5X-o3R\">\n                            <font key=\"font\" metaFont=\"system\"/>\n                            <color key=\"textColor\" red=\"0.20000000000000001\" green=\"0.40000000000000002\" blue=\"0.59999999999999998\" alpha=\"1\" colorSpace=\"calibratedRGB\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <imageView wantsLayer=\"YES\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"Tui-Hf-FLv\">\n                        <rect key=\"frame\" x=\"116\" y=\"155\" width=\"150\" height=\"150\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <shadow key=\"shadow\">\n                            <color key=\"color\" white=\"0.0\" alpha=\"1\" colorSpace=\"calibratedWhite\"/>\n                        </shadow>\n                        <imageCell key=\"cell\" refusesFirstResponder=\"YES\" alignment=\"left\" animates=\"YES\" imageScaling=\"proportionallyUpOrDown\" image=\"FermataIcon\" id=\"dBU-ZS-ZzA\"/>\n                    </imageView>\n                    <imageView wantsLayer=\"YES\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"R1R-Rd-xPC\">\n                        <rect key=\"frame\" x=\"116\" y=\"155\" width=\"150\" height=\"150\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <shadow key=\"shadow\">\n                            <color key=\"color\" white=\"0.0\" alpha=\"1\" colorSpace=\"calibratedWhite\"/>\n                        </shadow>\n                        <imageCell key=\"cell\" refusesFirstResponder=\"YES\" alignment=\"left\" animates=\"YES\" imageScaling=\"proportionallyUpOrDown\" image=\"FermataIcon\" id=\"1VP-dU-RCe\"/>\n                    </imageView>\n                    <scrollView fixedFrame=\"YES\" horizontalLineScroll=\"10\" horizontalPageScroll=\"10\" verticalLineScroll=\"10\" verticalPageScroll=\"10\" hasHorizontalScroller=\"NO\" usesPredominantAxisScrolling=\"NO\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"eqz-ap-PAC\">\n                        <rect key=\"frame\" x=\"415\" y=\"45\" width=\"567\" height=\"245\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <clipView key=\"contentView\" drawsBackground=\"NO\" id=\"Cdb-RA-YK0\">\n                            <rect key=\"frame\" x=\"1\" y=\"1\" width=\"565\" height=\"243\"/>\n                            <autoresizingMask key=\"autoresizingMask\"/>\n                            <subviews>\n                                <textView editable=\"NO\" importsGraphics=\"NO\" richText=\"NO\" verticallyResizable=\"YES\" id=\"LSG-PF-cl8\">\n                                    <rect key=\"frame\" x=\"-6\" y=\"0.0\" width=\"577\" height=\"243\"/>\n                                    <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                                    <color key=\"textColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                    <color key=\"backgroundColor\" name=\"textBackgroundColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                    <size key=\"minSize\" width=\"565\" height=\"243\"/>\n                                    <size key=\"maxSize\" width=\"594\" height=\"10000000\"/>\n                                    <color key=\"insertionPointColor\" name=\"controlTextColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                    <allowedInputSourceLocales>\n                                        <string>NSAllRomanInputSourcesLocaleIdentifier</string>\n                                    </allowedInputSourceLocales>\n                                </textView>\n                            </subviews>\n                        </clipView>\n                        <scroller key=\"horizontalScroller\" hidden=\"YES\" wantsLayer=\"YES\" verticalHuggingPriority=\"750\" doubleValue=\"1\" horizontal=\"YES\" id=\"3jV-aP-AB7\">\n                            <rect key=\"frame\" x=\"-100\" y=\"-100\" width=\"87\" height=\"18\"/>\n                            <autoresizingMask key=\"autoresizingMask\"/>\n                        </scroller>\n                        <scroller key=\"verticalScroller\" wantsLayer=\"YES\" verticalHuggingPriority=\"750\" horizontal=\"NO\" id=\"qCC-lY-zQ6\">\n                            <rect key=\"frame\" x=\"550\" y=\"1\" width=\"16\" height=\"243\"/>\n                            <autoresizingMask key=\"autoresizingMask\"/>\n                        </scroller>\n                    </scrollView>\n                    <textField focusRingType=\"none\" verticalHuggingPriority=\"750\" horizontalCompressionResistancePriority=\"250\" fixedFrame=\"YES\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"6qu-yI-r00\">\n                        <rect key=\"frame\" x=\"413\" y=\"20\" width=\"203\" height=\"11\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" lineBreakMode=\"truncatingTail\" sendsActionOnEndEditing=\"YES\" title=\"The AirPlay Logo is a trademark of Apple Inc.\" id=\"lx7-k3-q16\">\n                            <font key=\"font\" metaFont=\"menu\" size=\"9\"/>\n                            <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" tag=\"2\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"Vy4-dv-jQB\">\n                        <rect key=\"frame\" x=\"18\" y=\"75\" width=\"155\" height=\"16\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" alignment=\"center\" title=\"Copyright © 2016-2024\" placeholderString=\"\" id=\"ctF-95-uVu\">\n                            <font key=\"font\" metaFont=\"system\"/>\n                            <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                    <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" tag=\"4\" allowsCharacterPickerTouchBarItem=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"tRj-QC-GuQ\" customClass=\"BGMLinkField\">\n                        <rect key=\"frame\" x=\"168\" y=\"75\" width=\"194\" height=\"16\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                        <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" selectable=\"YES\" sendsActionOnEndEditing=\"YES\" alignment=\"left\" tag=\"3\" title=\"Background Music contributors\" placeholderString=\"\" allowsEditingTextAttributes=\"YES\" usesSingleLineMode=\"YES\" id=\"UC2-MX-fML\">\n                            <font key=\"font\" metaFont=\"system\"/>\n                            <color key=\"textColor\" red=\"0.20000000000000001\" green=\"0.40000000000000002\" blue=\"0.59999999999999998\" alpha=\"1\" colorSpace=\"calibratedRGB\"/>\n                            <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        </textFieldCell>\n                    </textField>\n                </subviews>\n            </view>\n            <point key=\"canvasLocation\" x=\"-200\" y=\"232.5\"/>\n        </window>\n        <textField focusRingType=\"none\" verticalHuggingPriority=\"750\" horizontalCompressionResistancePriority=\"250\" setsMaxLayoutWidthAtFirstLayout=\"YES\" allowsCharacterPickerTouchBarItem=\"YES\" id=\"IoN-sN-cCx\">\n            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"471\" height=\"180\"/>\n            <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n            <textFieldCell key=\"cell\" controlSize=\"mini\" sendsActionOnEndEditing=\"YES\" drawsBackground=\"YES\" id=\"Ay8-8n-FHi\">\n                <font key=\"font\" size=\"14\" name=\"Courier\"/>\n                <string key=\"title\">// The version and copyright strings are replaced with // the values from Info.plist in the setup code. These // values are just placeholders/fallbacks. // // The icon image was being drawn with about 50%\n// opacity and I couldn't figure out why. The only  // solution I found was to layer two copies of the icon // on top of each other and add a black shadow (with no // offset) to both. </string>\n                <color key=\"textColor\" red=\"0.11543657067200695\" green=\"0.4338699494949495\" blue=\"0.0\" alpha=\"1\" colorSpace=\"calibratedRGB\"/>\n                <color key=\"backgroundColor\" red=\"0.99215692281723022\" green=\"0.9960784912109375\" blue=\"0.9960784912109375\" alpha=\"1\" colorSpace=\"deviceRGB\"/>\n            </textFieldCell>\n            <point key=\"canvasLocation\" x=\"-559\" y=\"-118\"/>\n        </textField>\n        <customView id=\"JOz-H1-mj9\" userLabel=\"Output Volume View\">\n            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"269\" height=\"47\"/>\n            <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n            <subviews>\n                <slider identifier=\"Output Volume\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"9Ru-Sc-dqC\" userLabel=\"Output Volume Slider\">\n                    <rect key=\"frame\" x=\"20\" y=\"4\" width=\"220\" height=\"19\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <sliderCell key=\"cell\" controlSize=\"small\" continuous=\"YES\" state=\"on\" alignment=\"left\" maxValue=\"1\" tickMarkPosition=\"above\" sliderType=\"linear\" id=\"MzM-fe-nKb\"/>\n                    <accessibility description=\"Output Volume\" help=\"Sets the volume of your audio output device.\" identifier=\"Output Volume\"/>\n                </slider>\n                <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"wfC-C6-SLv\">\n                    <rect key=\"frame\" x=\"20\" y=\"25\" width=\"226\" height=\"17\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" title=\"Volume\" id=\"60O-ju-B5C\">\n                        <font key=\"font\" metaFont=\"system\"/>\n                        <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                    </textFieldCell>\n                </textField>\n            </subviews>\n            <point key=\"canvasLocation\" x=\"117\" y=\"-219\"/>\n        </customView>\n        <customView id=\"dBD-CE-4dw\" userLabel=\"Output Volume View\">\n            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"269\" height=\"16\"/>\n            <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n            <subviews>\n                <slider identifier=\"System Sounds Volume\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"gyd-WV-2ju\" userLabel=\"Output Volume Slider\">\n                    <rect key=\"frame\" x=\"163\" y=\"0.0\" width=\"74\" height=\"15\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <sliderCell key=\"cell\" controlSize=\"mini\" continuous=\"YES\" state=\"on\" alignment=\"left\" maxValue=\"1\" doubleValue=\"1\" tickMarkPosition=\"above\" sliderType=\"linear\" id=\"VDn-d8-XK3\"/>\n                    <accessibility description=\"System Sounds Volume\" help=\"Volume of alerts, notification sounds, etc. Usually short. Can be played by any app.\"/>\n                </slider>\n                <textField focusRingType=\"none\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"750\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"iKs-df-Hp6\">\n                    <rect key=\"frame\" x=\"44\" y=\"1\" width=\"86\" height=\"14\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <textFieldCell key=\"cell\" scrollable=\"YES\" lineBreakMode=\"clipping\" sendsActionOnEndEditing=\"YES\" title=\"System Sounds\" id=\"ATK-L8-s8z\">\n                        <font key=\"font\" metaFont=\"message\" size=\"11\"/>\n                        <color key=\"textColor\" name=\"labelColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                        <color key=\"backgroundColor\" name=\"controlColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                    </textFieldCell>\n                </textField>\n                <imageView identifier=\"SystemSoundsIcon\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" fixedFrame=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"V2D-eM-8yN\">\n                    <rect key=\"frame\" x=\"20\" y=\"1\" width=\"16\" height=\"16\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <imageCell key=\"cell\" refusesFirstResponder=\"YES\" alignment=\"left\" imageScaling=\"proportionallyDown\" image=\"NSComputer\" id=\"8Xp-9S-hOz\"/>\n                </imageView>\n                <button springLoaded=\"YES\" verticalHuggingPriority=\"750\" id=\"9bY-qU-4Fu\" customClass=\"BGMAVM_VolumeMute\">\n                    <rect key=\"frame\" x=\"139\" y=\"-2.5\" width=\"21.5\" height=\"21\"/>\n                    <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMinY=\"YES\"/>\n                    <buttonCell key=\"cell\" type=\"smallSquare\" bezelStyle=\"smallSquare\" image=\"NSAddTemplate\" imagePosition=\"overlaps\" alignment=\"center\" lineBreakMode=\"truncatingTail\" enabled=\"NO\" state=\"on\" transparent=\"YES\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"bs5-gp-FRF\">\n                        <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                        <font key=\"font\" metaFont=\"system\"/>\n                    </buttonCell>\n                </button>\n            </subviews>\n            <point key=\"canvasLocation\" x=\"116.5\" y=\"-133\"/>\n        </customView>\n    </objects>\n    <resources>\n        <image name=\"FermataIcon\" width=\"284\" height=\"284\"/>\n        <image name=\"NSAddTemplate\" width=\"18\" height=\"17\"/>\n        <image name=\"NSComputer\" width=\"32\" height=\"32\"/>\n        <image name=\"buttonCell:IXo-C7-3uE:image\" width=\"1\" height=\"1\">\n            <mutableData key=\"keyedArchiveRepresentation\">\nYnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T\nS2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwbHCEaJissMzY5P0JVJG51bGzXDQ4PEBESExQVFhcYGRpW\nTlNTaXplViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNSZXBzW05TVGludENvbG9yV05TQ29sb3JeTlNSZXNp\nemluZ01vZGWAAoANEiDDAACAA4AAgAsQAFZ7MSwgMX3SHQ4eIFpOUy5vYmplY3RzoR+ABIAK0h0OIiWi\nIySABYAGgAnTDicoKSoaXxAUTlNUSUZGUmVwcmVzZW50YXRpb25fEBlOU0ludGVybmFsTGF5b3V0RGly\nZWN0aW9ugAiAB08RCMRNTQAqAAAACgAAABABAAADAAAAAQABAAABAQADAAAAAQABAAABAgADAAAAAgAI\nAAgBAwADAAAAAQABAAABBgADAAAAAQABAAABCgADAAAAAQABAAABEQAEAAAAAQAAAAgBEgADAAAAAQAB\nAAABFQADAAAAAQACAAABFgADAAAAAQABAAABFwAEAAAAAQAAAAIBHAADAAAAAQABAAABKAADAAAAAQAC\nAAABUgADAAAAAQABAAABUwADAAAAAgABAAGHcwAHAAAH9AAAANAAAAAAAAAH9GFwcGwCIAAAbW50ckdS\nQVlYWVogB9AAAgAOAAwAAAAAYWNzcEFQUEwAAAAAbm9uZQAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA\n0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFZGVzYwAA\nAMAAAABvZHNjbQAAATAAAAZmY3BydAAAB5gAAAA4d3RwdAAAB9AAAAAUa1RSQwAAB+QAAAAOZGVzYwAA\nAAAAAAAVR2VuZXJpYyBHcmF5IFByb2ZpbGUAAAAAAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAHwAA\nAAxza1NLAAAAKgAAAYRlblVTAAAAKAAAAa5jYUVTAAAALAAAAdZ2aVZOAAAALAAAAgJwdEJSAAAAKgAA\nAi51a1VBAAAALAAAAlhmckZVAAAAKgAAAoRodUhVAAAALgAAAq56aFRXAAAAEAAAAtxuYk5PAAAALAAA\nAuxrb0tSAAAAGAAAAxhjc0NaAAAAJAAAAzBoZUlMAAAAIAAAA1Ryb1JPAAAAJAAAA3RkZURFAAAAOgAA\nA5hpdElUAAAALgAAA9JzdlNFAAAALgAABAB6aENOAAAAEAAABC5qYUpQAAAAFgAABD5lbEdSAAAAJAAA\nBFRwdFBPAAAAOAAABHhubE5MAAAAKgAABLBlc0VTAAAAKAAABNp0aFRIAAAAJAAABQJ0clRSAAAAIgAA\nBSZmaUZJAAAALAAABUhockhSAAAAOgAABXRwbFBMAAAANgAABa5ydVJVAAAAJgAABeRhckVHAAAAKAAA\nBgpkYURLAAAANAAABjIAVgFhAGUAbwBiAGUAYwBuAP0AIABzAGkAdgD9ACAAcAByAG8AZgBpAGwARwBl\nAG4AZQByAGkAYwAgAEcAcgBhAHkAIABQAHIAbwBmAGkAbABlAFAAZQByAGYAaQBsACAAZABlACAAZwBy\nAGkAcwAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1\nAG4AZwBQAGUAcgBmAGkAbAAgAEMAaQBuAHoAYQAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9\nBDgEOQAgBD8EQAQ+BEQEMAQ5BDsAIABHAHIAYQB5AFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1\nAGUAIABnAHIAaQBzAMEAbAB0AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABwAHIAbwBmAGkAbJAa\ndShwcJaOgnJfaWPPj/AARwBlAG4AZQByAGkAcwBrACAAZwByAOUAdABvAG4AZQBwAHIAbwBmAGkAbMd8\nvBgAIABHAHIAYQB5ACDVBLhc0wzHfABPAGIAZQBjAG4A/QAgAWEAZQBkAP0AIABwAHIAbwBmAGkAbAXk\nBegF1QXkBdkF3AAgAEcAcgBhAHkAIAXbBdwF3AXZAFAAcgBvAGYAaQBsACAAZwByAGkAIABnAGUAbgBl\nAHIAaQBjAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBm\nAGkAbABQAHIAbwBmAGkAbABvACAAZwByAGkAZwBpAG8AIABnAGUAbgBlAHIAaQBjAG8ARwBlAG4AZQBy\nAGkAcwBrACAAZwByAOUAcwBrAGEAbABlAHAAcgBvAGYAaQBsZm6QGnBwXqZjz4/wZYdO9k4AgiwwsDDs\nMKQw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgA7MDugPBA7kAUABlAHIAZgBp\nAGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwBBAGwAZwBlAG0AZQBl\nAG4AIABnAHIAaQBqAHMAcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAcgBpAHMAIABnAGUAbgDp\nAHIAaQBjAG8OQg4bDiMORA4fDiUOTA4qDjUOQA4XDjIOFw4xDkgOJw5EDhsARwBlAG4AZQBsACAARwBy\nAGkAIABQAHIAbwBmAGkAbABpAFkAbABlAGkAbgBlAG4AIABoAGEAcgBtAGEAYQBwAHIAbwBmAGkAaQBs\nAGkARwBlAG4AZQByAGkBDQBrAGkAIABwAHIAbwBmAGkAbAAgAHMAaQB2AGkAaAAgAHQAbwBuAG8AdgBh\nAFUAbgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAcwB6AGEAcgBvAVsAYwBpBB4EMQRJ\nBDgEOQAgBEEENQRABEsEOQAgBD8EQAQ+BEQEOAQ7BEwGRQZEBkEAIAYqBjkGMQZKBkEAIABHAHIAYQB5\nACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBlAGwAIABnAHIA5QB0AG8AbgBlAGIAZQBzAGsAcgBpAHYAZQBs\nAHMAZQAAdGV4dAAAAABDb3B5cmlnaHQgMjAwNyBBcHBsZSBJbmMuLCBhbGwgcmlnaHRzIHJlc2VydmVk\nLgBYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYAAAAAAAAAAQHNAADSLS4vMFokY2xhc3NuYW1lWCRjbGFz\nc2VzXxAQTlNCaXRtYXBJbWFnZVJlcKMvMTJaTlNJbWFnZVJlcFhOU09iamVjdNItLjQ1V05TQXJyYXmi\nNDLSLS43OF5OU011dGFibGVBcnJheaM3NDLTOjsOPD0+V05TV2hpdGVcTlNDb2xvclNwYWNlRDAgMAAQ\nA4AM0i0uQEFXTlNDb2xvcqJAMtItLkNEV05TSW1hZ2WiQzIACAARABoAJAApADIANwBJAEwAUQBTAGIA\naAB3AH4AhQCSAJkApQCtALwAvgDAAMUAxwDJAMsAzQDUANkA5ADmAOgA6gDvAPIA9AD2APgA/wEWATIB\nNAE2Cf4KAwoOChcKKgouCjkKQgpHCk8KUgpXCmYKagpxCnkKhgqLCo0KjwqUCpwKnwqkCqwAAAAAAAAC\nAQAAAAAAAABFAAAAAAAAAAAAAAAAAAAKrw\n</mutableData>\n        </image>\n    </resources>\n</document>\n"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/AirPlayIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"AirPlay.pdf\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_16.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_32.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"32x32\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_32.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"32x32\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_64.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_128.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_256.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"256x256\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_256.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"256x256\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_512.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"512x512\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_512.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"512x512\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_1024.png\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/FermataIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"FermataIcon.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume0.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume1.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume2.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume3.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "BGMApp/BGMApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>AudioHardwarePowerHint</key>\n\t<string>None</string>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.4.3</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.utilities</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>LSUIElement</key>\n\t<true/>\n\t<key>NSAppleEventsUsageDescription</key>\n\t<string>Background Music needs to control your music player app if you want it to automatically pause your music.</string>\n\t<key>NSAppleScriptEnabled</key>\n\t<true/>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2016-2024 Background Music contributors</string>\n\t<key>NSMainNibFile</key>\n\t<string>MainMenu</string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>The &quot;Background Music&quot; virtual audio device sends system audio to Background Music (the app) through a virtual input device.</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>NSServices</key>\n\t<array>\n\t\t<dict/>\n\t</array>\n\t<key>OSAScriptingDefinition</key>\n\t<string>BGMApp.sdef</string>\n    <!--\n    Hopefully this will keep Background Music from using the discrete GPU on multi-GPU systems,\n    which wastes energy. I don't have any hardware I could easily test this on, so it's untested.\n    -->\n\t<key>NSSupportsAutomaticGraphicsSwitching</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMApp/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMDecibel.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDecibel.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMDecibel : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMDecibel.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDecibel.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//  Copyright © 2016 Tanner Hoke\n//\n\n// Self Include\n#import \"BGMDecibel.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"Decibel.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMDecibel {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@\"A9790CD5-4886-47C7-9FFC-DD70743CF2BF\"]\n                                        name:@\"Decibel\"\n                                    bundleID:@\"org.sbooth.Decibel\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (DecibelApplication* __nullable) decibel {\n    return (DecibelApplication* __nullable)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    return self.decibel.running;\n}\n\n- (BOOL) isPlaying {\n    return self.running && self.decibel.playing;\n}\n\n- (BOOL) isPaused {\n    // We don't want to return true when Decibel is stopped, rather than paused. At least for me, Decibel\n    // returns -1 for playbackTime and playbackPosition when it's neither playing nor paused.\n    BOOL probablyNotStopped =\n        self.decibel.playbackTime >= 0 || self.decibel.playbackPosition >= 0;\n    \n    return self.running && !self.decibel.playing && probablyNotStopped;\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMDecibel::pause: Pausing Decibel\");\n        [self.decibel pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMDecibel::unpause: Unpausing Decibel\");\n        [self.decibel play];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayer.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMGooglePlayMusicDesktopPlayer.h\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n//  We have a lot more code for GPMDP than most music players largely because GPMDP has a WebSockets\n//  API and because the user has to enter a code from GPMDP to allow BGMApp to control it.\n//  Currently, the other music players all have AppleScript APIs, so for them the OS asks the user\n//  for permission on our behalf automatically and handles the whole process for us.\n//\n//  This class implements the usual BGMMusicPlayer methods and handles the UI for authenticating\n//  with GPMDP. BGMGooglePlayMusicDesktopPlayerConnection manages the connection to GPMDP and hides\n//  the details of its API.\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n#pragma clang assume_nonnull begin\n\nAPI_AVAILABLE(macos(10.10))\n@interface BGMGooglePlayMusicDesktopPlayer : BGMMusicPlayerBase<BGMMusicPlayer>\n\n+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayer.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMGooglePlayMusicDesktopPlayer.m\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMGooglePlayMusicDesktopPlayer.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGM_Utils.h\"\n#import \"BGMAppWatcher.h\"\n#import \"BGMGooglePlayMusicDesktopPlayerConnection.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMGooglePlayMusicDesktopPlayer {\n    BGMUserDefaults* userDefaults;\n    BGMGooglePlayMusicDesktopPlayerConnection* connection;\n    BGMAppWatcher* appWatcher;\n\n    // True while the auth code dialog is open. The user types in the four-digit auth code from\n    // GPMDP when we connect to it for the first time.\n    BOOL showingAuthCodeDialog;\n    // True if the user has cancelled the auth code dialog. We only show the auth code dialog again\n    // after the user has changed the music player and then changed it back to GPMDP (or restarted\n    // BGMApp).\n    BOOL authCancelled;\n}\n\n+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults {\n    return @[[[self alloc] initWithUserDefaults:userDefaults]];\n}\n\n- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults {\n    // If you're copying this class, replace the ID string with a new one generated by uuidgen (the\n    // command line tool).\n    NSUUID* playerID = [BGMMusicPlayerBase makeID:@\"FCDCC01F-4BF1-4AD2-BE3E-6B7659A90A3F\"];\n    if ((self = [super initWithMusicPlayerID:playerID\n                                        name:@\"GPMDP\"\n                                     toolTip:@\"Google Play Music Desktop Player\"\n                                    bundleID:@\"google-play-music-desktop-player\"])) {\n        userDefaults = defaults;\n        showingAuthCodeDialog = NO;\n        authCancelled = NO;\n\n        // We don't strictly need to use a weak ref (at least not yet), but it doesn't hurt.\n        BGMGooglePlayMusicDesktopPlayer* __weak weakSelf = self;\n\n        connection = [[BGMGooglePlayMusicDesktopPlayerConnection alloc]\n                initWithUserDefaults:userDefaults\n                 authRequiredHandler:^{\n                     BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;\n                     return [strongSelf requestAuthCodeFromUser];\n                 }\n              connectionErrorHandler:^{\n                  BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;\n                  [strongSelf showConnectionErrorDialog];\n              }\n           apiVersionMismatchHandler:^(NSString* reportedAPIVersion) {\n               BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;\n               [strongSelf showAPIVersionMismatchDialog:reportedAPIVersion];\n           }];\n\n        // Set up callbacks that run when GPMDP is opened or closed.\n        appWatcher = [[BGMAppWatcher alloc]\n                initWithBundleID:BGMNN(self.bundleID)\n                     appLaunched:^{\n                         BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;\n                         [strongSelf gpmdpWasLaunched];\n                     }\n                   appTerminated:^{\n                       BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;\n                       [strongSelf gpmdpWasTerminated];\n                   }];\n    }\n    \n    return self;\n}\n\n- (void) gpmdpWasLaunched {\n    if (self.selected) {\n        // Reconnect so we can control GPMDP.\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::gpmdpWasLaunched: GPMDP launched. Connecting\");\n\n        // Try up to 10 times because GPMDP won't start accepting connections until it's finished\n        // starting up.\n        //\n        // TODO: If GPMDP shows an alert before it finishes launching, it doesn't start accepting\n        //       connections until the alert is dismissed, which can make this can timeout.\n        // TODO: Is the error dialog still shown if the user closes GPMDP again while we're\n        //       retrying? It shouldn't be.\n        [connection connectWithRetries:10];\n    }\n}\n\n- (void) gpmdpWasTerminated {\n    if (self.selected) {\n        // Allow the connection to clean up and reset itself.\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::gpmdpWasTerminated: GPMDP has been closed.\");\n        [connection disconnect];\n    }\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n\n    // Allow the auth code dialog to be shown again if we were hiding it because the user cancelled\n    // it last time.\n    authCancelled = NO;\n\n    if (self.running) {\n        // Only retry once so the error message is shown fairly quickly if we fail to connect.\n        [connection connectWithRetries:1];\n    }\n}\n\n- (void) wasDeselected {\n    [super wasDeselected];\n    [connection disconnect];\n}\n\n- (NSString* __nullable) requestAuthCodeFromUser {\n    if (showingAuthCodeDialog) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::requestAuthCodeFromUser: \"\n                 \"Already showing the auth code dialog\");\n        return nil;\n    }\n\n    if (authCancelled) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::requestAuthCodeFromUser: \"\n                 \"Previously cancelled. Doing nothing.\");\n        return nil;\n    }\n\n    showingAuthCodeDialog = YES;\n\n    // Ask the user to read the auth code from GPMDP and type it in to BGMApp.\n    NSString* __nullable authCode = [self showAuthCodeDialog];\n\n    showingAuthCodeDialog = NO;\n\n    return authCode;\n}\n\n- (NSString* __nullable) showAuthCodeDialog {\n    // When this isn't being called because the user just changed something in BGMApp (e.g. GPMDP\n    // was closed, they selected it in BGMApp for the first time, then opened GPMDP later), we could\n    // use notifications instead of an NSAlert. But it probably wouldn't happen often enough to be\n    // worth the effort.\n    NSAlert* alert = [NSAlert new];\n    alert.messageText = @\"Background Music needs permission to control GPMDP.\";\n    alert.informativeText = @\"It should be displaying a four-digit code for you to enter.\";\n    [alert addButtonWithTitle:@\"OK\"];\n    [alert addButtonWithTitle:@\"Cancel\"];\n\n    // The text field to type the auth code in.\n    // TODO: Can we derive these dimensions from something instead of hardcoding them?\n    NSTextField* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 50, 24)];\n    [alert setAccessoryView:input];\n\n    // Focus the text field (so the user doesn't have to do it themselves).\n    [alert.window setInitialFirstResponder:input];\n\n    // Bring GMPDP to the front, underneath our NSAlert, so the user can see the auth code.\n    [self showGPMDPBehindAuthCodeDialog];\n\n    NSModalResponse buttonPressed = [alert runModal];\n\n    if (buttonPressed == NSAlertFirstButtonReturn) {\n        // Set input's value to the text entered by the user so we can access it.\n        [input validateEditing];\n\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::showAuthCodeDialog: Got auth code: <private>\");\n        return input.stringValue;\n    } else {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::showAuthCodeDialog: \"\n                 \"The user cancelled the auth code dialog\");\n        authCancelled = YES;\n        return nil;\n    }\n}\n\n- (void) showGPMDPBehindAuthCodeDialog {\n    // Dispatched because if we do this just before showing the auth code dialog, the user's current\n    // active window will be deactivated, the auth code dialog will become the active window and\n    // macOS will act as if the user activated it themselves. To avoid stealing key focus, it won't\n    // activate GPMDP.\n    //\n    // We could pass NSApplicationActivateIgnoringOtherApps to activateWithOptions instead, but then\n    // GPMDP would be activated even if the user really did activate a different application, which\n    // would steal focus from it.\n    //\n    // 250 ms is a reasonable value on my system, but won't always be long enough. When it isn't,\n    // GPMDP won't be activated, but that just means the user will have to do it themselves.\n    const int64_t delay = 250 * NSEC_PER_MSEC;\n\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),\n                   dispatch_get_main_queue(),\n                   ^{\n                       // Make GMPDP the frontmost app.\n                       NSArray<NSRunningApplication*>* gpmdpApps =\n                           [NSRunningApplication\n                            runningApplicationsWithBundleIdentifier:BGMNN(self.bundleID)];\n\n                       if (gpmdpApps.count > 0) {\n                           [gpmdpApps[0] activateWithOptions:0];\n                       }\n\n                       // Focus the auth code dialog. It will already be in front of GPMDP because\n                       // it's modal. Dispatched for the same reason as above.\n                       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),\n                                      dispatch_get_main_queue(),\n                                      ^{\n                                          [NSApp activateIgnoringOtherApps:YES];\n                                      });\n                   });\n}\n\n- (void) showConnectionErrorDialog {\n    NSString* errorMsg = @\"Could not connect to Google Play Music Desktop Player\";\n    NSString* troubleshootingMsg =\n        [NSString stringWithFormat:\n         @\"Make sure \\\"Enable JSON API\\\" and \\\"Enable Playback API\\\" are both checked in GPMDP's \"\n         \"settings, then restart GPMDP.\\n\\n\"\n         \"GPMDP should be listening on its default port, 5672.\\n\\n\"\n         \"Consider filing a bug report at %s\",\n         kBGMIssueTrackerURL];\n\n    [self showErrorDialog:errorMsg troubleshootingMsg:troubleshootingMsg];\n}\n\n- (void) showAPIVersionMismatchDialog:(NSString*)reportedAPIVersion {\n    NSString* errorMsg = @\"Google Play Music Desktop Player Version Not Supported\";\n    NSString* troubleshootingMsg =\n            [NSString stringWithFormat:\n             @\"GPMDP reported its API version as \\\"%@\\\", which Background Music doesn't support \"\n             \"yet. Background Music might not be able to control GPMDP properly.\\n\\n\"\n             \"Feel free to open an issue about this at %s\",\n             reportedAPIVersion,\n             kBGMIssueTrackerURL];\n\n    [self showErrorDialog:errorMsg troubleshootingMsg:troubleshootingMsg];\n}\n\n- (void) showErrorDialog:(NSString*)errorMsg troubleshootingMsg:(NSString*)troubleshootingMsg {\n    if (!self.running) {\n        // GPMDP isn't running, so there's no need to inform the user. (The \"Auto-pause GPMDP\" menu\n        // item will be greyed out, but that's handled elsewhere.)\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::showErrorDialog: Not running\");\n        return;\n    }\n\n    NSLog(@\"%@\", errorMsg);\n\n    // Show the error in a UI dialog.\n    NSAlert* alert = [NSAlert new];\n    alert.messageText = errorMsg;\n    alert.informativeText = troubleshootingMsg;\n    // TODO: Show the suppression checkbox and save its value in user defaults.\n    alert.showsSuppressionButton = NO;\n    [alert addButtonWithTitle:@\"OK\"];\n\n    [alert runModal];\n}\n\n- (BOOL) isRunning {\n    // We have to check with NSRunningApplication instead of just setting a flag in appWatcher's\n    // callbacks because BGMAutoPauseMenuItem calls this method when it's notified by its own\n    // instance of BGMAppWatcher. If BGMAutoPauseMenuItem got notified first, the flag wouldn't be\n    // updated in time.\n    //\n    // At some point we might want to try to avoid this by making the BGMMusicPlayers' running\n    // properties observable.\n    NSArray<NSRunningApplication*>* instances =\n        [NSRunningApplication runningApplicationsWithBundleIdentifier:BGMNN(self.bundleID)];\n\n    return instances.count > 0;\n}\n\n- (BOOL) isPlaying {\n    return self.running && connection.playing;\n}\n\n- (BOOL) isPaused {\n    return self.running && connection.paused;\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here.\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::pause: Pausing Google Play Music Desktop \"\n                 \"Player\");\n        // There's a race condition here and in unpause because, if the user paused GPMDP just\n        // before we called playPause, GPMDP would play instead of pausing. I'm not sure there's\n        // much we can/should do about it.\n        [connection playPause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here.\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayer::unpause: Unpausing Google Play Music Desktop \"\n                 \"Player\");\n        [connection playPause];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayerConnection.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMGooglePlayMusicDesktopPlayerConnection.h\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n#import <WebKit/WebKit.h>\n\n\n#pragma clang assume_nonnull begin\n\nAPI_AVAILABLE(macos(10.10))\n@interface BGMGooglePlayMusicDesktopPlayerConnection : NSObject<WKScriptMessageHandler>\n\n// authRequiredHandler: A UI callback that asks the user for the auth code GPMDP will display.\n//                      Returns the auth code they entered, or nil.\n// connectionErrorHandler: A UI callback that shows a connection error message.\n// apiVersionMismatchHandler: A UI callback that shows a warning dialog explaining that GPMDP\n//                            reported an API version that we don't support yet.\n- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults\n                  authRequiredHandler:(NSString* __nullable (^)(void))authHandler\n               connectionErrorHandler:(void (^)(void))errorHandler\n            apiVersionMismatchHandler:(void (^)(NSString* reportedAPIVersion))apiVersionHandler;\n\n// Returns before the connection has been fully established. The playing and paused properties will\n// remain false until the connection is complete, but playPause can be called at any time after\n// calling this method.\n//\n// If the connection fails, it will be retried after a one second delay, up to the number of times\n// given.\n- (void) connectWithRetries:(int)retries;\n- (void) disconnect;\n\n// Tell GPMDP to play if it's paused or pause if it's playing.\n- (void) playPause;\n\n@property (readonly) BOOL playing;\n@property (readonly) BOOL paused;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayerConnection.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMGooglePlayMusicDesktopPlayerConnection.m\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMGooglePlayMusicDesktopPlayerConnection.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n// When GooglePlayMusicDesktopPlayer.js sends a message to this class, it sets the message handler\n// name to one of these, which tells us what type of message it is. (This is a macro because you\n// can't make a static const NSArray.)\n#define kScriptMessageHandlerNames (@[@\"gpmdp\", @\"log\", @\"error\"])\n\n@implementation BGMGooglePlayMusicDesktopPlayerConnection {\n    // GPMDP has a WebSocket API, so we use a WKWebView to access it using Javascript. Using a\n    // proper library would make the code a bit cleaner and save a little memory, but I'm not sure\n    // it would be worth adding an external dependency for that.\n    WKWebView* webView;\n    NSString* __nullable permanentAuthCode;\n    BGMUserDefaults* userDefaults;\n    // The number of times to retry if we fail to connect. For example, if GPMDP is still starting\n    // up. Set to 0 when we aren't trying to connect.\n    int connectionRetries;\n\n    // A UI callback that asks the user for the auth code GPMDP will display.\n    NSString* __nullable (^authRequiredHandler)(void);\n    // A UI callback that shows a connection error message.\n    void (^connectionErrorHandler)(void);\n    // A UI callback that shows a warning dialog explaining that GPMDP reported an API version that\n    // we don't support yet.\n    void (^apiVersionMismatchHandler)(NSString* reportedAPIVersion);\n}\n\n- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults\n                  authRequiredHandler:(NSString* __nullable (^)(void))authHandler\n               connectionErrorHandler:(void (^)(void))errorHandler\n            apiVersionMismatchHandler:(void (^)(NSString* reportedAPIVersion))apiVersionHandler {\n    if((self = [super init])) {\n        userDefaults = defaults;\n        authRequiredHandler = authHandler;\n        connectionErrorHandler = errorHandler;\n        apiVersionMismatchHandler = apiVersionHandler;\n        connectionRetries = 0;\n\n        // Lazily initialised.\n        permanentAuthCode = nil;\n\n        // Report that GPMDP is stopped until we know otherwise.\n        _playing = NO;\n        _paused = NO;\n    }\n\n    return self;\n}\n\n// Creates and initialises webView, a WKWebView we use to communicate with GPMDP over WebSockets.\n- (void) createWebView {\n    // Read the Javascript we'll need for this.\n    NSString* __nullable jsPath =\n            [[NSBundle mainBundle] pathForResource:@\"GooglePlayMusicDesktopPlayer.js\"\n                                            ofType:nil];\n    NSError* err;\n    NSString* __nullable jsStr =\n        (!jsPath ? nil : [NSString stringWithContentsOfFile:BGMNN(jsPath)\n                                                   encoding:NSUTF8StringEncoding\n                                                      error:&err]);\n\n    if (err || !jsStr || [jsStr isEqualToString:@\"\"]) {\n        // TODO: Return an error so the caller can show an error dialog or something.\n        NSLog(@\"Error loading GPMDP Javascript file: %@\", err);\n    } else {\n        webView = [WKWebView new];\n\n        // Register to receive messages from our Javascript. The messages are handled in\n        // userContentController. We register several times using different names as a convenient\n        // way to separate messages from GPMDP, messages to log and errors.\n        for (NSString* name in kScriptMessageHandlerNames) {\n            [webView.configuration.userContentController addScriptMessageHandler:self name:name];\n        }\n\n        // Load our Javascript functions into webView so we can call them later.\n        [self evaluateJavaScript:BGMNN(jsStr)];\n    }\n}\n\n- (void) connectWithRetries:(int)retries {\n    if (retries < 0) {\n        BGMAssert(false, \"retries < 0\");\n        return;\n    }\n\n    if (!permanentAuthCode) {\n        // Read the API auth code from user defaults (actually the keychain), if there is one. If\n        // the user hasn't authenticated before, it will be nil.\n        //\n        // We do this lazily because it can show a password dialog in debug/unsigned builds.\n        permanentAuthCode = userDefaults.googlePlayMusicDesktopPlayerPermanentAuthCode;\n    }\n\n    connectionRetries = retries;\n\n    // Create the WKWebView we'll use to connect to GPMDP with WebSockets. Using a WKWebView means\n    // Background Music uses a bit more memory while connected to GPMDP, around 15 MB for me, but\n    // saves us having to complicate the build process to add a dependency on a proper library.\n    [self createWebView];\n\n    if (permanentAuthCode) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::connectWithRetries: \"\n                 \"Connecting with auth code\");\n\n        NSString* __nullable percentEncodedCode =\n                [BGMGooglePlayMusicDesktopPlayerConnection\n                        toPercentEncoded:BGMNN(permanentAuthCode)];\n\n        [self evaluateJavaScript:[NSString stringWithFormat:@\"connect('%@');\", percentEncodedCode]];\n    } else {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::connectWithRetries: \"\n                 \"Connecting without auth code\");\n        [self evaluateJavaScript:@\"connect();\"];\n    }\n\n    // Check whether GPMDP is playing, paused or stopped.\n    [self requestPlaybackState];\n}\n\n- (void) disconnect {\n    // Stop retrying if we're in the process of connecting.\n    connectionRetries = 0;\n\n    // evaluateJavaScript is only safe to call on the main thread.\n    dispatch_async(dispatch_get_main_queue(), ^{\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::disconnect: Disconnecting\");\n\n        [webView evaluateJavaScript:@\"disconnect();\"\n                  completionHandler:^(id __nullable result, NSError* __nullable error) {\n#pragma unused (result)\n                      if (error) {\n                          NSLog(@\"Error closing connection to GPMDP: %@\", error);\n                      }\n\n                      // Allow the WKWebView to be garbage collected.\n                      for (NSString* name in kScriptMessageHandlerNames) {\n                          [webView.configuration.userContentController\n                                  removeScriptMessageHandlerForName:name];\n                      }\n                      webView = nil;\n                  }];\n    });\n}\n\n- (void) evaluateJavaScript:(NSString*)js {\n    // evaluateJavaScript is only safe to call on the main thread.\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [webView evaluateJavaScript:js\n                  completionHandler:^(id __nullable result, NSError* __nullable error) {\n#pragma unused (result)\n                      if (error) {\n                          // TODO: We should probably show an error dialog in some cases.\n                          NSLog(@\"JS error: %@\", error);\n                      }\n                  }];\n    });\n}\n\n- (void) playPause {\n    [self evaluateJavaScript:@\"playPause();\"];\n}\n\n- (void) sendAuthCode:(NSString*)authCode {\n    // Don't log the code itself just in case it could be a security problem.\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::sendAuthCode: Sending GPMDP auth code\");\n\n    // Percent-encode the user input just in case they entered something that could execute as\n    // Javascript. We could limit the input to four digits instead, but this should be fine.\n    NSString* __nullable percentEncodedCode =\n            [BGMGooglePlayMusicDesktopPlayerConnection toPercentEncoded:authCode];\n\n    // We send the message to GPMDP even if percentEncodedCode is nil so it will reply with an error\n    // and BGMApp will ask the user for the auth code again.\n    NSString* js = [NSString stringWithFormat:@\"window.sendAuthCode('%@');\", percentEncodedCode];\n    [self evaluateJavaScript:js];\n}\n\n- (void) sendPermanentAuthCode {\n    NSString* __nullable code = permanentAuthCode;\n\n    if (code) {\n        // Don't log the code itself just in case it could be a security problem.\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::sendPermanentAuthCode: \"\n                 \"Sending GPMDP permanent auth code\");\n\n        // Percent-encode it just in case something it includes could be executed as Javascript.\n        NSString* __nullable percentEncodedCode =\n                [BGMGooglePlayMusicDesktopPlayerConnection toPercentEncoded:BGMNN(code)];\n\n        // Pass the code to our WKWebView so it can send it to GPMDP.\n        NSString* js =\n                [NSString stringWithFormat:@\"sendPermanentAuthCode('%@');\", percentEncodedCode];\n        [self evaluateJavaScript:js];\n    } else {\n        NSLog(@\"BGMGooglePlayMusicDesktopPlayerConnection::sendPermanentAuthCode: No code to send\");\n    }\n}\n\n+ (NSString* __nullable)toPercentEncoded:(NSString*)rawString {\n    // Just percent-encode every character (by passing an empty NSCharacterSet as the allowed\n    // characters).\n    NSString* __nullable percentEncoded = [rawString\n                                           stringByAddingPercentEncodingWithAllowedCharacters:\n                                           [NSCharacterSet characterSetWithCharactersInString:@\"\"]];\n    if (percentEncoded) {\n        return percentEncoded;\n    } else {\n        // The docs say that stringByAddingPercentEncodingWithAllowedCharacters returns nil \"if the\n        // transformation is not possible\", but don't explain when that could happen. According to\n        // https://stackoverflow.com/a/33558934/1091063 it can be caused by the string containing\n        // invalid unicode.\n        NSLog(@\"Could not encode\");\n        return nil;\n    }\n}\n\n// Ask GPMDP whether it's playing, paused or stopped. The response is handled asynchronously in\n// handleResultMessage.\n- (void) requestPlaybackState {\n    [self evaluateJavaScript:@\"requestPlaybackState();\"];\n}\n\n#pragma mark WKScriptMessageHandler Methods\n\n- (void) userContentController:(WKUserContentController*)userContentController\n       didReceiveScriptMessage:(WKScriptMessage*)message {\n#pragma unused (userContentController)\n\n    if ([@\"log\" isEqual:message.name]) {\n        // The message body is always a string in this case.\n        [self handleLogMessage:message.body];\n    } else if ([@\"error\" isEqual:message.name]) {\n        [self handleConnectionError];\n    } else {\n        BGMAssert([@\"gpmdp\" isEqual:message.name], \"Unexpected message handler name\");\n        [self handleGPMDPMessage:message];\n    }\n}\n\n- (void) handleLogMessage:(NSString*)message {\n    (void)message;\n#if DEBUG\n    if (permanentAuthCode) {\n        // Avoid logging the auth code, which would be a minor security issue.\n        message = [message stringByReplacingOccurrencesOfString:BGMNN(permanentAuthCode)\n                                                     withString:@\"<private>\"];\n    }\n\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::userContentController: %s\",\n             message.UTF8String);\n#endif\n}\n\n- (void) handleConnectionError {\n    if (connectionRetries > 0) {\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleConnectionError: \"\n                 \"Retrying in 1 second\");\n\n        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),\n                       dispatch_get_main_queue(),\n                       ^{\n                           // Check connectionRetries again because disconnect may have been called.\n                           if (connectionRetries > 0) {\n                               DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::\"\n                                        \"handleConnectionError: Retrying\");\n                               [self connectWithRetries:(connectionRetries - 1)];\n                           }\n                       });\n    } else {\n        NSLog(@\"BGMGooglePlayMusicDesktopPlayerConnection::handleConnectionError: \"\n               \"No retries left. Giving up.\");\n        connectionErrorHandler();\n    }\n}\n\n- (void) handleGPMDPMessage:(WKScriptMessage*)message {\n    // See https://github.com/MarshallOfSound/Google-Play-Music-Desktop-Player-UNOFFICIAL-/blob/master/docs/PlaybackAPI_WebSocket.md\n\n    // Type check.\n    if (![message.body isKindOfClass:[NSDictionary class]]) {\n        NSLog(@\"Unexpected message body type\");\n        return;\n    }\n\n    NSDictionary* body = message.body;\n    NSString* messageType;\n\n    // The key for the message type is \"channel\", except when the message is a response, in which\n    // case the key can be \"namespace\".\n    if ([body[@\"channel\"] isKindOfClass:[NSString class]]) {\n        messageType = body[@\"channel\"];\n    } else if ([body[@\"namespace\"] isKindOfClass:[NSString class]]) {\n        messageType = body[@\"namespace\"];\n    } else {\n        NSLog(@\"No channel/namespace\");\n        return;\n    }\n\n    // Handle the message depending on its type (or ignore it).\n    if ([@\"API_VERSION\" isEqual:messageType]) {\n        [self handleAPIVersionMessage:body];\n    } else if ([@\"connect\" isEqual:messageType]) {\n        [self handleConnectMessage:body];\n    } else if ([@\"playState\" isEqual:messageType]) {\n        [self handlePlayStateMessage:body];\n    } else if ([@\"result\" isEqual:messageType]) {\n        [self handleResultMessage:body];\n    }\n}\n\n- (void) handleAPIVersionMessage:(NSDictionary*)body {\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: Response: %s\",\n             [NSString stringWithFormat:@\"%@\", body].UTF8String);\n\n    // Type check.\n    if (![body[@\"payload\"] isKindOfClass:[NSString class]]) {\n        NSLog(@\"Unexpected payload type\");\n        [self handleConnectionError];\n        return;\n    }\n\n    NSString* apiVersion = body[@\"payload\"];\n    // \"1.0.0\" -> [\"1\", \"0\", \"0\"]\n    NSArray<NSString*>* versionParts = [apiVersion componentsSeparatedByString:@\".\"];\n\n    // Check the major version number is 1, which is the only major version we support.\n    if (versionParts.count > 0) {\n        NSInteger majorVersion = versionParts[0].integerValue;\n\n        DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: \"\n                 \"Major version: %lu\", majorVersion);\n\n        if (majorVersion == 1) {\n            // GPMDP uses SemVer, so as long as the major version number matches what we can handle,\n            // it should work.\n            DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: \"\n                     \"This API version is supported\");\n            return;\n        }\n    }\n\n    // Show a warning dialog box to the user, but try to continue anyway. There's probably a\n    // reasonable chance it'll still work.\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: \"\n             \"Unsupported GPMDP API version\");\n    apiVersionMismatchHandler(apiVersion);\n}\n\n- (void) handleConnectMessage:(NSDictionary*)body {\n    // Don't log the response as it may contain the auth code.\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleConnectMessage: Received response\");\n\n    // Type check.\n    if (![body[@\"payload\"] isKindOfClass:[NSString class]]) {\n        NSLog(@\"Unexpected payload type\");\n        [self handleConnectionError];\n        return;\n    }\n\n    NSString* payload = body[@\"payload\"];\n\n    if ([@\"CODE_REQUIRED\" isEqual:payload]) {\n        // Ask the user for the auth code GPMDP is displaying and send it to GPMDP to finish\n        // connecting.\n        NSString* __nullable authCode = authRequiredHandler();\n\n        if (authCode) {\n            [self sendAuthCode:BGMNN(authCode)];\n        }\n    } else {\n        // The payload should be the permanent auth code.\n        permanentAuthCode = payload;\n        [self sendPermanentAuthCode];\n\n        // Save the code to the keychain so we can use it when connecting to GPMDP in future.\n        userDefaults.googlePlayMusicDesktopPlayerPermanentAuthCode = permanentAuthCode;\n    }\n}\n\n- (void) handlePlayStateMessage:(NSDictionary*)body {\n    (void)body;\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handlePlayStateMessage: Response: %s\",\n             [NSString stringWithFormat:@\"%@\", body].UTF8String);\n\n    // This message tells us the playstate has changed, but doesn't differentiate between stopped\n    // and paused. The response to this API request will. See handleResultMessage.\n    // TODO: Can it transition from stopped to paused? Would that be a problem?\n    [self requestPlaybackState];\n}\n\n- (void) handleResultMessage:(NSDictionary*)body {\n    DebugMsg(\"BGMGooglePlayMusicDesktopPlayerConnection::handleResultMessage: Response: %s\",\n             [NSString stringWithFormat:@\"%@\", body].UTF8String);\n\n    // Type check.\n    if (![body[@\"value\"] isKindOfClass:[NSNumber class]]) {\n        NSLog(@\"No value\");\n        return;\n    }\n\n    // 0 - Playback is stopped\n    // 1 - Track is paused\n    // 2 - Track is playing\n    int state = ((NSNumber*)body[@\"value\"]).intValue;\n    _playing = (state == 2);\n    _paused = (state == 1);\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMHermes.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMHermes.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMHermes : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMHermes.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMHermes.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMHermes.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"Hermes.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMHermes {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    // If you're copying this class, replace the ID string with a new one generated by uuidgen. (Command line tool.)\n    if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@\"0CDC67B0-56D3-4D94-BC06-6E380D8F5E34\"]\n                                        name:@\"Hermes\"\n                                    bundleID:@\"com.alexcrichton.Hermes\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (HermesApplication* __nullable) hermes {\n    return (HermesApplication* __nullable)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    // Note that this will return NO if is self.hermes is nil (i.e. Hermes isn't running).\n    return self.hermes.running;\n}\n\n// isPlaying and isPaused check self.running first just in case Hermes is closed but self.hermes hasn't become\n// nil yet. In that case, reading self.hermes.playerState could make Scripting Bridge open Hermes.\n\n- (BOOL) isPlaying {\n    return self.running && (self.hermes.playbackState == HermesPlayerStatesPlaying);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.hermes.playbackState == HermesPlayerStatesPaused);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMHermes::pause: Pausing Hermes\");\n        [self.hermes pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMHermes::unpause: Unpausing Hermes\");\n        [self.hermes play];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusic.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusic.h\n//  BGMApp\n//\n//  Copyright © 2016, 2019 Kyle Neideck\n//  Copyright © 2019 theLMGN\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMMusic : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusic.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusic.m\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck, theLMGN\n//\n\n// Self Include\n#import \"BGMMusic.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"Music.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMMusic {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n+ (NSUUID*) sharedMusicPlayerID {\n    NSUUID* __nullable musicPlayerID =\n            [[NSUUID alloc] initWithUUIDString:@\"829B8069-8BD2-481D-BD40-54AB8CDAE228\"];\n    NSAssert(musicPlayerID, @\"BGMMusic::sharedMusicPlayerID: !musicPlayerID\");\n    return (NSUUID*)musicPlayerID;\n}\n\n- (instancetype) init {\n    if ((self = [super initWithMusicPlayerID:[BGMMusic sharedMusicPlayerID]\n                                        name:@\"Music\"\n                                    bundleID:@\"com.apple.Music\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (MusicApplication* __nullable) music {\n    return (MusicApplication*)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    return self.music.running;\n}\n\n// isPlaying and isPaused check self.running first just in case Music is closed but self.music\n// hasn't become nil yet. In that case, reading self.music.playerState could make Scripting Bridge\n// open Music.\n\n- (BOOL) isPlaying {\n    return self.running && (self.music.playerState == MusicEPlSPlaying);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.music.playerState == MusicEPlSPaused);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMMusic::pause: Pausing Music\");\n        [self.music pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMMusic::unpause: Unpausing Music\");\n        [self.music playpause];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayer.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusicPlayer.h\n//  BGMApp\n//\n//  Copyright © 2016, 2018, 2019 Kyle Neideck\n//\n//  The base classes and protocol for objects that represent a music player app.\n//\n//  To add support for a music player, create a class that implements the BGMMusicPlayer protocol\n//  and add it to initWithAudioDevices in BGMMusicPlayers.mm.\n//\n//  You'll probably want to subclass BGMMusicPlayerBase and, if the music player supports\n//  AppleScript, use BGMScriptingBridge. Your class might need to override the icon method if the\n//  default implementation from BGMMusicPlayerBase doesn't work.\n//\n//  BGMSpotify will probably be the most useful example to follow, but they're all pretty\n//  similar. The music player classes written so far all use Scripting Bridge to communicate with\n//  the music player apps (see iTunes.h/Spotify.h) but any other way is fine too.\n//\n//  BGMDriver will use either the music player's bundle ID or PID to match it to the audio it\n//  plays. (Though using PIDs hasn't been tested yet.)\n//\n//  If you're not sure what bundle ID the music player uses, install a debug build of BGMDriver\n//  and play something in the music player. The easiest way is to do\n//      build_and_install.sh -d\n//  BGMDriver will log the bundle ID to system.log when it becomes aware of the music player.\n//\n\n// Local Includes\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@protocol BGMMusicPlayer <NSObject>\n\n// Classes return an instance of themselves for each music player app they make available in\n// BGMApp. So far that's always been a single instance, but that will probably change eventually.\n// Most classes don't need to override the default implementation from BGMMusicPlayerBase.\n//\n// But, for example, a class for custom music players would probably return an instance for each\n// custom player the user has created. (Also note that it could return an empty array.)\n//\n// TODO: I think the return type should actually be NSArray<instancetype>*, but that doesn't seem\n//       to work. There's a Clang bug about this: https://llvm.org/bugs/show_bug.cgi?id=27323\n//       (though it hasn't been confirmed yet).\n+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;\n\n// We need a unique ID for each music player to store in user defaults. In the most common case,\n// classes that provide a static (or at least bounded) number of music players, you can generate\n// IDs with uuidgen (the command line tool) and include them in your class as constants. Otherwise,\n// you'll probably want to store them in user defaults and load them in createInstancesWithDefaults.\n@property (readonly) NSUUID* musicPlayerID;\n\n// The name, tool-tip and icon of the music player, to be used in the UI.\n@property (readonly) NSString* name;\n@property (readonly) NSString* __nullable toolTip;\n@property (readonly) NSImage* __nullable icon;\n\n@property (readonly) NSString* __nullable bundleID;\n\n// Classes will usually ignore this property and leave it nil unless the music player has no\n// bundle ID.\n//\n// TODO: If we ever add a music player class that uses this property, it'll need a way to inform\n//       BGMDevice of changes. It might be easiest to have BGMMusicPlayers to observe this property,\n//       on the selected music player, with KVO and update BGMDevice when it changes. Or\n//       BGMMusicPlayers could pass a pointer to itself to createInstancesWithDefaults.\n@property NSNumber* __nullable pid;\n\n// True if this is currently the selected music player.\n@property (readonly) BOOL selected;\n\n// The state of the music player.\n//\n// True if the music player app is open.\n@property (readonly, getter=isRunning) BOOL running;\n// True if the music player is playing a song or some other user-selected audio file. Note that\n// the music player playing audio for UI, notifications, etc. won't make this true (which is why we\n// need this property and can't just ask BGMDriver if the music player is playing audio).\n@property (readonly, getter=isPlaying) BOOL playing;\n// True if the music player has a current/open song (or whatever) and will continue playing it if\n// BGMMusicPlayer::unpause is called. Normally because the user was playing a song and they or\n// BGMApp paused it.\n@property (readonly, getter=isPaused) BOOL paused;\n\n// Called when the user selects this music player.\n- (void) wasSelected;\n// Called when this was the selected music player and the user just selected a different one.\n- (void) wasDeselected;\n\n// Pause the music player. Does nothing if the music player is already paused or isn't running.\n// Returns YES if the music player is paused now but wasn't before, returns NO otherwise.\n- (BOOL) pause;\n// Unpause the music player. Does nothing if the music player is already playing or isn't running.\n// Returns YES if the music player is playing now but wasn't before, returns NO otherwise.\n- (BOOL) unpause;\n\n@end\n\n\n@interface BGMMusicPlayerBase : NSObject\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                              bundleID:(NSString* __nullable)bundleID;\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                               toolTip:(NSString*)toolTip\n                              bundleID:(NSString* __nullable)bundleID;\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                               toolTip:(NSString* __nullable)toolTip\n                              bundleID:(NSString* __nullable)bundleID\n                                   pid:(NSNumber* __nullable)pid;\n\n// Convenience wrapper around NSUUID's initWithUUIDString. musicPlayerIDString must be a string\n// generated by uuidgen (command line tool), e.g. \"60BA9739-B6DD-4E6A-8134-51410A45BB84\".\n+ (NSUUID*) makeID:(NSString*)musicPlayerIDString;\n\n// BGMMusicPlayer default implementations\n+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;\n@property (readonly) NSImage* __nullable icon;\n@property (readonly) NSUUID* musicPlayerID;\n@property (readonly) NSString* name;\n@property (readonly) NSString* __nullable toolTip;\n@property (readonly) NSString* __nullable bundleID;\n@property NSNumber* __nullable pid;\n@property (readonly) BOOL selected;\n- (void) wasSelected;\n- (void) wasDeselected;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayer.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusicPlayer.m\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMMusicPlayer.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMMusicPlayerBase\n\n@synthesize musicPlayerID = _musicPlayerID;\n@synthesize name = _name;\n@synthesize toolTip = _toolTip;\n@synthesize bundleID = _bundleID;\n@synthesize pid = _pid;\n@synthesize selected = _selected;\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                              bundleID:(NSString* __nullable)bundleID {\n    return [self initWithMusicPlayerID:musicPlayerID\n                                  name:name\n                               toolTip:nil\n                              bundleID:bundleID\n                                   pid:nil];\n}\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                               toolTip:(NSString*)toolTip\n                              bundleID:(NSString* __nullable)bundleID {\n    return [self initWithMusicPlayerID:musicPlayerID\n                                  name:name\n                               toolTip:toolTip\n                              bundleID:bundleID\n                                   pid:nil];\n}\n\n- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID\n                                  name:(NSString*)name\n                               toolTip:(NSString* __nullable)toolTip\n                              bundleID:(NSString* __nullable)bundleID\n                                   pid:(NSNumber* __nullable)pid {\n    if ((self = [super init])) {\n        NSAssert(musicPlayerID, @\"BGMMusicPlayerBase::initWithMusicPlayerID: !musicPlayerID\");\n        \n        NSAssert([self conformsToProtocol:@protocol(BGMMusicPlayer)],\n                 @\"BGMMusicPlayerBase::initWithMusicPlayerID: !conformsToProtocol\");\n        \n        _musicPlayerID = musicPlayerID;\n        _name = name;\n        _toolTip = toolTip;\n        _bundleID = bundleID;\n        _pid = pid;\n        _selected = NO;\n    }\n\n    return self;\n}\n\n+ (NSUUID*) makeID:(NSString*)musicPlayerIDString {\n    NSUUID* __nullable musicPlayerID = [[NSUUID alloc] initWithUUIDString:musicPlayerIDString];\n    NSAssert(musicPlayerID, @\"BGMMusicPlayerBase::makeID: !musicPlayerID\");\n    \n    return (NSUUID*)musicPlayerID;\n}\n\n#pragma mark BGMMusicPlayer default implementations\n\n+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults {\n    #pragma unused (userDefaults)\n    // TODO: Fix this warning properly.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wobjc-literal-conversion\"\n    return @[ [self new] ];\n#pragma clang diagnostic pop\n}\n\n- (NSImage* __nullable) icon {\n    NSString* __nullable bundleID = self.bundleID;\n    NSString* __nullable bundlePath =\n        (!bundleID ? nil : [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:(NSString*)bundleID]);\n    \n    return (!bundlePath ? nil : [[NSWorkspace sharedWorkspace] iconForFile:(NSString*)bundlePath]);\n}\n\n- (void) wasSelected {\n    _selected = YES;\n}\n\n- (void) wasDeselected {\n    _selected = NO;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayers.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusicPlayers.h\n//  BGMApp\n//\n//  Copyright © 2016, 2019 Kyle Neideck\n//\n//  Holds the music players (i.e. BGMMusicPlayer objects) available in BGMApp. Also keeps track of\n//  which music player is currently selected by the user.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMMusicPlayer.h\"\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMMusicPlayers : NSObject\n\n// Calls initWithAudioDevices:musicPlayers: with sensible defaults.\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                         userDefaults:(BGMUserDefaults*)defaults;\n\n// defaultMusicPlayerID is the musicPlayerID (see BGMMusicPlayer.h) of the music player that should be\n// selected by default.\n//\n// The createInstancesWithDefaults method of each class in musicPlayerClasses will be called and\n// the results will be stored in the musicPlayers property.\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                 defaultMusicPlayerID:(NSUUID*)defaultMusicPlayerID\n                   musicPlayerClasses:(NSArray<Class<BGMMusicPlayer>>*)musicPlayerClasses\n                         userDefaults:(BGMUserDefaults*)defaults;\n\n@property (readonly) NSArray<id<BGMMusicPlayer>>* musicPlayers;\n\n// The music player currently selected in the preferences menu. BGMDevice is informed when this property\n// is changed.\n@property id<BGMMusicPlayer> selectedMusicPlayer;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusicPlayers.mm\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//\n\n// Self include\n#import \"BGMMusicPlayers.h\"\n\n// Local includes\n#import \"BGM_Types.h\"\n\n// Music player includes\n#import \"BGMiTunes.h\"\n#import \"BGMSpotify.h\"\n#import \"BGMVLC.h\"\n#import \"BGMVOX.h\"\n#import \"BGMDecibel.h\"\n#import \"BGMHermes.h\"\n#import \"BGMSwinsian.h\"\n#import \"BGMMusic.h\"\n#import \"BGMGooglePlayMusicDesktopPlayer.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMMusicPlayers {\n    BGMAudioDeviceManager* audioDevices;\n    BGMUserDefaults* userDefaults;\n}\n\n@synthesize selectedMusicPlayer = _selectedMusicPlayer;\n\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                         userDefaults:(BGMUserDefaults*)defaults {\n    // The classes handling each music player we support. If you write a new music player class, add\n    // it to this array.\n    NSArray<Class<BGMMusicPlayer>>* mpClasses = @[ [BGMVOX class],\n                                                   [BGMVLC class],\n                                                   [BGMSpotify class],\n                                                   [BGMiTunes class],\n                                                   [BGMDecibel class],\n                                                   [BGMHermes class],\n                                                   [BGMSwinsian class],\n                                                   [BGMMusic class] ];\n\n    // We only support Google Play Music Desktop Player on macOS 10.10 and higher.\n    if (@available(macOS 10.10, *)) {\n        mpClasses = [mpClasses arrayByAddingObject:[BGMGooglePlayMusicDesktopPlayer class]];\n    }\n\n    return [self initWithAudioDevices:devices\n                 defaultMusicPlayerID:[BGMiTunes sharedMusicPlayerID]\n                   musicPlayerClasses:mpClasses\n                         userDefaults:defaults];\n}\n\n- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices\n                 defaultMusicPlayerID:(NSUUID*)defaultMusicPlayerID\n                   musicPlayerClasses:(NSArray<Class<BGMMusicPlayer>>*)musicPlayerClasses\n                         userDefaults:(BGMUserDefaults*)defaults {\n    if ((self = [super init])) {\n        audioDevices = devices;\n        userDefaults = defaults;\n        \n        // Init _musicPlayers, an array containing one object for each music player in BGMApp.\n        //\n        // Each music player class has a factory method, createInstancesWithDefaults, that returns\n        // all the instances of that class BGMApp will use. (Though so far it's always just one\n        // instance.)\n        NSMutableArray* musicPlayers = [NSMutableArray new];\n        for (Class<BGMMusicPlayer> musicPlayerClass in musicPlayerClasses) {\n            NSArray<id<BGMMusicPlayer>>* instances =\n                    [musicPlayerClass createInstancesWithDefaults:userDefaults];\n            [musicPlayers addObjectsFromArray:instances];\n        }\n        \n        _musicPlayers = [NSArray arrayWithArray:musicPlayers];\n        \n        // Set _selectedMusicPlayer to its setting from last time BGMApp ran. (Unless this is the first run or\n        // that music player isn't available this time.)\n        [self initSelectedMusicPlayerFromUserDefaults];\n        \n        if (!_selectedMusicPlayer) {\n            // Couldn't set _selectedMusicPlayer from user defaults, so try BGMDevice's music player property.\n            [self initSelectedMusicPlayerFromBGMDevice];\n        }\n        \n        if (!_selectedMusicPlayer) {\n            // The user hasn't changed the music player yet, so we set the default music player as selected.\n            [self setSelectedMusicPlayerByID:defaultMusicPlayerID];\n        }\n        \n        NSAssert(_selectedMusicPlayer, @\"BGMMusicPlayers::initWithAudioDevices: !_selectedMusicPlayer\");\n    }\n    \n    return self;\n}\n\n- (void) initSelectedMusicPlayerFromUserDefaults {\n    // Load the selected music player setting from user defaults.\n    \n    NSString* __nullable selectedMusicPlayerIDStr = userDefaults.selectedMusicPlayerID;\n    NSUUID* __nullable selectedMusicPlayerID = nil;\n    \n    if (selectedMusicPlayerIDStr) {\n        NSString* idStrNN = selectedMusicPlayerIDStr;\n        selectedMusicPlayerID = [[NSUUID alloc] initWithUUIDString:idStrNN];\n        \n        NSAssert(selectedMusicPlayerID,\n                 @\"BGMMusicPlayers::initSelectedMusicPlayerFromUserDefaults: !selectedMusicPlayerID\");\n    }\n    \n    if (selectedMusicPlayerID) {\n        NSUUID* idNN = selectedMusicPlayerID;\n        BOOL didChangeMusicPlayer = [self setSelectedMusicPlayerByID:idNN];\n        \n#if DEBUG\n        DebugMsg(\"BGMMusicPlayers::initSelectedMusicPlayerFromUserDefaults: %s selectedMusicPlayerIDStr=%s\",\n                 (didChangeMusicPlayer ?\n                     \"Selected music player restored from user defaults.\" :\n                     \"The selected music player setting found in user defaults didn't match an available music player.\"),\n                 selectedMusicPlayerIDStr.UTF8String);\n#else\n        #pragma unused (didChangeMusicPlayer)\n#endif\n    }\n}\n\n- (void) initSelectedMusicPlayerFromBGMDevice {\n    // When the selected music player setting hasn't been stored in user defaults yet, we get the music player\n    // bundle ID from the driver and look for the music player with that bundle ID. This is mainly done for\n    // backwards compatibility.\n    \n    NSString* __nullable bundleID =\n        (__bridge_transfer NSString* __nullable)[audioDevices bgmDevice].GetMusicPlayerBundleID();\n\n    DebugMsg(\"BGMMusicPlayers::initSelectedMusicPlayerFromBGMDevice: \"\n             \"Trying to set selected music player by bundle ID (from BGMDriver). bundleID=%s\",\n             (bundleID ? bundleID.UTF8String : \"(null)\"));\n    \n    if (bundleID && ![bundleID isEqualToString:@\"\"]) {\n        // Find any music players with a bundle ID matching the one from BGMDriver.\n        NSArray<id<BGMMusicPlayer>>* matchingMusicPlayers = @[ ];\n        \n        for (id<BGMMusicPlayer> musicPlayer in _musicPlayers) {\n            NSString* bundleIDNN = bundleID;\n            if ([musicPlayer.bundleID isEqualToString:bundleIDNN]) {\n                DebugMsg(\"BGMMusicPlayers::initSelectedMusicPlayerFromBGMDevice: Bundle ID on BGMDevice matches %s\",\n                         musicPlayer.name.UTF8String);\n                \n                matchingMusicPlayers = [matchingMusicPlayers arrayByAddingObject:musicPlayer];\n            }\n        }\n        \n        // Currently, the music players all have different bundle IDs, but that might change at some point. We\n        // might want to consider some websites as music players, for example. So we don't change the setting\n        // unless the bundle ID only matches one music player.\n        if (matchingMusicPlayers.count == 1) {\n            // (Use setSelectedMusicPlayerImpl to avoid setSelectedMusicPlayer being called in init.)\n            [self setSelectedMusicPlayerImpl:matchingMusicPlayers[0]];\n        }\n    }\n}\n\n- (id<BGMMusicPlayer>) selectedMusicPlayer {\n    return _selectedMusicPlayer;\n}\n\n- (void) setSelectedMusicPlayer:(id<BGMMusicPlayer>)newSelectedMusicPlayer {\n    // Apparently you shouldn't call properties' setter methods in init (KVO notifications might trigger, etc.)\n    // so the actual work is done in setSelectedMusicPlayerImpl.\n    [self setSelectedMusicPlayerImpl:newSelectedMusicPlayer];\n    \n    NSAssert(self.selectedMusicPlayer == newSelectedMusicPlayer,\n             @\"BGMMusicPlayers::setSelectedMusicPlayer: selectedMusicPlayer wasn't set to the object expected\");\n}\n\n- (BOOL) setSelectedMusicPlayerByID:(NSUUID*)newSelectedMusicPlayerID {\n    id<BGMMusicPlayer> __nullable newSelectedMusicPlayer = nil;\n    \n    // Find the music player with the given ID, if there is one.\n    for (id<BGMMusicPlayer> musicPlayer in _musicPlayers) {\n        if ([musicPlayer.musicPlayerID isEqual:newSelectedMusicPlayerID]) {\n            NSAssert(!newSelectedMusicPlayer, @\"BGMMusicPlayers::setSelectedMusicPlayerByID: Non-unique musicPlayerID\");\n            \n            newSelectedMusicPlayer = musicPlayer;\n        }\n    }\n    \n    if (newSelectedMusicPlayer) {\n        // (Use setSelectedMusicPlayerImpl to avoid setSelectedMusicPlayer being called in init.)\n        id<BGMMusicPlayer> newPlayerNN = newSelectedMusicPlayer;\n        [self setSelectedMusicPlayerImpl:newPlayerNN];\n        \n        return YES;\n    } else {\n        return NO;\n    }\n}\n\n- (void) setSelectedMusicPlayerImpl:(id<BGMMusicPlayer>)newSelectedMusicPlayer {\n    NSAssert([_musicPlayers containsObject:newSelectedMusicPlayer],\n             @\"BGMMusicPlayers::setSelectedMusicPlayerImpl: Only the music players in the musicPlayers array can be selected. \"\n              \"newSelectedMusicPlayer=%@\",\n             newSelectedMusicPlayer.name);\n\n    if (_selectedMusicPlayer == newSelectedMusicPlayer) {\n        DebugMsg(\"BGMMusicPlayers::setSelectedMusicPlayerImpl: %s is already the selected music \"\n                 \"player.\",\n                 _selectedMusicPlayer.name.UTF8String);\n        return;\n    }\n\n    // Tell the current music player (object) a different player has been selected.\n    [_selectedMusicPlayer wasDeselected];\n    \n    _selectedMusicPlayer = newSelectedMusicPlayer;\n    \n    DebugMsg(\"BGMMusicPlayers::setSelectedMusicPlayerImpl: Set selected music player to %s\",\n             _selectedMusicPlayer.name.UTF8String);\n    \n    // Update the selected music player on the driver.\n    [self updateBGMDeviceMusicPlayerProperties];\n    \n    // Save the new setting in user defaults.\n    userDefaults.selectedMusicPlayerID = _selectedMusicPlayer.musicPlayerID.UUIDString;\n\n    // Tell the music player (object) it's been selected.\n    [_selectedMusicPlayer wasSelected];\n}\n\n- (void) updateBGMDeviceMusicPlayerProperties {\n    // Send the music player's PID and/or bundle ID to the driver.\n    \n    NSAssert(self.selectedMusicPlayer.pid || self.selectedMusicPlayer.bundleID,\n             @\"BGMMusicPlayers::updateBGMDeviceMusicPlayerProperties: Music player has neither bundle ID nor PID\");\n\n    if (self.selectedMusicPlayer.pid) {\n        [audioDevices bgmDevice].SetMusicPlayerProcessID((__bridge CFNumberRef)self.selectedMusicPlayer.pid);\n    }\n    \n    if (self.selectedMusicPlayer.bundleID) {\n        [audioDevices bgmDevice].SetMusicPlayerBundleID((__bridge CFStringRef)self.selectedMusicPlayer.bundleID);\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMScriptingBridge.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMScriptingBridge.h\n//  BGMApp\n//\n//  Copyright © 2016, 2018 Kyle Neideck\n//\n//  A wrapper around Scripting Bridge's SBApplication that tries to avoid ever launching the application.\n//\n//  We use Scripting Bridge to communicate with music player apps, which we never want to launch\n//  ourselves. But creating an SBApplication for an app, or sending messages/events to an existing one,\n//  can launch the app.\n//\n//  As a workaround, this class has an SBApplication property, application (see below), which is nil\n//  unless the music player app is running. That way messages sent while the app is closed are ignored.\n//\n\n// Local Includes\n#import \"BGMMusicPlayer.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMScriptingBridge : NSObject <SBApplicationDelegate>\n\n// Only keeps a weak ref to musicPlayer.\n- (instancetype) initWithMusicPlayer:(id<BGMMusicPlayer>)musicPlayer;\n\n// If the music player application is running, this property is the Scripting Bridge object representing\n// it. If not, it's set to nil. Used to send Apple events to the music player app.\n@property (readonly) __kindof SBApplication* __nullable application;\n\n// macOS 10.14 requires the user's permission to send Apple Events. If the music player that owns\n// this object (i.e. the one passed to initWithMusicPlayer) is currently the selected music player\n// and the user hasn't already given us permission to send it Apple Events, this method asks the\n// user for permission.\n- (void) ensurePermission;\n\n// SBApplicationDelegate\n\n// On 10.11, SBApplicationDelegate.h declares eventDidFail with a non-null return type, but the docs\n// specifically say that returning nil is allowed.\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wnullability\"\n- (id __nullable) eventDidFail:(const AppleEvent*)event withError:(NSError*)error;\n#pragma clang diagnostic pop\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMScriptingBridge.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMScriptingBridge.m\n//  BGMApp\n//\n//  Copyright © 2016-2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMScriptingBridge.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMAppWatcher.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMScriptingBridge {\n    id<BGMMusicPlayer> __weak _musicPlayer;\n    BGMAppWatcher* appWatcher;\n}\n\n@synthesize application = _application;\n\n- (instancetype) initWithMusicPlayer:(id<BGMMusicPlayer>)musicPlayer {\n    if ((self = [super init])) {\n        _musicPlayer = musicPlayer;\n        \n        [self initApplication];\n    }\n    \n    return self;\n}\n\n- (void) initApplication {\n    NSString* bundleID = _musicPlayer.bundleID;\n    BGMAssert(bundleID, \"Music players need a bundle ID to use ScriptingBridge\");\n\n    BGMScriptingBridge* __weak weakSelf = self;\n\n    void (^createSBApplication)(void) = ^{\n        BGMScriptingBridge* strongSelf = weakSelf;\n        strongSelf->_application = [SBApplication applicationWithBundleIdentifier:bundleID];\n        // TODO: The SBApplication will still keep a strong ref to this object, so we would have to\n        //       make a separate delegate object to avoid the retain cycle. Not currently a problem\n        //       because we only ever create instances that live forever.\n        strongSelf->_application.delegate = strongSelf;\n    };\n    \n    // Add observers that create/destroy the SBApplication when the music player is launched/terminated. We\n    // only create the SBApplication when the music player is open. If it isn't open, creating the\n    // SBApplication or sending it events could launch the music player. Whether or not it does depends on\n    // the music player, and possibly the version of the music player, so to be safe we assume they all do.\n    //\n    // From the docs for SBApplication's applicationWithBundleIdentifier method:\n    //     \"For applications that declare themselves to have a dynamic scripting interface, this method will\n    //     launch the application if it is not already running.\"\n    appWatcher =\n        [[BGMAppWatcher alloc] initWithBundleID:bundleID\n                                    appLaunched:^{\n                                        DebugMsg(\"BGMScriptingBridge::initApplication: %s launched\",\n                                                 bundleID.UTF8String);\n                                        createSBApplication();\n                                        [weakSelf ensurePermission];\n                                    }\n                                  appTerminated:^{\n                                      BGMScriptingBridge* strongSelf = weakSelf;\n                                      DebugMsg(\"BGMScriptingBridge::initApplication: %s terminated\",\n                                               bundleID.UTF8String);\n                                      strongSelf->_application = nil;\n                                  }];\n    \n    // Create the SBApplication if the music player is already running.\n    if ([NSRunningApplication runningApplicationsWithBundleIdentifier:bundleID].count > 0) {\n        createSBApplication();\n    }\n}\n\n- (void) ensurePermission {\n    // Skip this check if running on a version of macOS before 10.14. In that case, we don't require\n    // user permission to send Apple Events. Also skip it if compiling on an earlier version.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400  // MAC_OS_X_VERSION_10_14\n    if (@available(macOS 10.14, *)) {\n        id<BGMMusicPlayer> musicPlayer = _musicPlayer;\n\n        if (!musicPlayer.selected) {\n            DebugMsg(\"BGMScriptingBridge::ensurePermission: %s not selected. Nothing to do.\",\n                     musicPlayer.name.UTF8String);\n            return;\n        }\n\n        if (!musicPlayer.running) {\n            DebugMsg(\"BGMScriptingBridge::ensurePermission: %s not running. Nothing to do.\",\n                     musicPlayer.name.UTF8String);\n            return;\n        }\n\n        // AEDeterminePermissionToAutomateTarget will block if it has to show a dialog to the user\n        // to ask for permission, so dispatch this to make sure it doesn't run on the main thread.\n        dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{\n            NSAppleEventDescriptor* musicPlayerEventDescriptor =\n                [NSAppleEventDescriptor\n                 descriptorWithBundleIdentifier:(NSString*)musicPlayer.bundleID];\n\n            OSStatus status =\n                AEDeterminePermissionToAutomateTarget(musicPlayerEventDescriptor.aeDesc,\n                                                      typeWildCard,\n                                                      typeWildCard,\n                                                      true);\n\n            DebugMsg(\"BGMScriptingBridge::ensurePermission: \"\n                     \"Apple Events permission status for %s: %d\",\n                     musicPlayer.name.UTF8String,\n                     status);\n\n            if (status != noErr) {\n                // TODO: If they deny permission, we should grey-out the auto-pause menu item and\n                //       add something to the UI that indicates the problem. Maybe a warning icon\n                //       that shows an explanation when you hover your mouse over it. (We can't just\n                //       ask them again later because the API doesn't support it. They can only fix\n                //       it in System Preferences.)\n                NSLog(@\"BGMScriptingBridge::ensurePermission: Permission denied for %@. status=%d\",\n                      musicPlayer.name,\n                      status);\n            }\n        });\n    } else {\n        DebugMsg(\"BGMScriptingBridge::ensurePermission: Not macOS 10.14+. Nothing to do.\");\n    }\n#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 */\n}\n\n#pragma mark SBApplicationDelegate\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wnullability\"  // See explanation in the header file.\n- (id __nullable) eventDidFail:(const AppleEvent*)event withError:(NSError*)error {\n#pragma clang diagnostic pop\n    // So far, this just logs the error.\n    \n#if DEBUG\n    NSString* vars = [NSString stringWithFormat:@\"event='%4.4s' error=%@ application=%@\",\n                         (char*)&(event->descriptorType), error, self.application];\n    DebugMsg(\"BGMScriptingBridge::eventDidFail: Apple event sent to %s failed. %s\",\n             _musicPlayer.bundleID.UTF8String,\n             vars.UTF8String);\n#else\n    #pragma unused (event, error)\n#endif\n\n    return nil;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSpotify.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSpotify.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMSpotify : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSpotify.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSpotify.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n//  Spotify's AppleScript API looks to have been designed to match iTunes', so this file is basically\n//  just s/iTunes/Spotify/ on BGMiTunes.m\n//\n\n// Self Include\n#import \"BGMSpotify.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"Spotify.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMSpotify {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    // If you're copying this class, replace the ID string with a new one generated by uuidgen. (Command line tool.)\n    if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@\"EC2A907F-8515-4687-9570-1BF63176E6D8\"]\n                                        name:@\"Spotify\"\n                                    bundleID:@\"com.spotify.client\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (SpotifyApplication* __nullable) spotify {\n    return (SpotifyApplication* __nullable)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    // Note that this will return NO if is self.spotify is nil (i.e. Spotify isn't running).\n    return self.spotify.running;\n}\n\n// isPlaying and isPaused check self.running first just in case Spotify is closed but self.spotify hasn't become\n// nil yet. In that case, reading self.spotify.playerState could make Scripting Bridge open Spotify.\n\n- (BOOL) isPlaying {\n    return self.running && (self.spotify.playerState == SpotifyEPlSPlaying);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.spotify.playerState == SpotifyEPlSPaused);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMSpotify::pause: Pausing Spotify\");\n        [self.spotify pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMSpotify::unpause: Unpausing Spotify\");\n        [self.spotify playpause];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSwinsian.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSwinsian.h\n//  BGMApp\n//\n//  Copyright © 2018 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMSwinsian : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSwinsian.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMSwinsian.m\n//  BGMApp\n//\n//  Copyright © 2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMSwinsian.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"Swinsian.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMSwinsian {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    // If you're copying this class, replace the ID string with a new one generated by uuidgen (the\n    // command line tool).\n    NSUUID* musicPlayerID = [BGMMusicPlayerBase makeID:@\"B74D18F6-DFF7-4D88-B719-429CFF98CFFA\"];\n\n    if ((self = [super initWithMusicPlayerID:musicPlayerID\n                                        name:@\"Swinsian\"\n                                    bundleID:@\"com.swinsian.Swinsian\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (SwinsianApplication* __nullable) swinsian {\n    return (SwinsianApplication* __nullable)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    // Note that this will return NO if is self.swinsian is nil (i.e. Swinsian isn't running).\n    return self.swinsian.running;\n}\n\n// isPlaying and isPaused check self.running first just in case Swinsian is closed but self.swinsian\n// hasn't become nil yet. In that case, reading self.swinsian.playerState could make Scripting\n// Bridge open Swinsian.\n\n- (BOOL) isPlaying {\n    return self.running && (self.swinsian.playerState == SwinsianPlayerStatePlaying);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.swinsian.playerState == SwinsianPlayerStatePaused);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event.\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMSwinsian::pause: Pausing Swinsian\");\n        [self.swinsian pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event.\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMSwinsian::unpause: Unpausing Swinsian\");\n        [self.swinsian play];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVLC.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVLC.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMVLC : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVLC.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVLC.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//  Copyright (C) 2012 Peter Ljunglöf. All rights reserved.\n//\n\n// Self Include\n#import \"BGMVLC.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"VLC.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMVLC {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@\"5226F4B9-C740-4045-A273-4B8EABC0E8FC\"]\n                                        name:@\"VLC\"\n                                    bundleID:@\"org.videolan.vlc\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (VLCApplication* __nullable) vlc {\n    return (VLCApplication*)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    return self.vlc.running;\n}\n\n// isPlaying and isPaused check self.running first just in case VLC is closed but self.vlc hasn't become\n// nil yet. In that case, reading other properties of self.vlc could make Scripting Bridge open VLC.\n\n- (BOOL) isPlaying {\n    return self.running && self.vlc.playing;\n}\n\n- (BOOL) isPaused {\n    // VLC is paused if it has a file open but isn't playing it\n    return self.running && (self.vlc.nameOfCurrentItem != nil) && !self.vlc.playing;\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMVLC::pause: Pausing VLC\");\n        [BGMVLC togglePlay];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMVLC::unpause: Unpausing VLC\");\n        [BGMVLC togglePlay];\n    }\n    \n    return wasPaused;\n}\n\n// This is from SubTTS's STVLCPlayer class:\n// https://github.com/heatherleaf/subtts-mac/blob/master/SubTTS/STVLCPlayer.m\n//\n// VLC's Scripting Bridge interface doesn't seem to have a cleaner way to do this.\n+ (void) togglePlay {\n    NSString* src = @\"tell application id \\\"org.videolan.vlc\\\" to play\";\n    NSAppleScript* script = [[NSAppleScript alloc] initWithSource:src];\n    [script executeAndReturnError:nil];\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVOX.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVOX.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMVOX : BGMMusicPlayerBase<BGMMusicPlayer>\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVOX.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMVOX.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMVOX.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"VOX.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMVOX {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n- (instancetype) init {\n    if ((self = [super initWithMusicPlayerID:[BGMMusicPlayerBase makeID:@\"26498C5D-C18B-4689-8B41-9DA91A78FFAD\"]\n                                        name:@\"VOX\"\n                                    bundleID:@\"com.coppertino.Vox\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (VoxApplication* __nullable) vox {\n    return (VoxApplication*)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    return self.vox.running;\n}\n\n// isPlaying and isPaused check self.running first just in case VOX is closed but self.vox hasn't become\n// nil yet. In that case, reading self.vox.playerState could make Scripting Bridge open VOX.\n//\n// VOX's comment for its playerState property says \"playing = 1, paused = 0\".\n\n- (BOOL) isPlaying {\n    return self.running && (self.vox.playerState == 1);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.vox.playerState == 0);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMVOX::pause: Pausing VOX\");\n        [self.vox pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMVOX::unpause: Unpausing VOX\");\n        [self.vox playpause];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMiTunes.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMiTunes.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Superclass/Protocol Import\n#import \"BGMMusicPlayer.h\"\n\n\n@interface BGMiTunes : BGMMusicPlayerBase<BGMMusicPlayer>\n\n// The music player ID (see BGMMusicPlayer.h) used by BGMiTunes instances. (Though BGMApp only ever creates one instance of\n// BGMiTunes, sharedMusicPlayerID is exposed so iTunes can be set as the default music player.)\n+ (NSUUID*) sharedMusicPlayerID;\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMiTunes.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMiTunes.m\n//  BGMApp\n//\n//  Copyright © 2016-2018 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMiTunes.h\"\n\n// Auto-generated Scripting Bridge header\n#import \"iTunes.h\"\n\n// Local Includes\n#import \"BGMScriptingBridge.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMiTunes {\n    BGMScriptingBridge* scriptingBridge;\n}\n\n+ (NSUUID*) sharedMusicPlayerID {\n    NSUUID* __nullable musicPlayerID = [[NSUUID alloc] initWithUUIDString:@\"7B62B5BF-CF90-4938-84E3-F16DEDC3F608\"];\n    NSAssert(musicPlayerID, @\"BGMiTunes::sharedMusicPlayerID: !musicPlayerID\");\n    return (NSUUID*)musicPlayerID;\n}\n\n- (instancetype) init {\n    if ((self = [super initWithMusicPlayerID:[BGMiTunes sharedMusicPlayerID]\n                                        name:@\"iTunes\"\n                                    bundleID:@\"com.apple.iTunes\"])) {\n        scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];\n    }\n    \n    return self;\n}\n\n- (iTunesApplication* __nullable) iTunes {\n    return (iTunesApplication*)scriptingBridge.application;\n}\n\n- (void) wasSelected {\n    [super wasSelected];\n    [scriptingBridge ensurePermission];\n}\n\n- (BOOL) isRunning {\n    return self.iTunes.running;\n}\n\n// isPlaying and isPaused check self.running first just in case iTunes is closed but self.iTunes hasn't become\n// nil yet. In that case, reading self.iTunes.playerState could make Scripting Bridge open iTunes.\n\n- (BOOL) isPlaying {\n    return self.running && (self.iTunes.playerState == iTunesEPlSPlaying);\n}\n\n- (BOOL) isPaused {\n    return self.running && (self.iTunes.playerState == iTunesEPlSPaused);\n}\n\n- (BOOL) pause {\n    // isPlaying checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPlaying = self.playing;\n    \n    if (wasPlaying) {\n        DebugMsg(\"BGMiTunes::pause: Pausing iTunes\");\n        [self.iTunes pause];\n    }\n    \n    return wasPlaying;\n}\n\n- (BOOL) unpause {\n    // isPaused checks isRunning, so we don't need to check it here and waste an Apple event\n    BOOL wasPaused = self.paused;\n    \n    if (wasPaused) {\n        DebugMsg(\"BGMiTunes::unpause: Unpausing iTunes\");\n        [self.iTunes playpause];\n    }\n    \n    return wasPaused;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Decibel.h",
    "content": "/*\n * Decibel.h\n *\n * Generated with\n * sdef /Applications/Decibel.app | sdp -fh --basename Decibel\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class DecibelApplication, DecibelDocument, DecibelWindow, DecibelApplication, DecibelTrack;\n\nenum DecibelSaveOptions {\n\tDecibelSaveOptionsYes = 'yes ' /* Save the file. */,\n\tDecibelSaveOptionsNo = 'no  ' /* Do not save the file. */,\n\tDecibelSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */\n};\ntypedef enum DecibelSaveOptions DecibelSaveOptions;\n\nenum DecibelPrintingErrorHandling {\n\tDecibelPrintingErrorHandlingStandard = 'lwst' /* Standard PostScript error handling */,\n\tDecibelPrintingErrorHandlingDetailed = 'lwdt' /* print a detailed report of PostScript errors */\n};\ntypedef enum DecibelPrintingErrorHandling DecibelPrintingErrorHandling;\n\nenum DecibelShuffleMode {\n\tDecibelShuffleModeOff = 'off ' /* Off */,\n\tDecibelShuffleModeTrack = 'trck' /* Track */,\n\tDecibelShuffleModeAlbum = 'albm' /* Album */,\n\tDecibelShuffleModeArtist = 'arts' /* Artist */\n};\ntypedef enum DecibelShuffleMode DecibelShuffleMode;\n\nenum DecibelRepeatMode {\n\tDecibelRepeatModeOff = 'off ' /* Off */,\n\tDecibelRepeatModeTrack = 'trck' /* Track */,\n\tDecibelRepeatModeAlbum = 'albm' /* Album */,\n\tDecibelRepeatModeArtist = 'arts' /* Artist */,\n\tDecibelRepeatModeAll = 'all ' /* All */\n};\ntypedef enum DecibelRepeatMode DecibelRepeatMode;\n\n@protocol DecibelGenericMethods\n\n- (void) closeSaving:(DecibelSaveOptions)saving savingIn:(NSURL *)savingIn;  // Close a document.\n- (void) saveIn:(NSURL *)in_ as:(id)as;  // Save a document.\n- (void) printWithProperties:(NSDictionary *)withProperties printDialog:(BOOL)printDialog;  // Print a document.\n- (void) delete;  // Delete an object.\n- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties;  // Copy an object.\n- (void) moveTo:(SBObject *)to;  // Move an object to a new location.\n\n@end\n\n\n\n/*\n * Standard Suite\n */\n\n// The application's top-level scripting object.\n@interface DecibelApplication : SBApplication\n\n- (SBElementArray<DecibelDocument *> *) documents;\n- (SBElementArray<DecibelWindow *> *) windows;\n\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (readonly) BOOL frontmost;  // Is this the active application?\n@property (copy, readonly) NSString *version;  // The version number of the application.\n\n- (id) open:(id)x;  // Open a document.\n- (void) print:(id)x withProperties:(NSDictionary *)withProperties printDialog:(BOOL)printDialog;  // Print a document.\n- (void) quitSaving:(DecibelSaveOptions)saving;  // Quit the application.\n- (BOOL) exists:(id)x;  // Verify that an object exists.\n- (void) play;  // Begin audio playback\n- (void) pause;  // Suspend audio playback\n- (void) stop;  // Stop audio playback\n- (void) playPause;  // Begin or suspend audio playback\n- (void) seekForward;  // Seek forward three seconds\n- (void) seekBackward;  // Seek backward three seconds\n- (void) playSelection;  // Play the selected track, or the first track if more than one are selected\n- (void) playPreviousTrack;  // Play the previous logical track in the playlist\n- (void) playNextTrack;  // Play the next logical track in the playlist\n- (void) addFile:(NSURL *)x;  // Add a file to the playlist\n- (void) playFile:(NSURL *)x;  // Add a file to the playlist and play it\n- (void) playTrackAtIndex:(NSInteger)x;  // Play a track in the playlist\n- (void) increaseDeviceVolume;  // Increase the device volume\n- (void) decreaseDeviceVolume;  // Decrease the device volume\n- (void) increaseDigitalVolume;  // Increase the digital volume\n- (void) decreaseDigitalVolume;  // Decrease the digital volume\n- (void) clearPlaylist;  // Clear the playlist\n- (void) scramblePlaylist;  // Scramble the playlist\n\n@end\n\n// A document.\n@interface DecibelDocument : SBObject <DecibelGenericMethods>\n\n@property (copy, readonly) NSString *name;  // Its name.\n@property (readonly) BOOL modified;  // Has it been modified since the last save?\n@property (copy, readonly) NSURL *file;  // Its location on disk, if it has one.\n\n\n@end\n\n// A window.\n@interface DecibelWindow : SBObject <DecibelGenericMethods>\n\n@property (copy, readonly) NSString *name;  // The title of the window.\n- (NSInteger) id;  // The unique identifier of the window.\n@property NSInteger index;  // The index of the window, ordered front to back.\n@property NSRect bounds;  // The bounding rectangle of the window.\n@property (readonly) BOOL closeable;  // Does the window have a close button?\n@property (readonly) BOOL miniaturizable;  // Does the window have a minimize button?\n@property BOOL miniaturized;  // Is the window minimized right now?\n@property (readonly) BOOL resizable;  // Can the window be resized?\n@property BOOL visible;  // Is the window visible right now?\n@property (readonly) BOOL zoomable;  // Does the window have a zoom button?\n@property BOOL zoomed;  // Is the window zoomed right now?\n@property (copy, readonly) DecibelDocument *document;  // The document whose contents are displayed in the window.\n\n\n@end\n\n\n\n/*\n * Decibel Scripting Suite\n */\n\n// The Decibel application class.\n@interface DecibelApplication (DecibelScriptingSuite)\n\n- (SBElementArray<DecibelTrack *> *) tracks;\n\n@property (readonly) BOOL playing;  // Is the player currently playing?\n@property (readonly) BOOL shuffling;  // Is the player currently shuffling?\n@property (readonly) BOOL repeating;  // Is the player currently repeating?\n@property (copy, readonly) DecibelTrack *nowPlaying;  // The track that is currently playing?\n@property double deviceVolume;  // The current device volume\n@property double digitalVolume;  // The current digital volume\n@property double playbackPosition;  // The current playback position [0, 1]\n@property double playbackTime;  // The current playback time in seconds\n@property (readonly) BOOL canPlay;  // Is the player currently playing?\n@property (readonly) BOOL canPlayPreviousTrack;  // Is the player currently playing?\n@property (readonly) BOOL canPlayNextTrack;  // Is the player currently playing?\n@property (readonly) BOOL canAdjustDeviceVolume;  // Can the device volume be adjusted?\n@property DecibelShuffleMode shuffleMode;  // Player shuffle mode\n@property DecibelRepeatMode repeatMode;  // Player repeat mode\n@property (copy, readonly) SBObject *currentPlaylist;  // The current playlist\n\n@end\n\n// A track in the playlist\n@interface DecibelTrack : SBObject <DecibelGenericMethods>\n\n- (NSString *) id;  // The track's ID\n@property (copy, readonly) NSURL *file;  // The track's location\n@property (readonly) double duration;  // The track's duration in seconds\n@property (readonly) double sampleRate;  // The track's sample rate in Hz\n@property (readonly) NSInteger bitDepth;  // The bit depth\n@property (readonly) NSInteger channels;  // The track's channels\n@property (copy) NSString *title;  // The track's title\n@property (copy) NSString *artist;  // The track's artist\n@property (copy) NSString *albumTitle;  // The track's album title\n@property (copy) NSString *albumArtist;  // The track's album artist\n@property NSInteger trackNumber;  // The track's track number\n@property NSInteger trackTotal;  // The total number of tracks on the album\n@property NSInteger discNumber;  // The disc number containing the track\n@property NSInteger discTotal;  // The total number of discs (for multidisc albums)\n@property BOOL partOfACompilation;  // Is the track part of a compilation?\n@property (copy) NSString *genre;  // The track's genre\n@property (copy) NSString *composer;  // The track's composer\n@property (copy) NSString *releaseDate;  // The track's release date\n@property (copy) NSString *ISRC;  // The track's ISRC\n@property (copy) id MCN;  // The track's MCN\n\n- (void) playTrack;  // Play a track in the playlist\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/GooglePlayMusicDesktopPlayer.js",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  GooglePlayMusicDesktopPlayer.js\n//  BGMApp\n//\n//  Copyright © 2019 Kyle Neideck\n//\n\n// The specification for GPMDP's API:\n// https://github.com/MarshallOfSound/Google-Play-Music-Desktop-Player-UNOFFICIAL-/blob/master/docs/PlaybackAPI_WebSocket.md\n\ntry {\n\nwindow._log = msg => {\n    window.webkit.messageHandlers.log.postMessage(msg);\n};\n\n// Global JS error handler.\nwindow.onerror = (msg, url, line, col, error) => {\n    let extra = !col ? '' : '\\nColumn: ' + col;\n    extra += !error ? '' : '\\nError: ' + error;\n\n    // TODO: I'm not sure this log message is ever actually useful.\n    window._log('Error: ' + msg + '\\nURL: ' + url + '\\nLine: ' + line + extra);\n\n    window.webkit.messageHandlers.error.postMessage(error);\n};\n\n// Send a JSON message to GPMDP.\n//\n// If we're connecting, this function will return immediately and the message will be sent after we\n// finish connecting. Logs an error and returns if window.connect() hasn't been called yet.\nwindow._sendJSON = json => {\n    if (window._wsPromise) {\n        window._wsPromise.then(() => {\n            window._sendJSONImmediate(json);\n        }).catch(error => {\n            // TODO: Is there anything else we can do? Retries?\n            window._log('Error sending JSON: ' + JSON.stringify(error));\n        });\n    } else {\n        window._log('Error: No WebSocket promise. Discarding JSON message: ' +\n                    JSON.stringify(json));\n    }\n};\n\n// Send a JSON message to GPMDP, but don't wait if we're in the process of connecting.\n//\n// Logs an error and returns if window.connect() hasn't been called yet. The authCode param is\n// optional and only used to hide the code in log messages.\nwindow._sendJSONImmediate = (json, authCode) => {\n    let jsonStr = JSON.stringify(json);\n    let jsonStrSanitized = authCode ? jsonStr.replace(authCode, \"<private>\") : jsonStr;\n\n    if (window._ws) {\n        window._log('Sending JSON: ' + jsonStrSanitized);\n        window._ws.send(jsonStr);\n    } else {\n        window._log('Error: No WebSocket. Discarding JSON message: ' + jsonStrSanitized);\n    }\n};\n\n// permanentAuthCode is optional. If this is the first time they've selected GPMDP, we won't have a\n// permanent code yet.\nwindow.connect = permanentAuthCode => {\n    // Reset the connection state.\n    window._requestID = 1;\n\n    // Close the existing connection if we're already connected.\n    window.disconnect();\n\n    // Create the new connection.\n    window._ws = new WebSocket('ws://localhost:5672');\n\n    window._ws.onmessage = event => {\n        // Pass the message along to BGMGooglePlayMusicDesktopPlayerConnection.\n        let reply = JSON.parse(event.data);\n        window.webkit.messageHandlers.gpmdp.postMessage(reply);\n    };\n\n    window._wsPromise = new Promise((resolve, reject) => {\n        window._ws.onopen = () => {\n            // Send GPMDP the initial connection message.\n            if (permanentAuthCode) {\n                window._log('Connecting with auth code');\n                window.sendPermanentAuthCode(permanentAuthCode);\n            } else {\n                // Since we don't have an auth code, it will display a four-digit code and reply\n                // telling us to ask the user to type it into Background Music.\n                window._log('Connecting without auth code');\n\n                window._sendJSONImmediate({\n                    'namespace': 'connect',\n                    'method': 'connect',\n                    'arguments': ['Background Music']\n                });\n            }\n        };\n\n        window._ws.onerror = error => {\n            // Report the error to BGMGooglePlayMusicDesktopPlayerConnection.\n            window.webkit.messageHandlers.error.postMessage(error);\n            // Reject the connection promise.\n            reject(error);\n        };\n\n        // Store the function that resolves this promise. We resolve it after we finish\n        // authenticating.\n        window._resolveConnectionPromise = resolve;\n    });\n};\n\n// Close the connection to GPMDP. Does nothing if we aren't connected.\nwindow.disconnect = () => {\n    if (window._ws) {\n        window._log('Closing WebSocket');\n        window._ws.close();\n        window._ws = null;\n    }\n};\n\n// Send an authentication code to GPMDP. To send a four-digit code (i.e. one entered by the user),\n// call this directly. To send a permanent code received from GPMDP, use\n// window.sendPermanentAuthCode().\n//\n// authCode should be percent-encoded.\nwindow.sendAuthCode = authCode => {\n    // Percent-decode the auth code string. We pass it percent-encoded just to make sure nothing in\n    // it accidentally gets executed as Javascript.\n    authCode = window.decodeURIComponent(authCode);\n\n    window._sendJSONImmediate({\n        'namespace': 'connect',\n        'method': 'connect',\n        'arguments': ['Background Music', authCode]\n    }, authCode);\n};\n\n// Send a permanent authentication code, received from GPMDP previously, to GPMDP.\nwindow.sendPermanentAuthCode = permanentAuthCode => {\n    window._log('Sending permanent auth code');\n    window.sendAuthCode(permanentAuthCode);\n    // TODO: If the code is rejected, GPMDP will send us a connect message and we'll show the auth\n    //       code dialog, but accepting the promise here means some messages we send might get\n    //       ignored.\n    window._resolveConnectionPromise();\n};\n\n// Ask GPMDP to send us its current playback state (playing, paused or stopped).\nwindow.requestPlaybackState = () => {\n    window._sendJSON({\n        'namespace': 'playback',\n        'method': 'getPlaybackState',\n        // We don't send any other types of request, so the ID we send only needs to be unique.\n        'requestID': window._requestID++\n    });\n};\n\n// Tell GPMDP to toggle between playing and paused.\nwindow.playPause = () => {\n    window._sendJSON({\n        'namespace': 'playback',\n        'method': 'playPause'\n    });\n};\n\n} catch (error) {\n    window.webkit.messageHandlers.log.postMessage('Error: ' + JSON.stringify(error));\n    window.webkit.messageHandlers.log.postMessage(JSON.stringify(error.stack));\n    window.webkit.messageHandlers.error.postMessage(error);\n}\n\n// Return an empty string as returning some types can cause an error when this Javascript is loaded\n// into the WKWebView.\n\"\"\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Hermes.h",
    "content": "/*\n * Hermes.h\n *\n * Generated with\n * sdef /Applications/Hermes.app | sdp -fh --basename Hermes\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class HermesApplication, HermesSong, HermesStation;\n\n// Legal player states\nenum HermesPlayerStates {\n\tHermesPlayerStatesStopped = 'stop' /* Player is stopped */,\n\tHermesPlayerStatesPlaying = 'play' /* Player is playing */,\n\tHermesPlayerStatesPaused = 'paus' /* Player is paused */\n};\ntypedef enum HermesPlayerStates HermesPlayerStates;\n\n\n\n/*\n * Hermes Suite\n */\n\n// The Pandora player.\n@interface HermesApplication : SBApplication\n\n- (SBElementArray<HermesStation *> *) stations;\n\n@property NSInteger playbackVolume;  // The current playback volume (0–100).\n@property HermesPlayerStates playbackState;  // The current playback state.\n@property (readonly) double playbackPosition;  // The current song’s playback position, in seconds.\n@property (readonly) double currentSongDuration;  // The duration (length) of the current song, in seconds.\n@property (copy) HermesStation *currentStation;  // The currently selected Pandora station.\n@property (copy, readonly) HermesSong *currentSong;  // The currently playing (or paused) Pandora song (WARNING: This is an invalid reference in current versions of Hermes; you must access the current song’s properties individually or as a group directly instead.)\n\n- (void) playpause;  // Play the current song if it is paused; pause the current song if it is playing.\n- (void) pause;  // Pause the currently playing song.\n- (void) play;  // Resume playing the current song.\n- (void) nextSong;  // Skip to the next song on the current station.\n- (void) thumbsUp;  // Tell Pandora you like the current song.\n- (void) thumbsDown;  // Tell Pandora you don’t like the current song.\n- (void) tiredOfSong;  // Tell Pandora you’re tired of the current song.\n- (void) increaseVolume;  // Increase the playback volume.\n- (void) decreaseVolume;  // Decrease the playback volume.\n- (void) maximizeVolume;  // Set the playback volume to its maximum level.\n- (void) mute;  // Mutes playback, saving the current volume level.\n- (void) unmute;  // Restores the volume to the level prior to muting.\n\n@end\n\n// A Pandora song (track).\n@interface HermesSong : SBObject\n\n@property (copy, readonly) NSString *title;  // The song’s title.\n@property (copy, readonly) NSString *artist;  // The song’s artist.\n@property (copy, readonly) NSString *album;  // The song’s album.\n@property (copy, readonly) NSString *artworkURL;  // An image URL for the album’s cover artwork.\n@property (readonly) NSInteger rating;  // The song’s numeric rating.\n@property (copy, readonly) NSString *albumURL;  // A Pandora URL for more information on the album.\n@property (copy, readonly) NSString *artistURL;  // A Pandora URL for more information on the artist.\n@property (copy, readonly) NSString *trackURL;  // A Pandora URL for more information on the track.\n\n\n@end\n\n// A Pandora station.\n@interface HermesStation : SBObject\n\n@property (copy, readonly) NSString *name;  // The station’s name.\n@property (copy, readonly) NSString *stationID;  // The station’s ID.\n\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Music.h",
    "content": "/*\n * Music.h\n *\n * Generated with\n * sdef /System/Applications/Music.app | sdp -fh --basename Music\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class MusicApplication, MusicItem, MusicAirPlayDevice, MusicArtwork, MusicEncoder, MusicEQPreset, MusicPlaylist, MusicAudioCDPlaylist, MusicLibraryPlaylist, MusicRadioTunerPlaylist, MusicSource, MusicSubscriptionPlaylist, MusicTrack, MusicAudioCDTrack, MusicFileTrack, MusicSharedTrack, MusicURLTrack, MusicUserPlaylist, MusicFolderPlaylist, MusicVisual, MusicWindow, MusicBrowserWindow, MusicEQWindow, MusicMiniplayerWindow, MusicPlaylistWindow, MusicVideoWindow;\n\nenum MusicEKnd {\n\tMusicEKndTrackListing = 'kTrk' /* a basic listing of tracks within a playlist */,\n\tMusicEKndAlbumListing = 'kAlb' /* a listing of a playlist grouped by album */,\n\tMusicEKndCdInsert = 'kCDi' /* a printout of the playlist for jewel case inserts */\n};\ntypedef enum MusicEKnd MusicEKnd;\n\nenum MusicEnum {\n\tMusicEnumStandard = 'lwst' /* Standard PostScript error handling */,\n\tMusicEnumDetailed = 'lwdt' /* print a detailed report of PostScript errors */\n};\ntypedef enum MusicEnum MusicEnum;\n\nenum MusicEPlS {\n\tMusicEPlSStopped = 'kPSS',\n\tMusicEPlSPlaying = 'kPSP',\n\tMusicEPlSPaused = 'kPSp',\n\tMusicEPlSFastForwarding = 'kPSF',\n\tMusicEPlSRewinding = 'kPSR'\n};\ntypedef enum MusicEPlS MusicEPlS;\n\nenum MusicERpt {\n\tMusicERptOff = 'kRpO',\n\tMusicERptOne = 'kRp1',\n\tMusicERptAll = 'kAll'\n};\ntypedef enum MusicERpt MusicERpt;\n\nenum MusicEShM {\n\tMusicEShMSongs = 'kShS',\n\tMusicEShMAlbums = 'kShA',\n\tMusicEShMGroupings = 'kShG'\n};\ntypedef enum MusicEShM MusicEShM;\n\nenum MusicESrc {\n\tMusicESrcLibrary = 'kLib',\n\tMusicESrcIPod = 'kPod',\n\tMusicESrcAudioCD = 'kACD',\n\tMusicESrcMP3CD = 'kMCD',\n\tMusicESrcRadioTuner = 'kTun',\n\tMusicESrcSharedLibrary = 'kShd',\n\tMusicESrcITunesStore = 'kITS',\n\tMusicESrcUnknown = 'kUnk'\n};\ntypedef enum MusicESrc MusicESrc;\n\nenum MusicESrA {\n\tMusicESrAAlbums = 'kSrL' /* albums only */,\n\tMusicESrAAll = 'kAll' /* all text fields */,\n\tMusicESrAArtists = 'kSrR' /* artists only */,\n\tMusicESrAComposers = 'kSrC' /* composers only */,\n\tMusicESrADisplayed = 'kSrV' /* visible text fields */,\n\tMusicESrASongs = 'kSrS' /* song names only */\n};\ntypedef enum MusicESrA MusicESrA;\n\nenum MusicESpK {\n\tMusicESpKNone = 'kNon',\n\tMusicESpKFolder = 'kSpF',\n\tMusicESpKGenius = 'kSpG',\n\tMusicESpKLibrary = 'kSpL',\n\tMusicESpKMusic = 'kSpZ',\n\tMusicESpKPurchasedMusic = 'kSpM'\n};\ntypedef enum MusicESpK MusicESpK;\n\nenum MusicEMdK {\n\tMusicEMdKSong = 'kMdS' /* music track */,\n\tMusicEMdKMusicVideo = 'kVdV' /* music video track */,\n\tMusicEMdKUnknown = 'kUnk'\n};\ntypedef enum MusicEMdK MusicEMdK;\n\nenum MusicERtK {\n\tMusicERtKUser = 'kRtU' /* user-specified rating */,\n\tMusicERtKComputed = 'kRtC' /* iTunes-computed rating */\n};\ntypedef enum MusicERtK MusicERtK;\n\nenum MusicEAPD {\n\tMusicEAPDComputer = 'kAPC',\n\tMusicEAPDAirPortExpress = 'kAPX',\n\tMusicEAPDAppleTV = 'kAPT',\n\tMusicEAPDAirPlayDevice = 'kAPO',\n\tMusicEAPDBluetoothDevice = 'kAPB',\n\tMusicEAPDHomePod = 'kAPH',\n\tMusicEAPDUnknown = 'kAPU'\n};\ntypedef enum MusicEAPD MusicEAPD;\n\nenum MusicEClS {\n\tMusicEClSUnknown = 'kUnk',\n\tMusicEClSPurchased = 'kPur',\n\tMusicEClSMatched = 'kMat',\n\tMusicEClSUploaded = 'kUpl',\n\tMusicEClSIneligible = 'kRej',\n\tMusicEClSRemoved = 'kRem',\n\tMusicEClSError = 'kErr',\n\tMusicEClSDuplicate = 'kDup',\n\tMusicEClSSubscription = 'kSub',\n\tMusicEClSNoLongerAvailable = 'kRev',\n\tMusicEClSNotUploaded = 'kUpP'\n};\ntypedef enum MusicEClS MusicEClS;\n\n@protocol MusicGenericMethods\n\n- (void) printPrintDialog:(BOOL)printDialog withProperties:(NSDictionary *)withProperties kind:(MusicEKnd)kind theme:(NSString *)theme;  // Print the specified object(s)\n- (void) close;  // Close an object\n- (void) delete;  // Delete an element from an object\n- (SBObject *) duplicateTo:(SBObject *)to;  // Duplicate one or more object(s)\n- (BOOL) exists;  // Verify if an object exists\n- (void) open;  // Open the specified object(s)\n- (void) save;  // Save the specified object(s)\n- (void) playOnce:(BOOL)once;  // play the current track or the specified track or file.\n- (void) select;  // select the specified object(s)\n\n@end\n\n\n\n/*\n * iTunes Suite\n */\n\n// The application program\n@interface MusicApplication : SBApplication\n\n- (SBElementArray<MusicAirPlayDevice *> *) AirPlayDevices;\n- (SBElementArray<MusicBrowserWindow *> *) browserWindows;\n- (SBElementArray<MusicEncoder *> *) encoders;\n- (SBElementArray<MusicEQPreset *> *) EQPresets;\n- (SBElementArray<MusicEQWindow *> *) EQWindows;\n- (SBElementArray<MusicMiniplayerWindow *> *) miniplayerWindows;\n- (SBElementArray<MusicPlaylist *> *) playlists;\n- (SBElementArray<MusicPlaylistWindow *> *) playlistWindows;\n- (SBElementArray<MusicSource *> *) sources;\n- (SBElementArray<MusicTrack *> *) tracks;\n- (SBElementArray<MusicVideoWindow *> *) videoWindows;\n- (SBElementArray<MusicVisual *> *) visuals;\n- (SBElementArray<MusicWindow *> *) windows;\n\n@property (readonly) BOOL AirPlayEnabled;  // is AirPlay currently enabled?\n@property (readonly) BOOL converting;  // is a track currently being converted?\n@property (copy) NSArray<MusicAirPlayDevice *> *currentAirPlayDevices;  // the currently selected AirPlay device(s)\n@property (copy) MusicEncoder *currentEncoder;  // the currently selected encoder (MP3, AIFF, WAV, etc.)\n@property (copy) MusicEQPreset *currentEQPreset;  // the currently selected equalizer preset\n@property (copy, readonly) MusicPlaylist *currentPlaylist;  // the playlist containing the currently targeted track\n@property (copy, readonly) NSString *currentStreamTitle;  // the name of the current song in the playing stream (provided by streaming server)\n@property (copy, readonly) NSString *currentStreamURL;  // the URL of the playing stream or streaming web site (provided by streaming server)\n@property (copy, readonly) MusicTrack *currentTrack;  // the current targeted track\n@property (copy) MusicVisual *currentVisual;  // the currently selected visual plug-in\n@property BOOL EQEnabled;  // is the equalizer enabled?\n@property BOOL fixedIndexing;  // true if all AppleScript track indices should be independent of the play order of the owning playlist.\n@property BOOL frontmost;  // is iTunes the frontmost application?\n@property BOOL fullScreen;  // are visuals displayed using the entire screen?\n@property (copy, readonly) NSString *name;  // the name of the application\n@property BOOL mute;  // has the sound output been muted?\n@property double playerPosition;  // the player’s position within the currently playing track in seconds.\n@property (readonly) MusicEPlS playerState;  // is iTunes stopped, paused, or playing?\n@property (copy, readonly) SBObject *selection;  // the selection visible to the user\n@property BOOL shuffleEnabled;  // are songs played in random order?\n@property MusicEShM shuffleMode;  // the playback shuffle mode\n@property MusicERpt songRepeat;  // the playback repeat mode\n@property NSInteger soundVolume;  // the sound output volume (0 = minimum, 100 = maximum)\n@property (copy, readonly) NSString *version;  // the version of iTunes\n@property BOOL visualsEnabled;  // are visuals currently being displayed?\n\n- (void) printPrintDialog:(BOOL)printDialog withProperties:(NSDictionary *)withProperties kind:(MusicEKnd)kind theme:(NSString *)theme;  // Print the specified object(s)\n- (void) run;  // Run iTunes\n- (void) quit;  // Quit iTunes\n- (MusicTrack *) add:(NSArray<NSURL *> *)x to:(SBObject *)to;  // add one or more files to a playlist\n- (void) backTrack;  // reposition to beginning of current track or go to previous track if already at start of current track\n- (MusicTrack *) convert:(NSArray<SBObject *> *)x;  // convert one or more files or tracks\n- (void) fastForward;  // skip forward in a playing track\n- (void) nextTrack;  // advance to the next track in the current playlist\n- (void) pause;  // pause playback\n- (void) playOnce:(BOOL)once;  // play the current track or the specified track or file.\n- (void) playpause;  // toggle the playing/paused state of the current track\n- (void) previousTrack;  // return to the previous track in the current playlist\n- (void) resume;  // disable fast forward/rewind and resume playback, if playing.\n- (void) rewind;  // skip backwards in a playing track\n- (void) stop;  // stop playback\n- (void) openLocation:(NSString *)x;  // Opens a Music Store or audio stream URL\n\n@end\n\n// an item\n@interface MusicItem : SBObject <MusicGenericMethods>\n\n@property (copy, readonly) SBObject *container;  // the container of the item\n- (NSInteger) id;  // the id of the item\n@property (readonly) NSInteger index;  // The index of the item in internal application order.\n@property (copy) NSString *name;  // the name of the item\n@property (copy, readonly) NSString *persistentID;  // the id of the item as a hexadecimal string. This id does not change over time.\n@property (copy) NSDictionary *properties;  // every property of the item\n\n- (void) download;  // download a cloud track or playlist\n- (void) reveal;  // reveal and select a track or playlist\n\n@end\n\n// an AirPlay device\n@interface MusicAirPlayDevice : MusicItem\n\n@property (readonly) BOOL active;  // is the device currently being played to?\n@property (readonly) BOOL available;  // is the device currently available?\n@property (readonly) MusicEAPD kind;  // the kind of the device\n@property (copy, readonly) NSString *networkAddress;  // the network (MAC) address of the device\n- (BOOL) protected;  // is the device password- or passcode-protected?\n@property BOOL selected;  // is the device currently selected?\n@property (readonly) BOOL supportsAudio;  // does the device support audio playback?\n@property (readonly) BOOL supportsVideo;  // does the device support video playback?\n@property NSInteger soundVolume;  // the output volume for the device (0 = minimum, 100 = maximum)\n\n\n@end\n\n// a piece of art within a track or playlist\n@interface MusicArtwork : MusicItem\n\n@property (copy) NSImage *data;  // data for this artwork, in the form of a picture\n@property (copy) NSString *objectDescription;  // description of artwork as a string\n@property (readonly) BOOL downloaded;  // was this artwork downloaded by iTunes?\n@property (copy, readonly) NSNumber *format;  // the data format for this piece of artwork\n@property NSInteger kind;  // kind or purpose of this piece of artwork\n@property (copy) NSData *rawData;  // data for this artwork, in original format\n\n\n@end\n\n// converts a track to a specific file format\n@interface MusicEncoder : MusicItem\n\n@property (copy, readonly) NSString *format;  // the data format created by the encoder\n\n\n@end\n\n// equalizer preset configuration\n@interface MusicEQPreset : MusicItem\n\n@property double band1;  // the equalizer 32 Hz band level (-12.0 dB to +12.0 dB)\n@property double band2;  // the equalizer 64 Hz band level (-12.0 dB to +12.0 dB)\n@property double band3;  // the equalizer 125 Hz band level (-12.0 dB to +12.0 dB)\n@property double band4;  // the equalizer 250 Hz band level (-12.0 dB to +12.0 dB)\n@property double band5;  // the equalizer 500 Hz band level (-12.0 dB to +12.0 dB)\n@property double band6;  // the equalizer 1 kHz band level (-12.0 dB to +12.0 dB)\n@property double band7;  // the equalizer 2 kHz band level (-12.0 dB to +12.0 dB)\n@property double band8;  // the equalizer 4 kHz band level (-12.0 dB to +12.0 dB)\n@property double band9;  // the equalizer 8 kHz band level (-12.0 dB to +12.0 dB)\n@property double band10;  // the equalizer 16 kHz band level (-12.0 dB to +12.0 dB)\n@property (readonly) BOOL modifiable;  // can this preset be modified?\n@property double preamp;  // the equalizer preamp level (-12.0 dB to +12.0 dB)\n@property BOOL updateTracks;  // should tracks which refer to this preset be updated when the preset is renamed or deleted?\n\n\n@end\n\n// a list of songs/streams\n@interface MusicPlaylist : MusicItem\n\n- (SBElementArray<MusicTrack *> *) tracks;\n- (SBElementArray<MusicArtwork *> *) artworks;\n\n@property (copy) NSString *objectDescription;  // the description of the playlist\n@property BOOL disliked;  // is this playlist disliked?\n@property (readonly) NSInteger duration;  // the total length of all songs (in seconds)\n@property (copy) NSString *name;  // the name of the playlist\n@property BOOL loved;  // is this playlist loved?\n@property (copy, readonly) MusicPlaylist *parent;  // folder which contains this playlist (if any)\n@property (readonly) NSInteger size;  // the total size of all songs (in bytes)\n@property (readonly) MusicESpK specialKind;  // special playlist kind\n@property (copy, readonly) NSString *time;  // the length of all songs in MM:SS format\n@property (readonly) BOOL visible;  // is this playlist visible in the Source list?\n\n- (void) moveTo:(SBObject *)to;  // Move playlist(s) to a new location\n- (MusicTrack *) searchFor:(NSString *)for_ only:(MusicESrA)only;  // search a playlist for tracks matching the search string. Identical to entering search text in the Search field in iTunes.\n\n@end\n\n// a playlist representing an audio CD\n@interface MusicAudioCDPlaylist : MusicPlaylist\n\n- (SBElementArray<MusicAudioCDTrack *> *) audioCDTracks;\n\n@property (copy) NSString *artist;  // the artist of the CD\n@property BOOL compilation;  // is this CD a compilation album?\n@property (copy) NSString *composer;  // the composer of the CD\n@property NSInteger discCount;  // the total number of discs in this CD’s album\n@property NSInteger discNumber;  // the index of this CD disc in the source album\n@property (copy) NSString *genre;  // the genre of the CD\n@property NSInteger year;  // the year the album was recorded/released\n\n\n@end\n\n// the master music library playlist\n@interface MusicLibraryPlaylist : MusicPlaylist\n\n- (SBElementArray<MusicFileTrack *> *) fileTracks;\n- (SBElementArray<MusicURLTrack *> *) URLTracks;\n- (SBElementArray<MusicSharedTrack *> *) sharedTracks;\n\n\n@end\n\n// the radio tuner playlist\n@interface MusicRadioTunerPlaylist : MusicPlaylist\n\n- (SBElementArray<MusicURLTrack *> *) URLTracks;\n\n\n@end\n\n// a music source (music library, CD, device, etc.)\n@interface MusicSource : MusicItem\n\n- (SBElementArray<MusicAudioCDPlaylist *> *) audioCDPlaylists;\n- (SBElementArray<MusicLibraryPlaylist *> *) libraryPlaylists;\n- (SBElementArray<MusicPlaylist *> *) playlists;\n- (SBElementArray<MusicRadioTunerPlaylist *> *) radioTunerPlaylists;\n- (SBElementArray<MusicSubscriptionPlaylist *> *) subscriptionPlaylists;\n- (SBElementArray<MusicUserPlaylist *> *) userPlaylists;\n\n@property (readonly) long long capacity;  // the total size of the source if it has a fixed size\n@property (readonly) long long freeSpace;  // the free space on the source if it has a fixed size\n@property (readonly) MusicESrc kind;\n\n\n@end\n\n// a subscription playlist from Apple Music\n@interface MusicSubscriptionPlaylist : MusicPlaylist\n\n- (SBElementArray<MusicFileTrack *> *) fileTracks;\n- (SBElementArray<MusicURLTrack *> *) URLTracks;\n\n\n@end\n\n// playable audio source\n@interface MusicTrack : MusicItem\n\n- (SBElementArray<MusicArtwork *> *) artworks;\n\n@property (copy) NSString *album;  // the album name of the track\n@property (copy) NSString *albumArtist;  // the album artist of the track\n@property BOOL albumDisliked;  // is the album for this track disliked?\n@property BOOL albumLoved;  // is the album for this track loved?\n@property NSInteger albumRating;  // the rating of the album for this track (0 to 100)\n@property (readonly) MusicERtK albumRatingKind;  // the rating kind of the album rating for this track\n@property (copy) NSString *artist;  // the artist/source of the track\n@property (readonly) NSInteger bitRate;  // the bit rate of the track (in kbps)\n@property double bookmark;  // the bookmark time of the track in seconds\n@property BOOL bookmarkable;  // is the playback position for this track remembered?\n@property NSInteger bpm;  // the tempo of this track in beats per minute\n@property (copy) NSString *category;  // the category of the track\n@property (readonly) MusicEClS cloudStatus;  // the iCloud status of the track\n@property (copy) NSString *comment;  // freeform notes about the track\n@property BOOL compilation;  // is this track from a compilation album?\n@property (copy) NSString *composer;  // the composer of the track\n@property (readonly) NSInteger databaseID;  // the common, unique ID for this track. If two tracks in different playlists have the same database ID, they are sharing the same data.\n@property (copy, readonly) NSDate *dateAdded;  // the date the track was added to the playlist\n@property (copy) NSString *objectDescription;  // the description of the track\n@property NSInteger discCount;  // the total number of discs in the source album\n@property NSInteger discNumber;  // the index of the disc containing this track on the source album\n@property BOOL disliked;  // is this track disliked?\n@property (copy, readonly) NSString *downloaderAppleID;  // the Apple ID of the person who downloaded this track\n@property (copy, readonly) NSString *downloaderName;  // the name of the person who downloaded this track\n@property (readonly) double duration;  // the length of the track in seconds\n@property BOOL enabled;  // is this track checked for playback?\n@property (copy) NSString *episodeID;  // the episode ID of the track\n@property NSInteger episodeNumber;  // the episode number of the track\n@property (copy) NSString *EQ;  // the name of the EQ preset of the track\n@property double finish;  // the stop time of the track in seconds\n@property BOOL gapless;  // is this track from a gapless album?\n@property (copy) NSString *genre;  // the music/audio genre (category) of the track\n@property (copy) NSString *grouping;  // the grouping (piece) of the track. Generally used to denote movements within a classical work.\n@property (copy, readonly) NSString *kind;  // a text description of the track\n@property (copy) NSString *longDescription;\n@property BOOL loved;  // is this track loved?\n@property (copy) NSString *lyrics;  // the lyrics of the track\n@property MusicEMdK mediaKind;  // the media kind of the track\n@property (copy, readonly) NSDate *modificationDate;  // the modification date of the content of this track\n@property (copy) NSString *movement;  // the movement name of the track\n@property NSInteger movementCount;  // the total number of movements in the work\n@property NSInteger movementNumber;  // the index of the movement in the work\n@property NSInteger playedCount;  // number of times this track has been played\n@property (copy) NSDate *playedDate;  // the date and time this track was last played\n@property (copy, readonly) NSString *purchaserAppleID;  // the Apple ID of the person who purchased this track\n@property (copy, readonly) NSString *purchaserName;  // the name of the person who purchased this track\n@property NSInteger rating;  // the rating of this track (0 to 100)\n@property (readonly) MusicERtK ratingKind;  // the rating kind of this track\n@property (copy, readonly) NSDate *releaseDate;  // the release date of this track\n@property (readonly) NSInteger sampleRate;  // the sample rate of the track (in Hz)\n@property NSInteger seasonNumber;  // the season number of the track\n@property BOOL shufflable;  // is this track included when shuffling?\n@property NSInteger skippedCount;  // number of times this track has been skipped\n@property (copy) NSDate *skippedDate;  // the date and time this track was last skipped\n@property (copy) NSString *show;  // the show name of the track\n@property (copy) NSString *sortAlbum;  // override string to use for the track when sorting by album\n@property (copy) NSString *sortArtist;  // override string to use for the track when sorting by artist\n@property (copy) NSString *sortAlbumArtist;  // override string to use for the track when sorting by album artist\n@property (copy) NSString *sortName;  // override string to use for the track when sorting by name\n@property (copy) NSString *sortComposer;  // override string to use for the track when sorting by composer\n@property (copy) NSString *sortShow;  // override string to use for the track when sorting by show name\n@property (readonly) long long size;  // the size of the track (in bytes)\n@property double start;  // the start time of the track in seconds\n@property (copy, readonly) NSString *time;  // the length of the track in MM:SS format\n@property NSInteger trackCount;  // the total number of tracks on the source album\n@property NSInteger trackNumber;  // the index of the track on the source album\n@property BOOL unplayed;  // is this track unplayed?\n@property NSInteger volumeAdjustment;  // relative volume adjustment of the track (-100% to 100%)\n@property (copy) NSString *work;  // the work name of the track\n@property NSInteger year;  // the year the track was recorded/released\n\n\n@end\n\n// a track on an audio CD\n@interface MusicAudioCDTrack : MusicTrack\n\n@property (copy, readonly) NSURL *location;  // the location of the file represented by this track\n\n\n@end\n\n// a track representing an audio file (MP3, AIFF, etc.)\n@interface MusicFileTrack : MusicTrack\n\n@property (copy) NSURL *location;  // the location of the file represented by this track\n\n- (void) refresh;  // update file track information from the current information in the track’s file\n\n@end\n\n// a track residing in a shared library\n@interface MusicSharedTrack : MusicTrack\n\n\n@end\n\n// a track representing a network stream\n@interface MusicURLTrack : MusicTrack\n\n@property (copy) NSString *address;  // the URL for this track\n\n\n@end\n\n// custom playlists created by the user\n@interface MusicUserPlaylist : MusicPlaylist\n\n- (SBElementArray<MusicFileTrack *> *) fileTracks;\n- (SBElementArray<MusicURLTrack *> *) URLTracks;\n- (SBElementArray<MusicSharedTrack *> *) sharedTracks;\n\n@property BOOL shared;  // is this playlist shared?\n@property (readonly) BOOL smart;  // is this a Smart Playlist?\n@property (readonly) BOOL genius;  // is this a Genius Playlist?\n\n\n@end\n\n// a folder that contains other playlists\n@interface MusicFolderPlaylist : MusicUserPlaylist\n\n\n@end\n\n// a visual plug-in\n@interface MusicVisual : MusicItem\n\n\n@end\n\n// any window\n@interface MusicWindow : MusicItem\n\n@property NSRect bounds;  // the boundary rectangle for the window\n@property (readonly) BOOL closeable;  // does the window have a close button?\n@property (readonly) BOOL collapseable;  // does the window have a collapse button?\n@property BOOL collapsed;  // is the window collapsed?\n@property BOOL fullScreen;  // is the window full screen?\n@property NSPoint position;  // the upper left position of the window\n@property (readonly) BOOL resizable;  // is the window resizable?\n@property BOOL visible;  // is the window visible?\n@property (readonly) BOOL zoomable;  // is the window zoomable?\n@property BOOL zoomed;  // is the window zoomed?\n\n\n@end\n\n// the main iTunes window\n@interface MusicBrowserWindow : MusicWindow\n\n@property (copy, readonly) SBObject *selection;  // the selected songs\n@property (copy) MusicPlaylist *view;  // the playlist currently displayed in the window\n\n\n@end\n\n// the iTunes equalizer window\n@interface MusicEQWindow : MusicWindow\n\n\n@end\n\n// the miniplayer window\n@interface MusicMiniplayerWindow : MusicWindow\n\n\n@end\n\n// a sub-window showing a single playlist\n@interface MusicPlaylistWindow : MusicWindow\n\n@property (copy, readonly) SBObject *selection;  // the selected songs\n@property (copy, readonly) MusicPlaylist *view;  // the playlist displayed in the window\n\n\n@end\n\n// the video window\n@interface MusicVideoWindow : MusicWindow\n\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Spotify.h",
    "content": "/*\n * Spotify.h\n *\n * Generated with\n * sdef /Applications/Spotify.app | sdp -fh --basename Spotify\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class SpotifyApplication, SpotifyTrack, SpotifyApplication;\n\nenum SpotifyEPlS {\n\tSpotifyEPlSStopped = 'kPSS',\n\tSpotifyEPlSPlaying = 'kPSP',\n\tSpotifyEPlSPaused = 'kPSp'\n};\ntypedef enum SpotifyEPlS SpotifyEPlS;\n\n\n\n/*\n * Spotify Suite\n */\n\n// The Spotify application.\n@interface SpotifyApplication : SBApplication\n\n@property (copy, readonly) SpotifyTrack *currentTrack;  // The current playing track.\n@property NSInteger soundVolume;  // The sound output volume (0 = minimum, 100 = maximum)\n@property (readonly) SpotifyEPlS playerState;  // Is Spotify stopped, paused, or playing?\n@property double playerPosition;  // The player’s position within the currently playing track in seconds.\n@property (readonly) BOOL repeatingEnabled;  // Is repeating enabled in the current playback context?\n@property BOOL repeating;  // Is repeating on or off?\n@property (readonly) BOOL shufflingEnabled;  // Is shuffling enabled in the current playback context?\n@property BOOL shuffling;  // Is shuffling on or off?\n\n- (void) nextTrack;  // Skip to the next track.\n- (void) previousTrack;  // Skip to the previous track.\n- (void) playpause;  // Toggle play/pause.\n- (void) pause;  // Pause playback.\n- (void) play;  // Resume playback.\n- (void) playTrack:(NSString *)x inContext:(NSString *)inContext;  // Start playback of a track in the given context.\n\n@end\n\n// A Spotify track.\n@interface SpotifyTrack : SBObject\n\n@property (copy, readonly) NSString *artist;  // The artist of the track.\n@property (copy, readonly) NSString *album;  // The album of the track.\n@property (readonly) NSInteger discNumber;  // The disc number of the track.\n@property (readonly) NSInteger duration;  // The length of the track in seconds.\n@property (readonly) NSInteger playedCount;  // The number of times this track has been played.\n@property (readonly) NSInteger trackNumber;  // The index of the track in its album.\n@property (readonly) BOOL starred;  // Is the track starred?\n@property (readonly) NSInteger popularity;  // How popular is this track? 0-100\n- (NSString *) id;  // The ID of the item.\n@property (copy, readonly) NSString *name;  // The name of the track.\n@property (copy, readonly) NSImage *artwork;  // The track's album cover.\n@property (copy, readonly) NSString *albumArtist;  // That album artist of the track.\n@property (copy) NSString *spotifyUrl;  // The URL of the track.\n\n\n@end\n\n\n\n/*\n * Standard Suite\n */\n\n// The application's top level scripting object.\n@interface SpotifyApplication (StandardSuite)\n\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?\n@property (copy, readonly) NSString *version;  // The version of the application.\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Swinsian.h",
    "content": "/*\n * Swinsian.h\n *\n * Generated with\n * sdef /Applications/Swinsian.app | sdp -fh --basename Swinsian\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class SwinsianItem, SwinsianColor, SwinsianWindow, SwinsianApplication, SwinsianPlaylist, SwinsianLibrary, SwinsianTrack, SwinsianLibraryTrack, SwinsianIPodTrack, SwinsianQueue, SwinsianSmartPlaylist, SwinsianNormalPlaylist, SwinsianPlaylistFolder, SwinsianAudioDevice;\n\nenum SwinsianSaveOptions {\n\tSwinsianSaveOptionsYes = 'yes ' /* Save the file. */,\n\tSwinsianSaveOptionsNo = 'no  ' /* Do not save the file. */,\n\tSwinsianSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */\n};\ntypedef enum SwinsianSaveOptions SwinsianSaveOptions;\n\nenum SwinsianPlayerState {\n\tSwinsianPlayerStateStopped = 'kPSS',\n\tSwinsianPlayerStatePlaying = 'kPSP',\n\tSwinsianPlayerStatePaused = 'kPSp'\n};\ntypedef enum SwinsianPlayerState SwinsianPlayerState;\n\n@protocol SwinsianGenericMethods\n\n- (void) closeSaving:(SwinsianSaveOptions)saving savingIn:(NSURL *)savingIn;  // Close an object.\n- (void) delete;  // Delete an object.\n- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties;  // Copy object(s) and put the copies at a new location.\n- (BOOL) exists;  // Verify if an object exists.\n- (void) moveTo:(SBObject *)to;  // Move object(s) to a new location.\n- (void) saveIn:(NSURL *)in_ as:(NSString *)as;  // Save an object.\n\n@end\n\n\n\n/*\n * Standard Suite\n */\n\n// A scriptable object.\n@interface SwinsianItem : SBObject <SwinsianGenericMethods>\n\n@property (copy) NSDictionary *properties;  // All of the object's properties.\n\n\n@end\n\n// A color.\n@interface SwinsianColor : SBObject <SwinsianGenericMethods>\n\n\n@end\n\n// A window.\n@interface SwinsianWindow : SBObject <SwinsianGenericMethods>\n\n@property (copy) NSString *name;  // The full title of the window.\n- (NSNumber *) id;  // The unique identifier of the window.\n@property NSRect bounds;  // The bounding rectangle of the window.\n@property (readonly) BOOL closeable;  // Whether the window has a close box.\n@property (readonly) BOOL titled;  // Whether the window has a title bar.\n@property (copy) NSNumber *index;  // The index of the window in the back-to-front window ordering.\n@property (readonly) BOOL floating;  // Whether the window floats.\n@property (readonly) BOOL miniaturizable;  // Whether the window can be miniaturized.\n@property BOOL miniaturized;  // Whether the window is currently miniaturized.\n@property (readonly) BOOL modal;  // Whether the window is the application's current modal window.\n@property (readonly) BOOL resizable;  // Whether the window can be resized.\n@property BOOL visible;  // Whether the window is currently visible.\n@property (readonly) BOOL zoomable;  // Whether the window can be zoomed.\n@property BOOL zoomed;  // Whether the window is currently zoomed.\n@property (copy, readonly) NSArray<SwinsianTrack *> *selection;  // Currently seleted tracks\n\n\n@end\n\n\n\n/*\n * Swinsian Suite\n */\n\n// The application\n@interface SwinsianApplication : SBApplication\n\n- (SBElementArray<SwinsianWindow *> *) windows;\n- (SBElementArray<SwinsianPlaylist *> *) playlists;\n- (SBElementArray<SwinsianSmartPlaylist *> *) smartPlaylists;\n- (SBElementArray<SwinsianNormalPlaylist *> *) normalPlaylists;\n- (SBElementArray<SwinsianLibrary *> *) libraries;\n- (SBElementArray<SwinsianTrack *> *) tracks;\n- (SBElementArray<SwinsianAudioDevice *> *) audioDevices;\n\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?\n@property (copy, readonly) NSString *version;  // The version of the application.\n@property NSInteger playerPosition;  // the player’s position within the currently playing track in seconds.\n@property (copy, readonly) SwinsianTrack *currentTrack;  // the currently playing track\n@property (copy) NSNumber *soundVolume;  // the volume. (0 minimum, 100 maximum)\n@property (readonly) SwinsianPlayerState playerState;  // are we stopped, paused or still playing?\n@property (copy, readonly) SwinsianQueue *playbackQueue;  // the currently queued tracks\n@property (copy) SwinsianAudioDevice *outputDevice;  // current audio output device\n\n- (void) open:(NSURL *)x;  // Open an object.\n- (void) print:(NSURL *)x;  // Print an object.\n- (void) quitSaving:(SwinsianSaveOptions)saving;  // Quit an application.\n- (void) play;  // begin playing the current playlist\n- (void) pause;  // pause playback\n- (void) nextTrack;  // skip to the next track in the current playlist\n- (void) stop;  // stop playback\n- (NSArray<SwinsianTrack *> *) searchPlaylist:(SwinsianPlaylist *)playlist for:(NSString *)for_;  // search a playlist for tracks matching a string\n- (void) previousTrack;  // skip back to the previous track\n- (void) playpause;  // toggle play/pause\n- (void) addTracks:(NSArray<SwinsianTrack *> *)tracks to:(SwinsianNormalPlaylist *)to;  // add a track to a playlist\n- (void) notify;  // show currently playing track notification\n- (void) rescanTags:(NSArray<SwinsianTrack *> *)x;  // rescan tags on tracks\n- (NSArray<SwinsianTrack *> *) findTrack:(NSString *)x;  // Finds tracks for the given path\n- (void) removeTracks:(NSArray<SwinsianTrack *> *)tracks from:(SwinsianNormalPlaylist *)from;  // remove tracks from a playlist\n\n@end\n\n// generic playlist type, subcasses include smart playlist and normal playlist\n@interface SwinsianPlaylist : SwinsianItem\n\n- (SBElementArray<SwinsianTrack *> *) tracks;\n\n@property (copy) NSString *name;  // the name of the playlist\n@property (readonly) BOOL smart;  // is this a smart playlist\n\n\n@end\n\n@interface SwinsianLibrary : SwinsianItem\n\n- (SBElementArray<SwinsianTrack *> *) tracks;\n\n\n\n@end\n\n// a music track\n@interface SwinsianTrack : SwinsianItem\n\n@property (copy) NSString *album;  // the album of the track\n@property (copy) NSString *artist;  // the artist\n@property (copy) NSString *composer;  // the composer\n@property (copy) NSString *genre;  // the genre\n@property (copy, readonly) NSString *time;  // the length of the track in text format as MM:SS\n@property NSInteger year;  // the year the track was recorded\n@property (copy, readonly) NSDate *dateAdded;  // the date the track was added to the library\n@property (readonly) double duration;  // the length of the track in seconds\n@property (copy, readonly) NSString *location;  // location on disk\n@property (readonly) BOOL iPodTrack;  // TRUE if the track is on an iPod\n@property (copy) NSString *name;  // the title of the track (same as title)\n@property (readonly) NSInteger bitRate;  // the bitrate of the track\n@property (copy, readonly) NSString *kind;  // a text description of the type of file the track is\n@property (copy) NSNumber *rating;  // Track rating. 0-5\n@property NSInteger trackNumber;  // the Track number\n@property (readonly) NSInteger fileSize;  // file size in bytes\n@property (copy, readonly) NSImage *albumArt;  // the album artwork\n@property (copy, readonly) NSString *artFormat;  // the data format for this piece of artwork. text that will be \"PNG\" or \"JPEG\". getting the album art property first will mean this information has been retrieved already, otherwise the tags for the file will have to be re-read\n@property (copy) NSNumber *discNumber;  // the disc number\n@property (copy) NSNumber *discCount;  // the total number of discs in the album\n- (NSString *) id;  // uuid\n@property (copy) NSString *albumArtist;  // the album artist\n@property (copy, readonly) NSString *albumArtistOrArtist;  // the album artist of the track, or is none is set, the artist\n@property BOOL compilation;  // compilation flag\n@property (copy) NSString *title;  // track title (the same as name)\n@property (copy) NSString *comment;  // the comment\n@property (copy, readonly) NSDate *dateCreated;  // the date created\n@property (readonly) NSInteger channels;  // audio channel count\n@property (readonly) NSInteger sampleRate;  // audio sample rate\n@property (readonly) NSInteger bitDepth;  // the audio bit depth\n@property (copy) NSDate *lastPlayed;  // date track was last played\n@property (copy) NSString *lyrics;  // track lyrics\n@property (copy, readonly) NSString *path;  // POSIX style path\n@property (copy) NSString *grouping;  // grouping\n@property (copy) NSString *publisher;  // the publisher\n@property (copy) NSString *conductor;  // the conductor\n@property (copy) NSString *objectDescription;  // the description\n@property (copy, readonly) NSString *encoder;  // the encoder\n@property (copy, readonly) NSString *copyright;  // the copyright\n@property (copy) NSString *catalogNumber;  // the catalog number\n@property (copy, readonly) NSDate *dateModified;  // the date modified\n@property NSInteger playCount;  // the play count\n@property (copy) NSNumber *trackCount;  // the total number of tracks in the album\n\n\n@end\n\n@interface SwinsianLibraryTrack : SwinsianTrack\n\n\n\n@end\n\n@interface SwinsianIPodTrack : SwinsianTrack\n\n@property (copy, readonly) NSString *iPodName;  // the name of the iPod this track is on\n\n\n@end\n\n// The playback queue\n@interface SwinsianQueue : SwinsianItem\n\n- (SBElementArray<SwinsianTrack *> *) tracks;\n\n\n\n@end\n\n// a smart playlist\n@interface SwinsianSmartPlaylist : SwinsianPlaylist\n\n\n@end\n\n// a normal, non-smart, playlist\n@interface SwinsianNormalPlaylist : SwinsianPlaylist\n\n- (SBElementArray<SwinsianTrack *> *) tracks;\n\n- (NSString *) id;  // uuid\n\n\n@end\n\n// folder of playlists\n@interface SwinsianPlaylistFolder : SwinsianPlaylist\n\n- (SBElementArray<SwinsianPlaylist *> *) playlists;\n\n- (NSString *) id;  // uuid\n\n\n@end\n\n// an audio output device\n@interface SwinsianAudioDevice : SBObject <SwinsianGenericMethods>\n\n@property (copy, readonly) NSString *name;  // device name\n- (NSString *) id;  // uuid\n- (void) setId: (NSString *) id;\n\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/VLC.h",
    "content": "/*\n * VLC.h\n *\n * Generated with\n * sdef /Applications/VLC.app | sdp -fh --basename VLC\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class VLCItem, VLCApplication, VLCColor, VLCDocument, VLCWindow, VLCAttributeRun, VLCCharacter, VLCParagraph, VLCText, VLCAttachment, VLCWord, VLCPrintSettings;\n\nenum VLCSavo {\n\tVLCSavoAsk = 'ask ' /* Ask the user whether or not to save the file. */,\n\tVLCSavoNo = 'no  ' /* Do not save the file. */,\n\tVLCSavoYes = 'yes ' /* Save the file. */\n};\ntypedef enum VLCSavo VLCSavo;\n\nenum VLCEnum {\n\tVLCEnumStandard = 'lwst' /* Standard PostScript error handling */,\n\tVLCEnumDetailed = 'lwdt' /* print a detailed report of PostScript errors */\n};\ntypedef enum VLCEnum VLCEnum;\n\n@protocol VLCGenericMethods\n\n- (void) closeSaving:(VLCSavo)saving savingIn:(NSURL *)savingIn;  // Close an object.\n- (void) delete;  // Delete an object.\n- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties;  // Copy object(s) and put the copies at a new location.\n- (BOOL) exists;  // Verify if an object exists.\n- (void) moveTo:(SBObject *)to;  // Move object(s) to a new location.\n- (void) saveAs:(NSString *)as in:(NSURL *)in_;  // Save an object.\n- (void) fullscreen;  // Toggle between fullscreen and windowed mode.\n- (void) GetURL;  // Get a URL\n- (void) mute;  // Mute the audio\n- (void) next;  // Go to the next item in the playlist or the next chapter in the DVD/VCD.\n- (void) OpenURL;  // Open a URL\n- (void) play;  // Start playing the current playlistitem or pause it when it is already playing.\n- (void) previous;  // Go to the previous item in the playlist or the previous chapter in the DVD/VCD.\n- (void) stepBackward;  // Step the current playlist item backward the specified step width (default is 2) (1=extraShort, 2=short, 3=medium, 4=long).\n- (void) stepForward;  // Step the current playlist item forward the specified step width (default is 2) (1=extraShort, 2=short, 3=medium, 4=long).\n- (void) stop;  // Stop playing the current playlistitem\n- (void) volumeDown;  // Bring the volume down by one step. There are 32 steps from 0 to 400% volume.\n- (void) volumeUp;  // Bring the volume up by one step. There are 32 steps from 0 to 400% volume.\n\n@end\n\n\n\n/*\n * Standard Suite\n */\n\n// A scriptable object.\n@interface VLCItem : SBObject <VLCGenericMethods>\n\n@property (copy) NSDictionary *properties;  // All of the object's properties.\n\n\n@end\n\n// An application's top level scripting object.\n@interface VLCApplication : SBApplication\n\n- (SBElementArray<VLCDocument *> *) documents;\n- (SBElementArray<VLCWindow *> *) windows;\n\n@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (copy, readonly) NSString *version;  // The version of the application.\n\n- (VLCDocument *) open:(NSURL *)x;  // Open an object.\n- (void) print:(NSURL *)x printDialog:(BOOL)printDialog withProperties:(VLCPrintSettings *)withProperties;  // Print an object.\n- (void) quitSaving:(VLCSavo)saving;  // Quit an application.\n\n@end\n\n// A color.\n@interface VLCColor : VLCItem\n\n\n@end\n\n// A document.\n@interface VLCDocument : VLCItem\n\n@property (readonly) BOOL modified;  // Has the document been modified since the last save?\n@property (copy) NSString *name;  // The document's name.\n@property (copy) NSString *path;  // The document's path.\n\n\n@end\n\n// A window.\n@interface VLCWindow : VLCItem\n\n@property NSRect bounds;  // The bounding rectangle of the window.\n@property (readonly) BOOL closeable;  // Whether the window has a close box.\n@property (copy, readonly) VLCDocument *document;  // The document whose contents are being displayed in the window.\n@property (readonly) BOOL floating;  // Whether the window floats.\n- (NSInteger) id;  // The unique identifier of the window.\n@property NSInteger index;  // The index of the window, ordered front to back.\n@property (readonly) BOOL miniaturizable;  // Whether the window can be miniaturized.\n@property BOOL miniaturized;  // Whether the window is currently miniaturized.\n@property (readonly) BOOL modal;  // Whether the window is the application's current modal window.\n@property (copy) NSString *name;  // The full title of the window.\n@property (readonly) BOOL resizable;  // Whether the window can be resized.\n@property (readonly) BOOL titled;  // Whether the window has a title bar.\n@property BOOL visible;  // Whether the window is currently visible.\n@property (readonly) BOOL zoomable;  // Whether the window can be zoomed.\n@property BOOL zoomed;  // Whether the window is currently zoomed.\n\n\n@end\n\n\n\n/*\n * Text Suite\n */\n\n// This subdivides the text into chunks that all have the same attributes.\n@interface VLCAttributeRun : VLCItem\n\n- (SBElementArray<VLCAttachment *> *) attachments;\n- (SBElementArray<VLCAttributeRun *> *) attributeRuns;\n- (SBElementArray<VLCCharacter *> *) characters;\n- (SBElementArray<VLCParagraph *> *) paragraphs;\n- (SBElementArray<VLCWord *> *) words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// This subdivides the text into characters.\n@interface VLCCharacter : VLCItem\n\n- (SBElementArray<VLCAttachment *> *) attachments;\n- (SBElementArray<VLCAttributeRun *> *) attributeRuns;\n- (SBElementArray<VLCCharacter *> *) characters;\n- (SBElementArray<VLCParagraph *> *) paragraphs;\n- (SBElementArray<VLCWord *> *) words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// This subdivides the text into paragraphs.\n@interface VLCParagraph : VLCItem\n\n- (SBElementArray<VLCAttachment *> *) attachments;\n- (SBElementArray<VLCAttributeRun *> *) attributeRuns;\n- (SBElementArray<VLCCharacter *> *) characters;\n- (SBElementArray<VLCParagraph *> *) paragraphs;\n- (SBElementArray<VLCWord *> *) words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// Rich (styled) text\n@interface VLCText : VLCItem\n\n- (SBElementArray<VLCAttachment *> *) attachments;\n- (SBElementArray<VLCAttributeRun *> *) attributeRuns;\n- (SBElementArray<VLCCharacter *> *) characters;\n- (SBElementArray<VLCParagraph *> *) paragraphs;\n- (SBElementArray<VLCWord *> *) words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// Represents an inline text attachment.  This class is used mainly for make commands.\n@interface VLCAttachment : VLCText\n\n@property (copy) NSString *fileName;  // The path to the file for the attachment\n\n\n@end\n\n// This subdivides the text into words.\n@interface VLCWord : VLCItem\n\n- (SBElementArray<VLCAttachment *> *) attachments;\n- (SBElementArray<VLCAttributeRun *> *) attributeRuns;\n- (SBElementArray<VLCCharacter *> *) characters;\n- (SBElementArray<VLCParagraph *> *) paragraphs;\n- (SBElementArray<VLCWord *> *) words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n\n\n/*\n * VLC suite\n */\n\n// VLC's top level scripting object\n@interface VLCApplication (VLCSuite)\n\n@property NSInteger audioVolume;  // The volume of the current playlist item from 0 to 4, where 4 is 400%\n@property NSInteger currentTime;  // The current time of the current playlist item in seconds.\n@property (readonly) NSInteger durationOfCurrentItem;  // The duration of the current playlist item in seconds.\n@property BOOL fullscreenMode;  // indicates whether fullscreen is enabled or not\n@property (readonly) BOOL muted;  // Is VLC currently muted?\n@property (copy, readonly) NSString *nameOfCurrentItem;  // Name of the current playlist item.\n@property (copy, readonly) NSString *pathOfCurrentItem;  // Path to the current playlist item.\n@property (readonly) BOOL playing;  // Is VLC playing an item?\n\n@end\n\n\n\n/*\n * Type Definitions\n */\n\n@interface VLCPrintSettings : SBObject <VLCGenericMethods>\n\n@property NSInteger copies;  // the number of copies of a document to be printed\n@property BOOL collating;  // Should printed copies be collated?\n@property NSInteger startingPage;  // the first page of the document to be printed\n@property NSInteger endingPage;  // the last page of the document to be printed\n@property NSInteger pagesAcross;  // number of logical pages laid across a physical page\n@property NSInteger pagesDown;  // number of logical pages laid out down a physical page\n@property (copy) NSDate *requestedPrintTime;  // the time at which the desktop printer should print the document\n@property VLCEnum errorHandling;  // how errors are handled\n@property (copy) NSString *faxNumber;  // for fax number\n@property (copy) NSString *targetPrinter;  // for target printer\n\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/VOX.h",
    "content": "/*\n * VOX.h\n *\n * Generated with\n * sdef /Applications/VOX.app | sdp -fh --basename VOX\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class VoxApplication, VoxApplication;\n\n\n\n/*\n * Standard Suite\n */\n\n// The application's top level scripting object.\n@interface VoxApplication : SBApplication\n\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?\n@property (copy, readonly) NSString *version;  // The version of the application.\n\n- (void) quit;  // Quit an application.\n- (void) pause;  // Pause playback.\n- (void) play;  // Begin playback.\n- (void) playpause;  // Toggle playback between playing and paused.\n- (void) next;  // Skip to the next track in the playlist.\n- (void) previous;  // Skip to the previous track in the playlist.\n- (void) shuffle;  // Shuffle the tracks in the playlist.\n- (void) playUrl:(NSString *)x;  // Play specified URL.\n- (void) addUrl:(NSString *)x;  // Add specified URL to playlist\n- (void) rewindForward;  // Rewind current track forward.\n- (void) rewindForwardFast;  // Rewind current track forward fast.\n- (void) rewindBackward;  // Rewind current track backward.\n- (void) rewindBackwardFast;  // Rewind current track backward fast.\n- (void) increasVolume;  // Increase volume.\n- (void) decreaseVolume;  // Decrease volume.\n- (void) showHidePlaylist;  // Show/Hide playlist.\n\n@end\n\n\n\n/*\n * Vox Suite\n */\n\n// The application's top-level scripting object.\n@interface VoxApplication (VoxSuite)\n\n@property (copy, readonly) NSData *tiffArtworkData;  // Current track artwork data in TIFF format.\n@property (copy, readonly) NSImage *artworkImage;  // Current track artwork as an image.\n@property (readonly) NSInteger playerState;  // Player state (playing = 1, paused = 0)\n@property (copy, readonly) NSString *track;  // Current track title.\n@property (copy, readonly) NSString *trackUrl;  // Current track URL.\n@property (copy, readonly) NSString *artist;  // Current track artist.\n@property (copy, readonly) NSString *albumArtist;  // Current track album artist.\n@property (copy, readonly) NSString *album;  // Current track album.\n@property (copy, readonly) NSString *uniqueID;  // Unique identifier for the current track.\n@property double currentTime;  // The current playback position.\n@property (readonly) double totalTime;  // The total time of the currently playing track.\n@property double playerVolume;  // Player volume (0.0 to 1.0)\n@property NSInteger repeatState;  // Player repeat state (none = 0, repeat one = 1, repeat all = 2)\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/iTunes.h",
    "content": "/*\n * iTunes.h\n *\n * Generated with\n * sdef /Applications/iTunes.app | sdp -fh --basename iTunes\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class iTunesPrintSettings, iTunesApplication, iTunesItem, iTunesAirPlayDevice, iTunesArtwork, iTunesEncoder, iTunesEQPreset, iTunesPlaylist, iTunesAudioCDPlaylist, iTunesLibraryPlaylist, iTunesRadioTunerPlaylist, iTunesSource, iTunesTrack, iTunesAudioCDTrack, iTunesFileTrack, iTunesSharedTrack, iTunesURLTrack, iTunesUserPlaylist, iTunesFolderPlaylist, iTunesVisual, iTunesWindow, iTunesBrowserWindow, iTunesEQWindow, iTunesPlaylistWindow;\n\nenum iTunesEKnd {\n\tiTunesEKndTrackListing = 'kTrk' /* a basic listing of tracks within a playlist */,\n\tiTunesEKndAlbumListing = 'kAlb' /* a listing of a playlist grouped by album */,\n\tiTunesEKndCdInsert = 'kCDi' /* a printout of the playlist for jewel case inserts */\n};\ntypedef enum iTunesEKnd iTunesEKnd;\n\nenum iTunesEnum {\n\tiTunesEnumStandard = 'lwst' /* Standard PostScript error handling */,\n\tiTunesEnumDetailed = 'lwdt' /* print a detailed report of PostScript errors */\n};\ntypedef enum iTunesEnum iTunesEnum;\n\nenum iTunesEPlS {\n\tiTunesEPlSStopped = 'kPSS',\n\tiTunesEPlSPlaying = 'kPSP',\n\tiTunesEPlSPaused = 'kPSp',\n\tiTunesEPlSFastForwarding = 'kPSF',\n\tiTunesEPlSRewinding = 'kPSR'\n};\ntypedef enum iTunesEPlS iTunesEPlS;\n\nenum iTunesERpt {\n\tiTunesERptOff = 'kRpO',\n\tiTunesERptOne = 'kRp1',\n\tiTunesERptAll = 'kAll'\n};\ntypedef enum iTunesERpt iTunesERpt;\n\nenum iTunesEVSz {\n\tiTunesEVSzSmall = 'kVSS',\n\tiTunesEVSzMedium = 'kVSM',\n\tiTunesEVSzLarge = 'kVSL'\n};\ntypedef enum iTunesEVSz iTunesEVSz;\n\nenum iTunesESrc {\n\tiTunesESrcLibrary = 'kLib',\n\tiTunesESrcIPod = 'kPod',\n\tiTunesESrcAudioCD = 'kACD',\n\tiTunesESrcMP3CD = 'kMCD',\n\tiTunesESrcRadioTuner = 'kTun',\n\tiTunesESrcSharedLibrary = 'kShd',\n\tiTunesESrcUnknown = 'kUnk'\n};\ntypedef enum iTunesESrc iTunesESrc;\n\nenum iTunesESrA {\n\tiTunesESrAAlbums = 'kSrL' /* albums only */,\n\tiTunesESrAAll = 'kAll' /* all text fields */,\n\tiTunesESrAArtists = 'kSrR' /* artists only */,\n\tiTunesESrAComposers = 'kSrC' /* composers only */,\n\tiTunesESrADisplayed = 'kSrV' /* visible text fields */,\n\tiTunesESrASongs = 'kSrS' /* song names only */\n};\ntypedef enum iTunesESrA iTunesESrA;\n\nenum iTunesESpK {\n\tiTunesESpKNone = 'kNon',\n\tiTunesESpKBooks = 'kSpA',\n\tiTunesESpKFolder = 'kSpF',\n\tiTunesESpKGenius = 'kSpG',\n\tiTunesESpKITunesU = 'kSpU',\n\tiTunesESpKLibrary = 'kSpL',\n\tiTunesESpKMovies = 'kSpI',\n\tiTunesESpKMusic = 'kSpZ',\n\tiTunesESpKPodcasts = 'kSpP',\n\tiTunesESpKPurchasedMusic = 'kSpM',\n\tiTunesESpKTVShows = 'kSpT'\n};\ntypedef enum iTunesESpK iTunesESpK;\n\nenum iTunesEVdK {\n\tiTunesEVdKNone = 'kNon' /* not a video or unknown video kind */,\n\tiTunesEVdKHomeVideo = 'kVdH' /* home video track */,\n\tiTunesEVdKMovie = 'kVdM' /* movie track */,\n\tiTunesEVdKMusicVideo = 'kVdV' /* music video track */,\n\tiTunesEVdKTVShow = 'kVdT' /* TV show track */\n};\ntypedef enum iTunesEVdK iTunesEVdK;\n\nenum iTunesERtK {\n\tiTunesERtKUser = 'kRtU' /* user-specified rating */,\n\tiTunesERtKComputed = 'kRtC' /* iTunes-computed rating */\n};\ntypedef enum iTunesERtK iTunesERtK;\n\nenum iTunesEAPD {\n\tiTunesEAPDComputer = 'kAPC',\n\tiTunesEAPDAirPortExpress = 'kAPX',\n\tiTunesEAPDAppleTV = 'kAPT',\n\tiTunesEAPDAirPlayDevice = 'kAPO',\n\tiTunesEAPDUnknown = 'kAPU'\n};\ntypedef enum iTunesEAPD iTunesEAPD;\n\n\n\n/*\n * Standard Suite\n */\n\n@interface iTunesPrintSettings : SBObject\n\n@property (readonly) NSInteger copies;  // the number of copies of a document to be printed\n@property (readonly) BOOL collating;  // Should printed copies be collated?\n@property (readonly) NSInteger startingPage;  // the first page of the document to be printed\n@property (readonly) NSInteger endingPage;  // the last page of the document to be printed\n@property (readonly) NSInteger pagesAcross;  // number of logical pages laid across a physical page\n@property (readonly) NSInteger pagesDown;  // number of logical pages laid out down a physical page\n@property (readonly) iTunesEnum errorHandling;  // how errors are handled\n@property (copy, readonly) NSDate *requestedPrintTime;  // the time at which the desktop printer should print the document\n@property (copy, readonly) NSArray *printerFeatures;  // printer specific options\n@property (copy, readonly) NSString *faxNumber;  // for fax number\n@property (copy, readonly) NSString *targetPrinter;  // for target printer\n\n- (void) printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme;  // Print the specified object(s)\n- (void) close;  // Close an object\n- (void) delete;  // Delete an element from an object\n- (SBObject *) duplicateTo:(SBObject *)to;  // Duplicate one or more object(s)\n- (BOOL) exists;  // Verify if an object exists\n- (void) open;  // open the specified object(s)\n- (void) playOnce:(BOOL)once;  // play the current track or the specified track or file.\n\n@end\n\n\n\n/*\n * iTunes Suite\n */\n\n// The application program\n@interface iTunesApplication : SBApplication\n\n- (SBElementArray *) AirPlayDevices;\n- (SBElementArray *) browserWindows;\n- (SBElementArray *) encoders;\n- (SBElementArray *) EQPresets;\n- (SBElementArray *) EQWindows;\n- (SBElementArray *) playlistWindows;\n- (SBElementArray *) sources;\n- (SBElementArray *) visuals;\n- (SBElementArray *) windows;\n\n@property (readonly) BOOL AirPlayEnabled;  // is AirPlay currently enabled?\n@property (readonly) BOOL converting;  // is a track currently being converted?\n@property (copy) NSArray *currentAirPlayDevices;  // the currently selected AirPlay device(s)\n@property (copy) iTunesEncoder *currentEncoder;  // the currently selected encoder (MP3, AIFF, WAV, etc.)\n@property (copy) iTunesEQPreset *currentEQPreset;  // the currently selected equalizer preset\n@property (copy, readonly) iTunesPlaylist *currentPlaylist;  // the playlist containing the currently targeted track\n@property (copy, readonly) NSString *currentStreamTitle;  // the name of the current song in the playing stream (provided by streaming server)\n@property (copy, readonly) NSString *currentStreamURL;  // the URL of the playing stream or streaming web site (provided by streaming server)\n@property (copy, readonly) iTunesTrack *currentTrack;  // the current targeted track\n@property (copy) iTunesVisual *currentVisual;  //  the currently selected visual plug-in\n@property BOOL EQEnabled;  // is the equalizer enabled?\n@property BOOL fixedIndexing;  // true if all AppleScript track indices should be independent of the play order of the owning playlist.\n@property BOOL frontmost;  // is iTunes the frontmost application?\n@property BOOL fullScreen;  // are visuals displayed using the entire screen?\n@property (copy, readonly) NSString *name;  // the name of the application\n@property BOOL mute;  // has the sound output been muted?\n@property double playerPosition;  // the player’s position within the currently playing track in seconds.\n@property (readonly) iTunesEPlS playerState;  // is iTunes stopped, paused, or playing?\n@property (copy, readonly) SBObject *selection;  // the selection visible to the user\n@property NSInteger soundVolume;  // the sound output volume (0 = minimum, 100 = maximum)\n@property (copy, readonly) NSString *version;  // the version of iTunes\n@property BOOL visualsEnabled;  // are visuals currently being displayed?\n@property iTunesEVSz visualSize;  // the size of the displayed visual\n@property (copy, readonly) NSString *iAdIdentifier;  // the iAd identifier\n\n- (void) printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme;  // Print the specified object(s)\n- (void) run;  // run iTunes\n- (void) quit;  // quit iTunes\n- (iTunesTrack *) add:(NSArray *)x to:(SBObject *)to;  // add one or more files to a playlist\n- (void) backTrack;  // reposition to beginning of current track or go to previous track if already at start of current track\n- (iTunesTrack *) convert:(NSArray *)x;  // convert one or more files or tracks\n- (void) fastForward;  // skip forward in a playing track\n- (void) nextTrack;  // advance to the next track in the current playlist\n- (void) pause;  // pause playback\n- (void) playOnce:(BOOL)once;  // play the current track or the specified track or file.\n- (void) playpause;  // toggle the playing/paused state of the current track\n- (void) previousTrack;  // return to the previous track in the current playlist\n- (void) resume;  // disable fast forward/rewind and resume playback, if playing.\n- (void) rewind;  // skip backwards in a playing track\n- (void) stop;  // stop playback\n- (void) update;  // update the specified iPod\n- (void) eject;  // eject the specified iPod\n- (void) subscribe:(NSString *)x;  // subscribe to a podcast feed\n- (void) updateAllPodcasts;  // update all subscribed podcast feeds\n- (void) updatePodcast;  // update podcast feed\n- (void) openLocation:(NSString *)x;  // Opens a Music Store or audio stream URL\n\n@end\n\n// an item\n@interface iTunesItem : SBObject\n\n@property (copy, readonly) SBObject *container;  // the container of the item\n- (NSInteger) id;  // the id of the item\n@property (readonly) NSInteger index;  // The index of the item in internal application order.\n@property (copy) NSString *name;  // the name of the item\n@property (copy, readonly) NSString *persistentID;  // the id of the item as a hexadecimal string. This id does not change over time.\n@property (copy) NSDictionary *properties;  // every property of the item\n\n- (void) printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme;  // Print the specified object(s)\n- (void) close;  // Close an object\n- (void) delete;  // Delete an element from an object\n- (SBObject *) duplicateTo:(SBObject *)to;  // Duplicate one or more object(s)\n- (BOOL) exists;  // Verify if an object exists\n- (void) open;  // open the specified object(s)\n- (void) playOnce:(BOOL)once;  // play the current track or the specified track or file.\n- (void) reveal;  // reveal and select a track or playlist\n\n@end\n\n// an AirPlay device\n@interface iTunesAirPlayDevice : iTunesItem\n\n@property (readonly) BOOL active;  // is the device currently being played to?\n@property (readonly) BOOL available;  // is the device currently available?\n@property (readonly) iTunesEAPD kind;  // the kind of the device\n@property (copy, readonly) NSString *networkAddress;  // the network (MAC) address of the device\n- (BOOL) protected;  // is the device password- or passcode-protected?\n@property BOOL selected;  // is the device currently selected?\n@property (readonly) BOOL supportsAudio;  // does the device support audio playback?\n@property (readonly) BOOL supportsVideo;  // does the device support video playback?\n@property NSInteger soundVolume;  // the output volume for the device (0 = minimum, 100 = maximum)\n\n\n@end\n\n// a piece of art within a track\n@interface iTunesArtwork : iTunesItem\n\n@property (copy) NSImage *data;  // data for this artwork, in the form of a picture\n@property (copy) NSString *objectDescription;  // description of artwork as a string\n@property (readonly) BOOL downloaded;  // was this artwork downloaded by iTunes?\n@property (copy, readonly) NSNumber *format;  // the data format for this piece of artwork\n@property NSInteger kind;  // kind or purpose of this piece of artwork\n@property (copy) NSData *rawData;  // data for this artwork, in original format\n\n\n@end\n\n// converts a track to a specific file format\n@interface iTunesEncoder : iTunesItem\n\n@property (copy, readonly) NSString *format;  // the data format created by the encoder\n\n\n@end\n\n// equalizer preset configuration\n@interface iTunesEQPreset : iTunesItem\n\n@property double band1;  // the equalizer 32 Hz band level (-12.0 dB to +12.0 dB)\n@property double band2;  // the equalizer 64 Hz band level (-12.0 dB to +12.0 dB)\n@property double band3;  // the equalizer 125 Hz band level (-12.0 dB to +12.0 dB)\n@property double band4;  // the equalizer 250 Hz band level (-12.0 dB to +12.0 dB)\n@property double band5;  // the equalizer 500 Hz band level (-12.0 dB to +12.0 dB)\n@property double band6;  // the equalizer 1 kHz band level (-12.0 dB to +12.0 dB)\n@property double band7;  // the equalizer 2 kHz band level (-12.0 dB to +12.0 dB)\n@property double band8;  // the equalizer 4 kHz band level (-12.0 dB to +12.0 dB)\n@property double band9;  // the equalizer 8 kHz band level (-12.0 dB to +12.0 dB)\n@property double band10;  // the equalizer 16 kHz band level (-12.0 dB to +12.0 dB)\n@property (readonly) BOOL modifiable;  // can this preset be modified?\n@property double preamp;  // the equalizer preamp level (-12.0 dB to +12.0 dB)\n@property BOOL updateTracks;  // should tracks which refer to this preset be updated when the preset is renamed or deleted?\n\n\n@end\n\n// a list of songs/streams\n@interface iTunesPlaylist : iTunesItem\n\n- (SBElementArray *) tracks;\n\n@property (readonly) NSInteger duration;  // the total length of all songs (in seconds)\n@property (copy) NSString *name;  // the name of the playlist\n@property BOOL loved;  // is this plalist loved?\n@property (copy, readonly) iTunesPlaylist *parent;  // folder which contains this playlist (if any)\n@property BOOL shuffle;  // play the songs in this playlist in random order?\n@property (readonly) NSInteger size;  // the total size of all songs (in bytes)\n@property iTunesERpt songRepeat;  // playback repeat mode\n@property (readonly) iTunesESpK specialKind;  // special playlist kind\n@property (copy, readonly) NSString *time;  // the length of all songs in MM:SS format\n@property (readonly) BOOL visible;  // is this playlist visible in the Source list?\n\n- (void) moveTo:(SBObject *)to;  // Move playlist(s) to a new location\n- (iTunesTrack *) searchFor:(NSString *)for_ only:(iTunesESrA)only;  // search a playlist for tracks matching the search string. Identical to entering search text in the Search field in iTunes.\n\n@end\n\n// a playlist representing an audio CD\n@interface iTunesAudioCDPlaylist : iTunesPlaylist\n\n- (SBElementArray *) audioCDTracks;\n\n@property (copy) NSString *artist;  // the artist of the CD\n@property BOOL compilation;  // is this CD a compilation album?\n@property (copy) NSString *composer;  // the composer of the CD\n@property NSInteger discCount;  // the total number of discs in this CD’s album\n@property NSInteger discNumber;  // the index of this CD disc in the source album\n@property (copy) NSString *genre;  // the genre of the CD\n@property NSInteger year;  // the year the album was recorded/released\n\n\n@end\n\n// the master music library playlist\n@interface iTunesLibraryPlaylist : iTunesPlaylist\n\n- (SBElementArray *) fileTracks;\n- (SBElementArray *) URLTracks;\n- (SBElementArray *) sharedTracks;\n\n\n@end\n\n// the radio tuner playlist\n@interface iTunesRadioTunerPlaylist : iTunesPlaylist\n\n- (SBElementArray *) URLTracks;\n\n\n@end\n\n// a music source (music library, CD, device, etc.)\n@interface iTunesSource : iTunesItem\n\n- (SBElementArray *) audioCDPlaylists;\n- (SBElementArray *) libraryPlaylists;\n- (SBElementArray *) playlists;\n- (SBElementArray *) radioTunerPlaylists;\n- (SBElementArray *) userPlaylists;\n\n@property (readonly) long long capacity;  // the total size of the source if it has a fixed size\n@property (readonly) long long freeSpace;  // the free space on the source if it has a fixed size\n@property (readonly) iTunesESrc kind;\n\n- (void) update;  // update the specified iPod\n- (void) eject;  // eject the specified iPod\n\n@end\n\n// playable audio source\n@interface iTunesTrack : iTunesItem\n\n- (SBElementArray *) artworks;\n\n@property (copy) NSString *album;  // the album name of the track\n@property (copy) NSString *albumArtist;  // the album artist of the track\n@property BOOL albumLoved;  // is the album for this track loved?\n@property NSInteger albumRating;  // the rating of the album for this track (0 to 100)\n@property (readonly) iTunesERtK albumRatingKind;  // the rating kind of the album rating for this track\n@property (copy) NSString *artist;  // the artist/source of the track\n@property (readonly) NSInteger bitRate;  // the bit rate of the track (in kbps)\n@property double bookmark;  // the bookmark time of the track in seconds\n@property BOOL bookmarkable;  // is the playback position for this track remembered?\n@property NSInteger bpm;  // the tempo of this track in beats per minute\n@property (copy) NSString *category;  // the category of the track\n@property (copy) NSString *comment;  // freeform notes about the track\n@property BOOL compilation;  // is this track from a compilation album?\n@property (copy) NSString *composer;  // the composer of the track\n@property (readonly) NSInteger databaseID;  // the common, unique ID for this track. If two tracks in different playlists have the same database ID, they are sharing the same data.\n@property (copy, readonly) NSDate *dateAdded;  // the date the track was added to the playlist\n@property (copy) NSString *objectDescription;  // the description of the track\n@property NSInteger discCount;  // the total number of discs in the source album\n@property NSInteger discNumber;  // the index of the disc containing this track on the source album\n@property (readonly) double duration;  // the length of the track in seconds\n@property BOOL enabled;  // is this track checked for playback?\n@property (copy) NSString *episodeID;  // the episode ID of the track\n@property NSInteger episodeNumber;  // the episode number of the track\n@property (copy) NSString *EQ;  // the name of the EQ preset of the track\n@property double finish;  // the stop time of the track in seconds\n@property BOOL gapless;  // is this track from a gapless album?\n@property (copy) NSString *genre;  // the music/audio genre (category) of the track\n@property (copy) NSString *grouping;  // the grouping (piece) of the track. Generally used to denote movements within a classical work.\n@property (readonly) BOOL iTunesU;  // is this track an iTunes U episode?\n@property (copy, readonly) NSString *kind;  // a text description of the track\n@property (copy) NSString *longDescription;\n@property BOOL loved;  // is this track loved?\n@property (copy) NSString *lyrics;  // the lyrics of the track\n@property (copy, readonly) NSDate *modificationDate;  // the modification date of the content of this track\n@property NSInteger playedCount;  // number of times this track has been played\n@property (copy) NSDate *playedDate;  // the date and time this track was last played\n@property (readonly) BOOL podcast;  // is this track a podcast episode?\n@property NSInteger rating;  // the rating of this track (0 to 100)\n@property (readonly) iTunesERtK ratingKind;  // the rating kind of this track\n@property (copy, readonly) NSDate *releaseDate;  // the release date of this track\n@property (readonly) NSInteger sampleRate;  // the sample rate of the track (in Hz)\n@property NSInteger seasonNumber;  // the season number of the track\n@property BOOL shufflable;  // is this track included when shuffling?\n@property NSInteger skippedCount;  // number of times this track has been skipped\n@property (copy) NSDate *skippedDate;  // the date and time this track was last skipped\n@property (copy) NSString *show;  // the show name of the track\n@property (copy) NSString *sortAlbum;  // override string to use for the track when sorting by album\n@property (copy) NSString *sortArtist;  // override string to use for the track when sorting by artist\n@property (copy) NSString *sortAlbumArtist;  // override string to use for the track when sorting by album artist\n@property (copy) NSString *sortName;  // override string to use for the track when sorting by name\n@property (copy) NSString *sortComposer;  // override string to use for the track when sorting by composer\n@property (copy) NSString *sortShow;  // override string to use for the track when sorting by show name\n@property (readonly) long long size;  // the size of the track (in bytes)\n@property double start;  // the start time of the track in seconds\n@property (copy, readonly) NSString *time;  // the length of the track in MM:SS format\n@property NSInteger trackCount;  // the total number of tracks on the source album\n@property NSInteger trackNumber;  // the index of the track on the source album\n@property BOOL unplayed;  // is this track unplayed?\n@property iTunesEVdK videoKind;  // kind of video track\n@property NSInteger volumeAdjustment;  // relative volume adjustment of the track (-100% to 100%)\n@property NSInteger year;  // the year the track was recorded/released\n\n\n@end\n\n// a track on an audio CD\n@interface iTunesAudioCDTrack : iTunesTrack\n\n@property (copy, readonly) NSURL *location;  // the location of the file represented by this track\n\n\n@end\n\n// a track representing an audio file (MP3, AIFF, etc.)\n@interface iTunesFileTrack : iTunesTrack\n\n@property (copy) NSURL *location;  // the location of the file represented by this track\n\n- (void) refresh;  // update file track information from the current information in the track’s file\n\n@end\n\n// a track residing in a shared library\n@interface iTunesSharedTrack : iTunesTrack\n\n\n@end\n\n// a track representing a network stream\n@interface iTunesURLTrack : iTunesTrack\n\n@property (copy) NSString *address;  // the URL for this track\n\n- (void) download;  // download podcast episode\n\n@end\n\n// custom playlists created by the user\n@interface iTunesUserPlaylist : iTunesPlaylist\n\n- (SBElementArray *) fileTracks;\n- (SBElementArray *) URLTracks;\n- (SBElementArray *) sharedTracks;\n\n@property BOOL shared;  // is this playlist shared?\n@property (readonly) BOOL smart;  // is this a Smart Playlist?\n\n\n@end\n\n// a folder that contains other playlists\n@interface iTunesFolderPlaylist : iTunesUserPlaylist\n\n\n@end\n\n// a visual plug-in\n@interface iTunesVisual : iTunesItem\n\n\n@end\n\n// any window\n@interface iTunesWindow : iTunesItem\n\n@property NSRect bounds;  // the boundary rectangle for the window\n@property (readonly) BOOL closeable;  // does the window have a close box?\n@property (readonly) BOOL collapseable;  // does the window have a collapse (windowshade) box?\n@property BOOL collapsed;  // is the window collapsed?\n@property NSPoint position;  // the upper left position of the window\n@property (readonly) BOOL resizable;  // is the window resizable?\n@property BOOL visible;  // is the window visible?\n@property (readonly) BOOL zoomable;  // is the window zoomable?\n@property BOOL zoomed;  // is the window zoomed?\n\n\n@end\n\n// the main iTunes window\n@interface iTunesBrowserWindow : iTunesWindow\n\n@property BOOL minimized;  // is the small player visible?\n@property (copy, readonly) SBObject *selection;  // the selected songs\n@property (copy) iTunesPlaylist *view;  // the playlist currently displayed in the window\n\n\n@end\n\n// the iTunes equalizer window\n@interface iTunesEQWindow : iTunesWindow\n\n@property BOOL minimized;  // is the small EQ window visible?\n\n\n@end\n\n// a sub-window showing a single playlist\n@interface iTunesPlaylistWindow : iTunesWindow\n\n@property (copy, readonly) SBObject *selection;  // the selected songs\n@property (copy, readonly) iTunesPlaylist *view;  // the playlist displayed in the window\n\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAboutPanel.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAboutPanel.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  This class manages the \"About Background Music\" window.\n//\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface BGMAboutPanel : NSObject\n\n- (instancetype)initWithPanel:(NSPanel*)inAboutPanel licenseView:(NSTextView*)inLicenseView;\n- (void) show;\n\n@end\n\n\n@interface BGMLinkField : NSTextField\n@end\n\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAboutPanel.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAboutPanel.m\n//  BGMApp\n//\n//  Copyright © 2016, 2024 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMAboutPanel.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n\nNS_ASSUME_NONNULL_BEGIN\n\nstatic NSInteger const kVersionLabelTag = 1;\nstatic NSInteger const kCopyrightLabelTag = 2;\nstatic NSInteger const kProjectWebsiteLabelTag = 3;\nstatic NSInteger const kContributorsLabelTag = 4;\n\n@implementation BGMAboutPanel {\n    NSPanel* aboutPanel;\n    \n    NSTextField* versionLabel;\n    NSTextField* copyrightLabel;\n    NSTextField* websiteLabel;\n    NSTextField* contributorsLabel;\n    \n    NSTextView* licenseView;\n}\n\n- (instancetype)initWithPanel:(NSPanel*)inAboutPanel licenseView:(NSTextView*)inLicenseView {\n    if ((self = [super init])) {\n        aboutPanel = inAboutPanel;\n        \n        versionLabel = [[aboutPanel contentView] viewWithTag:kVersionLabelTag];\n        copyrightLabel = [[aboutPanel contentView] viewWithTag:kCopyrightLabelTag];\n        websiteLabel = [[aboutPanel contentView] viewWithTag:kProjectWebsiteLabelTag];\n        contributorsLabel = [[aboutPanel contentView] viewWithTag:kContributorsLabelTag];\n        \n        licenseView = inLicenseView;\n        \n        [self initAboutPanel];\n    }\n    \n    return self;\n}\n\n- (void) initAboutPanel {\n    // Set up the About Background Music window\n    \n    NSBundle* bundle = [NSBundle mainBundle];\n    \n    if (bundle == nil) {\n        NSLog(@\"Background Music: BGMAboutPanel::initAboutPanel: Could not find main bundle\");\n    } else {\n        // Version number label\n        NSString* __nullable version =\n            [[bundle infoDictionary] objectForKey:@\"CFBundleShortVersionString\"];\n        \n        if (version) {\n            versionLabel.stringValue = [NSString stringWithFormat:@\"Version %@\", version];\n        }\n        \n        // Copyright notice label\n        NSString* __nullable copyrightNotice =\n            [[bundle infoDictionary] objectForKey:@\"NSHumanReadableCopyright\"];\n        \n        if (copyrightNotice) {\n            // Remove the part that we replace with a link.\n            copyrightLabel.stringValue =\n                [((NSString*)copyrightNotice) stringByReplacingOccurrencesOfString:contributorsLabel.stringValue\n                                                                        withString:@\"\"];\n        }\n        \n        // Project website link label\n        websiteLabel.selectable = YES;\n        websiteLabel.allowsEditingTextAttributes = YES;\n        \n        NSString* projectURL = [NSString stringWithUTF8String:kBGMProjectURL];\n        NSFont* linkFont = websiteLabel.font ? websiteLabel.font : [NSFont labelFontOfSize:0.0];\n        websiteLabel.attributedStringValue =\n            [[NSAttributedString alloc] initWithString:projectURL\n                                            attributes:@{ NSLinkAttributeName: projectURL,\n                                                          NSFontAttributeName: linkFont }];\n        \n        // Contributors link label\n        // TODO: Proper credits (i.e. in the app instead of just a link)\n        contributorsLabel.selectable = YES;\n        contributorsLabel.allowsEditingTextAttributes = YES;\n        \n        NSString* contributorsURL = [NSString stringWithUTF8String:kBGMContributorsURL];\n        NSFont* cLinkFont = contributorsLabel.font ? contributorsLabel.font : [NSFont labelFontOfSize:0.0];\n        contributorsLabel.attributedStringValue =\n            [[NSAttributedString alloc] initWithString:contributorsLabel.stringValue\n                                            attributes:@{ NSLinkAttributeName: contributorsURL,\n                                                          NSFontAttributeName: cLinkFont }];\n        \n        // Load the text of the license into the text view\n        NSString* __nullable licensePath = [bundle pathForResource:@\"LICENSE\" ofType:nil];\n        \n        NSError* err;\n        NSString* __nullable licenseStr = (!licensePath ? nil :\n            [NSString stringWithContentsOfFile:(NSString*)licensePath\n                                      encoding:NSASCIIStringEncoding\n                                         error:&err]);\n        \n        if (err || !licenseStr || [licenseStr isEqualToString:@\"\"]) {\n            NSLog(@\"Error loading license file: %@\", err);\n            licenseStr = @\"Error: could not open license file.\";\n        }\n        \n        licenseView.string = (NSString*)licenseStr;\n        \n        NSFont* __nullable font = [NSFont fontWithName:@\"Andale Mono\" size:0.0];\n        if (font) {\n            licenseView.textStorage.font = font;\n        }\n    }\n}\n\n- (void) show {\n    DebugMsg(\"BGMAboutPanel::showAboutPanel: Opening \\\"About Background Music\\\" panel\");\n    \n    // We have to make aboutPanel visible before calling [NSApp activateIgnoringOtherApps:YES]\n    // or the app won't be activated the first time (not sure why it only happens the first\n    // time) and aboutPanel won't open. WindowServer logs this explanation:\n    // 0[SetFrontProcessWithInfo]: CPS: Rejecting the request for pid 1234 due to the activation count being 0; launch ts=19302059379458, current time=19314267188375, window count = 0.\n    [aboutPanel setIsVisible:YES];\n    [aboutPanel makeKeyAndOrderFront:self];\n    \n    // On macOS 14.4, aboutPanel needs \"Release When Closed\" unchecked in MainMenu.xib. Otherwise,\n    // aboutPanel will never open again if you click the close button.\n    \n    // This is deprecated for NSApplication.activate, but that stops aboutPanel from ever being shown.\n    [NSApp activateIgnoringOtherApps:YES];\n    \n    DebugMsg(\"BGMAboutPanel::showAboutPanel: Finished opening panel. \"\n             \"aboutPanel.isVisible %d, aboutPanel.isKeyWindow %d, NSApp.isActive %d\",\n             aboutPanel.isVisible,\n             aboutPanel.isKeyWindow,\n             NSApp.isActive);\n}\n\n@end\n\n@implementation BGMLinkField\n\n- (void) resetCursorRects {\n    // Change the mouse cursor when hovering over the link. (It does change by default, but only after\n    // you've clicked it once.)\n    [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];\n}\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAutoPauseMusicPrefs.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMusicPrefs.h\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMMusicPlayers.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAutoPauseMusicPrefs : NSObject\n\n- (id) initWithPreferencesMenu:(NSMenu*)inPrefsMenu\n                  audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n                  musicPlayers:(BGMMusicPlayers*)inMusicPlayers;\n\n@end\n\n#pragma clang assume_nonnull end \n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAutoPauseMusicPrefs.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAutoPauseMusicPrefs.mm\n//  BGMApp\n//\n//  Copyright © 2016, 2019 Kyle Neideck\n//\n\n// Self Includes\n#import \"BGMAutoPauseMusicPrefs.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n#import \"BGMMusicPlayer.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic float const kMenuItemIconScalingFactor = 1.15f;\nstatic NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;\n\n@implementation BGMAutoPauseMusicPrefs {\n    BGMAudioDeviceManager* audioDevices;\n    BGMMusicPlayers* musicPlayers;\n    NSMenu* prefsMenu;\n    NSArray<NSMenuItem*>* musicPlayerMenuItems;\n}\n\n- (id) initWithPreferencesMenu:(NSMenu*)inPrefsMenu\n                  audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n                  musicPlayers:(BGMMusicPlayers*)inMusicPlayers {\n    if ((self = [super init])) {\n        prefsMenu = inPrefsMenu;\n        audioDevices = inAudioDevices;\n        musicPlayers = inMusicPlayers;\n        \n        musicPlayerMenuItems = @[];\n        \n        [self initPreferencesMenuSection];\n    }\n    \n    return self;\n}\n\n- (void) initPreferencesMenuSection {\n    // Add the menu items related to auto-pausing music to the Preferences submenu\n    \n    // The index to start inserting music player menu items at\n    NSInteger musicPlayerItemsIndex = [prefsMenu indexOfItemWithTag:kPrefsMenuAutoPauseHeaderTag] + 1;\n    \n    // Insert the menu items used to change the music player app.\n    for (id<BGMMusicPlayer> musicPlayer in musicPlayers.musicPlayers) {\n        // Create an menu item for this music player.\n        NSMenuItem* menuItem = [prefsMenu insertItemWithTitle:musicPlayer.name\n                                                       action:@selector(handleMusicPlayerChange:)\n                                                keyEquivalent:@\"\"\n                                                      atIndex:musicPlayerItemsIndex];\n        menuItem.toolTip = musicPlayer.toolTip;\n        \n        musicPlayerMenuItems = [musicPlayerMenuItems arrayByAddingObject:menuItem];\n        \n        // Associate the music player with the menu item\n        menuItem.representedObject = musicPlayer;\n        \n        // Show the menu item for the selected music player as selected\n        if (musicPlayers.selectedMusicPlayer == musicPlayer) {\n            menuItem.state = NSOnState;\n        }\n        \n        // Set the menu item's icon\n        NSImage* __nullable icon = musicPlayer.icon;\n        if (icon == nil) {\n            // Set a blank icon so the text lines up\n            icon = [NSImage new];\n        }\n        \n        // Size the icon relative to the size of the item's text\n        CGFloat length = [NSFont menuBarFontOfSize:0].pointSize * kMenuItemIconScalingFactor;\n        icon.size = NSMakeSize(length, length);\n        menuItem.image = icon;\n        \n        menuItem.target = self;\n        menuItem.indentationLevel = 1;\n    }\n}\n\n- (void) handleMusicPlayerChange:(NSMenuItem*)sender {\n    // Set the new music player as the selected music player\n    id<BGMMusicPlayer> musicPlayer = sender.representedObject;\n    NSAssert(musicPlayer, @\"BGMAutoPauseMusicPrefs::handleMusicPlayerChange: !musicPlayer\");\n    \n    musicPlayers.selectedMusicPlayer = musicPlayer;\n    \n    // Select/deselect the menu items\n    for (NSMenuItem* item in musicPlayerMenuItems) {\n        BOOL isNewlySelectedMusicPlayer = (item == sender);\n        item.state = (isNewlySelectedMusicPlayer ? NSOnState : NSOffState);\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMPreferencesMenu.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPreferencesMenu.h\n//  BGMApp\n//\n//  Copyright © 2016, 2018, 2019 Kyle Neideck\n//\n//  Handles the preferences menu UI. The user's preference changes are often passed directly to the driver rather\n//  than to other BGMApp classes.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMMusicPlayers.h\"\n#import \"BGMStatusBarItem.h\"\n#import \"BGMUserDefaults.h\"\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface BGMPreferencesMenu : NSObject\n\n- (id) initWithBGMMenu:(NSMenu*)inBGMMenu\n          audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n          musicPlayers:(BGMMusicPlayers*)inMusicPlayers\n         statusBarItem:(BGMStatusBarItem*)inStatusBarItem\n            aboutPanel:(NSPanel*)inAboutPanel\n aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView\n          userDefaults:(BGMUserDefaults*)inUserDefaults;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMPreferencesMenu.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPreferencesMenu.mm\n//  BGMApp\n//\n//  Copyright © 2016, 2018, 2019 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMPreferencesMenu.h\"\n\n// Local Includes\n#import \"BGMAutoPauseMusicPrefs.h\"\n#import \"BGMAboutPanel.h\"\n\n// System Includes\n#import <math.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n// Interface Builder tags\nstatic NSInteger const kPreferencesMenuItemTag = 1;\nstatic NSInteger const kBGMIconMenuItemTag     = 2;\nstatic NSInteger const kVolumeIconMenuItemTag  = 3;\nstatic NSInteger const kAboutPanelMenuItemTag  = 4;\n\n@implementation BGMPreferencesMenu {\n    // Menu sections/items\n    BGMAutoPauseMusicPrefs* autoPauseMusicPrefs;\n    NSMenuItem* bgmIconMenuItem;\n    NSMenuItem* volumeIconMenuItem;\n\n    // The menu item you press to open BGMApp's main menu.\n    BGMStatusBarItem* statusBarItem;\n\n    // The About Background Music window\n    BGMAboutPanel* aboutPanel;\n    \n    // Delay preferences\n    NSMenuItem* pauseDelayMenuItem;\n    NSMenuItem* maxUnpauseDelayMenuItem;\n    NSSlider* pauseDelaySlider;\n    NSSlider* maxUnpauseDelaySlider;\n    NSTextField* pauseDelayLabel;\n    NSTextField* maxUnpauseDelayLabel;\n    BGMUserDefaults* userDefaults;\n}\n\n- (id) initWithBGMMenu:(NSMenu*)inBGMMenu\n          audioDevices:(BGMAudioDeviceManager*)inAudioDevices\n          musicPlayers:(BGMMusicPlayers*)inMusicPlayers\n         statusBarItem:(BGMStatusBarItem*)inStatusBarItem\n            aboutPanel:(NSPanel*)inAboutPanel\n aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView\n          userDefaults:(BGMUserDefaults*)inUserDefaults {\n    if ((self = [super init])) {\n        NSMenu* prefsMenu = [[inBGMMenu itemWithTag:kPreferencesMenuItemTag] submenu];\n        \n        autoPauseMusicPrefs = [[BGMAutoPauseMusicPrefs alloc] initWithPreferencesMenu:prefsMenu\n                                                                         audioDevices:inAudioDevices\n                                                                         musicPlayers:inMusicPlayers];\n        \n        aboutPanel = [[BGMAboutPanel alloc] initWithPanel:inAboutPanel licenseView:inAboutPanelLicenseView];\n\n        statusBarItem = inStatusBarItem;\n\n        // Set up the menu items under the \"Status Bar Icon\" heading.\n        bgmIconMenuItem = [prefsMenu itemWithTag:kBGMIconMenuItemTag];\n        bgmIconMenuItem.state =\n                (statusBarItem.icon == BGMFermataStatusBarIcon) ? NSOnState : NSOffState;\n        [bgmIconMenuItem setTarget:self];\n        [bgmIconMenuItem setAction:@selector(useBGMStatusBarIcon)];\n\n        volumeIconMenuItem = [prefsMenu itemWithTag:kVolumeIconMenuItemTag];\n        volumeIconMenuItem.state =\n                (statusBarItem.icon == BGMVolumeStatusBarIcon) ? NSOnState : NSOffState;\n        [volumeIconMenuItem setTarget:self];\n        [volumeIconMenuItem setAction:@selector(useVolumeStatusBarIcon)];\n\n        // Set up the \"About Background Music\" menu item\n        NSMenuItem* aboutMenuItem = [prefsMenu itemWithTag:kAboutPanelMenuItemTag];\n        [aboutMenuItem setTarget:aboutPanel];\n        [aboutMenuItem setAction:@selector(show)];\n        \n        // Set up delay preferences\n        userDefaults = inUserDefaults;\n        [self setupDelayPreferences:prefsMenu];\n    }\n    \n    return self;\n}\n\n- (void) useBGMStatusBarIcon {\n    // Change the icon.\n    statusBarItem.icon = BGMFermataStatusBarIcon;\n\n    // Select/deselect the menu items.\n    bgmIconMenuItem.state = NSOnState;\n    volumeIconMenuItem.state = NSOffState;\n}\n\n- (void) useVolumeStatusBarIcon {\n    // TODO: Maybe we should show a message that tells the user how to hide the built-in volume\n    //       icon. They probably won't want two status bar items that look the same. Or we might be\n    //       able to automatically hide the built-in icon while BGMApp is running and show it again\n    //       when BGMApp is closed.\n\n    // Change the icon.\n    statusBarItem.icon = BGMVolumeStatusBarIcon;\n\n    // Select/deselect the menu items.\n    bgmIconMenuItem.state = NSOffState;\n    volumeIconMenuItem.state = NSOnState;\n}\n\n- (void) setupDelayPreferences:(NSMenu*)prefsMenu {\n    // Create delay preference menu items dynamically\n    \n    // Add separator\n    [prefsMenu addItem:[NSMenuItem separatorItem]];\n    \n    // Add \"Auto-pause Delays\" header\n    NSMenuItem* delaysHeader = [[NSMenuItem alloc] initWithTitle:@\"Auto-pause Delays\" action:nil keyEquivalent:@\"\"];\n    delaysHeader.enabled = NO;\n    [prefsMenu addItem:delaysHeader];\n    \n    // Create pause delay menu item with slider\n    pauseDelayMenuItem = [[NSMenuItem alloc] initWithTitle:@\"\" action:nil keyEquivalent:@\"\"];\n    NSView* pauseDelayView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 280, 25)];\n    \n    // Pause delay label\n    NSTextField* pauseDelayTitleLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 5, 100, 15)];\n    pauseDelayTitleLabel.stringValue = @\"Pause Delay:\";\n    pauseDelayTitleLabel.editable = NO;\n    pauseDelayTitleLabel.bordered = NO;\n    pauseDelayTitleLabel.backgroundColor = [NSColor clearColor];\n    pauseDelayTitleLabel.font = [NSFont menuFontOfSize:13];\n    [pauseDelayView addSubview:pauseDelayTitleLabel];\n    \n    // Pause delay slider\n    pauseDelaySlider = [[NSSlider alloc] initWithFrame:NSMakeRect(115, 5, 100, 15)];\n    [pauseDelayView addSubview:pauseDelaySlider];\n    \n    // Pause delay value label\n    pauseDelayLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(220, 5, 55, 15)];\n    pauseDelayLabel.editable = NO;\n    pauseDelayLabel.bordered = NO;\n    pauseDelayLabel.backgroundColor = [NSColor clearColor];\n    pauseDelayLabel.font = [NSFont menuFontOfSize:11];\n    [pauseDelayView addSubview:pauseDelayLabel];\n    \n    pauseDelayMenuItem.view = pauseDelayView;\n    [prefsMenu addItem:pauseDelayMenuItem];\n    \n    // Create max unpause delay menu item with slider\n    maxUnpauseDelayMenuItem = [[NSMenuItem alloc] initWithTitle:@\"\" action:nil keyEquivalent:@\"\"];\n    NSView* maxUnpauseDelayView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 280, 25)];\n    \n    // Max unpause delay label\n    NSTextField* maxUnpauseDelayTitleLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 5, 100, 15)];\n    maxUnpauseDelayTitleLabel.stringValue = @\"Max Unpause:\";\n    maxUnpauseDelayTitleLabel.editable = NO;\n    maxUnpauseDelayTitleLabel.bordered = NO;\n    maxUnpauseDelayTitleLabel.backgroundColor = [NSColor clearColor];\n    maxUnpauseDelayTitleLabel.font = [NSFont menuFontOfSize:13];\n    [maxUnpauseDelayView addSubview:maxUnpauseDelayTitleLabel];\n    \n    // Max unpause delay slider\n    maxUnpauseDelaySlider = [[NSSlider alloc] initWithFrame:NSMakeRect(115, 5, 100, 15)];\n    [maxUnpauseDelayView addSubview:maxUnpauseDelaySlider];\n    \n    // Max unpause delay value label\n    maxUnpauseDelayLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(220, 5, 55, 15)];\n    maxUnpauseDelayLabel.editable = NO;\n    maxUnpauseDelayLabel.bordered = NO;\n    maxUnpauseDelayLabel.backgroundColor = [NSColor clearColor];\n    maxUnpauseDelayLabel.font = [NSFont menuFontOfSize:11];\n    [maxUnpauseDelayView addSubview:maxUnpauseDelayLabel];\n    \n    maxUnpauseDelayMenuItem.view = maxUnpauseDelayView;\n    [prefsMenu addItem:maxUnpauseDelayMenuItem];\n    \n    // Initialize the delay sliders with current values and targets\n    [self initDelaySliders];\n}\n\n- (void) initDelaySliders {\n    // Configure pause delay slider with logarithmic scale (0ms to 10000ms)\n    // Slider range 0-100 maps logarithmically to 0-10000ms\n    pauseDelaySlider.minValue = 0;\n    pauseDelaySlider.maxValue = 100;\n    pauseDelaySlider.integerValue = [self msToSliderValue:userDefaults.pauseDelayMS];\n    pauseDelaySlider.target = self;\n    pauseDelaySlider.action = @selector(pauseDelaySliderChanged:);\n    \n    // Configure max unpause delay slider with logarithmic scale (0ms to 10000ms)\n    // Slider range 0-100 maps logarithmically to 0-10000ms\n    maxUnpauseDelaySlider.minValue = 0;\n    maxUnpauseDelaySlider.maxValue = 100;\n    maxUnpauseDelaySlider.integerValue = [self msToSliderValue:userDefaults.maxUnpauseDelayMS];\n    maxUnpauseDelaySlider.target = self;\n    maxUnpauseDelaySlider.action = @selector(maxUnpauseDelaySliderChanged:);\n    \n    // Update labels with current values\n    [self updatePauseDelayLabel];\n    [self updateMaxUnpauseDelayLabel];\n}\n\n- (void) pauseDelaySliderChanged:(NSSlider*)sender {\n    NSUInteger delayMS = [self sliderValueToMS:sender.integerValue];\n    userDefaults.pauseDelayMS = delayMS;\n    [self updatePauseDelayLabel];\n}\n\n- (void) maxUnpauseDelaySliderChanged:(NSSlider*)sender {\n    NSUInteger delayMS = [self sliderValueToMS:sender.integerValue];\n    userDefaults.maxUnpauseDelayMS = delayMS;\n    [self updateMaxUnpauseDelayLabel];\n}\n\n\n- (void) updatePauseDelayLabel {\n    NSUInteger delayMS = userDefaults.pauseDelayMS;\n    if (delayMS == 0) {\n        pauseDelayLabel.stringValue = @\"Off\";\n    } else {\n        pauseDelayLabel.stringValue = [NSString stringWithFormat:@\"%lums\", (unsigned long)delayMS];\n    }\n}\n\n- (void) updateMaxUnpauseDelayLabel {\n    NSUInteger delayMS = userDefaults.maxUnpauseDelayMS;\n    if (delayMS == 0) {\n        maxUnpauseDelayLabel.stringValue = @\"Off\";\n    } else {\n        maxUnpauseDelayLabel.stringValue = [NSString stringWithFormat:@\"%lums\", (unsigned long)delayMS];\n    }\n}\n\n// Logarithmic conversion methods for better control at small values\n// Maps slider value (0-100) to milliseconds (0-10000) logarithmically\n- (NSUInteger) sliderValueToMS:(NSInteger)sliderValue {\n    if (sliderValue <= 0) {\n        return 0;\n    }\n    \n    // Logarithmic mapping: slider 0-100 -> ms 0-10000\n    // Formula: ms = (10^(sliderValue/50) - 1) * (10000/99)\n    // This gives finer control at small values and coarser control at large values\n    double normalizedSlider = (double)sliderValue / 100.0; // 0-1\n    double logValue = pow(10.0, normalizedSlider * 2.0) - 1.0; // 0-99\n    NSUInteger ms = (NSUInteger)(logValue * (10000.0 / 99.0));\n    \n    return MIN(ms, 10000); // Cap at 10000ms\n}\n\n// Maps milliseconds (0-10000) to slider value (0-100) logarithmically\n- (NSInteger) msToSliderValue:(NSUInteger)ms {\n    if (ms == 0) {\n        return 0;\n    }\n    \n    // Inverse of the logarithmic mapping\n    // Formula: sliderValue = 50 * log10((ms * 99/10000) + 1)\n    double normalizedMS = (double)ms / 10000.0; // 0-1\n    double logInput = (normalizedMS * 99.0) + 1.0; // 1-100\n    double sliderValue = (log10(logInput) / 2.0) * 100.0; // 0-100\n    \n    return (NSInteger)MIN(MAX(sliderValue, 0), 100);\n}\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASApplication.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMASApplication.h\n//  BGMApp\n//\n//  Copyright © 2021 Marcus Wu\n//  Copyright © 2021 Kyle Neideck\n//\n//  An AppleScript class for volume and pan settings for running applications.\n//\n\n\n// Local Includes\n#import \"BGMAppVolumesController.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n#import <Cocoa/Cocoa.h>\n\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface BGMASApplication : NSObject\n\n- (instancetype) initWithApplication:(NSRunningApplication*)app\n                          volumeController:(BGMAppVolumesController*)volumeController\n                     parentSpecifier:(NSScriptObjectSpecifier* __nullable)parentSpecifier\n                               index:(int)i;\n\n@property (readonly) NSString* name;\n@property (readonly) NSString* bundleID;\n@property int volume;\n@property int pan;\n@end\n\nNS_ASSUME_NONNULL_END\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASApplication.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMASApplication.m\n//  BGMApp\n//\n//  Copyright © 2021 Marcus Wu\n//  Copyright © 2021 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMASApplication.h\"\n\n// Local Includes\n#import \"BGM_Types.h\"\n\n@implementation BGMASApplication {\n    NSScriptObjectSpecifier* parentSpecifier;\n    NSRunningApplication *application;\n    BGMAppVolumesController* appVolumesController;\n    int index;\n}\n\n- (instancetype) initWithApplication:(NSRunningApplication*)app\n                          volumeController:(BGMAppVolumesController*)volumeController\n                     parentSpecifier:(NSScriptObjectSpecifier* __nullable)parent\n                               index:(int)i {\n    if ((self = [super init])) {\n        parentSpecifier = parent;\n        application = app;\n        appVolumesController = volumeController;\n        index = i;\n    }\n\n    return self;\n}\n\n- (NSString*) name {\n    return [NSString stringWithFormat:@\"%@\", [application localizedName]];\n}\n\n- (NSString*) bundleID {\n    return [NSString stringWithFormat:@\"%@\", [application bundleIdentifier]];\n}\n\n- (int) volume {\n    return [appVolumesController getVolumeAndPanForApp:application].volume;\n}\n\n- (void) setVolume:(int)vol {\n    BGMAppVolumeAndPan volume = {\n        .volume = vol,\n        .pan = kAppPanNoValue\n    };\n    [appVolumesController setVolumeAndPan:volume forApp:application];\n}\n\n- (int) pan {\n    return [appVolumesController getVolumeAndPanForApp:application].pan;\n}\n\n- (void) setPan:(int)pan {\n    BGMAppVolumeAndPan thePan = {\n        .volume = -1,\n        .pan = pan\n    };\n    [appVolumesController setVolumeAndPan:thePan forApp:application];\n}\n\n- (NSScriptObjectSpecifier* __nullable) objectSpecifier {\n    NSScriptClassDescription* parentClassDescription = [parentSpecifier keyClassDescription];\n    return [[NSNameSpecifier alloc] initWithContainerClassDescription:parentClassDescription\n                                                   containerSpecifier:parentSpecifier\n                                                                  key:@\"applications\"\n                                                                 name:self.name];\n}\n\n@end\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASOutputDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMASOutputDevice.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  An AppleScript class for the output devices that can be selected in the preferences menu.\n//\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMASOutputDevice : NSObject\n\n- (instancetype) initWithAudioObjectID:(AudioObjectID)objID\n                          audioDevices:(BGMAudioDeviceManager*)devices\n                       parentSpecifier:(NSScriptObjectSpecifier* __nullable)parentSpecifier;\n\n@property (readonly) NSString* name;\n@property BOOL selected;  // is this the device to be used for audio output?\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASOutputDevice.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMASOutputDevice.mm\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMASOutputDevice.h\"\n\n// Local Includes\n#import \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMASOutputDevice {\n    NSScriptObjectSpecifier* parentSpecifier;\n    BGMAudioDevice device;\n    BGMAudioDeviceManager* audioDevices;\n}\n\n- (instancetype) initWithAudioObjectID:(AudioObjectID)objID\n                          audioDevices:(BGMAudioDeviceManager*)devices\n                       parentSpecifier:(NSScriptObjectSpecifier* __nullable)parent {\n    if ((self = [super init])) {\n        parentSpecifier = parent;\n        device = objID;\n        audioDevices = devices;\n    }\n\n    return self;\n}\n\n- (NSString*) name {\n    return (NSString*)CFBridgingRelease(device.CopyName());\n}\n\n- (BOOL) selected {\n    return [audioDevices isOutputDevice:device];\n}\n\n- (void) setSelected:(BOOL)selected {\n    if (selected && ![self selected]) {\n        DebugMsg(\"BGMASOutputDevice::setSelected: A script is setting output device to %s\",\n                 [[self name] UTF8String]);\n\n        NSError* err = [audioDevices setOutputDeviceWithID:device revertOnFailure:YES];\n        (void)err;  // TODO: Return an error to the script somehow if this isn't nil. Also, should\n                    //       we return an error if the script tries to set this property to false?     \n    }\n}\n\n- (NSScriptObjectSpecifier* __nullable) objectSpecifier {\n    NSScriptClassDescription* parentClassDescription = [parentSpecifier keyClassDescription];\n    return [[NSNameSpecifier alloc] initWithContainerClassDescription:parentClassDescription\n                                                   containerSpecifier:parentSpecifier\n                                                                  key:@\"output devices\"\n                                                                 name:self.name];\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMApp.sdef",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE dictionary SYSTEM \"file://localhost/System/Library/DTDs/sdef.dtd\">\n<dictionary>\n    <suite name=\"Background Music\" code=\"BGMs\" description=\"Background Music specific classes\">\n             <!-- TODO: Figure out why Script Editor returns output device objects as\n                      «class» \"Built-in Output\" of application \"Background Music\"\n                  instead of\n                      device \"Built-in Output\" of application \"Background Music\" -->\n        <class name=\"output device\"\n               code=\"aDev\"\n               description=\"A hardware device that can play audio\"\n               plural=\"output devices\">\n            <synonym name=\"audio device\"/>\n\n            <cocoa class=\"BGMASOutputDevice\"/>\n\n            <property name=\"name\"\n                      code=\"pnam\"\n                      description=\"The name of the output device.\"\n                      type=\"text\"\n                      access=\"r\">\n                <cocoa key=\"name\"/>\n            </property>\n\n            <property name=\"selected\"\n                      code=\"Slcd\"\n                      type=\"boolean\"\n                      access=\"rw\"\n                      description=\"Is this the device to be used for audio output?\">\n\t\t<synonym name=\"default\"/>\n                <cocoa key=\"selected\"/>\n            </property>\n        </class>\n\n        <class name=\"audio application\"\n               code=\"aApp\"\n               description=\"An application that can play audio\"\n               plural=\"audio applications\">\n            <synonym name=\"audio app\"/>\n\n            <cocoa class=\"BGMASApplication\"/>\n\n            <property name=\"name\"\n                      code=\"pnam\"\n                      description=\"The name of the application.\"\n                      type=\"text\"\n                      access=\"r\">\n                <cocoa key=\"name\"/>\n            </property>\n\n            <property name=\"bundleID\"\n                      code=\"bdid\"\n                      description=\"The bundle ID of the application, e.g. 'com.somecompany.coolapp'.\"\n                      type=\"text\"\n                      access=\"r\">\n                <cocoa key=\"bundleID\"/>\n            </property>\n\n            <property name=\"vol\"\n                      code=\"pVol\"\n                      type=\"integer\"\n                      access=\"rw\"\n                      description=\"The volume setting of the application\">\n                <cocoa key=\"volume\"/>\n            </property>\n\n            <property name=\"pan\"\n                      code=\"pPan\"\n                      type=\"integer\"\n                      access=\"rw\"\n                      description=\"The pan setting of the application\">\n                <cocoa key=\"pan\"/>\n            </property>\n        </class>\n\n        <class name=\"application\"\n               code=\"capp\"\n               description=\"The application program\">\n            <cocoa class=\"NSApplication\"/>\n\n            <!-- We have a class called \"output device\", so we have to call this class something\n                 else. -->\n            <property name=\"selected output device\"\n                      type=\"output device\"\n                      code=\"selO\"\n                      access=\"rw\"\n                      description=\"The device to be used for audio output\">\n                <synonym name=\"selected device\"/>\n                <synonym name=\"default device\"/>\n                <synonym name=\"default output device\"/>\n\n                <cocoa key=\"selectedOutputDevice\"/>\n            </property>\n            <property name=\"output volume\"\n                type=\"real\"\n                code=\"oVol\"\n                access=\"rw\"\n                description=\"The main output volume\">\n                <synonym name=\"main volume\"/>\n\n                <cocoa key=\"mainVolume\"/>\n            </property>\n\n            <!-- Unintuitively, this is for the array of output devices. -->\n            <element type=\"output device\" access=\"r\">\n                <cocoa key=\"outputDevices\"/>\n            </element>\n            <element type=\"audio application\" access=\"r\">\n                <cocoa key=\"applications\"/>\n            </element>\n        </class>\n    </suite>\n</dictionary>\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppDelegate+AppleScript.h\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//\n\n#import \"BGMAppDelegate.h\"\n\n// Local Includes\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMAppVolumesController.h\"\n\n// Local Includes\n#import \"BGMASOutputDevice.h\"\n#import \"BGMASApplication.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMAppDelegate (AppleScript)\n\n- (BOOL) application:(NSApplication*)sender delegateHandlesKey:(NSString*)key;\n\n@property BGMASOutputDevice* selectedOutputDevice;\n@property (readonly) NSArray<BGMASOutputDevice*>* outputDevices;\n@property double mainVolume;\n@property (readonly) NSArray<BGMASApplication*>* applications;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppDelegate+AppleScript.mm\n//  BGMApp\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright © 2021 Marcus Wu\n//\n\n// Self Include\n#import \"BGMAppDelegate+AppleScript.h\"\n\n// Local Includes\n#import \"BGMAudioDevice.h\"\n\n// PublicUtility Includes\n#import \"CAHALAudioSystemObject.h\"\n#import \"CAAutoDisposer.h\"\n\nconst AudioObjectPropertyScope kScope                   = kAudioDevicePropertyScopeOutput;\n\n#pragma clang assume_nonnull begin\n\n@implementation BGMAppDelegate (AppleScript)\n\n- (BOOL) application:(NSApplication*)sender delegateHandlesKey:(NSString*)key {\n    #pragma unused (sender)\n\n    if (![key isEqual:@\"_keyWindow\"]) {\n        DebugMsg(\"BGMAppDelegate:application:delegateHandlesKey: Key queried: '%s'\",\n                 [key UTF8String]);\n    }\n\n    return [@[@\"selectedOutputDevice\", @\"outputDevices\", @\"mainVolume\", @\"applications\"] containsObject:key];\n}\n\n- (BGMASOutputDevice*) selectedOutputDevice {\n    AudioObjectID outputDeviceID = [self.audioDevices outputDevice].GetObjectID();\n\n    return [[BGMASOutputDevice alloc] initWithAudioObjectID:outputDeviceID\n                                               audioDevices:self.audioDevices\n                                            parentSpecifier:[self objectSpecifier]];\n}\n\n- (void) setSelectedOutputDevice:(BGMASOutputDevice*)device {\n    [device setSelected:YES];\n}\n\n- (NSArray<BGMASOutputDevice*>*) outputDevices {\n    UInt32 numDevices = CAHALAudioSystemObject().GetNumberAudioDevices();\n    \n    CAAutoArrayDelete<AudioObjectID> devices(numDevices);\n    CAHALAudioSystemObject().GetAudioDevices(numDevices, devices);\n\n    NSMutableArray<BGMASOutputDevice*>* outputDevices =\n        [NSMutableArray arrayWithCapacity:numDevices];\n\n    for (UInt32 i = 0; i < numDevices; i++) {\n        BGMAudioDevice device(devices[i]);\n\n        if (device.CanBeOutputDeviceInBGMApp()) {\n            BGMASOutputDevice* outputDevice =\n                [[BGMASOutputDevice alloc] initWithAudioObjectID:device.GetObjectID()\n                                                    audioDevices:self.audioDevices\n                                                 parentSpecifier:[self objectSpecifier]];\n\n            [outputDevices addObject:outputDevice];\n        }\n    }\n\n    return outputDevices;\n}\n\n- (double) mainVolume {\n    BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];\n    return bgmDevice.GetVolumeControlScalarValue(kScope, kMasterChannel);\n}\n\n- (void) setMainVolume:(double)mainVolume {\n    BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];\n    bgmDevice.SetMasterVolumeScalar(kScope, (Float32)mainVolume);\n    [self.outputVolumeSlider setFloatValue:(float)mainVolume];\n}\n\n- (NSArray<BGMASApplication*>*) applications {\n    NSArray<NSRunningApplication*>* apps = [[NSWorkspace sharedWorkspace] runningApplications];\n    NSMutableArray<BGMASApplication*>* applications = [NSMutableArray arrayWithCapacity:[apps count]];\n\n    for (UInt32 i = 0; i < [apps count]; i++) {\n        BGMASApplication *app = [[BGMASApplication alloc] initWithApplication:apps[i] volumeController:self.appVolumes parentSpecifier:[self objectSpecifier] index:i];\n\n        [applications addObject:app];\n    }\n\n    return applications;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/SystemPreferences.h",
    "content": "/*\n * SystemPreferences.h\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class SystemPreferencesItem, SystemPreferencesApplication, SystemPreferencesColor, SystemPreferencesDocument, SystemPreferencesWindow, SystemPreferencesAttributeRun, SystemPreferencesCharacter, SystemPreferencesParagraph, SystemPreferencesText, SystemPreferencesAttachment, SystemPreferencesWord, SystemPreferencesAnchor, SystemPreferencesPane, SystemPreferencesPrintSettings;\n\nenum SystemPreferencesSavo {\n\tSystemPreferencesSavoAsk = 'ask ' /* Ask the user whether or not to save the file. */,\n\tSystemPreferencesSavoNo = 'no  ' /* Do not save the file. */,\n\tSystemPreferencesSavoYes = 'yes ' /* Save the file. */\n};\ntypedef enum SystemPreferencesSavo SystemPreferencesSavo;\n\nenum SystemPreferencesEnum {\n\tSystemPreferencesEnumStandard = 'lwst' /* Standard PostScript error handling */,\n\tSystemPreferencesEnumDetailed = 'lwdt' /* print a detailed report of PostScript errors */\n};\ntypedef enum SystemPreferencesEnum SystemPreferencesEnum;\n\n\n\n/*\n * Standard Suite\n */\n\n// A scriptable object.\n@interface SystemPreferencesItem : SBObject\n\n@property (copy) NSDictionary *properties;  // All of the object's properties.\n\n- (void)closeSaving:(SystemPreferencesSavo) saving savingIn:(NSURL *)savingIn;  // Close an object.\n- (void)delete;   // Delete an object.\n- (void)duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties;   // Copy object(s) and put the copies at a new location.\n- (BOOL)exists;   // Verify if an object exists.\n- (void)moveTo:(SBObject *)to;   // Move object(s) to a new location.\n- (void)saveAs:(NSString *)as in:(NSURL *)in_;   // Save an object.\n\n@end\n\n// An application's top level scripting object.\n@interface SystemPreferencesApplication : SBApplication\n\n- (SBElementArray *)documents;\n- (SBElementArray *)windows;\n\n@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?\n@property (copy, readonly) NSString *name;  // The name of the application.\n@property (copy, readonly) NSString *version;  // The version of the application.\n\n- (SystemPreferencesDocument *)open:(NSURL *)x;   // Open an object.\n- (void)print:(NSURL *)x printDialog:(BOOL) printDialog withProperties:(SystemPreferencesPrintSettings *)withProperties;  // Print an object.\n- (void)quitSaving:(SystemPreferencesSavo)saving;   // Quit an application.\n\n@end\n\n// A color.\n@interface SystemPreferencesColor : SystemPreferencesItem\n\n\n@end\n\n// A document.\n@interface SystemPreferencesDocument : SystemPreferencesItem\n\n@property (readonly) BOOL modified;  // Has the document been modified since the last save?\n@property (copy) NSString *name;  // The document's name.\n@property (copy) NSString *path;  // The document's path.\n\n\n@end\n\n// A window.\n@interface SystemPreferencesWindow : SystemPreferencesItem\n\n@property NSRect bounds;  // The bounding rectangle of the window.\n@property (readonly) BOOL closeable;  // Whether the window has a close box.\n@property (copy, readonly) SystemPreferencesDocument *document;  // The document whose contents are being displayed in the window.\n@property (readonly) BOOL floating;  // Whether the window floats.\n- (NSInteger)id;   // The unique identifier of the window.\n@property NSInteger index;  // The index of the window, ordered front to back.\n@property (readonly) BOOL miniaturizable;  // Whether the window can be miniaturized.\n@property BOOL miniaturized;  // Whether the window is currently miniaturized.\n@property (readonly) BOOL modal;  // Whether the window is the application's current modal window.\n@property (copy) NSString *name;  // The full title of the window.\n@property (readonly) BOOL resizable;  // Whether the window can be resized.\n@property (readonly) BOOL titled;  // Whether the window has a title bar.\n@property BOOL visible;  // Whether the window is currently visible.\n@property (readonly) BOOL zoomable;  // Whether the window can be zoomed.\n@property BOOL zoomed;  // Whether the window is currently zoomed.\n\n\n@end\n\n\n\n/*\n * Text Suite\n */\n\n// This subdivides the text into chunks that all have the same attributes.\n@interface SystemPreferencesAttributeRun : SystemPreferencesItem\n\n- (SBElementArray *)attachments;\n- (SBElementArray *)attributeRuns;\n- (SBElementArray *)characters;\n- (SBElementArray *)paragraphs;\n- (SBElementArray *)words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// This subdivides the text into characters.\n@interface SystemPreferencesCharacter : SystemPreferencesItem\n\n- (SBElementArray *)attachments;\n- (SBElementArray *)attributeRuns;\n- (SBElementArray *)characters;\n- (SBElementArray *)paragraphs;\n- (SBElementArray *)words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// This subdivides the text into paragraphs.\n@interface SystemPreferencesParagraph : SystemPreferencesItem\n\n- (SBElementArray *)attachments;\n- (SBElementArray *)attributeRuns;\n- (SBElementArray *)characters;\n- (SBElementArray *)paragraphs;\n- (SBElementArray *)words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// Rich (styled) text\n@interface SystemPreferencesText : SystemPreferencesItem\n\n- (SBElementArray *)attachments;\n- (SBElementArray *)attributeRuns;\n- (SBElementArray *)characters;\n- (SBElementArray *)paragraphs;\n- (SBElementArray *)words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n// Represents an inline text attachment.  This class is used mainly for make commands.\n@interface SystemPreferencesAttachment : SystemPreferencesText\n\n@property (copy) NSString *fileName;  // The path to the file for the attachment\n\n\n@end\n\n// This subdivides the text into words.\n@interface SystemPreferencesWord : SystemPreferencesItem\n\n- (SBElementArray *)attachments;\n- (SBElementArray *)attributeRuns;\n- (SBElementArray *)characters;\n- (SBElementArray *)paragraphs;\n- (SBElementArray *)words;\n\n@property (copy) NSColor *color;  // The color of the first character.\n@property (copy) NSString *font;  // The name of the font of the first character.\n@property NSInteger size;  // The size in points of the first character.\n\n\n@end\n\n\n\n/*\n * System Preferences\n */\n\n// an anchor within a preference pane\n@interface SystemPreferencesAnchor : SystemPreferencesItem\n\n@property (copy, readonly) NSString *name;  // name of the anchor within a preference pane\n\n- (SystemPreferencesAnchor *)reveal;   // Reveals an anchor within a preference pane or preference pane itself\n\n@end\n\n// System Preferences top level scripting object\n@interface SystemPreferencesApplication (SystemPreferences)\n\n- (SBElementArray *)panes;\n\n@property (copy) SystemPreferencesPane *currentPane;  // the currently selected pane\n@property (copy, readonly) SystemPreferencesWindow *preferencesWindow;  // the main preferences window\n@property BOOL showAll;  // Is SystemPrefs in show all view. (Setting to false will do nothing)\n\n@end\n\n// a preference pane\n@interface SystemPreferencesPane : SystemPreferencesItem\n\n- (SBElementArray *)anchors;\n\n- (NSString *)id;   // locale independent name of the preference pane; can refer to a pane using the expression: pane id \"<name>\"\n@property (copy, readonly) NSString *localizedName;  // localized name of the preference pane\n@property (copy, readonly) NSString *name;  // name of the preference pane as it appears in the title bar; can refer to a pane using the expression: pane \"<name>\"\n\n- (NSInteger)timedLoad;   // This command does xxxx.\n\n@end\n\n\n\n/*\n * Type Definitions\n */\n\n@interface SystemPreferencesPrintSettings : SBObject\n\n@property NSInteger copies;  // the number of copies of a document to be printed\n@property BOOL collating;  // Should printed copies be collated?\n@property NSInteger startingPage;  // the first page of the document to be printed\n@property NSInteger endingPage;  // the last page of the document to be printed\n@property NSInteger pagesAcross;  // number of logical pages laid across a physical page\n@property NSInteger pagesDown;  // number of logical pages laid out down a physical page\n@property (copy) NSDate *requestedPrintTime;  // the time at which the desktop printer should print the document\n@property SystemPreferencesEnum errorHandling;  // how errors are handled\n@property (copy) NSString *faxNumber;  // for fax number\n@property (copy) NSString *targetPrinter;  // for target printer\n\n- (void)closeSaving:(SystemPreferencesSavo) saving savingIn:(NSURL *)savingIn;  // Close an object.\n- (void)delete;   // Delete an object.\n- (void)duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties;   // Copy object(s) and put the copies at a new location.\n- (BOOL)exists;   // Verify if an object exists.\n- (void)moveTo:(SBObject *)to;   // Move object(s) to a new location.\n- (void)saveAs:(NSString *)as in:(NSURL *)in_;   // Save an object.\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMApp/_uninstall-non-interactive.sh",
    "content": "#!/bin/bash\n# vim: tw=120:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# _uninstall-non-interactive.sh\n#\n# Copyright © 2016 Nick Jacques\n# Copyright © 2016, 2017, 2021 Kyle Neideck\n#\n# Removes BGMApp, BGMDriver and BGMXPCHelper from the system immediately. Run by uninstall.sh and the Homebrew formula.\n#\n\n# TODO: Log commands and their output to uninstall.log, like build_and_install.sh does, rather than just sending\n#       everything to /dev/null.\n\n# TODO: Show a custom error message if the script fails, like build_and_install.sh.\n\n# Halt on errors.\nset -e\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\nbold=$(tput bold)\nnormal=$(tput sgr0)\n\napp_path=\"/Applications/Background Music.app\"\ndriver_path=\"/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\nxpc_path1=\"/usr/local/libexec/BGMXPCHelper.xpc\"\nxpc_path2=\"/Library/Application Support/Background Music/BGMXPCHelper.xpc\"\n\n# Check that files/directories are at most this big before we delete them, just to be safe. Note that the bundles can\n# include debug symbols, e.g. if you use build_and_install.sh, which makes them a lot bigger.\nmax_size_mb_for_rm=30\n\nfile_paths=(\"${app_path}\" \"${driver_path}\" \"${xpc_path1}\" \"${xpc_path2}\")\n\nbgmapp_process_name=\"Background Music\"\n\nlaunchd_plist_label=\"com.bearisdriving.BGM.XPCHelper\"\nlaunchd_plist=\"/Library/LaunchDaemons/${launchd_plist_label}.plist\"\n\ncoreaudiod_plist=\"/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist\"\n\nuser_group_name=\"_BGMXPCHelper\"\n\n# We move files to this temp directory and then move the directory to the user's trash at the end of the script.\n# Unfortunately, this means that if the user tries to use the \"put back\" feature the files will just go back to the\n# temp directory.\ntrash_dir=\"$(mktemp -d -t UninstalledBackgroundMusicFiles)/\"\n\n# Takes a path to a file or directory and returns false if the file/directory is larger than $max_size_mb_for_rm.\nfunction size_check {\n  local size=\"$(du -sm \"$1\" 2>/dev/null | awk '{ print $1 }')\"\n  [[ \"${size}\" =~ ^[0-9]+$ ]] && [[ \"${size}\" -le ${max_size_mb_for_rm} ]]\n}\n\n# Ensure that the user can use sudo. (Use `sudo true` instead of `sudo -v` because that causes a\n# password prompt in Travis CI builds for some reason.)\nif ! sudo true; then\n  echo \"ERROR: This script must be run by a user with administrator (sudo) privileges.\" >&2\n  exit 1\nfi\n\n# Try to kill Background Music.app, in case it's running.\nkillall \"${bgmapp_process_name}\" &>/dev/null || true\n\n# TODO: Use\n#         mdfind kMDItemCFBundleIdentifier = \"com.bearisdriving.BGM.App\"\n#       to offer alternatives if Background Music.app isn't installed to /Applications. Or we could open it with\n#         open -b \"com.bearisdriving.BGM.App\" -- delete-yourself\n#       and have Background Music.app delete itself and close when it gets the \"delete-yourself\" argument. Though\n#       that wouldn't be backwards compatible.\n\n# Remove the files defined in file_paths\nfor path in \"${file_paths[@]}\"; do\n  if [ -e \"${path}\" ]; then\n    if size_check \"${path}\"; then\n      echo \"Moving \\\"${path}\\\" to the trash.\"\n      sudo mv -f \"${path}\" \"${trash_dir}\" &>/dev/null\n    else\n      echo \"Error: Refusing to delete \\\"${path}\\\" because it was much larger than expected.\" >&2\n    fi\n  fi\ndone\n\necho \"Removing Background Music launchd service.\"\nsudo launchctl list | grep \"${launchd_plist_label}\" >/dev/null && \\\n  (sudo launchctl bootout system \"${launchd_plist}\" &>/dev/null || \\\n    # Try older versions of the command in case the user has an old version of launchctl.\n    sudo launchctl unbootstrap system \"${launchd_plist}\" &>/dev/null || \\\n    sudo launchctl unload \"${launchd_plist}\" >/dev/null) || \\\n  echo \"  Service does not exist.\"\n\necho \"Removing Background Music launchd service configuration file.\"\nif [ -e \"${launchd_plist}\" ]; then\n  sudo mv -f \"${launchd_plist}\" \"${trash_dir}\"\nfi\n\n# Be paranoid about user_group_name because we really don't want to delete every user account.\nif ! [[ -z ${user_group_name} ]] && [[ \"${user_group_name}\" != \"\" ]]; then\n  echo \"Removing Background Music user.\"\n  dscl . -read /Users/\"${user_group_name}\" &>/dev/null && \\\n    sudo dscl . -delete /Users/\"${user_group_name}\" 1>/dev/null || \\\n    echo \"  User does not exist.\"\n\n  echo \"Removing Background Music group.\"\n  dscl . -read /Groups/\"${user_group_name}\" &>/dev/null && \\\n    sudo dscl . -delete /Groups/\"${user_group_name}\" 1>/dev/null || \\\n    echo \"  Group does not exist.\"\nelse\n  echo \"Warning: could not delete the Background Music user/group due to an internal error in $0.\" >&2\nfi\n\n# We're done removing files, so now actually move trash_dir into the trash. And if that fails, just delete it normally.\nosascript -e 'tell application id \"com.apple.finder\"\n                move the POSIX file \"'\"${trash_dir}\"'\" to trash\n              end tell' >/dev/null 2>&1 \\\n  || rm -rf \"${trash_dir}\" \\\n  || true\n\necho \"Restarting Core Audio.\"\n# Wait a little because moving files to the trash plays a short sound.\nsleep 2\n# The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently some of these commands \n# don't work with older versions of launchctl, so I figure there's no harm in trying a bunch of different ways until\n# one works.\n(sudo launchctl kickstart -k system/com.apple.audio.coreaudiod &>/dev/null || \\\n  sudo killall coreaudiod &>/dev/null || \\\n  sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n  sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n  sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n  sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n  (sudo launchctl unload \"${coreaudiod_plist}\" &>/dev/null && \\\n    sudo launchctl load \"${coreaudiod_plist}\" &>/dev/null))\n\necho \"...\"\nsleep 3\n\n# TODO: What if they only have one audio device?\necho \"\"\necho \"${bold}Done! Toggle your audio output device in the Sound section of System Settings to finish\" \\\n     \"uninstalling. (Or just restart your computer.)${normal}\"\n\n\n"
  },
  {
    "path": "BGMApp/BGMApp/main.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  main.m\n//  BGMApp\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// System Includes\n#import <Cocoa/Cocoa.h>\n\n\nint main(int argc, const char * argv[]) {\n    // Start BGMApp.\n    return NSApplicationMain(argc, argv);\n}\n\n"
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 47;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };\n\t\t19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMPlayThroughRTLogger.cpp\"; }; };\n\t\t19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };\n\t\t19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMVolumeChangeListener.cpp\"; }; };\n\t\t19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMMusic.m\"; }; };\n\t\t19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };\n\t\t19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };\n\t\t19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };\n\t\t19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };\n\t\t19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };\n\t\t19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };\n\t\t19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };\n\t\t19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };\n\t\t19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMDebugLogging.c\"; }; };\n\t\t19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */; };\n\t\t19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };\n\t\t19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };\n\t\t19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMStatusBarItem.mm\"; }; };\n\t\t1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAutoPauseMusicPrefs.mm\"; }; };\n\t\t1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMPreferencesMenu.mm\"; }; };\n\t\t1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAutoPauseMusic.mm\"; }; };\n\t\t1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CARingBuffer.cpp\"; }; };\n\t\t1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMPlayThrough.cpp\"; }; };\n\t\t1C1962F31BCABFC5008A4DF7 /* CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAHALAudioDevice.cpp\"; }; };\n\t\t1C1962F41BCABFC5008A4DF7 /* CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962ED1BCABFC5008A4DF7 /* CAHALAudioObject.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAHALAudioObject.cpp\"; }; };\n\t\t1C1962F51BCABFC5008A4DF7 /* CAHALAudioStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAHALAudioStream.cpp\"; }; };\n\t\t1C1962F61BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F11BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAHALAudioSystemObject.cpp\"; }; };\n\t\t1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CADebugMacros.cpp\"; }; };\n\t\t1C1962FD1BCAC0C3008A4DF7 /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CADebugPrintf.cpp\"; }; };\n\t\t1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CACFString.cpp\"; }; };\n\t\t1C1963031BCAC160008A4DF7 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1963021BCAC160008A4DF7 /* CoreAudio.framework */; };\n\t\t1C1963061BCAF468008A4DF7 /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAMutex.cpp\"; }; };\n\t\t1C1963091BCAF677008A4DF7 /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAHostTimeBase.cpp\"; }; };\n\t\t1C1AA4B01F9C673000BCFB22 /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-BGM_Utils.cpp\"; }; };\n\t\t1C1AA4B11F9DE3B700BCFB22 /* BGMAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-BGMAudioDevice.cpp\"; }; };\n\t\t1C1AA4B21F9DE3B700BCFB22 /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-BGMBackgroundMusicDevice.cpp\"; }; };\n\t\t1C1AA4B31F9DE40000BCFB22 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };\n\t\t1C227C0B1FA4C48200A95B6D /* BGMAppVolumes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */; };\n\t\t1C2336DA1BEAB6E7004C1C4E /* BGMMusicPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMMusicPlayer.m\"; }; };\n\t\t1C2336DF1BEAE10C004C1C4E /* BGMSpotify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMSpotify.m\"; }; };\n\t\t1C2FC3041EB4D6E700A76592 /* BGMApp.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */; };\n\t\t1C2FC3141EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C2FC3131EC706E000A76592 /* BGMAppDelegate+AppleScript.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAppDelegate+AppleScript.mm\"; }; };\n\t\t1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C2FC3131EC706E000A76592 /* BGMAppDelegate+AppleScript.mm */; };\n\t\t1C2FC31B1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMASOutputDevice.mm\"; }; };\n\t\t1C2FC31C1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */; };\n\t\t1C3D36721ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMDeviceControlsList.cpp\"; }; };\n\t\t1C3D36731ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */; };\n\t\t1C3D36741ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */; };\n\t\t1C3DB4891BE0885A00EC8160 /* BGMAppVolumes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAppVolumes.m\"; }; };\n\t\t1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C4699461BD5C0E400F78043 /* BGMiTunes.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMiTunes.m\"; }; };\n\t\t1C46994E1BD7694C00F78043 /* BGMDeviceControlSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMDeviceControlSync.cpp\"; }; };\n\t\t1C4D1A1D217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMPreferredOutputDevices.mm\"; }; };\n\t\t1C4D1A1E217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */; };\n\t\t1C50FF631EC9F4490031A6EA /* BGMAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */; };\n\t\t1C533C7A1EED28B700270802 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C791EED28B700270802 /* uninstall.sh */; };\n\t\t1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };\n\t\t1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; };\n\t\t1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; };\n\t\t1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */; };\n\t\t1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */; };\n\t\t1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */; };\n\t\t1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */; };\n\t\t1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */; };\n\t\t1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */; };\n\t\t1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C62FE5423D423D700B9B68E /* XCTest.framework */; };\n\t\t1C62FE5823D4278300B9B68E /* skip-ui-tests.py in Resources */ = {isa = PBXBuildFile; fileRef = 1C62FE5623D4278300B9B68E /* skip-ui-tests.py */; };\n\t\t1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */; };\n\t\t1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMSystemSoundsVolume.mm\"; }; };\n\t\t1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; };\n\t\t1C8034D520B0347A004BC50C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8034D420B0347A004BC50C /* Security.framework */; };\n\t\t1C80DED320A6718600045BBE /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAppWatcher.m\"; }; };\n\t\t1C80DED420A6718600045BBE /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; };\n\t\t1C8104AF22AD07E200B35517 /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; };\n\t\t1C8104B022AD082E00B35517 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; };\n\t\t1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMOutputVolumeMenuItem.mm\"; }; };\n\t\t1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };\n\t\t1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };\n\t\t1C86DA6A1F91EE3B000C8CCF /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CAPThread.cpp\"; }; };\n\t\t1C8B0C6A216205BF008C5679 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8B0C69216205BF008C5679 /* AVFoundation.framework */; };\n\t\t1C8B0C6B21645355008C5679 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8B0C69216205BF008C5679 /* AVFoundation.framework */; };\n\t\t1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMSwinsian.m\"; }; };\n\t\t1C8D830520423E1C00A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; };\n\t\t1C8D830620423E2400A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; };\n\t\t1C8D830B2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMGooglePlayMusicDesktopPlayer.m\"; }; };\n\t\t1C8D830C2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; };\n\t\t1C8D830E2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */ = {isa = PBXBuildFile; fileRef = 1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */; };\n\t\t1C8D830F2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */ = {isa = PBXBuildFile; fileRef = 1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */; };\n\t\t1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMGooglePlayMusicDesktopPlayerConnection.m\"; }; };\n\t\t1C9258482090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; };\n\t\t1C9258492090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; };\n\t\t1CACCF391F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMBackgroundMusicDevice.cpp\"; }; };\n\t\t1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };\n\t\t1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };\n\t\t1CB8B33D1BBA75EF000E2DD1 /* BGMAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAppDelegate.mm\"; }; };\n\t\t1CB8B33F1BBA75EF000E2DD1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33E1BBA75EF000E2DD1 /* main.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-main.m\"; }; };\n\t\t1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CACFArray.cpp\"; }; };\n\t\t1CC1DF821BE5068A00FB8FE4 /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CACFDictionary.cpp\"; }; };\n\t\t1CC1DF911BE5891300FB8FE4 /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CADebugger.cpp\"; }; };\n\t\t1CC1DF961BE8607700FB8FE4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CC1DF951BE8607700FB8FE4 /* Images.xcassets */; };\n\t\t1CC6593C1F91DEB400B0CCDC /* BGMTermination.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMTermination.mm\"; }; };\n\t\t1CC6593D1F91DEB400B0CCDC /* BGMTermination.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */; };\n\t\t1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */; };\n\t\t1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */; };\n\t\t1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */; };\n\t\t1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F611E584100008053E4 /* BGMAppUITests.mm */; };\n\t\t1CD1FD301BDDEAF2004F7E1B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };\n\t\t1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAppVolumesController.mm\"; }; };\n\t\t1CD410D51F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */; };\n\t\t1CD410D61F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */; };\n\t\t1CD989341ECFFC9E0014BBBF /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; };\n\t\t1CD989351ECFFC9E0014BBBF /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; };\n\t\t1CD989361ECFFC9E0014BBBF /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */; };\n\t\t1CD989371ECFFC9E0014BBBF /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */; };\n\t\t1CD989381ECFFC9E0014BBBF /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */; };\n\t\t1CD989391ECFFC9E0014BBBF /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */; };\n\t\t1CD9893A1ECFFC9E0014BBBF /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */; };\n\t\t1CD9893B1ECFFC9E0014BBBF /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */; };\n\t\t1CD9893C1ECFFC9E0014BBBF /* CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */; };\n\t\t1CD9893D1ECFFC9E0014BBBF /* CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962ED1BCABFC5008A4DF7 /* CAHALAudioObject.cpp */; };\n\t\t1CD9893E1ECFFC9E0014BBBF /* CAHALAudioStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */; };\n\t\t1CD9893F1ECFFC9E0014BBBF /* CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F11BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp */; };\n\t\t1CD989401ECFFCC50014BBBF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };\n\t\t1CD989411ECFFCD10014BBBF /* BGMAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */; };\n\t\t1CD989421ECFFCFC0014BBBF /* BGMAppVolumes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */; };\n\t\t1CD989431ECFFCFC0014BBBF /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; };\n\t\t1CD989441ECFFCFC0014BBBF /* BGMAutoPauseMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */; };\n\t\t1CD989451ECFFCFC0014BBBF /* BGMAutoPauseMusic.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */; };\n\t\t1CD989461ECFFCFC0014BBBF /* BGMMusicPlayers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9E81D852B350089613B /* BGMMusicPlayers.mm */; };\n\t\t1CD989471ECFFCFC0014BBBF /* BGMScriptingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9EA1D852B350089613B /* BGMScriptingBridge.m */; };\n\t\t1CD989481ECFFCFC0014BBBF /* BGMMusicPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */; };\n\t\t1CD989491ECFFCFC0014BBBF /* BGMDecibel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F7D48F1D2483B100821C4B /* BGMDecibel.m */; };\n\t\t1CD9894A1ECFFCFC0014BBBF /* BGMiTunes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C4699461BD5C0E400F78043 /* BGMiTunes.m */; };\n\t\t1CD9894B1ECFFCFC0014BBBF /* BGMSpotify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */; };\n\t\t1CD9894C1ECFFCFC0014BBBF /* BGMHermes.m in Sources */ = {isa = PBXBuildFile; fileRef = 279F48761DD6D73900768A85 /* BGMHermes.m */; };\n\t\t1CD9894D1ECFFCFC0014BBBF /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; };\n\t\t1CD9894E1ECFFCFC0014BBBF /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; };\n\t\t1CD9894F1ECFFCFC0014BBBF /* BGMPreferencesMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */; };\n\t\t1CD989501ECFFCFC0014BBBF /* BGMAboutPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D1D6BA1DD7226C0049E707 /* BGMAboutPanel.m */; };\n\t\t1CD989511ECFFCFC0014BBBF /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; };\n\t\t1CD989521ECFFCFC0014BBBF /* BGMOutputDeviceMenuSection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */; };\n\t\t1CD989531ECFFCFC0014BBBF /* BGMDeviceControlSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */; };\n\t\t1CD989541ECFFCFC0014BBBF /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; };\n\t\t1CD989551ECFFCFC0014BBBF /* BGMUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9F01D853FBB0089613B /* BGMUserDefaults.m */; };\n\t\t1CD989561ECFFCFC0014BBBF /* BGMXPCListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2795973A1C982E4E00A002FB /* BGMXPCListener.mm */; };\n\t\t1CD989571ECFFD250014BBBF /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */; };\n\t\t1CD989581ECFFD250014BBBF /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; };\n\t\t1CD989591ECFFD250014BBBF /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; };\n\t\t1CD9895A1ECFFD250014BBBF /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; };\n\t\t1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMDebugLoggingMenuItem.m\"; }; };\n\t\t1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };\n\t\t1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };\n\t\t1CE7064C1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMOutputDeviceMenuSection.mm\"; }; };\n\t\t1CED61691C3081C2002CAFCF /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 1CED61681C3081C2002CAFCF /* LICENSE */; };\n\t\t1CED616C1C316E1A002CAFCF /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAudioDeviceManager.mm\"; }; };\n\t\t1CF2D58F1F944773008B6E35 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1963021BCAC160008A4DF7 /* CoreAudio.framework */; };\n\t\t1CF2D5901F944789008B6E35 /* CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAHALAudioDevice.cpp\"; }; };\n\t\t1CF2D5911F944789008B6E35 /* CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962ED1BCABFC5008A4DF7 /* CAHALAudioObject.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAHALAudioObject.cpp\"; }; };\n\t\t1CF2D5921F944789008B6E35 /* CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F11BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAHALAudioSystemObject.cpp\"; }; };\n\t\t1CF2D5931F94479A008B6E35 /* CAHALAudioStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAHALAudioStream.cpp\"; }; };\n\t\t1CF2D5941F9447AE008B6E35 /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CACFArray.cpp\"; }; };\n\t\t1CF2D5951F9447AE008B6E35 /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CACFDictionary.cpp\"; }; };\n\t\t1CF2D5961F9447AE008B6E35 /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CACFNumber.cpp\"; }; };\n\t\t1CF2D5971F9447AE008B6E35 /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CACFString.cpp\"; }; };\n\t\t1CF2D5981F9447AE008B6E35 /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CADebugger.cpp\"; }; };\n\t\t1CF2D5991F9447AE008B6E35 /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CADebugMacros.cpp\"; }; };\n\t\t1CF2D59A1F9447AE008B6E35 /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CADebugPrintf.cpp\"; }; };\n\t\t1CF2D59B1F9447AE008B6E35 /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAHostTimeBase.cpp\"; }; };\n\t\t1CF2D59C1F9447AE008B6E35 /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAMutex.cpp\"; }; };\n\t\t1CF2D59D1F9447AE008B6E35 /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CAPThread.cpp\"; }; };\n\t\t1CF2D59E1F9447AE008B6E35 /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-CARingBuffer.cpp\"; }; };\n\t\t1CF5423C1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAudioDevice.cpp\"; }; };\n\t\t1CF5423D1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */; };\n\t\t270A84511E0044EF00F13C99 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 270A84501E0044EE00F13C99 /* ScriptingBridge.framework */; };\n\t\t271677BA1C6CBDFA0080B0A2 /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-CACFNumber.cpp\"; }; };\n\t\t27379B8A1C7C562D0084A24C /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMVLC.m\"; }; };\n\t\t273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMVOX.m\"; }; };\n\t\t2743C9EB1D852B360089613B /* BGMMusicPlayers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9E81D852B350089613B /* BGMMusicPlayers.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMMusicPlayers.mm\"; }; };\n\t\t2743C9EC1D852B360089613B /* BGMScriptingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9EA1D852B350089613B /* BGMScriptingBridge.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMScriptingBridge.m\"; }; };\n\t\t2743C9F11D853FBB0089613B /* BGMUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9F01D853FBB0089613B /* BGMUserDefaults.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMUserDefaults.m\"; }; };\n\t\t2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9E81D852B350089613B /* BGMMusicPlayers.mm */; };\n\t\t2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C4699461BD5C0E400F78043 /* BGMiTunes.m */; };\n\t\t2743CA031D86D41C0089613B /* BGMScriptingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9EA1D852B350089613B /* BGMScriptingBridge.m */; };\n\t\t2743CA041D86D41C0089613B /* BGMMusicPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */; };\n\t\t2743CA051D86D41C0089613B /* BGMDecibel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F7D48F1D2483B100821C4B /* BGMDecibel.m */; };\n\t\t2743CA061D86D41C0089613B /* BGMSpotify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */; };\n\t\t2743CA071D86D41C0089613B /* BGMVLC.m in Sources */ = {isa = PBXBuildFile; fileRef = 27379B891C7C562D0084A24C /* BGMVLC.m */; };\n\t\t2743CA081D86D41C0089613B /* BGMVOX.m in Sources */ = {isa = PBXBuildFile; fileRef = 273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */; };\n\t\t2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 2743C9F01D853FBB0089613B /* BGMUserDefaults.m */; };\n\t\t2743CA0A1D86D52D0089613B /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; };\n\t\t2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; };\n\t\t2743CA0D1D86D7FA0089613B /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */; };\n\t\t2743CA0E1D86D7FA0089613B /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */; };\n\t\t2743CA0F1D86D7FA0089613B /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */; };\n\t\t2743CA101D86D7FA0089613B /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */; };\n\t\t2743CA111D86D7FA0089613B /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */; };\n\t\t2743CA121D86D7FA0089613B /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */; };\n\t\t2743CA141D86D7FA0089613B /* CAHALAudioStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */; };\n\t\t2743CA161D86D7FA0089613B /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */; };\n\t\t2743CA171D86D7FA0089613B /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; };\n\t\t2743CA181D86D7FA0089613B /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; };\n\t\t2743CA191D86D7FA0089613B /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; };\n\t\t2743CA1D1D86DA9B0089613B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2743CA1C1D86DA9B0089613B /* Foundation.framework */; };\n\t\t2743CA211D86DE780089613B /* BGMDeviceControlSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */; };\n\t\t2743CA221D86DE960089613B /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1963021BCAC160008A4DF7 /* CoreAudio.framework */; };\n\t\t2743CA231D86DEA70089613B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };\n\t\t274827951E11052500B31D8D /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1CB8B3421BBA75EF000E2DD1 /* MainMenu.xib */; };\n\t\t277170161CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 277170151CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-BGMXPCListenerDelegate.m\"; }; };\n\t\t2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2795973A1C982E4E00A002FB /* BGMXPCListener.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMXPCListener.mm\"; }; };\n\t\t279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */ = {isa = PBXBuildFile; fileRef = 279F48761DD6D73900768A85 /* BGMHermes.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMHermes.m\"; }; };\n\t\t27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAutoPauseMenuItem.m\"; }; };\n\t\t27D1D6BB1DD7226C0049E707 /* BGMAboutPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D1D6BA1DD7226C0049E707 /* BGMAboutPanel.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMAboutPanel.m\"; }; };\n\t\t27D643C01C9FB99200737F6E /* BGMXPCHelperService.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27D643BA1C9FB84C00737F6E /* BGMXPCHelperService.mm */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-BGMXPCHelperService.mm\"; }; };\n\t\t27D643C11C9FB99200737F6E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D643BC1C9FB84C00737F6E /* main.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMXPCHelper-main.m\"; }; };\n\t\t27F7D4901D2483B100821C4B /* BGMDecibel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F7D48F1D2483B100821C4B /* BGMDecibel.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMDecibel.m\"; }; };\n\t\t27FB8C071DD75D0A0084DB9D /* BGMHermes.m in Sources */ = {isa = PBXBuildFile; fileRef = 279F48761DD6D73900768A85 /* BGMHermes.m */; };\n\t\t27FB8C2F1DE468320084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGM_Utils.cpp\"; }; };\n\t\t27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; };\n\t\t27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; };\n\t\t9E129A412602AE620005851B /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMApp-BGMASApplication.m\"; }; };\n\t\t9E542C7026057FBA0016C0B5 /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t1CCC4F591E584081008053E4 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 1CB8B32E1BBA75EF000E2DD1 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 1CB8B3351BBA75EF000E2DD1;\n\t\t\tremoteInfo = \"Background Music\";\n\t\t};\n\t\t278D71F31CABB88B00899CF9 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 1CB8B32E1BBA75EF000E2DD1 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 27379B8E1C7F57DA0084A24C;\n\t\t\tremoteInfo = BGMXPCHelper;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\t19FE70CF6C93F5007940CE91 /* BGMMusic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMMusic.h; path = \"Music Players/BGMMusic.h\"; sourceTree = \"<group>\"; };\n\t\t19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMVolumeChangeListener.cpp; sourceTree = \"<group>\"; };\n\t\t19FE71BCD79E7246F7345C16 /* BGMThreadSafetyAnalysis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMThreadSafetyAnalysis.h; sourceTree = \"<group>\"; };\n\t\t19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMPlayThroughRTLogger.h; sourceTree = \"<group>\"; };\n\t\t19FE73389459BF65748F531F /* BGMDebugLogging.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = BGMDebugLogging.c; path = PublicUtility/BGMDebugLogging.c; sourceTree = \"<group>\"; };\n\t\t19FE73822ADD50BA9120AB05 /* BGMMusic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMMusic.m; path = \"Music Players/BGMMusic.m\"; sourceTree = \"<group>\"; };\n\t\t19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughTests.mm; path = UnitTests/BGMPlayThroughTests.mm; sourceTree = \"<group>\"; };\n\t\t19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMStatusBarItem.mm; sourceTree = \"<group>\"; };\n\t\t19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMDebugLogging.h; path = PublicUtility/BGMDebugLogging.h; sourceTree = \"<group>\"; };\n\t\t19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMStatusBarItem.h; sourceTree = \"<group>\"; };\n\t\t19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMPlayThroughRTLogger.cpp; sourceTree = \"<group>\"; };\n\t\t19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMVolumeChangeListener.h; sourceTree = \"<group>\"; };\n\t\t1C09150723F010FB001EB0E1 /* set-version.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = \"set-version.sh\"; sourceTree = \"<group>\"; };\n\t\t1C0BD0A31BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAutoPauseMusicPrefs.h; path = Preferences/BGMAutoPauseMusicPrefs.h; sourceTree = \"<group>\"; };\n\t\t1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAutoPauseMusicPrefs.mm; path = Preferences/BGMAutoPauseMusicPrefs.mm; sourceTree = \"<group>\"; };\n\t\t1C0BD0A61BF1B029004F4CF5 /* BGMPreferencesMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMPreferencesMenu.h; path = Preferences/BGMPreferencesMenu.h; sourceTree = \"<group>\"; };\n\t\t1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPreferencesMenu.mm; path = Preferences/BGMPreferencesMenu.mm; sourceTree = \"<group>\"; };\n\t\t1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAutoPauseMusic.mm; sourceTree = \"<group>\"; };\n\t\t1C1465B91BCC49D1003AEFE6 /* BGMAutoPauseMusic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAutoPauseMusic.h; sourceTree = \"<group>\"; };\n\t\t1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CARingBuffer.cpp; path = PublicUtility/CARingBuffer.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962E31BC94E15008A4DF7 /* CARingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CARingBuffer.h; path = PublicUtility/CARingBuffer.h; sourceTree = \"<group>\"; };\n\t\t1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMPlayThrough.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMPlayThrough.h; sourceTree = \"<group>\"; };\n\t\t1C1962E81BC95301008A4DF7 /* CAAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAtomic.h; path = PublicUtility/CAAtomic.h; sourceTree = \"<group>\"; };\n\t\t1C1962E91BC95301008A4DF7 /* CAAutoDisposer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAutoDisposer.h; path = PublicUtility/CAAutoDisposer.h; sourceTree = \"<group>\"; };\n\t\t1C1962EA1BC95301008A4DF7 /* CABitOperations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CABitOperations.h; path = PublicUtility/CABitOperations.h; sourceTree = \"<group>\"; };\n\t\t1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHALAudioDevice.cpp; path = PublicUtility/CAHALAudioDevice.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962EC1BCABFC5008A4DF7 /* CAHALAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHALAudioDevice.h; path = PublicUtility/CAHALAudioDevice.h; sourceTree = \"<group>\"; };\n\t\t1C1962ED1BCABFC5008A4DF7 /* CAHALAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHALAudioObject.cpp; path = PublicUtility/CAHALAudioObject.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962EE1BCABFC5008A4DF7 /* CAHALAudioObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHALAudioObject.h; path = PublicUtility/CAHALAudioObject.h; sourceTree = \"<group>\"; };\n\t\t1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHALAudioStream.cpp; path = PublicUtility/CAHALAudioStream.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962F01BCABFC5008A4DF7 /* CAHALAudioStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHALAudioStream.h; path = PublicUtility/CAHALAudioStream.h; sourceTree = \"<group>\"; };\n\t\t1C1962F11BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHALAudioSystemObject.cpp; path = PublicUtility/CAHALAudioSystemObject.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962F21BCABFC5008A4DF7 /* CAHALAudioSystemObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHALAudioSystemObject.h; path = PublicUtility/CAHALAudioSystemObject.h; sourceTree = \"<group>\"; };\n\t\t1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugMacros.cpp; path = PublicUtility/CADebugMacros.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962F81BCAC061008A4DF7 /* CADebugMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugMacros.h; path = PublicUtility/CADebugMacros.h; sourceTree = \"<group>\"; };\n\t\t1C1962F91BCAC061008A4DF7 /* CAPropertyAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPropertyAddress.h; path = PublicUtility/CAPropertyAddress.h; sourceTree = \"<group>\"; };\n\t\t1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugPrintf.cpp; path = PublicUtility/CADebugPrintf.cpp; sourceTree = \"<group>\"; };\n\t\t1C1962FC1BCAC0C3008A4DF7 /* CADebugPrintf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugPrintf.h; path = PublicUtility/CADebugPrintf.h; sourceTree = \"<group>\"; };\n\t\t1C1962FE1BCAC0DB008A4DF7 /* CAException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CAException.h; path = PublicUtility/CAException.h; sourceTree = \"<group>\"; };\n\t\t1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFString.cpp; path = PublicUtility/CACFString.cpp; sourceTree = \"<group>\"; };\n\t\t1C1963001BCAC0F6008A4DF7 /* CACFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFString.h; path = PublicUtility/CACFString.h; sourceTree = \"<group>\"; };\n\t\t1C1963021BCAC160008A4DF7 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };\n\t\t1C1963041BCAF468008A4DF7 /* CAMutex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAMutex.cpp; path = PublicUtility/CAMutex.cpp; sourceTree = \"<group>\"; };\n\t\t1C1963051BCAF468008A4DF7 /* CAMutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAMutex.h; path = PublicUtility/CAMutex.h; sourceTree = \"<group>\"; };\n\t\t1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHostTimeBase.cpp; path = PublicUtility/CAHostTimeBase.cpp; sourceTree = \"<group>\"; };\n\t\t1C1963081BCAF677008A4DF7 /* CAHostTimeBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHostTimeBase.h; path = PublicUtility/CAHostTimeBase.h; sourceTree = \"<group>\"; };\n\t\t1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMMusicPlayer.m; path = \"Music Players/BGMMusicPlayer.m\"; sourceTree = \"<group>\"; };\n\t\t1C2336DB1BEAB73F004C1C4E /* BGMiTunes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMiTunes.h; path = \"Music Players/BGMiTunes.h\"; sourceTree = \"<group>\"; };\n\t\t1C2336DC1BEAB73F004C1C4E /* BGMMusicPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMMusicPlayer.h; path = \"Music Players/BGMMusicPlayer.h\"; sourceTree = \"<group>\"; };\n\t\t1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMSpotify.h; path = \"Music Players/BGMSpotify.h\"; sourceTree = \"<group>\"; };\n\t\t1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMSpotify.m; path = \"Music Players/BGMSpotify.m\"; sourceTree = \"<group>\"; };\n\t\t1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = BGMApp.sdef; path = Scripting/BGMApp.sdef; sourceTree = \"<group>\"; };\n\t\t1C2FC30D1EBC97DA00A76592 /* BGMApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMApp.h; path = BGMAppTests/UITests/BGMApp.h; sourceTree = SOURCE_ROOT; };\n\t\t1C2FC3121EC706E000A76592 /* BGMAppDelegate+AppleScript.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = \"BGMAppDelegate+AppleScript.h\"; path = \"Scripting/BGMAppDelegate+AppleScript.h\"; sourceTree = \"<group>\"; };\n\t\t1C2FC3131EC706E000A76592 /* BGMAppDelegate+AppleScript.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = \"BGMAppDelegate+AppleScript.mm\"; path = \"Scripting/BGMAppDelegate+AppleScript.mm\"; sourceTree = \"<group>\"; };\n\t\t1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMASOutputDevice.mm; path = Scripting/BGMASOutputDevice.mm; sourceTree = \"<group>\"; };\n\t\t1C2FC31D1EC723A100A76592 /* BGMASOutputDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMASOutputDevice.h; path = Scripting/BGMASOutputDevice.h; sourceTree = \"<group>\"; };\n\t\t1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMDeviceControlsList.cpp; sourceTree = \"<group>\"; };\n\t\t1C3D36711ED90E8600F98E66 /* BGMDeviceControlsList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlsList.h; sourceTree = \"<group>\"; };\n\t\t1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMAppVolumes.m; sourceTree = \"<group>\"; };\n\t\t1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumes.h; sourceTree = \"<group>\"; };\n\t\t1C43DABE22F582780004AF35 /* BGMApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BGMApp.entitlements; sourceTree = \"<group>\"; };\n\t\t1C4699461BD5C0E400F78043 /* BGMiTunes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMiTunes.m; path = \"Music Players/BGMiTunes.m\"; sourceTree = \"<group>\"; };\n\t\t1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMDeviceControlSync.cpp; sourceTree = \"<group>\"; };\n\t\t1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlSync.h; sourceTree = \"<group>\"; };\n\t\t1C4D1A1B217C7D6400A1ACD0 /* BGMPreferredOutputDevices.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMPreferredOutputDevices.h; sourceTree = \"<group>\"; };\n\t\t1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMPreferredOutputDevices.mm; sourceTree = \"<group>\"; };\n\t\t1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = \"<group>\"; };\n\t\t1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = \"_uninstall-non-interactive.sh\"; sourceTree = \"<group>\"; };\n\t\t1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObject.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE4623D3EB2D00B9B68E /* 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; };\n\t\t1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObjects.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE4823D3EB2D00B9B68E /* 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; };\n\t\t1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioDevice.h; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.h; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE4A23D3EB2E00B9B68E /* 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; };\n\t\t1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioDevice.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObject.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.h; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObjects.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.h; sourceTree = SOURCE_ROOT; };\n\t\t1C62FE5423D423D700B9B68E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };\n\t\t1C62FE5623D4278300B9B68E /* skip-ui-tests.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = \"skip-ui-tests.py\"; path = \"UITests/skip-ui-tests.py\"; sourceTree = \"<group>\"; };\n\t\t1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = \"BGMApp-Debug.entitlements\"; sourceTree = \"<group>\"; };\n\t\t1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughRTLoggerTests.mm; path = UnitTests/BGMPlayThroughRTLoggerTests.mm; sourceTree = \"<group>\"; };\n\t\t1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMSystemSoundsVolume.h; sourceTree = \"<group>\"; };\n\t\t1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMSystemSoundsVolume.mm; sourceTree = \"<group>\"; };\n\t\t1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = \"<group>\"; };\n\t\t1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = \"<group>\"; };\n\t\t1C8034D420B0347A004BC50C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };\n\t\t1C80DED120A6718600045BBE /* BGMAppWatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppWatcher.h; sourceTree = \"<group>\"; };\n\t\t1C80DED220A6718600045BBE /* BGMAppWatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BGMAppWatcher.m; sourceTree = \"<group>\"; };\n\t\t1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputVolumeMenuItem.h; sourceTree = \"<group>\"; };\n\t\t1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputVolumeMenuItem.mm; sourceTree = \"<group>\"; };\n\t\t1C8B0C69216205BF008C5679 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };\n\t\t1C8D8301204238DB00A838F2 /* Swinsian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Swinsian.h; path = \"Music Players/Swinsian.h\"; sourceTree = \"<group>\"; };\n\t\t1C8D8302204238DB00A838F2 /* BGMSwinsian.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMSwinsian.m; path = \"Music Players/BGMSwinsian.m\"; sourceTree = \"<group>\"; };\n\t\t1C8D8303204238DB00A838F2 /* BGMSwinsian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMSwinsian.h; path = \"Music Players/BGMSwinsian.h\"; sourceTree = \"<group>\"; };\n\t\t1C8D83092042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMGooglePlayMusicDesktopPlayer.h; path = \"Music Players/BGMGooglePlayMusicDesktopPlayer.h\"; sourceTree = \"<group>\"; };\n\t\t1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMGooglePlayMusicDesktopPlayer.m; path = \"Music Players/BGMGooglePlayMusicDesktopPlayer.m\"; sourceTree = \"<group>\"; };\n\t\t1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = GooglePlayMusicDesktopPlayer.js; path = \"Music Players/GooglePlayMusicDesktopPlayer.js\"; sourceTree = \"<group>\"; };\n\t\t1C9258452090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMGooglePlayMusicDesktopPlayerConnection.h; path = \"Music Players/BGMGooglePlayMusicDesktopPlayerConnection.h\"; sourceTree = \"<group>\"; };\n\t\t1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMGooglePlayMusicDesktopPlayerConnection.m; path = \"Music Players/BGMGooglePlayMusicDesktopPlayerConnection.m\"; sourceTree = \"<group>\"; };\n\t\t1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMBackgroundMusicDevice.cpp; sourceTree = \"<group>\"; };\n\t\t1CACCF381F3175AD007F86CA /* BGMBackgroundMusicDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMBackgroundMusicDevice.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3361BBA75EF000E2DD1 /* Background Music.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"Background Music.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t1CB8B33A1BBA75EF000E2DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t1CB8B33B1BBA75EF000E2DD1 /* BGMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppDelegate.h; sourceTree = \"<group>\"; };\n\t\t1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAppDelegate.mm; sourceTree = \"<group>\"; };\n\t\t1CB8B33E1BBA75EF000E2DD1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = \"<group>\"; };\n\t\t1CB8B3431BBA75EF000E2DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = \"<group>\"; };\n\t\t1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFArray.cpp; path = PublicUtility/CACFArray.cpp; sourceTree = \"<group>\"; };\n\t\t1CC1DF7E1BE5068A00FB8FE4 /* CACFArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFArray.h; path = PublicUtility/CACFArray.h; sourceTree = \"<group>\"; };\n\t\t1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFDictionary.cpp; path = PublicUtility/CACFDictionary.cpp; sourceTree = \"<group>\"; };\n\t\t1CC1DF801BE5068A00FB8FE4 /* CACFDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFDictionary.h; path = PublicUtility/CACFDictionary.h; sourceTree = \"<group>\"; };\n\t\t1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugger.cpp; path = PublicUtility/CADebugger.cpp; sourceTree = \"<group>\"; };\n\t\t1CC1DF901BE5891300FB8FE4 /* CADebugger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugger.h; path = PublicUtility/CADebugger.h; sourceTree = \"<group>\"; };\n\t\t1CC1DF951BE8607700FB8FE4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = \"<group>\"; };\n\t\t1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMTermination.mm; sourceTree = \"<group>\"; };\n\t\t1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMTermination.h; sourceTree = \"<group>\"; };\n\t\t1CCC4F3B1E58196C008053E4 /* BGMXPCHelperTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = \"BGMXPCHelperTests-Info.plist\"; path = \"BGMXPCHelperTests/BGMXPCHelperTests-Info.plist\"; sourceTree = SOURCE_ROOT; };\n\t\t1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMXPCHelperTests.m; path = BGMXPCHelperTests/BGMXPCHelperTests.m; sourceTree = SOURCE_ROOT; };\n\t\t1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = \"BGMAppUnitTests-Info.plist\"; path = \"UnitTests/BGMAppUnitTests-Info.plist\"; sourceTree = \"<group>\"; };\n\t\t1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMMusicPlayersUnitTests.mm; path = UnitTests/BGMMusicPlayersUnitTests.mm; sourceTree = \"<group>\"; };\n\t\t1CCC4F541E584081008053E4 /* BGMAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = \"BGMAppUITests-Info.plist\"; path = \"UITests/BGMAppUITests-Info.plist\"; sourceTree = \"<group>\"; };\n\t\t1CCC4F611E584100008053E4 /* BGMAppUITests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAppUITests.mm; path = BGMAppTests/UITests/BGMAppUITests.mm; sourceTree = SOURCE_ROOT; };\n\t\t1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };\n\t\t1CD410D21F9EDDAD0070A094 /* BGMAppVolumesController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumesController.h; sourceTree = \"<group>\"; };\n\t\t1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAppVolumesController.mm; sourceTree = \"<group>\"; };\n\t\t1CDE224022CBB95B0008E3AC /* Music.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Music.h; path = \"Music Players/Music.h\"; sourceTree = \"<group>\"; };\n\t\t1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMDebugLoggingMenuItem.h; sourceTree = \"<group>\"; };\n\t\t1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BGMDebugLoggingMenuItem.m; sourceTree = \"<group>\"; };\n\t\t1CE7064A1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputDeviceMenuSection.h; sourceTree = \"<group>\"; };\n\t\t1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputDeviceMenuSection.mm; sourceTree = \"<group>\"; };\n\t\t1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = \"<group>\"; };\n\t\t1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = \"<group>\"; };\n\t\t1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = \"<group>\"; };\n\t\t1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMAudioDevice.cpp; sourceTree = \"<group>\"; };\n\t\t1CF5423B1EAAEE4300445AD8 /* BGMAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDevice.h; sourceTree = \"<group>\"; };\n\t\t1CF69BA41BCFF59C009B5D1F /* BGMApp.profdata */ = {isa = PBXFileReference; lastKnownFileType = file; path = BGMApp.profdata; sourceTree = \"<group>\"; };\n\t\t270A84501E0044EE00F13C99 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; };\n\t\t271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFNumber.cpp; path = PublicUtility/CACFNumber.cpp; sourceTree = \"<group>\"; };\n\t\t271677B91C6CBDFA0080B0A2 /* CACFNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFNumber.h; path = PublicUtility/CACFNumber.h; sourceTree = \"<group>\"; };\n\t\t27379B851C7C54870084A24C /* iTunes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iTunes.h; path = \"Music Players/iTunes.h\"; sourceTree = \"<group>\"; };\n\t\t27379B861C7C54870084A24C /* Spotify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Spotify.h; path = \"Music Players/Spotify.h\"; sourceTree = \"<group>\"; };\n\t\t27379B871C7C552A0084A24C /* VLC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLC.h; path = \"Music Players/VLC.h\"; sourceTree = \"<group>\"; };\n\t\t27379B881C7C562D0084A24C /* BGMVLC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMVLC.h; path = \"Music Players/BGMVLC.h\"; sourceTree = \"<group>\"; };\n\t\t27379B891C7C562D0084A24C /* BGMVLC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMVLC.m; path = \"Music Players/BGMVLC.m\"; sourceTree = \"<group>\"; };\n\t\t27379B8F1C7F57DA0084A24C /* BGMXPCHelper.xpc */ = {isa = PBXFileReference; explicitFileType = \"wrapper.xpc-service\"; includeInIndex = 0; path = BGMXPCHelper.xpc; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t273F10DC1CC3CF9C00C1C6DA /* VOX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VOX.h; path = \"Music Players/VOX.h\"; sourceTree = \"<group>\"; };\n\t\t273F10DD1CC3D0B900C1C6DA /* BGMVOX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMVOX.h; path = \"Music Players/BGMVOX.h\"; sourceTree = \"<group>\"; };\n\t\t273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMVOX.m; path = \"Music Players/BGMVOX.m\"; sourceTree = \"<group>\"; };\n\t\t2743C9E71D852B350089613B /* BGMMusicPlayers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMMusicPlayers.h; path = \"Music Players/BGMMusicPlayers.h\"; sourceTree = \"<group>\"; };\n\t\t2743C9E81D852B350089613B /* BGMMusicPlayers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMMusicPlayers.mm; path = \"Music Players/BGMMusicPlayers.mm\"; sourceTree = \"<group>\"; };\n\t\t2743C9E91D852B350089613B /* BGMScriptingBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMScriptingBridge.h; path = \"Music Players/BGMScriptingBridge.h\"; sourceTree = \"<group>\"; };\n\t\t2743C9EA1D852B350089613B /* BGMScriptingBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMScriptingBridge.m; path = \"Music Players/BGMScriptingBridge.m\"; sourceTree = \"<group>\"; };\n\t\t2743C9ED1D8538700089613B /* BGMUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMUserDefaults.h; sourceTree = \"<group>\"; };\n\t\t2743C9F01D853FBB0089613B /* BGMUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMUserDefaults.m; sourceTree = \"<group>\"; };\n\t\t2743C9F61D86CFF90089613B /* BGMAppUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMAppUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t2743CA1C1D86DA9B0089613B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };\n\t\t275343BF1DFD01BC00DF3858 /* SystemPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemPreferences.h; sourceTree = \"<group>\"; };\n\t\t2769728B1CAFCEE8007A2F7C /* post_install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = post_install.sh; path = BGMXPCHelper/post_install.sh; sourceTree = SOURCE_ROOT; };\n\t\t2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */ = {isa = PBXFileReference; explicitFileType = text.xml; fileEncoding = 4; name = com.bearisdriving.BGM.XPCHelper.plist.template; path = BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template; sourceTree = SOURCE_ROOT; };\n\t\t276972901CB16008007A2F7C /* safe_install_dir.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = safe_install_dir.sh; path = BGMXPCHelper/safe_install_dir.sh; sourceTree = SOURCE_ROOT; };\n\t\t2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Utils.h; path = ../SharedSource/BGM_Utils.h; sourceTree = \"<group>\"; };\n\t\t277170141CA24D7C00AB34B4 /* BGMXPCListenerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCListenerDelegate.h; path = BGMXPCHelper/BGMXPCListenerDelegate.h; sourceTree = SOURCE_ROOT; };\n\t\t277170151CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMXPCListenerDelegate.m; path = BGMXPCHelper/BGMXPCListenerDelegate.m; sourceTree = SOURCE_ROOT; };\n\t\t278D71F11CABB6FF00899CF9 /* BGMXPCHelperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMXPCHelperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t2795970D1C91589B00A002FB /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };\n\t\t2795973A1C982E4E00A002FB /* BGMXPCListener.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMXPCListener.mm; sourceTree = \"<group>\"; };\n\t\t2795973C1C982E8C00A002FB /* BGMXPCListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMXPCListener.h; sourceTree = \"<group>\"; };\n\t\t279F48751DD6D73900768A85 /* BGMHermes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMHermes.h; path = \"Music Players/BGMHermes.h\"; sourceTree = \"<group>\"; };\n\t\t279F48761DD6D73900768A85 /* BGMHermes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMHermes.m; path = \"Music Players/BGMHermes.m\"; sourceTree = \"<group>\"; };\n\t\t279F48781DD6D94000768A85 /* Hermes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Hermes.h; path = \"Music Players/Hermes.h\"; sourceTree = \"<group>\"; };\n\t\t27C457E41CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAutoPauseMenuItem.h; sourceTree = \"<group>\"; };\n\t\t27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMAutoPauseMenuItem.m; sourceTree = \"<group>\"; };\n\t\t27D1D6B91DD7226C0049E707 /* BGMAboutPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAboutPanel.h; path = Preferences/BGMAboutPanel.h; sourceTree = \"<group>\"; };\n\t\t27D1D6BA1DD7226C0049E707 /* BGMAboutPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMAboutPanel.m; path = Preferences/BGMAboutPanel.m; sourceTree = \"<group>\"; };\n\t\t27D643B41C9FABBD00737F6E /* BGM_Types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Types.h; path = ../SharedSource/BGM_Types.h; sourceTree = \"<group>\"; };\n\t\t27D643B51C9FABBD00737F6E /* BGMXPCProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCProtocols.h; path = ../SharedSource/BGMXPCProtocols.h; sourceTree = \"<group>\"; };\n\t\t27D643B91C9FB84C00737F6E /* BGMXPCHelperService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCHelperService.h; path = BGMXPCHelper/BGMXPCHelperService.h; sourceTree = SOURCE_ROOT; };\n\t\t27D643BA1C9FB84C00737F6E /* BGMXPCHelperService.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMXPCHelperService.mm; path = BGMXPCHelper/BGMXPCHelperService.mm; sourceTree = SOURCE_ROOT; };\n\t\t27D643BB1C9FB84C00737F6E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BGMXPCHelper/Info.plist; sourceTree = SOURCE_ROOT; };\n\t\t27D643BC1C9FB84C00737F6E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = BGMXPCHelper/main.m; sourceTree = SOURCE_ROOT; };\n\t\t27D643C41C9FBE5600737F6E /* BGM_TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_TestUtils.h; path = ../SharedSource/BGM_TestUtils.h; sourceTree = \"<group>\"; };\n\t\t27F7D48E1D2483B100821C4B /* BGMDecibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMDecibel.h; path = \"Music Players/BGMDecibel.h\"; sourceTree = \"<group>\"; };\n\t\t27F7D48F1D2483B100821C4B /* BGMDecibel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMDecibel.m; path = \"Music Players/BGMDecibel.m\"; sourceTree = \"<group>\"; };\n\t\t27F7D4911D2484A300821C4B /* Decibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Decibel.h; path = \"Music Players/Decibel.h\"; sourceTree = \"<group>\"; };\n\t\t27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BGM_Utils.cpp; path = ../SharedSource/BGM_Utils.cpp; sourceTree = \"<group>\"; };\n\t\t9E129A3F2602AE620005851B /* BGMASApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMASApplication.h; path = Scripting/BGMASApplication.h; sourceTree = \"<group>\"; };\n\t\t9E129A402602AE620005851B /* BGMASApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMASApplication.m; path = Scripting/BGMASApplication.m; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t1CB8B3331BBA75EF000E2DD1 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C8B0C6A216205BF008C5679 /* AVFoundation.framework in Frameworks */,\n\t\t\t\t1C8034D520B0347A004BC50C /* Security.framework in Frameworks */,\n\t\t\t\t270A84511E0044EF00F13C99 /* ScriptingBridge.framework in Frameworks */,\n\t\t\t\t1CD1FD301BDDEAF2004F7E1B /* AudioToolbox.framework in Frameworks */,\n\t\t\t\t1C1963031BCAC160008A4DF7 /* CoreAudio.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CCC4F511E584081008053E4 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */,\n\t\t\t\t1C8B0C6B21645355008C5679 /* AVFoundation.framework in Frameworks */,\n\t\t\t\t1CD989401ECFFCC50014BBBF /* AudioToolbox.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t27379B8C1C7F57DA0084A24C /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C1AA4B31F9DE40000BCFB22 /* AudioToolbox.framework in Frameworks */,\n\t\t\t\t1CF2D58F1F944773008B6E35 /* CoreAudio.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2743C9F31D86CFF90089613B /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2743CA231D86DEA70089613B /* AudioToolbox.framework in Frameworks */,\n\t\t\t\t2743CA221D86DE960089613B /* CoreAudio.framework in Frameworks */,\n\t\t\t\t2743CA1D1D86DA9B0089613B /* Foundation.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t278D71EB1CABB6FF00899CF9 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t1C09150623F010FB001EB0E1 /* Scripts */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C09150723F010FB001EB0E1 /* set-version.sh */,\n\t\t\t);\n\t\t\tname = Scripts;\n\t\t\tpath = ../SharedSource/Scripts;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C0BD0A21BF1A827004F4CF5 /* Preferences Menu */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C0BD0A61BF1B029004F4CF5 /* BGMPreferencesMenu.h */,\n\t\t\t\t1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */,\n\t\t\t\t27D1D6B91DD7226C0049E707 /* BGMAboutPanel.h */,\n\t\t\t\t27D1D6BA1DD7226C0049E707 /* BGMAboutPanel.m */,\n\t\t\t\t1C0BD0A31BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.h */,\n\t\t\t\t1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */,\n\t\t\t);\n\t\t\tname = \"Preferences Menu\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C1962E11BC94DDF008A4DF7 /* PublicUtility */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C1962E81BC95301008A4DF7 /* CAAtomic.h */,\n\t\t\t\t1C1962E91BC95301008A4DF7 /* CAAutoDisposer.h */,\n\t\t\t\t1C1962EA1BC95301008A4DF7 /* CABitOperations.h */,\n\t\t\t\t1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */,\n\t\t\t\t1CC1DF7E1BE5068A00FB8FE4 /* CACFArray.h */,\n\t\t\t\t1CC1DF7F1BE5068A00FB8FE4 /* CACFDictionary.cpp */,\n\t\t\t\t1CC1DF801BE5068A00FB8FE4 /* CACFDictionary.h */,\n\t\t\t\t271677B81C6CBDFA0080B0A2 /* CACFNumber.cpp */,\n\t\t\t\t271677B91C6CBDFA0080B0A2 /* CACFNumber.h */,\n\t\t\t\t1C1962FF1BCAC0F6008A4DF7 /* CACFString.cpp */,\n\t\t\t\t1C1963001BCAC0F6008A4DF7 /* CACFString.h */,\n\t\t\t\t1CC1DF8F1BE5891300FB8FE4 /* CADebugger.cpp */,\n\t\t\t\t1CC1DF901BE5891300FB8FE4 /* CADebugger.h */,\n\t\t\t\t1C1962F71BCAC061008A4DF7 /* CADebugMacros.cpp */,\n\t\t\t\t1C1962F81BCAC061008A4DF7 /* CADebugMacros.h */,\n\t\t\t\t1C1962FB1BCAC0C3008A4DF7 /* CADebugPrintf.cpp */,\n\t\t\t\t1C1962FC1BCAC0C3008A4DF7 /* CADebugPrintf.h */,\n\t\t\t\t1C1962FE1BCAC0DB008A4DF7 /* CAException.h */,\n\t\t\t\t1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */,\n\t\t\t\t1C1962EC1BCABFC5008A4DF7 /* CAHALAudioDevice.h */,\n\t\t\t\t1C1962ED1BCABFC5008A4DF7 /* CAHALAudioObject.cpp */,\n\t\t\t\t1C1962EE1BCABFC5008A4DF7 /* CAHALAudioObject.h */,\n\t\t\t\t1C1962EF1BCABFC5008A4DF7 /* CAHALAudioStream.cpp */,\n\t\t\t\t1C1962F01BCABFC5008A4DF7 /* CAHALAudioStream.h */,\n\t\t\t\t1C1962F11BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp */,\n\t\t\t\t1C1962F21BCABFC5008A4DF7 /* CAHALAudioSystemObject.h */,\n\t\t\t\t1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */,\n\t\t\t\t1C1963081BCAF677008A4DF7 /* CAHostTimeBase.h */,\n\t\t\t\t1C1963041BCAF468008A4DF7 /* CAMutex.cpp */,\n\t\t\t\t1C1963051BCAF468008A4DF7 /* CAMutex.h */,\n\t\t\t\t1C1962F91BCAC061008A4DF7 /* CAPropertyAddress.h */,\n\t\t\t\t1C8034C21BDAFD5700668E00 /* CAPThread.cpp */,\n\t\t\t\t1C8034C31BDAFD5700668E00 /* CAPThread.h */,\n\t\t\t\t1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */,\n\t\t\t\t1C1962E31BC94E15008A4DF7 /* CARingBuffer.h */,\n\t\t\t\t19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */,\n\t\t\t\t19FE73389459BF65748F531F /* BGMDebugLogging.c */,\n\t\t\t\t19FE71BCD79E7246F7345C16 /* BGMThreadSafetyAnalysis.h */,\n\t\t\t);\n\t\t\tname = PublicUtility;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C2FC3161EC7078F00A76592 /* Scripting */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C2FC3121EC706E000A76592 /* BGMAppDelegate+AppleScript.h */,\n\t\t\t\t1C2FC3131EC706E000A76592 /* BGMAppDelegate+AppleScript.mm */,\n\t\t\t\t1C2FC31D1EC723A100A76592 /* BGMASOutputDevice.h */,\n\t\t\t\t1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */,\n\t\t\t\t1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */,\n\t\t\t\t9E129A3F2602AE620005851B /* BGMASApplication.h */,\n\t\t\t\t9E129A402602AE620005851B /* BGMASApplication.m */,\n\t\t\t);\n\t\t\tname = Scripting;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C4699401BD5BA1700F78043 /* SharedSource */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27D643B41C9FABBD00737F6E /* BGM_Types.h */,\n\t\t\t\t1C09150623F010FB001EB0E1 /* Scripts */,\n\t\t\t\t2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */,\n\t\t\t\t27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */,\n\t\t\t\t27D643C41C9FBE5600737F6E /* BGM_TestUtils.h */,\n\t\t\t\t27D643B51C9FABBD00737F6E /* BGMXPCProtocols.h */,\n\t\t\t);\n\t\t\tname = SharedSource;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C4699451BD5BF2E00F78043 /* Music Players */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2743C9E71D852B350089613B /* BGMMusicPlayers.h */,\n\t\t\t\t2743C9E81D852B350089613B /* BGMMusicPlayers.mm */,\n\t\t\t\t2743C9E91D852B350089613B /* BGMScriptingBridge.h */,\n\t\t\t\t2743C9EA1D852B350089613B /* BGMScriptingBridge.m */,\n\t\t\t\t1C2336DC1BEAB73F004C1C4E /* BGMMusicPlayer.h */,\n\t\t\t\t1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */,\n\t\t\t\t27F7D48E1D2483B100821C4B /* BGMDecibel.h */,\n\t\t\t\t27F7D48F1D2483B100821C4B /* BGMDecibel.m */,\n\t\t\t\t1C8D83092042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.h */,\n\t\t\t\t1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */,\n\t\t\t\t1C9258452090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.h */,\n\t\t\t\t1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */,\n\t\t\t\t1C2336DB1BEAB73F004C1C4E /* BGMiTunes.h */,\n\t\t\t\t1C4699461BD5C0E400F78043 /* BGMiTunes.m */,\n\t\t\t\t19FE70CF6C93F5007940CE91 /* BGMMusic.h */,\n\t\t\t\t19FE73822ADD50BA9120AB05 /* BGMMusic.m */,\n\t\t\t\t1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */,\n\t\t\t\t1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */,\n\t\t\t\t279F48751DD6D73900768A85 /* BGMHermes.h */,\n\t\t\t\t279F48761DD6D73900768A85 /* BGMHermes.m */,\n\t\t\t\t1C8D8303204238DB00A838F2 /* BGMSwinsian.h */,\n\t\t\t\t1C8D8302204238DB00A838F2 /* BGMSwinsian.m */,\n\t\t\t\t27379B881C7C562D0084A24C /* BGMVLC.h */,\n\t\t\t\t27379B891C7C562D0084A24C /* BGMVLC.m */,\n\t\t\t\t273F10DD1CC3D0B900C1C6DA /* BGMVOX.h */,\n\t\t\t\t273F10DE1CC3D0B900C1C6DA /* BGMVOX.m */,\n\t\t\t\t27379B841C7C53BE0084A24C /* Supporting Files */,\n\t\t\t);\n\t\t\tname = \"Music Players\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C62FE4423D3EAC500B9B68E /* Mocks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */,\n\t\t\t\t1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */,\n\t\t\t\t1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */,\n\t\t\t\t1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */,\n\t\t\t\t1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */,\n\t\t\t\t1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */,\n\t\t\t\t1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */,\n\t\t\t\t1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */,\n\t\t\t\t1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */,\n\t\t\t);\n\t\t\tpath = Mocks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B32D1BBA75EF000E2DD1 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B3381BBA75EF000E2DD1 /* BGMApp */,\n\t\t\t\t1CB8B34C1BBA75F0000E2DD1 /* BGMApp Tests */,\n\t\t\t\t27379B901C7F57DB0084A24C /* BGMXPCHelper */,\n\t\t\t\t1CCC4F3A1E581691008053E4 /* BGMXPCHelper Tests */,\n\t\t\t\t1C4699401BD5BA1700F78043 /* SharedSource */,\n\t\t\t\t1C1962E11BC94DDF008A4DF7 /* PublicUtility */,\n\t\t\t\t1CB8B3371BBA75EF000E2DD1 /* Products */,\n\t\t\t\t1CF69BA31BCFF59C009B5D1F /* OptimizationProfiles */,\n\t\t\t\t2743CA1B1D86DA9B0089613B /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3371BBA75EF000E2DD1 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B3361BBA75EF000E2DD1 /* Background Music.app */,\n\t\t\t\t27379B8F1C7F57DA0084A24C /* BGMXPCHelper.xpc */,\n\t\t\t\t278D71F11CABB6FF00899CF9 /* BGMXPCHelperTests.xctest */,\n\t\t\t\t2743C9F61D86CFF90089613B /* BGMAppUnitTests.xctest */,\n\t\t\t\t1CCC4F541E584081008053E4 /* BGMAppUITests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3381BBA75EF000E2DD1 /* BGMApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B33B1BBA75EF000E2DD1 /* BGMAppDelegate.h */,\n\t\t\t\t1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */,\n\t\t\t\t1C80DED120A6718600045BBE /* BGMAppWatcher.h */,\n\t\t\t\t1C80DED220A6718600045BBE /* BGMAppWatcher.m */,\n\t\t\t\t1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */,\n\t\t\t\t1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */,\n\t\t\t\t1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */,\n\t\t\t\t1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */,\n\t\t\t\t1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */,\n\t\t\t\t1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */,\n\t\t\t\t1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */,\n\t\t\t\t1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */,\n\t\t\t\t1CD410D21F9EDDAD0070A094 /* BGMAppVolumesController.h */,\n\t\t\t\t1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */,\n\t\t\t\t1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */,\n\t\t\t\t1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */,\n\t\t\t\t1C4D1A1B217C7D6400A1ACD0 /* BGMPreferredOutputDevices.h */,\n\t\t\t\t1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */,\n\t\t\t\t1CF5423B1EAAEE4300445AD8 /* BGMAudioDevice.h */,\n\t\t\t\t1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */,\n\t\t\t\t1CACCF381F3175AD007F86CA /* BGMBackgroundMusicDevice.h */,\n\t\t\t\t1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */,\n\t\t\t\t27C457E41CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.h */,\n\t\t\t\t27C457E51CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m */,\n\t\t\t\t1C1465B91BCC49D1003AEFE6 /* BGMAutoPauseMusic.h */,\n\t\t\t\t1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */,\n\t\t\t\t1C4699451BD5BF2E00F78043 /* Music Players */,\n\t\t\t\t1C0BD0A21BF1A827004F4CF5 /* Preferences Menu */,\n\t\t\t\t1CE7064A1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.h */,\n\t\t\t\t1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */,\n\t\t\t\t1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */,\n\t\t\t\t1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */,\n\t\t\t\t1C3D36711ED90E8600F98E66 /* BGMDeviceControlsList.h */,\n\t\t\t\t1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */,\n\t\t\t\t1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,\n\t\t\t\t1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,\n\t\t\t\t19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */,\n\t\t\t\t19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */,\n\t\t\t\t19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */,\n\t\t\t\t19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */,\n\t\t\t\t1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,\n\t\t\t\t1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */,\n\t\t\t\t2743C9ED1D8538700089613B /* BGMUserDefaults.h */,\n\t\t\t\t2743C9F01D853FBB0089613B /* BGMUserDefaults.m */,\n\t\t\t\t19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */,\n\t\t\t\t19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */,\n\t\t\t\t2795973C1C982E8C00A002FB /* BGMXPCListener.h */,\n\t\t\t\t2795973A1C982E4E00A002FB /* BGMXPCListener.mm */,\n\t\t\t\t1C2FC3161EC7078F00A76592 /* Scripting */,\n\t\t\t\t1CB8B3421BBA75EF000E2DD1 /* MainMenu.xib */,\n\t\t\t\t1CB8B3391BBA75EF000E2DD1 /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = BGMApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3391BBA75EF000E2DD1 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C43DABE22F582780004AF35 /* BGMApp.entitlements */,\n\t\t\t\t1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */,\n\t\t\t\t275343BF1DFD01BC00DF3858 /* SystemPreferences.h */,\n\t\t\t\t1CED61681C3081C2002CAFCF /* LICENSE */,\n\t\t\t\t1CC1DF951BE8607700FB8FE4 /* Images.xcassets */,\n\t\t\t\t1CB8B33A1BBA75EF000E2DD1 /* Info.plist */,\n\t\t\t\t1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */,\n\t\t\t\t1C533C791EED28B700270802 /* uninstall.sh */,\n\t\t\t\t1CB8B33E1BBA75EF000E2DD1 /* main.m */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B34C1BBA75F0000E2DD1 /* BGMApp Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CCC4F551E584081008053E4 /* UI Tests */,\n\t\t\t\t1CCC4F4F1E581C52008053E4 /* Unit Tests */,\n\t\t\t\t1CCC4F481E581BAA008053E4 /* Supporting Files */,\n\t\t\t);\n\t\t\tname = \"BGMApp Tests\";\n\t\t\tpath = BGMAppTests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CCC4F3A1E581691008053E4 /* BGMXPCHelper Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */,\n\t\t\t\t1CCC4F3F1E58198C008053E4 /* Supporting Files */,\n\t\t\t);\n\t\t\tname = \"BGMXPCHelper Tests\";\n\t\t\tpath = BGMService;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CCC4F3F1E58198C008053E4 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CCC4F3B1E58196C008053E4 /* BGMXPCHelperTests-Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CCC4F481E581BAA008053E4 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */,\n\t\t\t\t1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */,\n\t\t\t\t1C62FE5623D4278300B9B68E /* skip-ui-tests.py */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CCC4F4F1E581C52008053E4 /* Unit Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */,\n\t\t\t\t19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */,\n\t\t\t\t1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */,\n\t\t\t\t1C62FE4423D3EAC500B9B68E /* Mocks */,\n\t\t\t);\n\t\t\tname = \"Unit Tests\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CCC4F551E584081008053E4 /* UI Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C2FC30D1EBC97DA00A76592 /* BGMApp.h */,\n\t\t\t\t1CCC4F611E584100008053E4 /* BGMAppUITests.mm */,\n\t\t\t);\n\t\t\tname = \"UI Tests\";\n\t\t\tpath = ../BGMAppUITests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CF69BA31BCFF59C009B5D1F /* OptimizationProfiles */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CF69BA41BCFF59C009B5D1F /* BGMApp.profdata */,\n\t\t\t);\n\t\t\tpath = OptimizationProfiles;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27379B841C7C53BE0084A24C /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27F7D4911D2484A300821C4B /* Decibel.h */,\n\t\t\t\t279F48781DD6D94000768A85 /* Hermes.h */,\n\t\t\t\t27379B851C7C54870084A24C /* iTunes.h */,\n\t\t\t\t1CDE224022CBB95B0008E3AC /* Music.h */,\n\t\t\t\t1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */,\n\t\t\t\t27379B861C7C54870084A24C /* Spotify.h */,\n\t\t\t\t1C8D8301204238DB00A838F2 /* Swinsian.h */,\n\t\t\t\t27379B871C7C552A0084A24C /* VLC.h */,\n\t\t\t\t273F10DC1CC3CF9C00C1C6DA /* VOX.h */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27379B901C7F57DB0084A24C /* BGMXPCHelper */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27D643B91C9FB84C00737F6E /* BGMXPCHelperService.h */,\n\t\t\t\t27D643BA1C9FB84C00737F6E /* BGMXPCHelperService.mm */,\n\t\t\t\t277170141CA24D7C00AB34B4 /* BGMXPCListenerDelegate.h */,\n\t\t\t\t277170151CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m */,\n\t\t\t\t27D643BC1C9FB84C00737F6E /* main.m */,\n\t\t\t\t2769728A1CAFCEA9007A2F7C /* Supporting Files */,\n\t\t\t);\n\t\t\tname = BGMXPCHelper;\n\t\t\tpath = BGMService;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2743CA1B1D86DA9B0089613B /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C62FE5423D423D700B9B68E /* XCTest.framework */,\n\t\t\t\t1C8034D420B0347A004BC50C /* Security.framework */,\n\t\t\t\t1C8B0C69216205BF008C5679 /* AVFoundation.framework */,\n\t\t\t\t270A84501E0044EE00F13C99 /* ScriptingBridge.framework */,\n\t\t\t\t1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */,\n\t\t\t\t1C1963021BCAC160008A4DF7 /* CoreAudio.framework */,\n\t\t\t\t2795970D1C91589B00A002FB /* CoreFoundation.framework */,\n\t\t\t\t2743CA1C1D86DA9B0089613B /* Foundation.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2769728A1CAFCEA9007A2F7C /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */,\n\t\t\t\t2769728B1CAFCEE8007A2F7C /* post_install.sh */,\n\t\t\t\t276972901CB16008007A2F7C /* safe_install_dir.sh */,\n\t\t\t\t27D643BB1C9FB84C00737F6E /* Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t1CB8B3351BBA75EF000E2DD1 /* Background Music */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 1CB8B3531BBA75F0000E2DD1 /* Build configuration list for PBXNativeTarget \"Background Music\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t1CB8B3321BBA75EF000E2DD1 /* Sources */,\n\t\t\t\t1CB8B3331BBA75EF000E2DD1 /* Frameworks */,\n\t\t\t\t1CB8B3341BBA75EF000E2DD1 /* Resources */,\n\t\t\t\t1CD440581E593DDD0064E0BC /* Run Script - set-version.sh */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = \"Background Music\";\n\t\t\tproductName = BGMApp;\n\t\t\tproductReference = 1CB8B3361BBA75EF000E2DD1 /* Background Music.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t1CCC4F531E584081008053E4 /* BGMAppUITests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 1CCC4F5B1E584081008053E4 /* Build configuration list for PBXNativeTarget \"BGMAppUITests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t1CCC4F501E584081008053E4 /* Sources */,\n\t\t\t\t1CCC4F511E584081008053E4 /* Frameworks */,\n\t\t\t\t1CCC4F521E584081008053E4 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t1CCC4F5A1E584081008053E4 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = BGMAppUITests;\n\t\t\tproductName = BGMAppUITests;\n\t\t\tproductReference = 1CCC4F541E584081008053E4 /* BGMAppUITests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.ui-testing\";\n\t\t};\n\t\t27379B8E1C7F57DA0084A24C /* BGMXPCHelper */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 27379B981C7F57DB0084A24C /* Build configuration list for PBXNativeTarget \"BGMXPCHelper\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t27379B8B1C7F57DA0084A24C /* Sources */,\n\t\t\t\t27379B8C1C7F57DA0084A24C /* Frameworks */,\n\t\t\t\t27379B8D1C7F57DA0084A24C /* Resources */,\n\t\t\t\t1C09150923F0208F001EB0E1 /* Run Script - set-version.sh */,\n\t\t\t\t276972891CAFCE91007A2F7C /* Run Script - post_install.sh */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = BGMXPCHelper;\n\t\t\tproductName = BGMService;\n\t\t\tproductReference = 27379B8F1C7F57DA0084A24C /* BGMXPCHelper.xpc */;\n\t\t\tproductType = \"com.apple.product-type.xpc-service\";\n\t\t};\n\t\t2743C9F51D86CFF90089613B /* BGMAppUnitTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 2743C9FD1D86CFFA0089613B /* Build configuration list for PBXNativeTarget \"BGMAppUnitTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t2743C9F21D86CFF90089613B /* Sources */,\n\t\t\t\t2743C9F31D86CFF90089613B /* Frameworks */,\n\t\t\t\t2743C9F41D86CFF90089613B /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = BGMAppUnitTests;\n\t\t\tproductName = AppUnitTests;\n\t\t\tproductReference = 2743C9F61D86CFF90089613B /* BGMAppUnitTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t278D71E51CABB6FF00899CF9 /* BGMXPCHelperTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 278D71ED1CABB6FF00899CF9 /* Build configuration list for PBXNativeTarget \"BGMXPCHelperTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t278D71E81CABB6FF00899CF9 /* Sources */,\n\t\t\t\t278D71EB1CABB6FF00899CF9 /* Frameworks */,\n\t\t\t\t278D71EC1CABB6FF00899CF9 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t278D71F41CABB88B00899CF9 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = BGMXPCHelperTests;\n\t\t\tproductName = BGMAppTests;\n\t\t\tproductReference = 278D71F11CABB6FF00899CF9 /* BGMXPCHelperTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t1CB8B32E1BBA75EF000E2DD1 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tCLASSPREFIX = BGM;\n\t\t\t\tKnownAssetTags = (\n\t\t\t\t\tNew,\n\t\t\t\t);\n\t\t\t\tLastUpgradeCheck = 0900;\n\t\t\t\tORGANIZATIONNAME = \"Background Music contributors\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t1CB8B3351BBA75EF000E2DD1 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.4;\n\t\t\t\t\t\tSystemCapabilities = {\n\t\t\t\t\t\t\tcom.apple.Sandbox = {\n\t\t\t\t\t\t\t\tenabled = 0;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t\t1CCC4F531E584081008053E4 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.2.1;\n\t\t\t\t\t\tProvisioningStyle = Manual;\n\t\t\t\t\t\tTestTargetID = 1CB8B3351BBA75EF000E2DD1;\n\t\t\t\t\t};\n\t\t\t\t\t27379B8E1C7F57DA0084A24C = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.2.1;\n\t\t\t\t\t\tDevelopmentTeam = PR7PXC66S5;\n\t\t\t\t\t};\n\t\t\t\t\t2743C9F51D86CFF90089613B = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.0;\n\t\t\t\t\t\tProvisioningStyle = Manual;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 1CB8B3311BBA75EF000E2DD1 /* Build configuration list for PBXProject \"BGMApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 6.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 1CB8B32D1BBA75EF000E2DD1;\n\t\t\tproductRefGroup = 1CB8B3371BBA75EF000E2DD1 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t1CB8B3351BBA75EF000E2DD1 /* Background Music */,\n\t\t\t\t1CCC4F531E584081008053E4 /* BGMAppUITests */,\n\t\t\t\t2743C9F51D86CFF90089613B /* BGMAppUnitTests */,\n\t\t\t\t27379B8E1C7F57DA0084A24C /* BGMXPCHelper */,\n\t\t\t\t278D71E51CABB6FF00899CF9 /* BGMXPCHelperTests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t1CB8B3341BBA75EF000E2DD1 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t274827951E11052500B31D8D /* MainMenu.xib in Resources */,\n\t\t\t\t1C533C7A1EED28B700270802 /* uninstall.sh in Resources */,\n\t\t\t\t1C8D830E2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */,\n\t\t\t\t1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */,\n\t\t\t\t1CED61691C3081C2002CAFCF /* LICENSE in Resources */,\n\t\t\t\t1C2FC3041EB4D6E700A76592 /* BGMApp.sdef in Resources */,\n\t\t\t\t1CC1DF961BE8607700FB8FE4 /* Images.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CCC4F521E584081008053E4 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C62FE5823D4278300B9B68E /* skip-ui-tests.py in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t27379B8D1C7F57DA0084A24C /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */,\n\t\t\t\t1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2743C9F41D86CFF90089613B /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C8D830F2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t278D71EC1CABB6FF00899CF9 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t1C09150923F0208F001EB0E1 /* Run Script - set-version.sh */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script - set-version.sh\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds.\\n\\\"$SRCROOT/../SharedSource/Scripts/set-version.sh\\\"\\n\";\n\t\t};\n\t\t1CD440581E593DDD0064E0BC /* Run Script - set-version.sh */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script - set-version.sh\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds.\\n\\\"$SRCROOT/../SharedSource/Scripts/set-version.sh\\\"\\n\";\n\t\t};\n\t\t276972891CAFCE91007A2F7C /* Run Script - post_install.sh */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 8;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script - post_install.sh\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 1;\n\t\t\tshellPath = /bin/bash;\n\t\t\tshellScript = \"/bin/bash \\\"$SRCROOT/BGMXPCHelper/post_install.sh\\\"\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t1CB8B3321BBA75EF000E2DD1 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C86DA6A1F91EE3B000C8CCF /* CAPThread.cpp in Sources */,\n\t\t\t\t1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,\n\t\t\t\t1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,\n\t\t\t\t1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */,\n\t\t\t\t1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,\n\t\t\t\t1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */,\n\t\t\t\t1C8D830B2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,\n\t\t\t\t273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */,\n\t\t\t\t1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */,\n\t\t\t\t279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */,\n\t\t\t\t1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */,\n\t\t\t\t1C1962F41BCABFC5008A4DF7 /* CAHALAudioObject.cpp in Sources */,\n\t\t\t\t27F7D4901D2483B100821C4B /* BGMDecibel.m in Sources */,\n\t\t\t\t1C4D1A1D217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm in Sources */,\n\t\t\t\t1C2336DA1BEAB6E7004C1C4E /* BGMMusicPlayer.m in Sources */,\n\t\t\t\t1C1962F61BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp in Sources */,\n\t\t\t\t1CC1DF821BE5068A00FB8FE4 /* CACFDictionary.cpp in Sources */,\n\t\t\t\t1C3DB4891BE0885A00EC8160 /* BGMAppVolumes.m in Sources */,\n\t\t\t\t1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */,\n\t\t\t\t1C1963061BCAF468008A4DF7 /* CAMutex.cpp in Sources */,\n\t\t\t\t1CE7064C1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm in Sources */,\n\t\t\t\t1CACCF391F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,\n\t\t\t\t1C1962F51BCABFC5008A4DF7 /* CAHALAudioStream.cpp in Sources */,\n\t\t\t\t1C46994E1BD7694C00F78043 /* BGMDeviceControlSync.cpp in Sources */,\n\t\t\t\t2743C9EB1D852B360089613B /* BGMMusicPlayers.mm in Sources */,\n\t\t\t\t1C1963091BCAF677008A4DF7 /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t1C2FC3141EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,\n\t\t\t\t1C2FC31B1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */,\n\t\t\t\t1CB8B33F1BBA75EF000E2DD1 /* main.m in Sources */,\n\t\t\t\t1CB8B33D1BBA75EF000E2DD1 /* BGMAppDelegate.mm in Sources */,\n\t\t\t\t271677BA1C6CBDFA0080B0A2 /* CACFNumber.cpp in Sources */,\n\t\t\t\t27D1D6BB1DD7226C0049E707 /* BGMAboutPanel.m in Sources */,\n\t\t\t\t27379B8A1C7C562D0084A24C /* BGMVLC.m in Sources */,\n\t\t\t\t1C2336DF1BEAE10C004C1C4E /* BGMSpotify.m in Sources */,\n\t\t\t\t1CED616C1C316E1A002CAFCF /* BGMAudioDeviceManager.mm in Sources */,\n\t\t\t\t2743C9F11D853FBB0089613B /* BGMUserDefaults.m in Sources */,\n\t\t\t\t1C1962FD1BCAC0C3008A4DF7 /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t1C80DED320A6718600045BBE /* BGMAppWatcher.m in Sources */,\n\t\t\t\t2743C9EC1D852B360089613B /* BGMScriptingBridge.m in Sources */,\n\t\t\t\t1CC6593C1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,\n\t\t\t\t1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,\n\t\t\t\t1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,\n\t\t\t\t1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */,\n\t\t\t\t9E129A412602AE620005851B /* BGMASApplication.m in Sources */,\n\t\t\t\t1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */,\n\t\t\t\t1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */,\n\t\t\t\t1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */,\n\t\t\t\t27FB8C2F1DE468320084DB9D /* BGM_Utils.cpp in Sources */,\n\t\t\t\t1C1962F31BCABFC5008A4DF7 /* CAHALAudioDevice.cpp in Sources */,\n\t\t\t\t1CF5423C1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */,\n\t\t\t\t1CC1DF911BE5891300FB8FE4 /* CADebugger.cpp in Sources */,\n\t\t\t\t1C3D36721ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,\n\t\t\t\t2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */,\n\t\t\t\t27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */,\n\t\t\t\t1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */,\n\t\t\t\t19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */,\n\t\t\t\t19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */,\n\t\t\t\t19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */,\n\t\t\t\t19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */,\n\t\t\t\t19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CCC4F501E584081008053E4 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C8104B022AD082E00B35517 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,\n\t\t\t\t1C8D830520423E1C00A838F2 /* BGMSwinsian.m in Sources */,\n\t\t\t\t1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,\n\t\t\t\t1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,\n\t\t\t\t1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,\n\t\t\t\t1CC6593D1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,\n\t\t\t\t1C80DED420A6718600045BBE /* BGMAppWatcher.m in Sources */,\n\t\t\t\t1CD410D51F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,\n\t\t\t\t1CD989571ECFFD250014BBBF /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t1CD989581ECFFD250014BBBF /* CAMutex.cpp in Sources */,\n\t\t\t\t1CD989591ECFFD250014BBBF /* CAPThread.cpp in Sources */,\n\t\t\t\t1CD9895A1ECFFD250014BBBF /* CARingBuffer.cpp in Sources */,\n\t\t\t\t1CD989421ECFFCFC0014BBBF /* BGMAppVolumes.m in Sources */,\n\t\t\t\t1CD989431ECFFCFC0014BBBF /* BGMAudioDeviceManager.mm in Sources */,\n\t\t\t\t1C4D1A1E217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm in Sources */,\n\t\t\t\t1CD989441ECFFCFC0014BBBF /* BGMAutoPauseMenuItem.m in Sources */,\n\t\t\t\t1CD989451ECFFCFC0014BBBF /* BGMAutoPauseMusic.mm in Sources */,\n\t\t\t\t1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,\n\t\t\t\t1CD989461ECFFCFC0014BBBF /* BGMMusicPlayers.mm in Sources */,\n\t\t\t\t1CD989471ECFFCFC0014BBBF /* BGMScriptingBridge.m in Sources */,\n\t\t\t\t1CD989481ECFFCFC0014BBBF /* BGMMusicPlayer.m in Sources */,\n\t\t\t\t1CD989491ECFFCFC0014BBBF /* BGMDecibel.m in Sources */,\n\t\t\t\t1C3D36731ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,\n\t\t\t\t1C9258482090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,\n\t\t\t\t1CD9894A1ECFFCFC0014BBBF /* BGMiTunes.m in Sources */,\n\t\t\t\t1CD9894B1ECFFCFC0014BBBF /* BGMSpotify.m in Sources */,\n\t\t\t\t1CD9894C1ECFFCFC0014BBBF /* BGMHermes.m in Sources */,\n\t\t\t\t1CD9894D1ECFFCFC0014BBBF /* BGMVLC.m in Sources */,\n\t\t\t\t1CD9894E1ECFFCFC0014BBBF /* BGMVOX.m in Sources */,\n\t\t\t\t1CD9894F1ECFFCFC0014BBBF /* BGMPreferencesMenu.mm in Sources */,\n\t\t\t\t1CD989501ECFFCFC0014BBBF /* BGMAboutPanel.m in Sources */,\n\t\t\t\t1CD989511ECFFCFC0014BBBF /* BGMAutoPauseMusicPrefs.mm in Sources */,\n\t\t\t\t1CD989521ECFFCFC0014BBBF /* BGMOutputDeviceMenuSection.mm in Sources */,\n\t\t\t\t1CD989531ECFFCFC0014BBBF /* BGMDeviceControlSync.cpp in Sources */,\n\t\t\t\t1CD989541ECFFCFC0014BBBF /* BGMPlayThrough.cpp in Sources */,\n\t\t\t\t1CD989551ECFFCFC0014BBBF /* BGMUserDefaults.m in Sources */,\n\t\t\t\t1CD989561ECFFCFC0014BBBF /* BGMXPCListener.mm in Sources */,\n\t\t\t\t1CD989411ECFFCD10014BBBF /* BGMAppDelegate.mm in Sources */,\n\t\t\t\t1CD989341ECFFC9E0014BBBF /* BGM_Utils.cpp in Sources */,\n\t\t\t\t1CD989351ECFFC9E0014BBBF /* CACFArray.cpp in Sources */,\n\t\t\t\t1CD989361ECFFC9E0014BBBF /* CACFDictionary.cpp in Sources */,\n\t\t\t\t1CD989371ECFFC9E0014BBBF /* CACFNumber.cpp in Sources */,\n\t\t\t\t1CD989381ECFFC9E0014BBBF /* CACFString.cpp in Sources */,\n\t\t\t\t9E542C7026057FBA0016C0B5 /* BGMASApplication.m in Sources */,\n\t\t\t\t1CD989391ECFFC9E0014BBBF /* CADebugger.cpp in Sources */,\n\t\t\t\t1CD9893A1ECFFC9E0014BBBF /* CADebugMacros.cpp in Sources */,\n\t\t\t\t1CD9893B1ECFFC9E0014BBBF /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t1CD9893C1ECFFC9E0014BBBF /* CAHALAudioDevice.cpp in Sources */,\n\t\t\t\t1CD9893D1ECFFC9E0014BBBF /* CAHALAudioObject.cpp in Sources */,\n\t\t\t\t1CD9893E1ECFFC9E0014BBBF /* CAHALAudioStream.cpp in Sources */,\n\t\t\t\t1CD9893F1ECFFC9E0014BBBF /* CAHALAudioSystemObject.cpp in Sources */,\n\t\t\t\t1C50FF631EC9F4490031A6EA /* BGMAudioDevice.cpp in Sources */,\n\t\t\t\t1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */,\n\t\t\t\t1C2FC31C1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */,\n\t\t\t\t1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,\n\t\t\t\t19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */,\n\t\t\t\t19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */,\n\t\t\t\t19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */,\n\t\t\t\t19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */,\n\t\t\t\t19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t27379B8B1C7F57DA0084A24C /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C1AA4B11F9DE3B700BCFB22 /* BGMAudioDevice.cpp in Sources */,\n\t\t\t\t1C1AA4B21F9DE3B700BCFB22 /* BGMBackgroundMusicDevice.cpp in Sources */,\n\t\t\t\t1C1AA4B01F9C673000BCFB22 /* BGM_Utils.cpp in Sources */,\n\t\t\t\t1CF2D5941F9447AE008B6E35 /* CACFArray.cpp in Sources */,\n\t\t\t\t1CF2D5951F9447AE008B6E35 /* CACFDictionary.cpp in Sources */,\n\t\t\t\t1CF2D5961F9447AE008B6E35 /* CACFNumber.cpp in Sources */,\n\t\t\t\t1CF2D5971F9447AE008B6E35 /* CACFString.cpp in Sources */,\n\t\t\t\t1CF2D5981F9447AE008B6E35 /* CADebugger.cpp in Sources */,\n\t\t\t\t1CF2D5991F9447AE008B6E35 /* CADebugMacros.cpp in Sources */,\n\t\t\t\t1CF2D59A1F9447AE008B6E35 /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t1CF2D59B1F9447AE008B6E35 /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t1CF2D59C1F9447AE008B6E35 /* CAMutex.cpp in Sources */,\n\t\t\t\t1CF2D59D1F9447AE008B6E35 /* CAPThread.cpp in Sources */,\n\t\t\t\t1CF2D59E1F9447AE008B6E35 /* CARingBuffer.cpp in Sources */,\n\t\t\t\t1CF2D5931F94479A008B6E35 /* CAHALAudioStream.cpp in Sources */,\n\t\t\t\t1CF2D5901F944789008B6E35 /* CAHALAudioDevice.cpp in Sources */,\n\t\t\t\t1CF2D5911F944789008B6E35 /* CAHALAudioObject.cpp in Sources */,\n\t\t\t\t1CF2D5921F944789008B6E35 /* CAHALAudioSystemObject.cpp in Sources */,\n\t\t\t\t27D643C01C9FB99200737F6E /* BGMXPCHelperService.mm in Sources */,\n\t\t\t\t27D643C11C9FB99200737F6E /* main.m in Sources */,\n\t\t\t\t277170161CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m in Sources */,\n\t\t\t\t19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2743C9F21D86CFF90089613B /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */,\n\t\t\t\t1C8104AF22AD07E200B35517 /* BGMAppWatcher.m in Sources */,\n\t\t\t\t1C8D830620423E2400A838F2 /* BGMSwinsian.m in Sources */,\n\t\t\t\t1C227C0B1FA4C48200A95B6D /* BGMAppVolumes.m in Sources */,\n\t\t\t\t1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,\n\t\t\t\t1C8D830C2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,\n\t\t\t\t1C3D36741ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,\n\t\t\t\t27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */,\n\t\t\t\t27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */,\n\t\t\t\t27FB8C071DD75D0A0084DB9D /* BGMHermes.m in Sources */,\n\t\t\t\t2743CA211D86DE780089613B /* BGMDeviceControlSync.cpp in Sources */,\n\t\t\t\t1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,\n\t\t\t\t2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */,\n\t\t\t\t1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,\n\t\t\t\t2743CA0D1D86D7FA0089613B /* CACFDictionary.cpp in Sources */,\n\t\t\t\t2743CA0E1D86D7FA0089613B /* CACFNumber.cpp in Sources */,\n\t\t\t\t2743CA0F1D86D7FA0089613B /* CACFString.cpp in Sources */,\n\t\t\t\t2743CA101D86D7FA0089613B /* CADebugger.cpp in Sources */,\n\t\t\t\t1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */,\n\t\t\t\t2743CA111D86D7FA0089613B /* CADebugMacros.cpp in Sources */,\n\t\t\t\t1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */,\n\t\t\t\t2743CA121D86D7FA0089613B /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */,\n\t\t\t\t2743CA141D86D7FA0089613B /* CAHALAudioStream.cpp in Sources */,\n\t\t\t\t2743CA161D86D7FA0089613B /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t2743CA171D86D7FA0089613B /* CAMutex.cpp in Sources */,\n\t\t\t\t2743CA181D86D7FA0089613B /* CAPThread.cpp in Sources */,\n\t\t\t\t2743CA191D86D7FA0089613B /* CARingBuffer.cpp in Sources */,\n\t\t\t\t2743CA0A1D86D52D0089613B /* BGMAudioDeviceManager.mm in Sources */,\n\t\t\t\t2743CA031D86D41C0089613B /* BGMScriptingBridge.m in Sources */,\n\t\t\t\t2743CA041D86D41C0089613B /* BGMMusicPlayer.m in Sources */,\n\t\t\t\t1CD410D61F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,\n\t\t\t\t2743CA051D86D41C0089613B /* BGMDecibel.m in Sources */,\n\t\t\t\t2743CA061D86D41C0089613B /* BGMSpotify.m in Sources */,\n\t\t\t\t2743CA071D86D41C0089613B /* BGMVLC.m in Sources */,\n\t\t\t\t1CF5423D1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */,\n\t\t\t\t2743CA081D86D41C0089613B /* BGMVOX.m in Sources */,\n\t\t\t\t1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */,\n\t\t\t\t1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */,\n\t\t\t\t2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */,\n\t\t\t\t1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */,\n\t\t\t\t1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,\n\t\t\t\t2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,\n\t\t\t\t1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */,\n\t\t\t\t2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,\n\t\t\t\t19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */,\n\t\t\t\t19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */,\n\t\t\t\t1C9258492090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,\n\t\t\t\t19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */,\n\t\t\t\t19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */,\n\t\t\t\t19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */,\n\t\t\t\t19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t278D71E81CABB6FF00899CF9 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */,\n\t\t\t\t19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t1CCC4F5A1E584081008053E4 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 1CB8B3351BBA75EF000E2DD1 /* Background Music */;\n\t\t\ttargetProxy = 1CCC4F591E584081008053E4 /* PBXContainerItemProxy */;\n\t\t};\n\t\t278D71F41CABB88B00899CF9 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 27379B8E1C7F57DA0084A24C /* BGMXPCHelper */;\n\t\t\ttargetProxy = 278D71F31CABB88B00899CF9 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\t1CB8B3421BBA75EF000E2DD1 /* MainMenu.xib */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B3431BBA75EF000E2DD1 /* Base */,\n\t\t\t);\n\t\t\tname = MainMenu.xib;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t1C4603C51C05963C00150F9B /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS = 0;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS = 1;\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_ASSIGN_ENUM = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;\n\t\t\t\tCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = YES;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = c11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = s;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_NEWLINE = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;\n\t\t\t\tGCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;\n\t\t\t\tGCC_WARN_SHADOW = YES;\n\t\t\t\tGCC_WARN_STRICT_SELECTOR_MATCH = YES;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_LABEL = YES;\n\t\t\t\tGCC_WARN_UNUSED_PARAMETER = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.13;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tOTHER_CFLAGS = \"-fno-omit-frame-pointer\";\n\t\t\t\tRUN_CLANG_STATIC_ANALYZER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tWARNING_CFLAGS = \"-Wpartial-availability\";\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n\t\t1C4603C61C05963C00150F9B /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_USE_OPTIMIZATION_PROFILE = NO;\n\t\t\t\tCLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"BGMApp/BGMApp-Debug.entitlements\";\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = NO;\n\t\t\t\tDWARF_DSYM_FILE_NAME = \"$(EXECUTABLE_NAME).dSYM\";\n\t\t\t\tDWARF_DSYM_FOLDER_PATH = \"$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)\";\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tINFOPLIST_FILE = BGMApp/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.App;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSTRIP_STYLE = \"non-global\";\n\t\t\t\tWARNING_CFLAGS = (\n\t\t\t\t\t\"-Wpartial-availability\",\n\t\t\t\t\t\"-Wthread-safety\",\n\t\t\t\t);\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n\t\t1CB8B3511BBA75F0000E2DD1 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS = 0;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS = 1;\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_ASSIGN_ENUM = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;\n\t\t\t\tCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = c11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_NEWLINE = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;\n\t\t\t\tGCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;\n\t\t\t\tGCC_WARN_SHADOW = YES;\n\t\t\t\tGCC_WARN_STRICT_SELECTOR_MATCH = YES;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_LABEL = YES;\n\t\t\t\tGCC_WARN_UNUSED_PARAMETER = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.13;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tOTHER_CFLAGS = \"-fno-omit-frame-pointer\";\n\t\t\t\tRUN_CLANG_STATIC_ANALYZER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tWARNING_CFLAGS = \"-Wpartial-availability\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1CB8B3521BBA75F0000E2DD1 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS = 0;\n\t\t\t\tBGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS = 0;\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_ASSIGN_ENUM = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;\n\t\t\t\tCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = c11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = s;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=0\",\n\t\t\t\t\t\"CoreAudio_Debug=0\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_NEWLINE = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;\n\t\t\t\tGCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;\n\t\t\t\tGCC_WARN_SHADOW = YES;\n\t\t\t\tGCC_WARN_STRICT_SELECTOR_MATCH = YES;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_LABEL = YES;\n\t\t\t\tGCC_WARN_UNUSED_PARAMETER = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.13;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tOTHER_CFLAGS = \"\";\n\t\t\t\tRUN_CLANG_STATIC_ANALYZER = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tWARNING_CFLAGS = \"-Wpartial-availability\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t1CB8B3541BBA75F0000E2DD1 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_USE_OPTIMIZATION_PROFILE = NO;\n\t\t\t\tCLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"BGMApp/BGMApp-Debug.entitlements\";\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = NO;\n\t\t\t\tDWARF_DSYM_FILE_NAME = \"$(EXECUTABLE_NAME).dSYM\";\n\t\t\t\tDWARF_DSYM_FOLDER_PATH = \"$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)\";\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tINFOPLIST_FILE = BGMApp/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.App;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSTRIP_STYLE = \"non-global\";\n\t\t\t\tWARNING_CFLAGS = (\n\t\t\t\t\t\"-Wpartial-availability\",\n\t\t\t\t\t\"-Wthread-safety\",\n\t\t\t\t);\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1CB8B3551BBA75F0000E2DD1 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_USE_OPTIMIZATION_PROFILE = NO;\n\t\t\t\tCLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = BGMApp/BGMApp.entitlements;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = YES;\n\t\t\t\tDWARF_DSYM_FILE_NAME = \"$(EXECUTABLE_NAME).dSYM\";\n\t\t\t\tDWARF_DSYM_FOLDER_PATH = \"$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)\";\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=0\",\n\t\t\t\t\t\"CoreAudio_Debug=0\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tINFOPLIST_FILE = BGMApp/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tLLVM_LTO = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.App;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSTRIP_STYLE = \"non-global\";\n\t\t\t\tWARNING_CFLAGS = (\n\t\t\t\t\t\"-Wno-profile-instr-out-of-date\",\n\t\t\t\t\t\"-Wpartial-availability\",\n\t\t\t\t\t\"-Wthread-safety\",\n\t\t\t\t);\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t1CCC4F5C1E584081008053E4 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"BGMApp/BGMApp-Debug.entitlements\";\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UITests/BGMAppUITests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tTEST_TARGET_NAME = \"Background Music\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1CCC4F5D1E584081008053E4 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"BGMApp/BGMApp-Debug.entitlements\";\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UITests/BGMAppUITests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tTEST_TARGET_NAME = \"Background Music\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t1CCC4F5E1E584081008053E4 /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"BGMApp/BGMApp-Debug.entitlements\";\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UITests/BGMAppUITests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tTEST_TARGET_NAME = \"Background Music\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n\t\t27379B991C7F57DB0084A24C /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = NO;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = BGMXPCHelper/Info.plist;\n\t\t\t\tINSTALL_GROUP = wheel;\n\t\t\t\tINSTALL_OWNER = root;\n\t\t\t\tINSTALL_PATH = /usr/local/libexec;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;\n\t\t\t\tPRODUCT_NAME = BGMXPCHelper;\n\t\t\t\tSTRIP_INSTALLED_PRODUCT = NO;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t27379B9A1C7F57DB0084A24C /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = YES;\n\t\t\t\tINFOPLIST_FILE = BGMXPCHelper/Info.plist;\n\t\t\t\tINSTALL_GROUP = wheel;\n\t\t\t\tINSTALL_OWNER = root;\n\t\t\t\tINSTALL_PATH = /usr/local/libexec;\n\t\t\t\tLLVM_LTO = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;\n\t\t\t\tPRODUCT_NAME = BGMXPCHelper;\n\t\t\t\tSTRIP_INSTALLED_PRODUCT = NO;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t27379B9B1C7F57DB0084A24C /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = NO;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = BGMXPCHelper/Info.plist;\n\t\t\t\tINSTALL_GROUP = wheel;\n\t\t\t\tINSTALL_OWNER = root;\n\t\t\t\tINSTALL_PATH = /usr/local/libexec;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;\n\t\t\t\tPRODUCT_NAME = BGMXPCHelper;\n\t\t\t\tSTRIP_INSTALLED_PRODUCT = NO;\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n\t\t2743C9FE1D86CFFA0089613B /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t\t\"BGM_UnitTest=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t2743C9FF1D86CFFA0089613B /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=0\",\n\t\t\t\t\t\"CoreAudio_Debug=0\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t\t\"BGM_UnitTest=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t2743CA001D86CFFA0089613B /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ADDRESS_SANITIZER_CONTAINER_OVERFLOW = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)\",\n\t\t\t\t\t\"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)\",\n\t\t\t\t\t\"CoreAudio_StopOnThrow=0\",\n\t\t\t\t\t\"BGM_UnitTest=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = \"BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n\t\t278D71EE1CABB6FF00899CF9 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tINFOPLIST_FILE = \"BGMXPCHelperTests/BGMXPCHelperTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelperTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t278D71EF1CABB6FF00899CF9 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tINFOPLIST_FILE = \"BGMXPCHelperTests/BGMXPCHelperTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelperTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t278D71F01CABB6FF00899CF9 /* DebugOpt */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tINFOPLIST_FILE = \"BGMXPCHelperTests/BGMXPCHelperTests-Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelperTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = DebugOpt;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t1CB8B3311BBA75EF000E2DD1 /* Build configuration list for PBXProject \"BGMApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1CB8B3511BBA75F0000E2DD1 /* Debug */,\n\t\t\t\t1CB8B3521BBA75F0000E2DD1 /* Release */,\n\t\t\t\t1C4603C51C05963C00150F9B /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t1CB8B3531BBA75F0000E2DD1 /* Build configuration list for PBXNativeTarget \"Background Music\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1CB8B3541BBA75F0000E2DD1 /* Debug */,\n\t\t\t\t1CB8B3551BBA75F0000E2DD1 /* Release */,\n\t\t\t\t1C4603C61C05963C00150F9B /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t1CCC4F5B1E584081008053E4 /* Build configuration list for PBXNativeTarget \"BGMAppUITests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1CCC4F5C1E584081008053E4 /* Debug */,\n\t\t\t\t1CCC4F5D1E584081008053E4 /* Release */,\n\t\t\t\t1CCC4F5E1E584081008053E4 /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t27379B981C7F57DB0084A24C /* Build configuration list for PBXNativeTarget \"BGMXPCHelper\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t27379B991C7F57DB0084A24C /* Debug */,\n\t\t\t\t27379B9A1C7F57DB0084A24C /* Release */,\n\t\t\t\t27379B9B1C7F57DB0084A24C /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t2743C9FD1D86CFFA0089613B /* Build configuration list for PBXNativeTarget \"BGMAppUnitTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t2743C9FE1D86CFFA0089613B /* Debug */,\n\t\t\t\t2743C9FF1D86CFFA0089613B /* Release */,\n\t\t\t\t2743CA001D86CFFA0089613B /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t278D71ED1CABB6FF00899CF9 /* Build configuration list for PBXNativeTarget \"BGMXPCHelperTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t278D71EE1CABB6FF00899CF9 /* Debug */,\n\t\t\t\t278D71EF1CABB6FF00899CF9 /* Release */,\n\t\t\t\t278D71F01CABB6FF00899CF9 /* DebugOpt */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 1CB8B32E1BBA75EF000E2DD1 /* Project object */;\n}\n"
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/xcshareddata/xcschemes/BGMXPCHelper.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"27379B8E1C7F57DA0084A24C\"\n               BuildableName = \"BGMXPCHelper.xpc\"\n               BlueprintName = \"BGMXPCHelper\"\n               ReferencedContainer = \"container:BGMApp.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      codeCoverageEnabled = \"YES\"\n      onlyGenerateCoverageForSpecifiedTargets = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"27379B8E1C7F57DA0084A24C\"\n            BuildableName = \"BGMXPCHelper.xpc\"\n            BlueprintName = \"BGMXPCHelper\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n         <AdditionalOption\n            key = \"NSZombieEnabled\"\n            value = \"YES\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n         <AdditionalOption\n            key = \"MallocScribble\"\n            value = \"\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n      </AdditionalOptions>\n      <CodeCoverageTargets>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"27379B8E1C7F57DA0084A24C\"\n            BuildableName = \"BGMXPCHelper.xpc\"\n            BlueprintName = \"BGMXPCHelper\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </CodeCoverageTargets>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"278D71E51CABB6FF00899CF9\"\n               BuildableName = \"BGMXPCHelperTests.xctest\"\n               BlueprintName = \"BGMXPCHelperTests\"\n               ReferencedContainer = \"container:BGMApp.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"27379B8E1C7F57DA0084A24C\"\n            BuildableName = \"BGMXPCHelper.xpc\"\n            BlueprintName = \"BGMXPCHelper\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <EnvironmentVariables>\n         <EnvironmentVariable\n            key = \"ASAN_OPTIONS\"\n            value = \"detect_odr_violation=1:use_odr_indicator=1:detect_stack_use_after_return=1:detect_invalid_pointer_pairs=2:check_initialization_order=1\"\n            isEnabled = \"YES\">\n         </EnvironmentVariable>\n      </EnvironmentVariables>\n      <AdditionalOptions>\n         <AdditionalOption\n            key = \"NSZombieEnabled\"\n            value = \"YES\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n         <AdditionalOption\n            key = \"MallocScribble\"\n            value = \"\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"27379B8E1C7F57DA0084A24C\"\n            BuildableName = \"BGMXPCHelper.xpc\"\n            BlueprintName = \"BGMXPCHelper\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/xcshareddata/xcschemes/Background Music.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"1CB8B3351BBA75EF000E2DD1\"\n               BuildableName = \"Background Music.app\"\n               BlueprintName = \"Background Music\"\n               ReferencedContainer = \"container:BGMApp.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      codeCoverageEnabled = \"YES\"\n      onlyGenerateCoverageForSpecifiedTargets = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3351BBA75EF000E2DD1\"\n            BuildableName = \"Background Music.app\"\n            BlueprintName = \"Background Music\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <AdditionalOptions>\n         <AdditionalOption\n            key = \"NSZombieEnabled\"\n            value = \"YES\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n         <AdditionalOption\n            key = \"MallocScribble\"\n            value = \"\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n      </AdditionalOptions>\n      <CodeCoverageTargets>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3351BBA75EF000E2DD1\"\n            BuildableName = \"Background Music.app\"\n            BlueprintName = \"Background Music\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </CodeCoverageTargets>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\"\n            parallelizable = \"YES\"\n            testExecutionOrdering = \"random\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"2743C9F51D86CFF90089613B\"\n               BuildableName = \"BGMAppUnitTests.xctest\"\n               BlueprintName = \"BGMAppUnitTests\"\n               ReferencedContainer = \"container:BGMApp.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"1CCC4F531E584081008053E4\"\n               BuildableName = \"BGMAppUITests.xctest\"\n               BlueprintName = \"BGMAppUITests\"\n               ReferencedContainer = \"container:BGMApp.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"NO\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3351BBA75EF000E2DD1\"\n            BuildableName = \"Background Music.app\"\n            BlueprintName = \"Background Music\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n      <CommandLineArguments>\n         <CommandLineArgument\n            argument = \"--show-dock-icon\"\n            isEnabled = \"NO\">\n         </CommandLineArgument>\n         <CommandLineArgument\n            argument = \"--no-persistent-data\"\n            isEnabled = \"NO\">\n         </CommandLineArgument>\n      </CommandLineArguments>\n      <EnvironmentVariables>\n         <EnvironmentVariable\n            key = \"ASAN_OPTIONS\"\n            value = \"detect_odr_violation=1:use_odr_indicator=1:detect_stack_use_after_return=1:detect_invalid_pointer_pairs=2:check_initialization_order=1\"\n            isEnabled = \"YES\">\n         </EnvironmentVariable>\n      </EnvironmentVariables>\n      <AdditionalOptions>\n         <AdditionalOption\n            key = \"MallocScribble\"\n            value = \"\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n         <AdditionalOption\n            key = \"NSZombieEnabled\"\n            value = \"YES\"\n            isEnabled = \"YES\">\n         </AdditionalOption>\n      </AdditionalOptions>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3351BBA75EF000E2DD1\"\n            BuildableName = \"Background Music.app\"\n            BlueprintName = \"Background Music\"\n            ReferencedContainer = \"container:BGMApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMApp.h",
    "content": "/*\n * BGMApp.h\n *\n * Generated with\n * sdef \"/Applications/Background Music.app\" | sdp -fh --basename BGMApp\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class BGMAppOutputDevice, BGMAppApplication;\n\n\n\n/*\n * Background Music\n */\n\n// A hardware device that can play audio\n@interface BGMAppOutputDevice : SBObject\n\n@property (copy, readonly) NSString *name;  // The name of the output device.\n@property BOOL selected;  // Is this the device to be used for audio output?\n\n@end\n\n// The application program\n@interface BGMAppApplication : SBApplication\n\n- (SBElementArray<BGMAppOutputDevice *> *) outputDevices;\n\n@property (copy) BGMAppOutputDevice *selectedOutputDevice;  // The device to be used for audio output\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMAppUITests-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMAppUITests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMAppUITests.mm\n//  BGMAppUITests\n//\n//  Copyright © 2017, 2018, 2020, 2022 Kyle Neideck\n//\n//  You might want to use Xcode's UI test recording feature if you add new tests.\n//\n\n// Local Includes\n#import \"BGM_TestUtils.h\"\n#import \"BGM_Types.h\"\n#import \"BGMBackgroundMusicDevice.h\"\n\n// Scripting Bridge Includes\n#import \"BGMApp.h\"\n\n// System Includes\n#import <XCTest/XCTest.h>\n\n\n// TODO: Mock BGMDevice and music players.\n\n#if __clang_major__ >= 9\n\n@interface BGMAppUITests : XCTestCase\n@end\n\n@implementation BGMAppUITests {\n    // The BGMApp instance.\n    XCUIApplication* app;\n\n    // Convenience vars.\n    //\n    // The menu bar icon. (Called the status bar icon in some places.)\n    XCUIElement* icon;\n    // The menu items in the main menu.\n    XCUIElementQuery* menuItems;\n    // The Preferences menu item.\n    XCUIElement* prefs;\n}\n\n- (void) setUp {\n    [super setUp];\n    \n    // In UI tests it is usually best to stop immediately when a failure occurs.\n    self.continueAfterFailure = NO;\n\n    // Set up the app object and some convenience vars.\n    app = [[XCUIApplication alloc] init];\n    menuItems = app.menuBars.menuItems;\n    prefs = menuItems[@\"Preferences\"];\n    icon = [app.menuBars childrenMatchingType:XCUIElementTypeStatusItem].element;\n\n    // TODO: Make sure BGMDevice isn't set as the OS X default device before launching BGMApp.\n\n    // Tell BGMApp not to load/store user defaults (settings) and to use\n    // NSApplicationActivationPolicyRegular. If it used the \"accessory\" policy as usual, the tests\n    // would fail to start because of a bug in Xcode.\n    app.launchArguments = @[ @\"--no-persistent-data\", @\"--show-dock-icon\" ];\n\n    // Make the \"Background Music wants to use the microphone\" dialog appear every time so the test\n    // doesn't need logic to handle both cases.\n    // TODO: Commented out until acceptMicrophoneAuthorizationDialog work again. See below.\n    // if (@available(macOS 10.15.4, *)) {\n    //     [app resetAuthorizationStatusForResource:XCUIProtectedResourceMicrophone];\n    // }\n\n    // Launch BGMApp.\n    [app launch];\n\n    // TODO: This doesn't seem to be working on macOS 12.4 (21F79). You can click OK manually for\n    //       now.\n    // [self acceptMicrophoneAuthorizationDialog];\n\n    if (![icon waitForExistenceWithTimeout:20.0]) {\n        // The status bar icon/button has this type when using older versions of XCTest, so try\n        // both. (Actually, it might depend on the macOS or Xcode version. I'm not sure.)\n        XCUIElement* iconOldType =\n            [app.menuBars childrenMatchingType:XCUIElementTypeMenuBarItem].element;\n        if ([iconOldType waitForExistenceWithTimeout:20.0]) {\n            NSLog(@\"icon = iconOldType\");\n            icon = iconOldType;\n        }\n    }\n\n    // Wait for the initial elements.\n    XCTAssert([app waitForExistenceWithTimeout:20.0]);\n    XCTAssert([icon waitForExistenceWithTimeout:20.0]);\n}\n\n// Clicks the OK button in the \"Background Music wants to use the microphone\" dialog.\n- (void) acceptMicrophoneAuthorizationDialog {\n    XCUIApplication* unc =\n        [[XCUIApplication alloc] initWithBundleIdentifier:@\"com.apple.UserNotificationCenter\"];\n    NSLog(@\"UserNotificationCenter: %@\", unc);\n    XCUIElement* okButton = unc.dialogs.buttons[@\"OK\"];\n\n    XCTAssert([okButton waitForExistenceWithTimeout:20.0]);\n\n    // This click is failing on GH Actions. No idea why, so try a sleep.\n    (void)[XCTWaiter waitForExpectations:@[[XCTestExpectation new]] timeout:5.0];\n    [okButton click];\n\n    int retries = 10;\n    while (retries > 0 && [okButton waitForExistenceWithTimeout:3.0]) {\n        NSLog(@\"Microphone authorization dialog is still open. Trying to click OK again.\");\n        [okButton click];\n        retries--;\n    }\n}\n\n- (void) tearDown {\n    // Click the quit menu item.\n    if (!menuItems.count) {\n        [icon click];\n    }\n\n    [menuItems[@\"Quit Background Music\"] click];\n\n    // BGMApp should quit.\n    XCTAssertTrue([app waitForState:XCUIApplicationStateNotRunning timeout:10.0]);\n    \n    [super tearDown];\n}\n\n- (void) testCycleOutputDevices {\n    const int NUM_CYCLES = 1;\n\n    // sbApp lets us use AppleScript to query BGMApp and check the test has made the changes to its\n    // settings we expect.\n    BGMAppApplication* sbApp = [SBApplication applicationWithBundleIdentifier:@kBGMAppBundleID];\n\n    // Get macOS to show the \"'Xcode' wants to control 'Background Music'\" dialog before we start\n    // the test so it doesn't interrupt it.\n    [[sbApp selectedOutputDevice] name];\n\n    // Click the icon to open the main menu.\n    [icon click];\n\n    // Get the list of output devices from the main menu.\n    // BGMOutputDeviceMenuSection::createMenuItemForDevice gives every output device menu item the\n    // accessibility identifier \"output-device\" so we can find all of them here.\n    NSArray<XCUIElement*>* outputDeviceMenuItems =\n        [menuItems matchingIdentifier:@\"output-device\"].allElementsBoundByIndex;\n\n    // For debugging certain issues, it can be useful to repeatedly switch between two\n    // devices:\n    // outputDeviceMenuItems = [outputDeviceMenuItems subarrayWithRange:NSMakeRange(0,2)];\n\n    XCTAssertGreaterThan(outputDeviceMenuItems.count, 0);\n\n    // Click the last device to close the menu again.\n    [outputDeviceMenuItems.lastObject click];\n\n    for (int i = 0; i < NUM_CYCLES; i++) {\n        // Select each output device.\n        for (XCUIElement* item in outputDeviceMenuItems) {\n            [icon click];\n            [item click];\n\n            // Assert that the device we clicked is the selected device now.\n            for (BGMAppOutputDevice* device in [sbApp outputDevices]) {\n                // TODO: This seems a bit fragile. Would it still work with long device names?\n                if ([device.name isEqualToString:[item title]]) {\n                    XCTAssert(device.selected);\n                } else {\n                    XCTAssertFalse(device.selected);\n                }\n            }\n        }\n    }\n}\n\n- (void) testSelectMusicPlayer {\n    // Select VLC as the music player.\n    [icon click];\n    [prefs hover];\n    [prefs.menuItems[@\"VLC\"] click];\n\n    // The name of the Auto-pause menu item should change. Also check the accessibility identifier.\n    [icon click];\n    XCTAssertEqualObjects(menuItems[@\"Auto-pause VLC\"].identifier, @\"Auto-pause enabled\");\n    \n    // Select iTunes as the music player.\n    [prefs hover];\n    [prefs.menuItems[@\"iTunes\"] click];\n\n    // The name of the Auto-pause menu item should change back.\n    [icon click];\n    XCTAssert(menuItems[@\"Auto-pause iTunes\"].exists);\n}\n\n- (void) testOutputVolumeSlider {\n    const AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;\n    const UInt32 channel = kMasterChannel;\n\n    [icon click];\n    \n    XCUIElement* slider = menuItems.sliders[@\"Output Volume\"];\n\n    // Try to slide the slider all the way to the right.\n    [slider adjustToNormalizedSliderPosition:1.0f];\n\n    // For whatever reason, XCTest usually doesn't quite make it to the position you ask for. So\n    // just check that it got close enough.\n    XCTAssertGreaterThan(slider.normalizedSliderPosition, 0.9f);\n\n    // BGMDevice's volume should be set to its max, or as close as XCTest was able to get the\n    // slider. Probably shouldn't be comparing floats for equality like this, but it's working fine\n    // so far.\n    BGMBackgroundMusicDevice bgmDevice;\n    XCTAssertEqual(slider.normalizedSliderPosition,\n                   bgmDevice.GetVolumeControlScalarValue(scope, channel));\n\n    // Try to slide the slider all the way to the left.\n    [slider adjustToNormalizedSliderPosition:0.0f];\n\n    // BGMDevice's volume should be set to the new value of the slider.\n    XCTAssertLessThan(slider.normalizedSliderPosition, 0.1f);\n    XCTAssertEqual(slider.normalizedSliderPosition,\n                   bgmDevice.GetVolumeControlScalarValue(scope, channel));\n\n    // Try to slide the slider to 75%.\n    [slider adjustToNormalizedSliderPosition:0.75f];\n\n    // BGMDevice's volume should be set to the new value of the slider, about 75% of its max.\n    XCTAssertEqual(slider.normalizedSliderPosition,\n                   bgmDevice.GetVolumeControlScalarValue(scope, channel));\n\n    // BGMDevice should be unmuted.\n    XCTAssertEqual(false, bgmDevice.GetMuteControlValue(scope, channel));\n\n    // Set BGMDevice's volume to its min.\n    bgmDevice.SetVolumeControlScalarValue(scope, channel, 0.0f);\n\n    // The slider should be set to its min value. Use a wait for this check because the change\n    // happens asynchronously.\n    [self expectationForPredicate:[NSPredicate predicateWithFormat:@\"normalizedSliderPosition == 0\"]\n              evaluatedWithObject:slider\n                          handler:nil];\n    [self waitForExpectationsWithTimeout:10.0 handler:nil];\n\n    XCTAssertEqual(0.0f, slider.normalizedSliderPosition);\n\n    // Click the slider without changing it to simulate the user setting the slider to zero.\n    [slider adjustToNormalizedSliderPosition:0.0f];\n\n    // BGMDevice's volume should still be set to its min.\n    XCTAssertEqual(0.0f, bgmDevice.GetVolumeControlScalarValue(scope, channel));\n\n    // BGMDevice should now be muted.\n    XCTAssertEqual(true, bgmDevice.GetMuteControlValue(scope, channel));\n}\n\n@end\n\n#endif /* __clang_major__ >= 9 */\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/skip-ui-tests.py",
    "content": "#!/usr/bin/env python3\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# skip-ui-tests.py\n# BGMAppUITests\n#\n# Copyright (c) 2017, 2024 Kyle Neideck\n#\n# Disables the UI tests. This is mainly useful on CI systems that don't support UI tests.\n#\n\nimport xml.etree.ElementTree as ET\n\nSCHEME_FILE = \"BGMApp/BGMApp.xcodeproj/xcshareddata/xcschemes/Background Music.xcscheme\"\nUI_REF_XPATH = \".//BuildableReference[@BlueprintName='BGMAppUITests']/..\"\n\n# Parse the Xcode scheme.\ntree = ET.parse(SCHEME_FILE)\n\n# Set the TestableReference for the UI tests to skipped.\ntree.getroot().findall(UI_REF_XPATH)[0].set(\"skipped\", \"YES\")\n\n# Save the scheme.\ntree.write(SCHEME_FILE)\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMMusicPlayersUnitTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMMusicPlayersUnitTests.mm\n//  BGMAppUnitTests\n//\n//  Copyright © 2016-2020 Kyle Neideck\n//\n\n// Unit include\n#import \"BGMMusicPlayers.h\"\n\n// BGM includes\n#import \"BGM_Types.h\"\n#import \"BGMAudioDeviceManager.h\"\n#import \"BGMiTunes.h\"\n#import \"BGMDecibel.h\"\n#import \"BGMSpotify.h\"\n#import \"BGMVLC.h\"\n\n// Local includes\n#import \"BGM_TestUtils.h\"\n#import \"MockAudioObject.h\"\n#import \"MockAudioObjects.h\"\n\n// System includes\n#import <Foundation/Foundation.h>\n#import <XCTest/XCTest.h>\n\n\n// Note that the PublicUtility classes that we use to communicate with the HAL, CAHALAudioObject and\n// CAHALAudioSystemObject, are also mocked. The unit tests are compiled with mock implementations:\n// Mock_CAHALAudioObject.cpp and Mock_CAHALAudioSystemObject.cpp.\n\n@interface BGMMockUserDefaults : BGMUserDefaults\n\n@property NSUUID* selectedPlayerID;\n\n@end\n\n@implementation BGMMockUserDefaults\n\n- (void) registerDefaults {\n}\n\n- (NSString* __nullable) selectedMusicPlayerID {\n    return [self.selectedPlayerID UUIDString];\n}\n\n- (void) setSelectedMusicPlayerID:(NSString* __nullable)selectedMusicPlayerID {\n    #pragma unused (selectedMusicPlayerID)\n}\n\n- (BOOL) autoPauseMusicEnabled {\n    return YES;\n}\n\n- (void) setAutoPauseMusicEnabled:(BOOL)autoPauseMusicEnabled {\n    #pragma unused (autoPauseMusicEnabled)\n}\n\n@end\n\n// -------------------------------------------------------------------------------------------------\n\n@interface BGMMockAudioDeviceManager : BGMAudioDeviceManager\n@end\n\n@implementation BGMMockAudioDeviceManager {\n    BGMBackgroundMusicDevice bgmDevice;\n}\n\n- (BGMBackgroundMusicDevice) bgmDevice {\n    return bgmDevice;\n}\n\n@end\n\n// -------------------------------------------------------------------------------------------------\n\n@interface BGMMusicPlayersUnitTests : XCTestCase\n@end\n\n@implementation BGMMusicPlayersUnitTests {\n    BGMAudioDeviceManager* devices;\n    BGMMockUserDefaults* defaults;\n    \n    NSUUID* spotifyID;\n    NSUUID* vlcID;\n}\n\n- (void) setUp {\n    [super setUp];\n\n    // Mock BGMDevice.\n    MockAudioObjects::CreateMockDevice(kBGMDeviceUID);\n    MockAudioObjects::CreateMockDevice(kBGMDeviceUID_UISounds);\n\n    devices = [BGMMockAudioDeviceManager new];\n    defaults = [BGMMockUserDefaults new];\n    \n    // These are the IDs hardcoded in BGMSpotify and BGMVLC.\n    spotifyID = [[NSUUID alloc] initWithUUIDString:@\"EC2A907F-8515-4687-9570-1BF63176E6D8\"];\n    vlcID = [[NSUUID alloc] initWithUUIDString:@\"5226F4B9-C740-4045-A273-4B8EABC0E8FC\"];\n}\n\n- (void) tearDown {\n    [super tearDown];\n    MockAudioObjects::DestroyMocks();\n}\n\n- (void) testNoSelectedMusicPlayerStored_iTunesDefault {\n    // Test the case where the user has never changed the music player preference.\n    \n    // Test with iTunes as the default.\n    BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices\n                                                        defaultMusicPlayerID:[BGMiTunes sharedMusicPlayerID]\n                                                          musicPlayerClasses:@[ BGMiTunes.class, BGMVLC.class ]\n                                                                userDefaults:defaults];\n    \n    XCTAssertEqual(players.musicPlayers.count, 2);\n    \n    for (id<BGMMusicPlayer> player in players.musicPlayers) {\n        XCTAssertTrue([player isKindOfClass:BGMiTunes.class] || [player isKindOfClass:BGMVLC.class]);\n    }\n    \n    XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, [BGMiTunes sharedMusicPlayerID]);\n    XCTAssertEqualObjects(players.selectedMusicPlayer.name, @\"iTunes\");\n}\n\n\n- (void) testNoSelectedMusicPlayerStored_vlcDefault {\n    // Test the case where the user has never changed the music player preference.\n\n    // Test with VLC as the default.\n    BGMMusicPlayers* players =\n            [[BGMMusicPlayers alloc] initWithAudioDevices:devices\n                                     defaultMusicPlayerID:vlcID\n                                       musicPlayerClasses:@[ BGMiTunes.class,\n                                                             BGMVLC.class,\n                                                             BGMDecibel.class ]\n                                             userDefaults:defaults];\n    \n    XCTAssertEqual(players.musicPlayers.count, 3);\n    \n    for (id<BGMMusicPlayer> player in players.musicPlayers) {\n        XCTAssertTrue([player isKindOfClass:BGMiTunes.class] ||\n                      [player isKindOfClass:BGMVLC.class] ||\n                      [player isKindOfClass:BGMDecibel.class]);\n    }\n    \n    XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, vlcID);\n    XCTAssertEqualObjects(players.selectedMusicPlayer.name, @\"VLC\");\n}\n\n- (void) testSelectedMusicPlayerInUserDefaults {\n    defaults.selectedPlayerID = spotifyID;\n    \n    BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices\n                                                        defaultMusicPlayerID:[BGMiTunes sharedMusicPlayerID]\n                                                          musicPlayerClasses:@[ BGMiTunes.class,\n                                                                                BGMVLC.class,\n                                                                                BGMSpotify.class ]\n                                                                userDefaults:defaults];\n    \n    XCTAssertEqual(players.musicPlayers.count, 3);\n    \n    XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, spotifyID);\n    XCTAssertEqualObjects(players.selectedMusicPlayer.name, @\"Spotify\");\n}\n\n- (void) testUnrecognizedSelectedMusicPlayerInUserDefaults {\n    // If there's an unrecognized ID in user defaults, the default music player should be selected.\n    defaults.selectedPlayerID = [[NSUUID alloc] initWithUUIDString:@\"11111111-1111-1111-0000-000000000000\"];\n    \n    // This initializer sets iTunes as the default music player and adds all the other music players.\n    BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices\n                                                                userDefaults:defaults];\n    \n    XCTAssert(players.musicPlayers.count >= 6);\n    \n    XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, [BGMiTunes sharedMusicPlayerID]);\n    XCTAssertEqualObjects(players.selectedMusicPlayer.name, @\"iTunes\");\n}\n\n- (void) testSelectedMusicPlayerInBGMDeviceProperties {\n    // When it doesn't find a selected music player in user defaults, it should check BGMDevice's music\n    // player properties.\n    \n    [devices bgmDevice].SetMusicPlayerBundleID(CFSTR(\"org.videolan.vlc\"));\n    \n    BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices\n                                                                userDefaults:defaults];\n    \n    XCTAssert(players.musicPlayers.count >= 6);\n    \n    XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, vlcID);\n    XCTAssertEqualObjects(players.selectedMusicPlayer.name, @\"VLC\");\n}\n\n// TODO: Test setting the selectedMusicPlayer property\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMPlayThroughRTLoggerTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThroughRTLoggerTests.mm\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Unit Include\n#import \"BGMPlayThroughRTLogger.h\"\n\n// PublicUtility Includes\n#import \"CARingBuffer.h\"\n\n// System Includes\n#import <CoreAudio/CoreAudio.h>\n#import <XCTest/XCTest.h>\n\n\n@interface BGMPlayThroughRTLoggerTests : XCTestCase\n\n@end\n\n@implementation BGMPlayThroughRTLoggerTests\n{\n    BGMPlayThroughRTLogger* logger;\n}\n\n- (void) setUp {\n    [super setUp];\n    logger = new BGMPlayThroughRTLogger;\n}\n\n- (void) tearDown {\n    [super tearDown];\n    delete logger;\n}\n\n- (void) testLogReleasingWaitingThreads {\n    logger->LogReleasingWaitingThreads();\n    [self assertLoggedOneDebugMessage];\n}\n\n- (void) testLogIfMachError_ReleaseWaitingThreadsSignal {\n    logger->LogIfMachError_ReleaseWaitingThreadsSignal(KERN_SUCCESS);\n    [self assertLoggedNoMessages];\n}\n\n- (void) testLogIfDroppedFrames_didNotDropFrames {\n    logger->LogIfDroppedFrames(11256.0, 11256.0);\n    [self assertLoggedNoMessages];\n}\n\n- (void) testLogIfDroppedFrames_didDropFrames {\n    logger->LogIfDroppedFrames(11256.0, 11768.0);\n    [self assertLoggedOneDebugMessage];\n}\n\n- (void) testLogNoSamplesReady {\n    logger->LogNoSamplesReady(512, 1024, 512.0);\n    [self assertLoggedOneDebugMessage];\n}\n\n- (void) testLogExceptionStoppingIOProc_withoutErrorCode {\n    // Set a test-only flag that keeps it from calling abort() after logging the error when the\n    // tests are compiled with the debug configuration.\n    logger->mContinueOnErrorLogged = true;\n\n    logger->LogExceptionStoppingIOProc(\"InputDeviceIOProc\");\n    [self assertLoggedOneErrorMessage];\n}\n\n- (void) testLogExceptionStoppingIOProc_withErrorCode {\n    // Set a test-only flag that keeps it from calling abort() after logging the error when the\n    // tests are compiled with the debug configuration.\n    logger->mContinueOnErrorLogged = true;\n\n    logger->LogExceptionStoppingIOProc(\"OutputDeviceIOProc\", kAudioHardwareUnknownPropertyError);\n    [self assertLoggedOneErrorMessage];\n}\n\n- (void) testLogUnexpectedIOStateAfterStopping {\n    logger->LogUnexpectedIOStateAfterStopping(\"OutputDeviceIOProc\", 1);\n    [self assertLoggedOneWarningMessage];\n}\n\n- (void) testLogRingBufferUnavailable {\n    logger->LogRingBufferUnavailable(\"OutputDeviceIOProc\", false);\n    [self assertLoggedOneWarningMessage];\n}\n\n- (void) testLogIfRingBufferError_Fetch_noError {\n    logger->LogIfRingBufferError_Fetch(kCARingBufferError_OK);\n    [self assertLoggedNoMessages];\n}\n\n- (void) testLogIfRingBufferError_Fetch_errorCPUOverload {\n    logger->LogIfRingBufferError_Fetch(kCARingBufferError_CPUOverload);\n    [self assertLoggedOneWarningMessage];\n}\n\n- (void) testLogIfRingBufferError_Fetch_errorTooMuch {\n    // Set a test-only flag that keeps it from calling abort() after logging the error when the\n    // tests are compiled with the debug configuration.\n    logger->mContinueOnErrorLogged = true;\n\n    logger->LogIfRingBufferError_Fetch(kCARingBufferError_TooMuch);\n    [self assertLoggedOneErrorMessage];\n}\n\n- (void) testLogIfRingBufferError_Store_noError {\n    logger->LogIfRingBufferError_Store(kCARingBufferError_OK);\n    [self assertLoggedNoMessages];\n}\n\n- (void) testLogIfRingBufferError_Store_errorCPUOverload {\n    logger->LogIfRingBufferError_Store(kCARingBufferError_CPUOverload);\n    [self assertLoggedOneWarningMessage];\n}\n\n- (void) testLogIfRingBufferError_Store_errorTooMuch {\n    // Set a test-only flag that keeps it from calling abort() after logging the error when the\n    // tests are compiled with the debug configuration.\n    logger->mContinueOnErrorLogged = true;\n\n    logger->LogIfRingBufferError_Store(kCARingBufferError_TooMuch);\n    [self assertLoggedOneErrorMessage];\n}\n\n- (void) waitForLoggingThread {\n    // Wait for it to finish logging the messages.\n    bool noMessagesLeft = logger->WaitUntilLoggerThreadIdle();\n    XCTAssert(noMessagesLeft);\n}\n\n- (void) assertLoggedNoMessages {\n    [self waitForLoggingThread];\n    XCTAssertEqual(0, logger->mNumDebugMessagesLogged);\n    XCTAssertEqual(0, logger->mNumWarningMessagesLogged);\n    XCTAssertEqual(0, logger->mNumErrorMessagesLogged);\n}\n\n- (void) assertLoggedOneDebugMessage {\n    [self waitForLoggingThread];\n    XCTAssertEqual(1, logger->mNumDebugMessagesLogged);\n    XCTAssertEqual(0, logger->mNumWarningMessagesLogged);\n    XCTAssertEqual(0, logger->mNumErrorMessagesLogged);\n}\n\n- (void) assertLoggedOneWarningMessage {\n    [self waitForLoggingThread];\n    XCTAssertEqual(0, logger->mNumDebugMessagesLogged);\n    XCTAssertEqual(1, logger->mNumWarningMessagesLogged);\n    XCTAssertEqual(0, logger->mNumErrorMessagesLogged);\n}\n\n- (void) assertLoggedOneErrorMessage {\n    [self waitForLoggingThread];\n    XCTAssertEqual(0, logger->mNumDebugMessagesLogged);\n    XCTAssertEqual(0, logger->mNumWarningMessagesLogged);\n    XCTAssertEqual(1, logger->mNumErrorMessagesLogged);\n}\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMPlayThroughTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMPlayThroughTests.mm\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Unit Include\n#import \"BGMPlayThrough.h\"\n\n// Local Includes\n#import \"MockAudioDevice.h\"\n#import \"MockAudioObjects.h\"\n\n// BGM Includes\n#import \"BGM_Types.h\"\n#import \"BGMAudioDevice.h\"\n\n// STL Includes\n#import <memory>\n\n// System Includes\n#import <XCTest/XCTest.h>\n\n\n@interface BGMPlayThroughTests : XCTestCase\n\n@end\n\n@implementation BGMPlayThroughTests {\n    BGMAudioDevice inputDevice;\n    BGMAudioDevice outputDevice;\n\n    // The unit tests use mock implementations of CAHALAudioObject and CAHALAudioDevice, which are\n    // the superclasses of BGMAudioDevice. When BGMPlayThrough calls methods on inputDevice or\n    // outputDevice, some of them will update these objects.\n    std::shared_ptr<MockAudioDevice> mockInputDevice;\n    std::shared_ptr<MockAudioDevice> mockOutputDevice;\n}\n\n- (void) setUp {\n    [super setUp];\n\n    // Set up the mocks.\n    mockInputDevice = MockAudioObjects::CreateMockDevice(kBGMDeviceUID);\n    mockOutputDevice = MockAudioObjects::CreateMockDevice(\"Mock Output Device\");\n\n    inputDevice = BGMAudioDevice(mockInputDevice->GetObjectID());\n    outputDevice = BGMAudioDevice(mockOutputDevice->GetObjectID());\n}\n\n- (void) tearDown {\n    [super tearDown];\n    MockAudioObjects::DestroyMocks();\n}\n\n- (void) testActivate {\n    // Set the mock output device's sample rate and IO buffer size.\n    outputDevice.SetNominalSampleRate(12345.0);\n    outputDevice.SetIOBufferSize(123);\n\n    // Create an instance and activate it.\n    BGMPlayThrough playThrough(inputDevice, outputDevice);\n    playThrough.Activate();\n\n    // It should set the input device's sample rate and IO buffer size to match the output device.\n    XCTAssertEqual(12345.0, inputDevice.GetNominalSampleRate());\n    XCTAssertEqual(123, inputDevice.GetIOBufferSize());\n\n    // It should add the property listeners it needs.\n    std::set<AudioObjectPropertySelector> expectedProperties {\n            kAudioDevicePropertyDeviceIsRunning,\n            kAudioDeviceProcessorOverload,\n            kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp\n    };\n\n    XCTAssertEqual(expectedProperties, mockInputDevice->mPropertiesWithListeners);\n}\n\n- (void) testDeactivate {\n    BGMPlayThrough playThrough(inputDevice, outputDevice);\n\n    playThrough.Activate();\n    playThrough.Deactivate();\n\n    // It should remove the property listeners added by Activate.\n    XCTAssert(mockInputDevice->mPropertiesWithListeners.empty());\n}\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioDevice.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"MockAudioDevice.h\"\n\n// BGM Includes\n#include \"BGM_Types.h\"\n\n// STL Includes\n#include <functional>\n\n\nMockAudioDevice::MockAudioDevice(const std::string& inUID)\n:\n    mUID(inUID),\n    mNominalSampleRate(44100.0),\n    mIOBufferSize(512),\n    MockAudioObject(static_cast<AudioObjectID>(std::hash<std::string>{}(inUID)))\n{\n}\n\nCACFString MockAudioDevice::GetPlayerBundleID() const\n{\n    if(mUID != kBGMDeviceUID)\n    {\n        throw \"Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID\";\n    }\n\n    return mPlayerBundleID;\n}\n\nvoid MockAudioDevice::SetPlayerBundleID(const CACFString& inPlayerBundleID)\n{\n    if(mUID != kBGMDeviceUID)\n    {\n        throw \"Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID\";\n    }\n\n    mPlayerBundleID = inPlayerBundleID;\n}\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioObject.h\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n#ifndef BGMAppUnitTests__MockAudioDevice\n#define BGMAppUnitTests__MockAudioDevice\n\n// Superclass Includes\n#include \"MockAudioObject.h\"\n\n// STL Includes\n#include <string>\n\n\n/*!\n * A mock audio device in our mock CoreAudio HAL. In the HAL's API class hierarchy, the base class\n * for audio devices, kAudioDeviceClassID, is the audio objects class, kAudioObjectClassID.\n *\n * The unit tests generally use instances of this class to verify the HAL is being queried correctly\n * and to control the responses that the code they're testing will receive from the mock HAL.\n */\nclass MockAudioDevice\n:\n        public MockAudioObject\n{\n\npublic:\n    MockAudioDevice(const std::string& inUID);\n\n    /*!\n     * @return This device's music player bundle ID property.\n     * @throws If this device isn't a mock of BGMDevice.\n     */\n    CACFString GetPlayerBundleID() const;\n    /*!\n     * Set this device's music player bundle ID property.\n     * @throws If this device isn't a mock of BGMDevice.\n     */\n    void SetPlayerBundleID(const CACFString& inPlayerBundleID);\n\n    /*!\n     * The device's UID. The UID is a persistent token used to identify a particular audio device\n     * across boot sessions.\n     */\n    const std::string mUID;\n    Float64 mNominalSampleRate;\n    UInt32 mIOBufferSize;\n\nprivate:\n    CACFString mPlayerBundleID { \"\" };\n\n};\n\n#endif /* BGMAppUnitTests__MockAudioDevice */\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioObject.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"MockAudioObject.h\"\n\n\nMockAudioObject::MockAudioObject(AudioObjectID inAudioObjectID)\n:\n        mAudioObjectID(inAudioObjectID)\n{\n}\n\nAudioObjectID MockAudioObject::GetObjectID() const\n{\n    return mAudioObjectID;\n}\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioObject.h\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n#ifndef BGMAppUnitTests__MockAudioObject\n#define BGMAppUnitTests__MockAudioObject\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n\n// STL Includes\n#include <set>\n\n// System Includes\n#include <CoreAudio/CoreAudio.h>\n\n\n/*!\n * The base class for mock audio objects in our mock CoreAudio HAL. Maps to kAudioObjectClassID\n * (AudioHardwareBase.h) in the HAL's API class hierarchy.\n */\nclass MockAudioObject\n{\n\npublic:\n    MockAudioObject(AudioObjectID inAudioObjectID);\n    virtual ~MockAudioObject() = default;\n\n    AudioObjectID GetObjectID() const;\n\n    /*!\n     * The properties that callers have added listeners for (and haven't since removed). See\n     * CAHALAudioObject::AddPropertyListener and CAHALAudioObject::RemovePropertyListener.\n     */\n    std::set<AudioObjectPropertySelector> mPropertiesWithListeners;\n\nprivate:\n    AudioObjectID mAudioObjectID;\n\n};\n\n#endif /* BGMAppUnitTests__MockAudioObject */\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioObjects.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2020, 2025 Kyle Neideck\n//\n\n// Self Include\n#include \"MockAudioObjects.h\"\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n\n// STL Includes\n#include <vector>\n\n\n// static\nMockAudioObjects::MockDeviceMap MockAudioObjects::sDevices;\n\n// static\nMockAudioObjects::MockDeviceMapByUID MockAudioObjects::sDevicesByUID;\n\n// static\nstd::shared_ptr<MockAudioDevice> MockAudioObjects::CreateMockDevice(const std::string& inUID)\n{\n    std::shared_ptr<MockAudioDevice> mockDevice = std::make_shared<MockAudioDevice>(inUID);\n\n    sDevices.insert(MockDeviceMap::value_type(mockDevice->GetObjectID(), mockDevice));\n    sDevicesByUID.insert(MockDeviceMapByUID::value_type(inUID, mockDevice));\n\n    return mockDevice;\n}\n\n// static\nvoid MockAudioObjects::DestroyMocks()\n{\n    sDevices.clear();\n}\n\n// static\nstd::shared_ptr<MockAudioObject> MockAudioObjects::GetAudioObject(AudioObjectID inAudioObjectID)\n{\n    auto device = GetAudioDeviceOrNull(inAudioObjectID);\n\n    if(device)\n    {\n        return device;\n    }\n\n    // Devices are the only audio objects we currently mock.\n\n    // Tests have to create mocks for all of the audio objects they expect the code they test to\n    // access. They should fail if it accesses any others.\n    throw \"Mock audio object not found.\";\n}\n\n// static\nstd::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(AudioObjectID inAudioObjectID)\n{\n    auto device = GetAudioDeviceOrNull(inAudioObjectID);\n\n    if(device)\n    {\n        return device;\n    }\n\n    // Tests have to create mocks for all of the audio devices they expect the code they test to\n    // access. They should fail if it accesses any others.\n    throw \"Mock audio device not found.\";\n}\n\n// static\nstd::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(CFStringRef inUID)\n{\n    // Convert inUID to a std::string.\n    UInt32 uidCStringLen = CACFString::GetStringByteLength(inUID) + 1;\n    std::vector<char> uidCString(uidCStringLen);\n    CACFString::GetCString(inUID, uidCString.data(), uidCStringLen);\n    std::string uid = std::string(uidCString.data());\n\n    return GetAudioDevice(uid);\n}\n\n// static\nstd::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(const std::string& inUID)\n{\n    auto device = sDevicesByUID.find(inUID);\n\n    if(device != sDevicesByUID.end())\n    {\n        return device->second;\n    }\n\n    return nullptr;\n}\n\n// static\nstd::shared_ptr<MockAudioDevice>\nMockAudioObjects::GetAudioDeviceOrNull(AudioObjectID inAudioObjectID)\n{\n    auto device = sDevices.find(inAudioObjectID);\n\n    if(device != sDevices.end())\n    {\n        return device->second;\n    }\n\n    return nullptr;\n}\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  MockAudioObjects.h\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n#ifndef BGMAppUnitTests__MockAudioObjects\n#define BGMAppUnitTests__MockAudioObjects\n\n// Local Includes\n#include \"MockAudioObject.h\"\n#include \"MockAudioDevice.h\"\n\n// STL Includes\n#include <map>\n#include <memory>\n#include <string>\n\n// System Includes\n#include <CoreAudio/CoreAudio.h>\n\n\nclass MockAudioObjects\n{\n\npublic:\n    /*!\n     * Create a mock audio device in the mock CoreAudio HAL.\n     *\n     * The mock device will then be accessible using GetAudioObject and GetAudioDevice. The\n     * Mock_CAHAL* implementations will access the mock device when they query the mock HAL.\n     *\n     * Unit tests can check the mock device to verify the code they're testing has called the mocked\n     * CAHAL classes correctly. They can also modify the mock device to control the Mock_CAHAL*\n     * implementations, e.g. to have CAHALAudioDevice::IsAlive return false so the test can cover\n     * the case where a device is being removed from the system.\n     *\n     * @param inUID The UID string to give the device. The UID is a persistent token used to\n     *              identify a particular audio device across boot sessions.\n     * @return The mock device.\n     */\n    static std::shared_ptr<MockAudioDevice> CreateMockDevice(const std::string& inUID);\n\n    /*!\n     * Remove all mock audio objects from the mock HAL. (Currently, mock devices are the only mock\n     * objects that can be created.)\n     */\n    static void DestroyMocks();\n\n    /*! Get a mock audio object by its ID. */\n    static std::shared_ptr<MockAudioObject> GetAudioObject(AudioObjectID inAudioObjectID);\n\n    /*! Get a mock audio device by its ID. */\n    static std::shared_ptr<MockAudioDevice> GetAudioDevice(AudioObjectID inAudioDeviceID);\n    /*! Get a mock audio device by its UID. */\n    static std::shared_ptr<MockAudioDevice> GetAudioDevice(const std::string& inUID);\n    /*! Get a mock audio device by its UID. */\n    static std::shared_ptr<MockAudioDevice> GetAudioDevice(CFStringRef inUID);\n\nprivate:\n    typedef std::map<AudioObjectID, std::shared_ptr<MockAudioDevice>> MockDeviceMap;\n    typedef std::map<std::string, std::shared_ptr<MockAudioDevice>> MockDeviceMapByUID;\n\n    static std::shared_ptr<MockAudioDevice> GetAudioDeviceOrNull(AudioObjectID inAudioDeviceID);\n\n    /*! Maps IDs to mocked audio devices. */\n    static MockDeviceMap sDevices;\n    /*! Maps UIDs (ID strings) to mocked audio devices. */\n    static MockDeviceMapByUID sDevicesByUID;\n\n};\n\n#endif /* BGMAppUnitTests__MockAudioObjects */\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  Mock_CAHALAudioDevice.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"CAHALAudioDevice.h\"\n\n// Local Includes\n#include \"MockAudioDevice.h\"\n#include \"MockAudioObjects.h\"\n\n// BGM Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n#include \"CAHALAudioSystemObject.h\"\n#include \"CAPropertyAddress.h\"\n\n\n#pragma clang diagnostic ignored \"-Wunused-parameter\"\n\nCAHALAudioDevice::CAHALAudioDevice(AudioObjectID inObjectID)\n:\n    CAHALAudioObject(inObjectID)\n{\n}\n\nCAHALAudioDevice::CAHALAudioDevice(CFStringRef inUID)\n:\n        CAHALAudioObject(CAHALAudioSystemObject().GetAudioDeviceForUID(inUID))\n{\n}\n\nCAHALAudioDevice::~CAHALAudioDevice()\n{\n}\n\nvoid\tCAHALAudioDevice::GetCurrentVirtualFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const\n{\n    ioNumberStreams = 1;\n    CAPropertyAddress theAddress(kAudioStreamPropertyVirtualFormat);\n    UInt32 theSize = sizeof(AudioStreamBasicDescription);\n    GetPropertyData(theAddress, 0, NULL, theSize, outFormats);\n}\n\nUInt32\tCAHALAudioDevice::GetIOBufferSize() const\n{\n    return MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize;\n}\n\nvoid\tCAHALAudioDevice::SetIOBufferSize(UInt32 inBufferSize)\n{\n    MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize = inBufferSize;\n}\n\nbool\tCAHALAudioDevice::IsAlive() const\n{\n    return true;\n}\n\nAudioDeviceIOProcID\tCAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc inIOProc, void* inClientData)\n{\n    return reinterpret_cast<AudioDeviceIOProcID>(0x99990000);\n}\n\nvoid\tCAHALAudioDevice::DestroyIOProcID(AudioDeviceIOProcID inIOProcID)\n{\n}\n\nFloat64\tCAHALAudioDevice::GetNominalSampleRate() const\n{\n    return MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate;\n}\n\nvoid\tCAHALAudioDevice::SetNominalSampleRate(Float64 inSampleRate)\n{\n    MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate = inSampleRate;\n}\n\nCFStringRef    CAHALAudioDevice::CopyDeviceUID() const\n{\n    std::string uid = MockAudioObjects::GetAudioDevice(GetObjectID())->mUID;\n    return CACFString(uid.c_str()).CopyCFString();\n}\n\n#pragma mark Unimplemented Methods\n\nbool\tCAHALAudioDevice::HasModelUID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioDevice::CopyModelUID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioDevice::CopyConfigurationApplicationBundleID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFURLRef\tCAHALAudioDevice::CopyIconLocation() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetTransportType() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::CanBeDefaultDevice(bool inIsInput, bool inIsSystem) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasDevicePlugInStatus() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nOSStatus\tCAHALAudioDevice::GetDevicePlugInStatus() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsHidden() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\npid_t\tCAHALAudioDevice::GetHogModeOwner() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsHogModeSettable() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::TakeHogMode()\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::ReleaseHogMode()\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasPreferredStereoChannels(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetPreferredStereoChannels(bool inIsInput, UInt32& outLeft, UInt32& outRight) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, UInt32 inLeft, UInt32 inRight)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasPreferredChannelLayout(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetPreferredChannelLayout(bool inIsInput, AudioChannelLayout& outChannelLayout) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, AudioChannelLayout& inChannelLayout)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberRelatedAudioDevices() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetRelatedAudioDevices(UInt32& ioNumberRelatedDevices, AudioObjectID* outRelatedDevices) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberStreams(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetStreams(bool inIsInput, UInt32& ioNumberStreams, AudioObjectID* outStreamList) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetCurrentPhysicalFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsRunning() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsRunningSomewhere() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetLatency(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetSafetyOffset(bool inIsInput) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasClockDomain() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetClockDomain() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat64\tCAHALAudioDevice::GetActualSampleRate() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetAvailableNominalSampleRateRanges(UInt32& ioNumberRanges, AudioValueRange* outRanges) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetAvailableNominalSampleRateRangeByIndex(UInt32 inIndex, Float64& outMinimum, Float64& outMaximum) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsValidNominalSampleRate(Float64 inSampleRate) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::IsIOBufferSizeSettable() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::UsesVariableIOBufferSizes() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetMaximumVariableIOBufferSize() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasIOBufferSizeRange() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetIOBufferSizeRange(UInt32& outMinimum, UInt32& outMaximum) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::StartIOProc(AudioDeviceIOProcID inIOProcID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::StartIOProcAtTime(AudioDeviceIOProcID inIOProcID, AudioTimeStamp& ioStartTime, bool inIsInput, bool inIgnoreHardware)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::StopIOProc(AudioDeviceIOProcID inIOProcID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, bool* outStreamUsage) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, const bool* inStreamUsage)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetIOCycleUsage() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetIOCycleUsage(Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetCurrentTime(AudioTimeStamp& outTime)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::TranslateTime(const AudioTimeStamp& inTime, AudioTimeStamp& outTime)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetNearestStartTime(AudioTimeStamp& ioTime, bool inIsInput, bool inIgnoreHardware)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::VolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasSubVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::SubVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::MuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::GetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasSoloControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::SoloControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::GetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasStereoPanControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::StereoPanControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nFloat32\tCAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetStereoPanControlChannels(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& outLeftChannel, UInt32& outRightChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasJackControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::GetJackControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasSubMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::SubMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::GetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasiSubOwnerControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::iSubOwnerControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::GetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasDataSourceControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::DataSourceControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetCurrentDataSourceByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberSources, UInt32* outSources) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasDataDestinationControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::DataDestinationControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetCurrentDataDestinationByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberDestinations, UInt32* outDestinations) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::HasClockSourceControl() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioDevice::ClockSourceControlIsSettable() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentClockSourceID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::SetCurrentClockSourceByID(UInt32 inID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableClockSources() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioDevice::GetAvailableClockSources(UInt32& ioNumberSources, UInt32* outSources) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioObject.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  Mock_CAHALAudioObject.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2016, 2020 Kyle Neideck\n//\n\n// Self Include\n#include \"CAHALAudioObject.h\"\n\n// Local Includes\n#include \"MockAudioObjects.h\"\n\n// BGM Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n\n\n#pragma clang diagnostic ignored \"-Wunused-parameter\"\n\nCAHALAudioObject::CAHALAudioObject(AudioObjectID inObjectID)\n:\n    mObjectID(inObjectID)\n{\n}\n\nCAHALAudioObject::~CAHALAudioObject()\n{\n}\n\nAudioObjectID\tCAHALAudioObject::GetObjectID() const\n{\n    return mObjectID;\n}\n\nvoid\tCAHALAudioObject::GetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32& ioDataSize, void* outData) const\n{\n    switch(inAddress.mSelector)\n    {\n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n            *reinterpret_cast<CFStringRef*>(outData) =\n                    MockAudioObjects::GetAudioDevice(GetObjectID())->\n                            GetPlayerBundleID().CopyCFString();\n            break;\n\n        case kAudioDevicePropertyStreams:\n            reinterpret_cast<AudioObjectID*>(outData)[0] = 1;\n            if(inAddress.mScope == kAudioObjectPropertyScopeGlobal)\n            {\n                reinterpret_cast<AudioObjectID*>(outData)[1] = 2;\n            }\n            break;\n\n        case kAudioDevicePropertyBufferFrameSize:\n            *reinterpret_cast<UInt32*>(outData) = 512;\n            break;\n\n        case kAudioDevicePropertyDeviceIsAlive:\n            *reinterpret_cast<UInt32*>(outData) = 1;\n            break;\n\n        case kAudioStreamPropertyVirtualFormat:\n        {\n            AudioStreamBasicDescription* outASBD =\n                    reinterpret_cast<AudioStreamBasicDescription*>(outData);\n            outASBD->mSampleRate = 44100.0;\n            outASBD->mFormatID = kAudioFormatLinearPCM;\n            outASBD->mFormatFlags =\n                    kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;\n            outASBD->mBytesPerPacket = 8;\n            outASBD->mFramesPerPacket = 1;\n            outASBD->mBytesPerFrame = 8;\n            outASBD->mChannelsPerFrame = 2;\n            outASBD->mBitsPerChannel = 32;\n            break;\n        }\n\n        default:\n            Throw(new CAException(kAudio_UnimplementedError));\n    }\n}\n\nvoid\tCAHALAudioObject::SetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n    switch(inAddress.mSelector)\n    {\n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n            MockAudioObjects::GetAudioDevice(GetObjectID())->SetPlayerBundleID(\n                    CACFString(*reinterpret_cast<const CFStringRef*>(inData), false));\n            break;\n        default:\n            break;\n    }\n}\n\nUInt32\tCAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n    switch(inAddress.mSelector)\n    {\n        case kAudioDevicePropertyStreams:\n            return (inAddress.mScope == kAudioObjectPropertyScopeGlobal ? 2 : 1) *\n                    sizeof(AudioObjectID);\n        default:\n            Throw(new CAException(kAudio_UnimplementedError));\n    }\n}\n\nvoid\tCAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)\n{\n    MockAudioObjects::GetAudioObject(GetObjectID())->\n            mPropertiesWithListeners.insert(inAddress.mSelector);\n}\n\nvoid\tCAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)\n{\n    MockAudioObjects::GetAudioObject(GetObjectID())->\n            mPropertiesWithListeners.erase(inAddress.mSelector);\n}\n\n#pragma mark Unimplemented Methods\n\nvoid\tCAHALAudioObject::SetObjectID(AudioObjectID inObjectID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioClassID\tCAHALAudioObject::GetClassID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioObject::GetOwnerObjectID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyOwningPlugInBundleID() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyName() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyManufacturer() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyCategoryNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nCFStringRef\tCAHALAudioObject::CopyNumberNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioObject::ObjectExists(AudioObjectID inObjectID)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nUInt32\tCAHALAudioObject::GetNumberOwnedObjects(AudioClassID inClass) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioObject::GetAllOwnedObjects(AudioClassID inClass, UInt32& ioNumberObjects, AudioObjectID* ioObjectIDs) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioObject::GetOwnedObjectByIndex(AudioClassID inClass, UInt32 inIndex)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioObject::HasProperty(const AudioObjectPropertyAddress& inAddress) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nbool\tCAHALAudioObject::IsPropertySettable(const AudioObjectPropertyAddress& inAddress) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\n"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioSystemObject.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  Mock_CAHALAudioSystemObject.cpp\n//  BGMAppUnitTests\n//\n//  Copyright © 2017, 2020 Kyle Neideck\n//\n\n// Self include\n#include \"CAHALAudioSystemObject.h\"\n\n// BGM Includes\n#include \"BGM_Types.h\"\n\n// Local Includes\n#include \"MockAudioObjects.h\"\n\n\nCAHALAudioSystemObject::CAHALAudioSystemObject()\n:\n    CAHALAudioObject(kAudioObjectSystemObject)\n{\n}\n\nCAHALAudioSystemObject::~CAHALAudioSystemObject()\n{\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef inUID) const\n{\n    auto device = MockAudioObjects::GetAudioDevice(inUID);\n\n    if(device)\n    {\n        return device->GetObjectID();\n    }\n\n    return kAudioObjectUnknown;\n}\n\n#pragma mark Unimplemented Methods\n\n#pragma clang diagnostic ignored \"-Wunused-parameter\"\n\nUInt32\tCAHALAudioSystemObject::GetNumberAudioDevices() const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioSystemObject::GetAudioDevices(UInt32& ioNumberAudioDevices, AudioObjectID* outAudioDevices) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioDeviceAtIndex(UInt32 inIndex) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioSystemObject::LogBasicDeviceInfo()\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetDefaultAudioDevice(bool inIsInput, bool inIsSystem) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nvoid\tCAHALAudioSystemObject::SetDefaultAudioDevice(bool inIsInput, bool inIsSystem, AudioObjectID inNewDefaultDevice)\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioPlugInForBundleID(CFStringRef inUID) const\n{\n    Throw(new CAException(kAudio_UnimplementedError));\n}\n\n"
  },
  {
    "path": "BGMApp/BGMThreadSafetyAnalysis.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n// Original licence at the end of this file.\n\n//\n//  BGMThreadSafetyAnalysis.h\n//  PublicUtility\n//\n//  © Copyright 2007-2020, The Clang Team\n//  Copyright © 2020 Kyle Neideck\n//\n//  Macros that wrap Clang's attributes for statically checking concurrency properties. From\n//  <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutexheader>.\n//\n\n\n#ifndef PublicUtility__BGMThreadSafetyAnalysis\n#define PublicUtility__BGMThreadSafetyAnalysis\n\n// Enable thread safety attributes only with clang.\n// The attributes can be safely erased when compiling with other compilers.\n#if defined(__clang__) && (!defined(SWIG))\n#define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))\n#else\n#define THREAD_ANNOTATION_ATTRIBUTE__(x)   // no-op\n#endif\n\n#define CAPABILITY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(capability(x))\n\n#define SCOPED_CAPABILITY \\\n  THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)\n\n#define GUARDED_BY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))\n\n#define PT_GUARDED_BY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))\n\n#define ACQUIRED_BEFORE(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))\n\n#define ACQUIRED_AFTER(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))\n\n#define REQUIRES(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))\n\n#define REQUIRES_SHARED(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))\n\n#define ACQUIRE(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))\n\n#define ACQUIRE_SHARED(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))\n\n#define RELEASE(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))\n\n#define RELEASE_SHARED(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))\n\n#define TRY_ACQUIRE(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))\n\n#define TRY_ACQUIRE_SHARED(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))\n\n#define EXCLUDES(...) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))\n\n#define ASSERT_CAPABILITY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))\n\n#define ASSERT_SHARED_CAPABILITY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))\n\n#define RETURN_CAPABILITY(x) \\\n  THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))\n\n#define NO_THREAD_SAFETY_ANALYSIS \\\n  THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)\n\n#endif /* PublicUtility__BGMThreadSafetyAnalysis */\n\n/*\nThis file is derived from \"mutex.h\" from the Clang documentation at\n<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>. This is the original license of \"mutex.h\".\n\n==============================================================================\nThe LLVM Project is under the Apache License v2.0 with LLVM Exceptions:\n==============================================================================\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n    1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n    2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n    3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n    4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n    5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n    6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n    7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n    8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n    9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n    END OF TERMS AND CONDITIONS\n\n    APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n    Copyright [yyyy] [name of copyright owner]\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n\n---- LLVM Exceptions to the Apache 2.0 License ----\n\nAs an exception, if, as a result of your compiling your source code, portions\nof this Software are embedded into an Object form of such source code, you\nmay redistribute such embedded portions in such Object form without complying\nwith the conditions of Sections 4(a), 4(b) and 4(d) of the License.\n\nIn addition, if you combine or link compiled forms of this Software with\nsoftware that is licensed under the GPLv2 (\"Combined Software\") and if a\ncourt of competent jurisdiction determines that the patent provision (Section\n3), the indemnity provision (Section 9) or other Section of the License\nconflicts with the conditions of the GPLv2, you may retroactively and\nprospectively choose to deem waived or otherwise exclude such Section(s) of\nthe License, but only in their entirety and only with respect to the Combined\nSoftware.\n\n==============================================================================\nSoftware from third parties included in the LLVM Project:\n==============================================================================\nThe LLVM Project contains third party software which is under different license\nterms. All such code will be identified clearly using at least one of two\nmechanisms:\n1) It will be in a separate directory tree with its own `LICENSE.txt` or\n   `LICENSE` file at the top containing the specific license and restrictions\n   which apply to that software, or\n2) It will contain specific license and restriction terms at the top of every\n   file.\n\n==============================================================================\nLegacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):\n==============================================================================\nUniversity of Illinois/NCSA\nOpen Source License\n\nCopyright (c) 2007-2019 University of Illinois at Urbana-Champaign.\nAll rights reserved.\n\nDeveloped by:\n\n    LLVM Team\n\n    University of Illinois at Urbana-Champaign\n\n    http://llvm.org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal with\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\n    * Redistributions of source code must retain the above copyright notice,\n      this list of conditions and the following disclaimers.\n\n    * Redistributions in binary form must reproduce the above copyright notice,\n      this list of conditions and the following disclaimers in the\n      documentation and/or other materials provided with the distribution.\n\n    * Neither the names of the LLVM Team, University of Illinois at\n      Urbana-Champaign, nor the names of its contributors may be used to\n      endorse or promote products derived from this Software without specific\n      prior written permission.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\nCONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE\nSOFTWARE.\n */\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCHelperService.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCHelperService.h\n//  BGMXPCHelper\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGMXPCProtocols.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n// This object implements the protocol which we have defined. It provides the actual behavior for the service. It is\n// 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection.\n@interface BGMXPCHelperService : NSObject <BGMXPCHelperXPCProtocol>\n\n- (id) initWithConnection:newConnection;\n\n@end\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCHelperService.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCHelperService.mm\n//  BGMXPCHelper\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMXPCHelperService.h\"\n\n// Local Includes\n#import \"BGM_Utils.h\"\n#import \"BGMXPCListenerDelegate.h\"\n#import \"BGMBackgroundMusicDevice.h\"\n\n// PublicUtility Includes\n#import \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic const int DELAY_BEFORE_CLEANING_UP_FOR_BGMAPP_SECS = 1;\n\nstatic NSXPCListenerEndpoint* __nullable sBGMAppEndpoint = nil;\nstatic NSXPCConnection* __nullable sBGMAppConnection = nil;\n\n@implementation BGMXPCHelperService {\n    NSXPCConnection* connection;\n    AudioObjectID outputDeviceToMakeDefaultOnAbnormalTermination;\n}\n\n- (id) initWithConnection:_connection {\n    if ((self = [super init])) {\n        connection = _connection;\n        outputDeviceToMakeDefaultOnAbnormalTermination = kAudioObjectUnknown;\n    }\n    \n    return self;\n}\n\n+ (NSError*) errorWithCode:(NSInteger)code description:(NSString*)description {\n        return [NSError errorWithDomain:kBGMXPCHelperMachServiceName\n                                   code:code\n                               userInfo:@{ NSLocalizedDescriptionKey: description }];\n}\n\n+ (NSError*) errorWithCode:(NSInteger)code description:(NSString*)description underlyingError:(NSError*)underlyingError {\n        return [NSError errorWithDomain:kBGMXPCHelperMachServiceName\n                                   code:code\n                               userInfo:@{ NSLocalizedDescriptionKey: description,\n                                           NSUnderlyingErrorKey: underlyingError }];\n}\n\n+ (void) withBGMAppRemoteProxy:(void (^)(id))block errorHandler:(void (^)(NSError* error))errorHandler {\n    // Retry by default\n    return [BGMXPCHelperService withBGMAppRemoteProxy:block errorHandler:errorHandler retryOnError:YES];\n}\n\n+ (void) withBGMAppRemoteProxy:(void (^)(id))block errorHandler:(void (^)(NSError* error))errorHandler retryOnError:(BOOL)retry {\n    // Wraps some error handling around a block that calls BGMApp remotely, and runs it.\n    \n    if (!sBGMAppConnection && sBGMAppEndpoint) {\n        // Create a new connection to BGMApp from the endpoint\n        @synchronized(self) {\n            sBGMAppConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:(NSXPCListenerEndpoint* __nonnull)sBGMAppEndpoint];\n            NSAssert(sBGMAppConnection, @\"NSXPCConnection::initWithListenerEndpoint returned nil\");\n            \n            [sBGMAppConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(BGMAppXPCProtocol)]];\n            sBGMAppConnection.invalidationHandler = ^{\n                sBGMAppConnection = nil;\n            };\n            \n            [sBGMAppConnection resume];\n        }\n    }\n    \n    if (sBGMAppConnection) {\n        id proxy = [sBGMAppConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {\n            if (retry) {\n                DebugMsg(\"BGMXPCHelperService::withBGMAppRemoteProxy: %s error=<%lu, %s>\",\n                         \"Error sending message to BGMApp. Creating new connection and retrying.\",\n                         [error code],\n                         [[error localizedDescription] UTF8String]);\n                \n                // Clear the stored connection so a new one will be created when we retry. (The connection might still be\n                // valid, but it's simpler to just make a new one every time.)\n                @synchronized(self) {\n                    sBGMAppConnection = nil;\n                }\n                \n                // Retry the message.\n                [BGMXPCHelperService withBGMAppRemoteProxy:block errorHandler:errorHandler retryOnError:NO];\n            } else {\n                NSLog(@\"BGMXPCHelperService::withBGMAppRemoteProxy: Error sending message to BGMApp: %@\", error);\n                errorHandler(error);\n            }\n        }];\n        \n        block(proxy);\n    } else {\n        errorHandler([BGMXPCHelperService errorWithCode:kBGMXPC_MessageFailure description:@\"No connection to BGMApp\"]);\n    }\n}\n\n// Called after the connection from BGMApp is invalidated. (If it's only been interrupted, launchd\n// might restore it, so we wait for it to be invalidated.)\n- (void) cleanUpForBGMApp {\n    // Wait a bit to see if BGMApp reconnects. Then, if it doesn't, check to see if BGMDevice has\n    // been left as the default output device. That would probably mean BGMApp crashed or was force\n    // quit or something like that, so we try to restore the user's output device from BGMXPCHelper.\n    int64_t delay = DELAY_BEFORE_CLEANING_UP_FOR_BGMAPP_SECS * NSEC_PER_SEC;\n\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),\n                   dispatch_get_main_queue(),\n                   ^{\n                       [self unsetBGMDeviceAsDefault];\n                   });\n}\n\n- (void) unsetBGMDeviceAsDefault {\n    // Check that BGMApp hasn't reconnected.\n    if (sBGMAppConnection) {\n        DebugMsg(\"BGMXPCHelperService::unsetBGMDeviceAsDefault: BGMApp connected. Doing nothing.\");\n        return;\n    }\n\n    AudioObjectID outputDevice = outputDeviceToMakeDefaultOnAbnormalTermination;\n\n    if (outputDevice == kAudioObjectUnknown) {\n        // We could set the default device arbitrarily, but it's probably not worth the effort.\n        DebugMsg(\"BGMXPCHelperService::unsetBGMDeviceAsDefault: No device to set. Doing nothing.\");\n        return;\n    }\n\n    // If BGMDevice has been left as the default device, change it to the real output device.\n    BGMLogAndSwallowExceptions(\"BGMXPCHelperService::unsetBGMDeviceAsDefault\", ([&] {\n        NSLog(@\"BGMXPCHelperService::unsetBGMDeviceAsDefault: Changing default device to %u\",\n              outputDevice);\n\n        BGMBackgroundMusicDevice().UnsetAsOSDefault(outputDevice);\n    }));\n}\n\n#pragma mark Exported Methods\n\n- (void) registerAsBGMAppWithListenerEndpoint:(NSXPCListenerEndpoint*)endpoint reply:(void (^)(void))reply {\n    [self debugWarnIfCalledByBGMDriver];\n    \n    DebugMsg(\"BGMXPCHelperService::registerAsBGMAppWithListenerEndpoint: Received BGMApp listener endpoint\");\n    \n    // Store the connection (which we now know is from BGMApp) and endpoint so all instances of this class can use them.\n    @synchronized([self class]) {\n        sBGMAppEndpoint = endpoint;\n        sBGMAppConnection = connection;\n        \n        [sBGMAppConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(BGMAppXPCProtocol)]];\n        \n        // Set the stored connection back to nil when BGMApp closes, dies or invalidates the connection.\n        sBGMAppConnection.interruptionHandler = ^{\n            @synchronized([self class]) {\n                sBGMAppConnection = nil;\n            }\n        };\n\n        sBGMAppConnection.invalidationHandler = ^{\n            @synchronized([self class]) {\n                sBGMAppConnection = nil;\n                [self cleanUpForBGMApp];\n            }\n        };\n    }\n    \n    reply();\n}\n\n- (void) unregisterAsBGMApp {\n    [self debugWarnIfCalledByBGMDriver];\n    \n    DebugMsg(\"BGMXPCHelperService::unregisterAsBGMApp: Destroying connection to BGMApp\");\n    \n    // TODO: We don't want to assume only one instance of BGMApp will be running, in case multiple users are running it.\n    @synchronized([self class]) {\n        if (sBGMAppConnection) {\n            [sBGMAppConnection invalidate];\n            sBGMAppConnection = nil;\n            sBGMAppEndpoint = nil;\n        }\n    }\n}\n\n- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI {\n    [self debugWarnIfCalledByBGMApp];\n    \n    // If this reply string isn't set before the end of this method, it's a bug\n    __block NSError* replyToBGMDriver = [BGMXPCHelperService errorWithCode:kBGMXPC_InternalError\n                                                               description:@\"Reply not set in startBGMAppPlayThroughSyncWithReply\"];\n    \n    // I couldn't find the Obj-C equivalent of xpc_connection_send_message_with_reply_sync so just wait on this\n    // semaphore until we get a reply from BGMApp (or timeout). Note that ARC handles dispatch semaphores.\n    dispatch_semaphore_t bgmAppReplySemaphore = dispatch_semaphore_create(0);\n    \n    DebugMsg(\"BGMXPCHelperService::startBGMAppPlayThroughSyncWithReply: Waiting for BGMApp to start IO on the output device\");\n    \n    // Send the message to BGMApp\n    [BGMXPCHelperService withBGMAppRemoteProxy:^(id remoteObjectProxy) {\n        [remoteObjectProxy startPlayThroughSyncWithReply:^(NSError* bgmAppReply) {\n            replyToBGMDriver = bgmAppReply;\n            dispatch_semaphore_signal(bgmAppReplySemaphore);\n        } forUISoundsDevice:isUI];\n    } errorHandler:^(NSError* error) {\n        replyToBGMDriver = [BGMXPCHelperService errorWithCode:kBGMXPC_MessageFailure\n                                                  description:[error localizedDescription]\n                                              underlyingError:error];\n        dispatch_semaphore_signal(bgmAppReplySemaphore);\n    }];\n    \n    // Wait for BGMApp's reply\n    long err = dispatch_semaphore_wait(bgmAppReplySemaphore, dispatch_time(DISPATCH_TIME_NOW, kStartIOTimeoutNsec));\n    \n    if (err != 0) {\n        replyToBGMDriver = [BGMXPCHelperService errorWithCode:kBGMXPC_Timeout\n                                                  description:@\"Timed out waiting for BGMApp\"];\n    }\n\n    // Return the reply to BGMDriver\n    DebugMsg(\"BGMXPCHelperService::startBGMAppPlayThroughSyncWithReply: Reply to BGMDriver: %s\",\n             [[replyToBGMDriver localizedDescription] UTF8String]);\n    reply(replyToBGMDriver);\n}\n\n- (void) setOutputDeviceToMakeDefaultOnAbnormalTermination:(AudioObjectID)deviceID {\n    outputDeviceToMakeDefaultOnAbnormalTermination = deviceID;\n    DebugMsg(\"BGMXPCHelperService::setOutputDeviceToMakeDefaultOnAbnormalTermination: ID set to %u\",\n             deviceID);\n}\n\n#pragma mark Debug Utils\n\n- (void) debugWarnIfCalledByBGMApp {\n#if DEBUG\n    if ([connection effectiveUserIdentifier] != [BGMXPCListenerDelegate _coreaudiodUID]) {\n        DebugMsg(\"BGMXPCHelperService::debugWarnIfCalledByBGMDriver: A method intended for BGMDriver only was (probably) called \"\n                 \"by BGMApp. Or it could have just been the tests.\");\n        NSLog(@\"%@\", [NSThread callStackSymbols]);\n    }\n#endif\n}\n\n- (void) debugWarnIfCalledByBGMDriver {\n#if DEBUG\n    if ([connection effectiveUserIdentifier] == [BGMXPCListenerDelegate _coreaudiodUID]) {\n        DebugMsg(\"BGMXPCHelperService::debugWarnIfCalledByBGMDriver: A method intended for BGMApp only was (probably) called by BGMDriver\");\n        NSLog(@\"%@\", [NSThread callStackSymbols]);\n    }\n#endif\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCListenerDelegate.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCListenerDelegate.h\n//  BGMXPCHelper\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n@interface BGMXPCListenerDelegate : NSObject <NSXPCListenerDelegate>\n\n// The UID of the _coreaudiod user, which BGMDriver runs under. This is used in debug builds, usually to warn if a remote method is\n// called by BGMApp when it's only meant to be called by BGMDriver, or vice versa.\n+ (uid_t) _coreaudiodUID;\n\n- (BOOL) listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection;\n    \n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCListenerDelegate.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCListenerDelegate.m\n//  BGMXPCHelper\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Self Include\n#import \"BGMXPCListenerDelegate.h\"\n\n// Local Includes\n#import \"BGMXPCHelperService.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n// System Includes\n#import <pwd.h>\n\n\n#pragma clang assume_nonnull begin\n\nstatic uid_t sCoreaudiodUID = 0; // Uses 0 for unset\n\n@implementation BGMXPCListenerDelegate\n\n+ (uid_t) _coreaudiodUID {\n    // Lazily get the UID of the _coreaudiod user.\n    if (sCoreaudiodUID == 0) {\n        long bufferSize = sysconf(_SC_GETPW_R_SIZE_MAX);\n        if (-1 == bufferSize) {\n            @throw @\"BGMXPCListenerDelegate::_coreaudiodUID: sysconf failed\";\n        }\n        char unusedBuffer[bufferSize];\n        struct passwd passwd, *resultPtr = NULL;\n        \n        if (0 != getpwnam_r(\"_coreaudiod\", &passwd, unusedBuffer, bufferSize, &resultPtr)) {\n            @throw @\"BGMXPCListenerDelegate::_coreaudiodUID: getpwnam_r failed\";\n        }\n        \n        // When getpwnam_r can't find the user it leaves resultPtr null\n        if (resultPtr) {\n            DebugMsg(\"BGMXPCListenerDelegate::_coreaudiodUID: Found user: name=%s uid=%u\", passwd.pw_name, passwd.pw_uid);\n            sCoreaudiodUID = passwd.pw_uid;\n        } else {\n            DebugMsg(\"BGMXPCListenerDelegate::_coreaudiodUID: Your system doesn't appear to have a user named \\\"_coreaudiod\\\". Unless \"\n                     \"there's something weird going on with your system, this is probably a bug.\");\n            sCoreaudiodUID = 0;\n        }\n    }\n    \n    return sCoreaudiodUID;\n}\n\n- (BOOL) listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection {\n    #pragma unused (listener)\n    \n    // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.\n    \n    DebugMsg(\"BGMXPCListenerDelegate::listener: Received new connection. effectiveUserIdentifier=%u _coreaudiodUID=%u\",\n             [newConnection effectiveUserIdentifier],\n             [BGMXPCListenerDelegate _coreaudiodUID]);\n    \n    // Configure the connection.\n    // First, set the interface that the exported object implements.\n    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMXPCHelperXPCProtocol)];\n    \n    // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to\n    // the exported object to handle. The connection retains the exported object.\n    newConnection.exportedObject = [[BGMXPCHelperService alloc] initWithConnection:newConnection];\n    \n    // Resuming the connection allows the system to deliver more incoming messages.\n    [newConnection resume];\n    \n    // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the\n    // connection for some reason, call -invalidate on the connection and return NO.\n    return YES;\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>BGMXPCHelper</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>XPC!</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.4.3</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2016-2024 Background Music contributors</string>\n\t<key>XPCService</key>\n\t<dict>\n\t\t<key>ServiceType</key>\n\t\t<string>User</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<!--\n    {{# post_install.sh replaces the variables formatted like {{TEMPLATE_VARIABLE}} and comments like this one. #}}\n\n    Installing/upgrading overwrites this file (/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist), so\n    you might not want to edit it directly.\n -->\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>com.bearisdriving.BGM.XPCHelper</string>\n\n    <!--\n    So macOS will list BGMXPCHelper as \"Background Music\" in the Login Items pane in System Settings. Without this\n    it uses the name from the code signing certificate, which is confusing to users.\n\n    Note: `BGMXPCHelper.xpc` and `Background Music.app` both need to be signed (and not ad hoc signed) and\n    `Background Music.app` needs to be registered with Launch Services. You might also need to unregister any\n    unsigned copies of `Background Music.app` if you have them. See\n    <https://developer.apple.com/documentation/servicemanagement/updating-helper-executables-from-earlier-versions-of-macos#Connect-services-to-app-names-in-System-Settings>.\n    -->\n    <key>AssociatedBundleIdentifiers</key>\n    <string>com.bearisdriving.BGM.App</string>\n\n    <key>ProgramArguments</key>\n    <array>\n            <!-- <string>/usr/local/libexec/BGMXPCHelper.xpc/Contents/MacOS/BGMXPCHelper</string> -->\n            <string>{{PATH_TO_BGMXPCHELPER}}/{{BGMXPCHELPER_EXECUTABLE_PATH}}</string>\n    </array>\n\n    <key>MachServices</key>\n    <dict>\n        <key>com.bearisdriving.BGM.XPCHelper</key>\n        <true/>\n    </dict>\n\n    <key>ProcessType</key>\n    <string>Adaptive</string>\n\n    <!--\n    {{# post_install.sh creates this user and group. #}}\n    -->\n\n    <key>UserName</key>\n    <string>{{BGMXPCHELPER_USER_NAME}}</string>\n    <key>GroupName</key>\n    <string>{{BGMXPCHELPER_GROUP_NAME}}</string>\n\n    <!-- \n    {{# Uncomment this to log output from BGMXPCHelper. (It logs to syslog by default, so you'd have to change  #}}\n    {{# that if you want logs to go to these files.)                                                            #}}\n    {{#                                                                                                         #}}\n    {{# <key>StandardInPath</key>                                                                               #}}\n    {{# <string>/tmp/BGMXPCHelper.stdin.log</string>                                                            #}}\n    {{# <key>StandardOutPath</key>                                                                              #}}\n    {{# <string>/tmp/BGMXPCHelper.stdout.log</string>                                                           #}}\n    {{# <key>StandardErrorPath</key>                                                                            #}}\n    {{# <string>/tmp/BGMXPCHelper.stderr.log</string>                                                           #}}\n    -->\n</dict>\n</plist>\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/main.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  main.m\n//  BGMXPCHelper\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  BGMXPCHelper passes XPC messages between BGMDriver and BGMApp. So far it's only used for synchronization while starting IO.\n//\n//  BGMApp and BGMDriver usually communicate by changing device properties and listening for notifications about those changes, which the\n//  HAL sends. We use XPC, or plan to use it, for the few cases that notifications don't suit. For example, shared memory regions can be\n//  sent via XPC. So we might be able to share a ring buffer between BGMDriver and BGMApp and avoid the latency and CPU overhead of\n//  sending the audio data through an input stream. XPC should also let us safely send messages from real-time functions.\n//\n//  Notifications would have probably worked fine for synchronizing BGMDriver and BGMApp while starting IO. But the Core Audio headers\n//  don't specify which threads notifications are sent/received on, what (if anything) they can block on, or what can be blocked by their\n//  handler callbacks. So using XPC instead makes it a bit easier to reason about the process and avoid potential deadlocks. (That said,\n//  I ended up adding a huge amount of error handling and boilerplate code for XPC, so this way is probably more confusing overall.)\n//\n//  CFMessagePort would be much simpler than XPC to use, but as far as I could tell there's no way to create a remote CFMessagePort for\n//  a Mach port without looking the Mach port up using the bootstrap service. And because BGMXPCHelper and BGMApp have to use different\n//  bootstrap namespaces, BGMXPCHelper wouldn't be able to see services vended by BGMApp. So BGMXPCHelper would be able to receive\n//  messages from BGMApp and reply to them, but not send them.\n//\n//  BGMXPCHelper has to run as a launchd daemon so the Mach service it vends will use the global bootstrap namespace. See Table 2 in\n//  <https://developer.apple.com/library/mac/technotes/tn2083/_index.html>. This is because the coreaudiod process, where BGMDriver runs,\n//  uses the global bootstrap namespace, which means it can only see services in that namespace. In practical terms, this just means we\n//  have to run BGMXPCHelper by creating a launchd.plist file for it in /Library/LaunchDaemons and loading it with a command like\n//      launchctl bootstrap system /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\n//\n\n// Local Includes\n#import \"BGMXPCProtocols.h\"\n#import \"BGMXPCListenerDelegate.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\nint main(int argc, const char* __nullable argv[]) {\n    #pragma unused (argc, argv)\n    \n    DebugMsg(\"BGMXPCHelper::main: Service starting up\");\n    \n    // Set up the one NSXPCListener for this service. It will handle all incoming connections. This checks our service in with\n    // the bootstrap service.\n    NSXPCListener* listener = [[NSXPCListener alloc] initWithMachServiceName:kBGMXPCHelperMachServiceName];\n    BGMXPCListenerDelegate* delegate = [BGMXPCListenerDelegate new];\n    listener.delegate = delegate;\n    \n    // Start receiving requests.\n    [listener resume];\n    \n    [[NSRunLoop currentRunLoop] run];\n    \n    return 0;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/post_install.sh",
    "content": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# post_install.sh\n# BGMXPCHelper\n#\n# Copyright © 2016-2020 Kyle Neideck\n#\n# Installs BGMXPCHelper's launchd plist file and \"bootstraps\" (registers/enables) it with launchd.\n#\n# When you install BGMXPCHelper with xcodebuild (or Xcode itself, if that's possible) this script\n# runs as the final build phase.\n#\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\n# Check we have the paths we need, either in environment variables from Xcode or from the args.\nif [[ -z $1 ]]; then\n    if [[ -z ${INSTALL_DIR} ]]; then\n        echo \"Environment variable INSTALL_DIR was not set.\" >&2\n        exit 1\n    fi\nelse\n    INSTALL_DIR=\"$1\"\nfi\n\nif [[ -z $2 ]]; then\n    if [[ -z ${EXECUTABLE_PATH} ]]; then\n        echo \"Environment variable EXECUTABLE_PATH was not set.\" >&2\n        exit 1\n    fi\nelse\n    EXECUTABLE_PATH=\"$2\"\nfi\n\nif [[ -z $3 ]]; then\n    if [[ -z ${TARGET_BUILD_DIR} ]]; then\n        echo \"Environment variable TARGET_BUILD_DIR was not set.\" >&2\n        exit 1\n    fi\n\n    if [[ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH} ]]; then\n        echo \"Environment variable UNLOCALIZED_RESOURCES_FOLDER_PATH was not set.\" >&2\n        exit 1\n    fi\n\n    RESOURCES_PATH=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nelse\n    RESOURCES_PATH=\"$3\"\nfi\n\n# If DEPLOYMENT_POSTPROCESSING is true, xcodebuild calls this script even if you're just building\n# (and not also installing). I'm not sure why, as we have the \"run script only when installing\"\n# option enabled.\n#\n# REAL_ACTION is a workaround for xcodebuild setting ACTION to \"install\" even if we're actually\n# making an archive.\n#\n# TODO: Archiving BGMXPCHelper from Xcode instead of using build_and_install.sh still fails.\nif ( ! [[ -z ${ACTION} ]] && [[ \"${ACTION}\" != \"install\" ]] ) || \\\n        ( ! [[ -z ${REAL_ACTION} ]] && [[ \"${REAL_ACTION}\" == \"archive\" ]] ); then\n    echo \"$0 should only be called during an install. Exiting.\"\n    exit 0\nfi\n\n# Safe mode.\nset -euo pipefail\nIFS=$'\\n\\t'\n\n# Show a warning if INSTALL_DIR isn't set to a safe installation directory.\nif [[ $(bash \"${RESOURCES_PATH}/safe_install_dir.sh\" \"${INSTALL_DIR}\") != 1 ]]; then\n    echo \"$(tput setaf 11)WARNING$(tput sgr0): Installing to \\\"${INSTALL_DIR}\\\" may be\" \\\n         \"insecure. See safe_install_dir.sh for details.\" >&2\nfi\n\nHELPER_USER=_BGMXPCHelper\n\nLAUNCHD_PLIST_INSTALL_PATH=/Library/LaunchDaemons\nLAUNCHD_PLIST_FILENAME=com.bearisdriving.BGM.XPCHelper.plist\nLAUNCHD_PLIST=\"${LAUNCHD_PLIST_INSTALL_PATH}/${LAUNCHD_PLIST_FILENAME}\"\n\n# Create an unprivileged user for BGMXPCHelper to run as.\n\nif [[ \"$(dscl . -search /Users RecordName ${HELPER_USER})\" == \"\" ]]; then\n    # Find an unused UID and GID. UIDs from 0 to 500 are reserved by OSX.\n    HELPER_UID=$(dscl . -list /Users UniqueID | \\\n        awk '{ a[$2] } END { for (i=501; i in a; i++); print i }')\n    HELPER_GID=$(dscl . -list /Groups PrimaryGroupID | \\\n        awk '{ a[$2] } END { for (i='${HELPER_UID}'; i in a; i++); print i }')\n\n    # Check the UID and GID.\n    NUMERIC_ID_REGEX='^[1-9][0-9]*$'\n\n    ([[ \"${HELPER_UID}\" =~ ${NUMERIC_ID_REGEX} ]] && \\\n        [[ ${HELPER_UID} -gt 500 ]] && \\\n        [[ \"$(dscl . -search /Users UniqueID ${HELPER_UID})\" == \"\" ]]) || \\\n        (echo \"Internal error. Failed to generate a user ID. HELPER_UID=${HELPER_UID}\" >&2; exit 1)\n\n    ([[ \"${HELPER_GID}\" =~ ${NUMERIC_ID_REGEX} ]] && \\\n        [[ ${HELPER_GID} -gt 500 ]] && \\\n        [[ \"$(dscl . -search /Groups PrimaryGroupID ${HELPER_GID})\" == \"\" ]]) || \\\n        (echo \"Internal error. Failed to generate a group ID. HELPER_GID=${HELPER_GID}\" >&2; exit 1)\n\n    # Create the group.\n    sudo dscl . -create /Groups/${HELPER_USER} PrimaryGroupID ${HELPER_GID}\n    sudo dscl . -create /Groups/${HELPER_USER} RealName \"Background Music XPC Helper Group\"\n    sudo dscl . -create /Groups/${HELPER_USER} Password '*'\n\n    # Create the user.\n    sudo dscl . -create /Users/${HELPER_USER} UniqueID ${HELPER_UID}\n    sudo dscl . -create /Users/${HELPER_USER} PrimaryGroupID ${HELPER_GID}\n    sudo dscl . -create /Users/${HELPER_USER} RealName \"Background Music XPC Helper\"\n    sudo dscl . -create /Users/${HELPER_USER} Password '*'\n    sudo dscl . -create /Users/${HELPER_USER} UserShell /usr/bin/false\n    sudo dscl . -create /Users/${HELPER_USER} NFSHomeDirectory /var/empty\n    sudo dscl . -delete /Users/${HELPER_USER} AuthenticationAuthority\n\n    # Add the user to the group\n    sudo dscl . -append /Groups/${HELPER_USER} GroupMembership ${HELPER_USER}\n\n    # Check the user and group were created.\n    [[ \"$(dscl . -search /Users RecordName ${HELPER_USER})\" != \"\" ]] || \\\n        (echo \"Internal error. Failed to ${HELPER_USER} user.\" >&2; exit 1)\n    [[ \"$(dscl . -search /Groups RecordName ${HELPER_USER})\" != \"\" ]] || \\\n        (echo \"Internal error. Failed to ${HELPER_USER} group.\" >&2; exit 1)\n\n    echo \"Created ${HELPER_USER} user.\"\nfi\n\n# Copy the plist template into place.\nsudo cp \"${RESOURCES_PATH}/${LAUNCHD_PLIST_FILENAME}.template\" \"${LAUNCHD_PLIST}\"\n\n# Set the plist's owner and permissions. (Probably not necessary, but just in case.)\nsudo chown root:wheel \"${LAUNCHD_PLIST}\"\nsudo chmod 0644 \"${LAUNCHD_PLIST}\"\n\n# Replace the template variables in the plist.\n\n# First, escape the / characters in the values because we're using / in our sed pattern. The\n# solution usually recommended is to use | or # instead of /, but file and directory names can\n# contain those characters so I think this solution is safer.\n#\n# This is using Bash substring replacement to replace every occurrence of \"/\" with \"\\/\". The general\n# form is ${string//substring/replacement}. (${string/substring/replacement} only replaces the first\n# match.)\nINSTALL_DIR_ESCAPED=\"${INSTALL_DIR//\\//\\\\/}\"\nEXECUTABLE_PATH_ESCAPED=\"${EXECUTABLE_PATH//\\//\\\\/}\"\n\n# Replace the variables in-place. They're formatted like \"{{TEMPLATE_VARIABLE}}\".\nsudo sed -i.tmp \"s/{{PATH_TO_BGMXPCHELPER}}/${INSTALL_DIR_ESCAPED}/g\" \"${LAUNCHD_PLIST}\"\n# EXECUTABLE_PATH is set by Xcode, currently to \"BGMXPCHelper.xpc/Contents/MacOS/BGMXPCHelper\".\nsudo sed -i.tmp \"s/{{BGMXPCHELPER_EXECUTABLE_PATH}}/${EXECUTABLE_PATH_ESCAPED}/g\" \"${LAUNCHD_PLIST}\"\nsudo sed -i.tmp \"s/{{BGMXPCHELPER_USER_NAME}}/${HELPER_USER}/g\" \"${LAUNCHD_PLIST}\"\nsudo sed -i.tmp \"s/{{BGMXPCHELPER_GROUP_NAME}}/${HELPER_USER}/g\" \"${LAUNCHD_PLIST}\"\n\n# Remove template-only comments.\nsudo sed -i.tmp 's/{{#.*#}}//g' \"${LAUNCHD_PLIST}\"\n\n# Clean up sed's temporary file.\nsudo rm -f \"${LAUNCHD_PLIST}.tmp\"\n\necho \"Installed ${LAUNCHD_PLIST_FILENAME} to ${LAUNCHD_PLIST_INSTALL_PATH}.\"\n\n# Unregister the plist. This disables the existing version of BGMXPCHelper.\n\necho \"Unregistering ${LAUNCHD_PLIST}. This will print errors if you don't already have a version\" \\\n     \"of it registered, or your system uses an older version of launchctl. They're safe to ignore.\"\necho \"----\"\n\n# The fallback versions of this command are for OS X 10.10 and 10.9, respectively. The \"|| true\"\n# part is so the command can fail, which it does if the plist isn't already installed, without -e\n# killing the script.\nsudo launchctl bootout system \"${LAUNCHD_PLIST}\" || \\\n    sudo launchctl unbootstrap system \"${LAUNCHD_PLIST}\" || \\\n    sudo launchctl unload \"${LAUNCHD_PLIST}\" || \\\n    true\n\necho \"----\"\n\n# Register the plist with launchd. This enables BGMXPCHelper.\n\n# The fallback version of this command is for OS X 10.9.\nsudo launchctl bootstrap system \"${LAUNCHD_PLIST}\" || \\\n    sudo launchctl load \"${LAUNCHD_PLIST}\"\n\necho \"Started the BGMXPCHelper service.\"\n\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelper/safe_install_dir.sh",
    "content": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# safe_install_dir.sh\n# BGMXPCHelper\n#\n# Copyright © 2016, 2017 Kyle Neideck\n#\n# Prints the path to a directory the BGMXPCHelper bundle can safely be installed to. Intended to be\n# used as the INSTALL_DIR environment variable for xcodebuild commands. For example,\n#     xcodebuild -project BGMApp/BGMApp.xcodeproj -target BGMXPCHelper -configuration Debug \\\n#     DSTROOT=\"/\" INSTALL_DIR=\"$(BGMApp/BGMXPCHelper/safe_install_dir.sh)\" install\n#\n# Instead of setting INSTALL_DIR, we could just move the installed bundle in post_install.sh, but\n# then we would have to leave an unused copy of the bundle in the default installation directory. If\n# we didn't, xcodebuild would fail in one of the post-processing steps it does after running\n# post_install.sh.\n#\n# The default installation directory comes from the Daemonomicon\n# <https://developer.apple.com/library/mac/technotes/tn2083/_index.html>, which recommends\n# installing daemons to /usr/local/libexec. But that isn't safe on many users' systems, because the\n# Daemonomicon also recommends that\n#\n# \"[...] daemons be owned by root, have an owning group of wheel, and use permissions 755\n# (rwxr-xr-x) for executables and directories, and 644 (rw-r--r--) for files. In addition, every\n# directory from your daemon up to the root directory must be owned by root and only writable by the\n# owner (or owned by root and sticky). If you don't do this correctly, a non-admin user might be\n# able to escalate their privileges by modifying your daemon (or shuffling it aside).\"\n#\n# BGMXPCHelper runs as the unprivileged _BGMXPCHelper user, so I don't think it would be a security\n# risk right now, but I could be wrong about that. We could also forget about this and give the\n# _BGMXPCHelper privileges of some kind in a later version.\n#\n# Installing to /usr/local/libexec would be fine on a default OS X install, which doesn't have a\n# /usr/local directory, but if the user has Homebrew installed it will have set them as the owner of\n# /usr/local (even if it already existed and was owned by root). So if the owner or permissions for\n# /usr/local/libexec aren't what we want we try /Library/Application Support instead.\n#\n# If given a directory as an argument, this script will print \"1\" if the directory meets the\n# recommendation above, or \"0\" otherwise.\n#\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\n# Safe mode.\nset -euo pipefail\nIFS=$'\\n\\t'\n\n# Checks that a directory, and its parent directories, are owned by root and are only writable by\n# root.\n#\n# Takes one param, the directory to check. Sets DIR_IS_SAFE=1 if the directory is suitable.\ncheck_dir() {\n    # Remember the current directory so we can come back here at the end of the function.\n    pushd . > /dev/null\n\n    # Normalize the path and follow symlinks.\n    REAL_PATH=$(cd \"$1\" && pwd -P)\n    cd \"${REAL_PATH}\"\n\n    DIR_IS_SAFE=0\n\n    # While the current directory's owner has UID 0...\n    while [[ \"$(stat -f '%u' .)\" == 0 ]] && \\\n        # ...and isn't writable by the group or others...\n        # (The stat command prints the current directory's permissions in octal, e.g. 755. We add a\n        # leading 0 so Bash will interpret it as an octal value.)\n        [[ $((0$(stat -f '%Lp' .) & 0022)) -eq 0 ]]; do\n        # ...go upwards until we reach the root directory.\n        cd ..\n        if [[ \"${PWD}\" -ef / ]]; then\n            DIR_IS_SAFE=1\n            break\n        fi\n    done\n\n    # Go back to the directory we were in before this function was called. (No real reason to do\n    # this yet, but it seemed like good practice.)\n    popd > /dev/null\n}\n\n# Used when we can't find a suitable installation directory. Prints an error message and exits.\n# (Reaching this point should be very uncommon.)\nfail() {\n    if [[ $ALLOW_UNSAFE_FALLBACK -eq 1 ]]; then\n        CONTINUE_ANYWAY=\"y\"\n    else\n        echo \"$(tput setaf 11)WARNING$(tput sgr0): Installing BGMXPCHelper to its default\" \\\n             \"location (${INSTALL_DIR} or, as a backup, ${BACKUP_INSTALL_DIR}) might not be\" \\\n             \"secure on this system. It's recommended that each directory from the installation\" \\\n             \"directory up to the root directory should be owned by root and not writable by any\" \\\n             \"other user. See safe_install_dir.sh for more details.\" >&2\n\n        read -e -p \"Continue anyway? [y/N]\" CONTINUE_ANYWAY\n    fi\n\n    if [[ \"${CONTINUE_ANYWAY}\" == \"y\" ]] || [[ \"${CONTINUE_ANYWAY}\" == \"Y\" ]]; then\n        echo \"${INSTALL_DIR}\"\n        exit 0\n    else\n        # Stops xcodebuild if the output is being used to set xcodebuild's INSTALL_DIR variable.\n        echo \"/dev/null\"\n        exit 1\n    fi\n}\n\nALLOW_UNSAFE_FALLBACK=0\n\n# This script can be given a directory to check as an argument, or the -y option, which tells this\n# script to print the default dir if neither of the dirs are safe. The pkg installer uses -y so it\n# can install anyway and show the user instructions to fix the permissions, rather than just\n# failing.\n#\n# (This line uses \"${1+x}\" instead of \"$1\" because having our \"safe mode\" enabled makes the script\n# fail if you reference an unset variable, even to check whether it's set or not.)\nif [[ ! -z \"${1+x}\" ]]; then\n    if [[ \"$1\" == \"-y\" ]]; then\n        ALLOW_UNSAFE_FALLBACK=1\n    else\n        # Check the given path exists and is a directory.\n        if [[ ! -d \"$1\" ]]; then echo \"$1 is not a directory.\" >&2; exit 1; fi\n\n        check_dir \"$1\"\n        echo ${DIR_IS_SAFE}\n        exit 0\n    fi\nfi\n\n# These are just for readability and to save keystrokes. If you change them, you'll have to change\n# other parts of the code as well.\nINSTALL_DIR=\"/usr/local/libexec\"\nBACKUP_INSTALL_DIR=\"/Library/Application Support/Background Music\"\n\n# Create the installation directory if it doesn't exist already.\nif [[ ! -e /usr/local ]]; then\n    sudo mkdir /usr/local\n    sudo chown root:wheel /usr/local \n    sudo chmod go-w /usr/local \nfi\nif [[ ! -e \"${INSTALL_DIR}\" ]]; then\n    sudo mkdir \"${INSTALL_DIR}\"\n    sudo chown root:wheel \"${INSTALL_DIR}\" \n    sudo chmod go-w \"${INSTALL_DIR}\" \nfi\n\n# Check the directory the build was installed to.\ncheck_dir \"${INSTALL_DIR}\"\n\nif [[ ${DIR_IS_SAFE} -eq 1 ]]; then\n    FINAL_INSTALL_DIR=\"${INSTALL_DIR}\"\nelse\n    # Try the backup directory instead.\n\n    # Make sure /Library/Application Support exists.\n    if [[ ! -e \"/Library/Application Support\" ]]; then fail; fi\n\n    # Check /Library/Application Support before adding our directory.\n    check_dir \"/Library/Application Support\"\n    if [[ ${DIR_IS_SAFE} -ne 1 ]]; then fail; fi\n\n    # Add a \"Background Music\" directory to /Library/Application Support.\n    # (Check whether it exists first so we don't sudo unless we need to.)\n    if [[ ! -e \"${BACKUP_INSTALL_DIR}\" ]]; then\n        sudo mkdir \"${BACKUP_INSTALL_DIR}\"\n        sudo chown root:wheel \"${BACKUP_INSTALL_DIR}\" \n        sudo chmod go-w \"${BACKUP_INSTALL_DIR}\" \n    fi\n\n    # Check the backup directory just to be sure.\n    check_dir \"${BACKUP_INSTALL_DIR}\"\n    if [[ ${DIR_IS_SAFE} -ne 1 ]]; then fail; fi\n\n    FINAL_INSTALL_DIR=\"${BACKUP_INSTALL_DIR}\"\nfi\n\necho \"${FINAL_INSTALL_DIR}\"\n\n\n"
  },
  {
    "path": "BGMApp/BGMXPCHelperTests/BGMXPCHelperTests-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMApp/BGMXPCHelperTests/BGMXPCHelperTests.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCHelperTests.m\n//  BGMXPCHelperTests\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Local Includes\n#import \"BGM_TestUtils.h\"\n#import \"BGMXPCProtocols.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\n// To run these tests, BGMXPCHelper has to be installed and its launchd job enabled.\n\n@interface BGMXPCHelperTests : XCTestCase\n@end\n\n@implementation BGMXPCHelperTests {\n    NSXPCConnection* connection;\n}\n\n- (void) setUp {\n    [super setUp];\n    \n    connection = [[NSXPCConnection alloc] initWithMachServiceName:kBGMXPCHelperMachServiceName\n                                                          options:NSXPCConnectionPrivileged];\n    \n    connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMXPCHelperXPCProtocol)];\n    [connection resume];\n}\n\n- (void) tearDown {\n    [connection invalidate];\n    \n    [super tearDown];\n}\n\n- (void) testStartOutputDeviceWithoutBGMAppConnected {\n    dispatch_semaphore_t replySemaphore = dispatch_semaphore_create(0);\n\n    // Unregister BGMXPCHelper's connection to BGMApp in case BGMApp didn't shutdown cleanly the last time it ran.\n    [[connection remoteObjectProxy] unregisterAsBGMApp];\n        \n    [[connection remoteObjectProxy] startBGMAppPlayThroughSyncWithReply:^(NSError* reply) {\n        XCTAssertEqual([reply code],\n                       kBGMXPC_MessageFailure,\n                       @\"Check that BGMApp isn't running, which would cause this failure\");\n        \n        dispatch_semaphore_signal(replySemaphore);\n    } forUISoundsDevice:NO];\n\n    // Very long timeout to make it less likely to fail in CI builds when there's high contention.\n    if (0 != dispatch_semaphore_wait(replySemaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * 60 * NSEC_PER_SEC))) {\n        XCTFail(@\"Timed out waiting for BGMXPCHelper\");\n    }\n}\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/BGMDebugLogging.c",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDebugLogging.c\n//  PublicUtility\n//\n//  Copyright © 2020, 2024 Kyle Neideck\n//\n\n// Self Include\n#include \"BGMDebugLogging.h\"\n\n\n#pragma clang assume_nonnull begin\n\n// It's probably not ideal to use a global variable for this, but it's a lot easier.\n#if DEBUG || CoreAudio_Debug\n    // Enable debug logging by default in debug builds.\n    int gDebugLoggingIsEnabled = 1;\n#else\n    int gDebugLoggingIsEnabled = 0;\n#endif\n\n// We don't bother synchronising accesses of gDebugLoggingIsEnabled because it isn't really\n// necessary and would complicate code that accesses it on realtime threads.\nint BGMDebugLoggingIsEnabled(void)\n{\n    return gDebugLoggingIsEnabled;\n}\n\nvoid BGMSetDebugLoggingEnabled(int inEnabled)\n{\n    gDebugLoggingIsEnabled = inEnabled;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/BGMDebugLogging.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMDebugLogging.h\n//  PublicUtility\n//\n//  Copyright © 2020 Kyle Neideck\n//\n//  Functions to globally enable/disable debug logging, i.e. more detailed logging to help diagnose\n//  bugs. If debug logging is enabled, the DebugMsg macro from CADebugMacros.h (and possibly others)\n//  will log messages. If not, it won't do anything.\n//\n//  If the preprocessor macro CoreAudio_UseSysLog is true, which is currently the case for all build\n//  variants (see BGMApp/BGMApp.xcodeproj/project.pbxproj and\n//  BGMDriver/BGMDriver.xcodeproj/project.pbxproj), those messages will be logged using syslog and\n//  can be read using Console.app. Try searching for \"background music\", \"bgm\" or \"coreaudiod\".\n//\n//  Debug logging is enabled by default in debug builds, but in release builds you have to enable it\n//  by option-clicking the status bar icon and then checking the Debug Logging menu item. Enabling\n//  debug logging probably won't cause glitches, but we don't try to guarantee that and it's not\n//  well tested.\n//\n\n#ifndef PublicUtility__BGMDebugLogging\n#define PublicUtility__BGMDebugLogging\n\n#pragma clang assume_nonnull begin\n\n/*!\n * @return Non-zero if debug logging is globally enabled. (Probably -- it's not synchronised.)\n *         Real-time safe.\n */\n#if defined(__cplusplus)\nextern \"C\"\n#endif\nint BGMDebugLoggingIsEnabled(void);\n\n/*!\n * @param inEnabled Non-zero to globally enable debug logging, zero to disable it. The change might\n *                  not be visible to other threads immediately.\n */\n#if defined(__cplusplus)\nextern \"C\"\n#endif\nvoid BGMSetDebugLoggingEnabled(int inEnabled);\n\n#pragma clang assume_nonnull end\n\n#endif /* PublicUtility__BGMDebugLogging */\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAAtomic.h",
    "content": "/*\n     File: CAAtomic.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n/*\n\tThis file implements all Atomic operations using Interlocked functions specified in\n\tWinbase.h\nNOTE: According to Microsoft documentation, all Interlocked functions generates a\nfull barrier. \n\tOn Windows:\n\tAs the Interlocked functions returns the Old value, Extra checks and operations \n\tare made after the atomic operation to return value consistent with OSX counterparts.\n*/\n#ifndef __CAAtomic_h__\n#define __CAAtomic_h__\n\n#if TARGET_OS_WIN32\n\t#include <windows.h>\n\t#include <intrin.h>\n\t#pragma intrinsic(_InterlockedOr)\n\t#pragma intrinsic(_InterlockedAnd)\n#else\n\t#include <CoreFoundation/CFBase.h>\n\t#include <libkern/OSAtomic.h>\n#endif\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\ninline void CAMemoryBarrier() \n{\n#if TARGET_OS_WIN32\n\tMemoryBarrier();\n#else\n\tOSMemoryBarrier();\n#endif\n}\n\ninline SInt32 CAAtomicAdd32Barrier(SInt32 theAmt, volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\tlong lRetVal = InterlockedExchangeAdd((volatile long*)theValue, theAmt);\n\t// InterlockedExchangeAdd returns the original value which differs from OSX version. \n\t// At this point the addition would have occured and hence returning the new value\n\t// to keep it sync with OSX.\n\treturn lRetVal + theAmt;\n#else\n\treturn OSAtomicAdd32Barrier(theAmt, (volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicOr32Barrier(UInt32 theMask, volatile UInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\t// InterlockedAnd macro is not defined in x86 platform, and hence using the intrinsic\n\t// function instead.\n\tlong j = _InterlockedOr((volatile long*)theValue, theMask);\n\t// _InterlockedOr returns the original value which differs from OSX version.\n\t// Returning the new value similar to OSX\n\treturn (SInt32)(j | theMask);\n#else\n\treturn OSAtomicOr32Barrier(theMask, (volatile uint32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicAnd32Barrier(UInt32 theMask, volatile UInt32* theValue)\n{\n#if TARGET_OS_WIN32\n// InterlockedAnd macro is not defined in x86 platform, and hence using the intrinsic\n// function instead.\n\tlong j = _InterlockedAnd((volatile long*)theValue, theMask);\n\t// _InterlockedAnd returns the original value which differs from OSX version.\n\t// Returning the new value similar to OSX\n\treturn (SInt32)(j & theMask);\n#else\n\treturn OSAtomicAnd32Barrier(theMask, (volatile uint32_t *)theValue);\n#endif\n}\n\ninline bool CAAtomicCompareAndSwap32Barrier(SInt32 oldValue, SInt32 newValue, volatile SInt32 *theValue)\n{\n#if TARGET_OS_WIN32\n\t// InterlockedCompareExchange returns the old value. But we need to return bool value.\n\tlong lRetVal = InterlockedCompareExchange((volatile long*)theValue, newValue, oldValue);\n// Hence we check if the new value is set and if it is we return true else false.\n// If theValue is equal to oldValue then the swap happens. Otherwise swap doesn't happen.\n\treturn (oldValue == lRetVal);\n#else\n\treturn OSAtomicCompareAndSwap32Barrier(oldValue, newValue, (volatile int32_t *)theValue);\n#endif\n}\n\n\ninline SInt32 CAAtomicIncrement32(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn (SInt32)InterlockedIncrement((volatile long*)theValue);\n#else\n\treturn OSAtomicIncrement32((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicDecrement32(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn (SInt32)InterlockedDecrement((volatile long*)theValue);\n#else\n\treturn OSAtomicDecrement32((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicIncrement32Barrier(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn CAAtomicIncrement32(theValue);\n#else\n\treturn OSAtomicIncrement32Barrier((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicDecrement32Barrier(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn CAAtomicDecrement32(theValue);\n#else\n\treturn OSAtomicDecrement32Barrier((volatile int32_t *)theValue);\n#endif\n}\n\ninline bool CAAtomicTestAndClearBarrier(int bitToClear, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = InterlockedBitTestAndReset((long*)theAddress, bitToClear);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndClearBarrier(bitToClear, (volatile void *)theAddress);\n#endif\n}\n\ninline bool CAAtomicTestAndClear(int bitToClear, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = CAAtomicTestAndClearBarrier(bitToClear, (long*)theAddress);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndClear(bitToClear, (volatile void *)theAddress);\n#endif\n}\n\ninline bool CAAtomicTestAndSetBarrier(int bitToSet, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = InterlockedBitTestAndSet((long*)theAddress, bitToSet);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndSetBarrier(bitToSet, (volatile void *)theAddress);\n#endif\n}\n\n#pragma clang diagnostic pop\n\n// int32_t flavors -- for C++ only since we can't overload in C\n// CFBase.h defines SInt32 as signed int which is similar to int32_t. If CFBase.h is included, then\n// this will generate redefinition error. But on Mac, CFBase.h, still includes MacTypes.h where\n// SInt32 is defined as signed long so this would work there.\n// So in order to fix the redefinition errors, we define these functions only if MacTypes.h is included.\n#if defined(__cplusplus) && defined(__MACTYPES__) && !__LP64__\ninline int32_t CAAtomicAdd32Barrier(int32_t theAmt, volatile int32_t* theValue)\n{\n\treturn CAAtomicAdd32Barrier(theAmt, (volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicOr32Barrier(uint32_t theMask, volatile uint32_t* theValue)\n{\n\treturn CAAtomicOr32Barrier(theMask, (volatile UInt32 *)theValue);\n}\n\ninline int32_t CAAtomicAnd32Barrier(uint32_t theMask, volatile uint32_t* theValue)\n{\n\treturn CAAtomicAnd32Barrier(theMask, (volatile UInt32 *)theValue);\n}\n\ninline bool CAAtomicCompareAndSwap32Barrier(int32_t oldValue, int32_t newValue, volatile int32_t *theValue)\n{\n\treturn CAAtomicCompareAndSwap32Barrier(oldValue, newValue, (volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicIncrement32(volatile int32_t* theValue)\n{\n\treturn CAAtomicIncrement32((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicDecrement32(volatile int32_t* theValue)\n{\n\treturn CAAtomicDecrement32((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicIncrement32Barrier(volatile int32_t* theValue)\n{\n\treturn CAAtomicIncrement32Barrier((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicDecrement32Barrier(volatile int32_t* theValue)\n{\n\treturn CAAtomicDecrement32Barrier((volatile SInt32 *)theValue);\n}\n#endif // __cplusplus && !__LP64__\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\n#if __LP64__\ninline bool CAAtomicCompareAndSwap64Barrier( int64_t __oldValue, int64_t __newValue, volatile int64_t *__theValue )\n{\n\treturn OSAtomicCompareAndSwap64Barrier(__oldValue, __newValue, __theValue);\n}\n#endif\n\ninline bool CAAtomicCompareAndSwapPtrBarrier(void *__oldValue, void *__newValue, volatile void ** __theValue)\n{\n#if __LP64__\n\treturn CAAtomicCompareAndSwap64Barrier((int64_t)__oldValue, (int64_t)__newValue, (int64_t *)__theValue);\n#else\n\treturn CAAtomicCompareAndSwap32Barrier((int32_t)__oldValue, (int32_t)__newValue, (int32_t *)__theValue);\n#endif\n}\n\n#pragma clang diagnostic pop\n\n/* Spinlocks.  These use memory barriers as required to synchronize access to shared\n * memory protected by the lock.  The lock operation spins, but employs various strategies\n * to back off if the lock is held, making it immune to most priority-inversion livelocks.\n * The try operation immediately returns false if the lock was held, true if it took the\n * lock.  The convention is that unlocked is zero, locked is nonzero.\n */\n#define\tCA_SPINLOCK_INIT    0\n\ntypedef int32_t CASpinLock;\n\nbool    CASpinLockTry( volatile CASpinLock *__lock );\nvoid    CASpinLockLock( volatile CASpinLock *__lock );\nvoid    CASpinLockUnlock( volatile CASpinLock *__lock );\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\ninline void    CASpinLockLock( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\tOSSpinLockLock(__lock);\n#else\n\twhile (CAAtomicTestAndSetBarrier(0, (void*)__lock))\n\t\tusleep(1000); // ???\n#endif\n}\n\ninline void    CASpinLockUnlock( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\tOSSpinLockUnlock(__lock);\n#else\n\tCAAtomicTestAndClearBarrier(0, (void*)__lock);\n#endif\n}\n\ninline bool    CASpinLockTry( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\treturn OSSpinLockTry(__lock);\n#else\n\treturn (CAAtomicTestAndSetBarrier(0, (void*)__lock) == 0);\n#endif\n}\n\n#pragma clang diagnostic pop\n\n#endif // __CAAtomic_h__\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAAutoDisposer.h",
    "content": "/*\n     File: CAAutoDisposer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPtr_h__)\n#define __CAPtr_h__\n\n#include <stdlib.h>\t\t// for malloc\n#include <new>\t\t\t// for bad_alloc\n#include <string.h>\t\t// for memset\n\ninline void* CA_malloc(size_t size)\n{\n\tvoid* p = malloc(size);\n\tif (!p && size) throw std::bad_alloc();\n\treturn p;\n}\n\ninline void* CA_realloc(void* old, size_t size)\n{\n#if TARGET_OS_WIN32\n\tvoid* p = realloc(old, size);\n#else\n\tvoid* p = reallocf(old, size); // reallocf ensures the old pointer is freed if memory is full (p is NULL).\n#endif\n\tif (!p && size) throw std::bad_alloc();\n\treturn p;\n}\n\n#ifndef UINTPTR_MAX\n#if __LP64__\n#define UINTPTR_MAX\t  18446744073709551615ULL\n#else\n#define UINTPTR_MAX\t  4294967295U\n#endif\n#endif\n\ninline void* CA_calloc(size_t n, size_t size)\n{\t\n\t// ensure that multiplication will not overflow\n\tif (n && UINTPTR_MAX / n < size) throw std::bad_alloc();\n\t\n\tsize_t nsize = n*size;\n\tvoid* p = malloc(nsize);\n\tif (!p && nsize) throw std::bad_alloc();\n\n\tmemset(p, 0, nsize);\n\treturn p;\n}\n\n\n// helper class for automatic conversions\ntemplate <typename T>\nstruct CAPtrRef\n{\n\tT* ptr_;\n\n\texplicit CAPtrRef(T* ptr) : ptr_(ptr) {}\n};\n\ntemplate <typename T>\nclass CAAutoFree\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\t\n\tCAAutoFree() : ptr_(0) {}\n\t\n\texplicit CAAutoFree(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoFree(CAAutoFree<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoFree(CAAutoFree<T>& that) : ptr_(that.release()) {} // take ownership\n\n\tCAAutoFree(size_t n, bool clear = false) \n\t\t// this becomes an ambiguous call if n == 0\n\t\t: ptr_(0) \n\t\t{\n\t\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\t\tif (n > maxItems) \n\t\t\t\tthrow std::bad_alloc();\n\n\t\t\tptr_ = static_cast<T*>(clear ? CA_calloc(n, sizeof(T)) : CA_malloc(n * sizeof(T)));\n\t\t}\n\t\n\t~CAAutoFree() { free(); }\n\t\n\tvoid alloc(size_t numItems, bool clear = false) \n\t{\n\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\tif (numItems > maxItems) throw std::bad_alloc();\n\t\t\n\t\tfree();\n\t\tptr_ = static_cast<T*>(clear ? CA_calloc(numItems, sizeof(T)) : CA_malloc(numItems * sizeof(T)));\n\t}\n\t\n\tvoid allocBytes(size_t numBytes, bool clear = false) \n\t{\n\t\tfree();\n\t\tptr_ = static_cast<T*>(clear ? CA_calloc(1, numBytes) : CA_malloc(numBytes));\n\t}\n\t\n\tvoid reallocBytes(size_t numBytes) \n\t{\n\t\tptr_ = static_cast<T*>(CA_realloc(ptr_, numBytes));\n\t}\n\n\tvoid reallocItems(size_t numItems) \n\t{\n\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\tif (numItems > maxItems) throw std::bad_alloc();\n\t\t\n\t\tptr_ = static_cast<T*>(CA_realloc(ptr_, numItems * sizeof(T)));\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoFree& operator=(CAAutoFree<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoFree& operator=(CAAutoFree& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoFree& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoFree& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\n\tbool operator==(CAAutoFree const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoFree const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\t::free(ptr_);\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoFree(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoFree& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoFree<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\ntemplate <typename T>\nclass CAAutoDelete\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\tCAAutoDelete() : ptr_(0) {}\n\n\texplicit CAAutoDelete(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoDelete(CAAutoDelete<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoDelete(CAAutoDelete<T>& that) : ptr_(that.release()) {} // take ownership\n\t\n\t~CAAutoDelete() { free(); }\n\t\n\ttemplate <typename U>\n\tCAAutoDelete& operator=(CAAutoDelete<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoDelete& operator=(CAAutoDelete& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoDelete& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoDelete& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\t\n\tbool operator==(CAAutoDelete const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoDelete const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\tdelete ptr_;\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoDelete(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoDelete& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoFree<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\ntemplate <typename T>\nclass CAAutoArrayDelete\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\tCAAutoArrayDelete() : ptr_(0) {}\n\n\texplicit CAAutoArrayDelete(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoArrayDelete(CAAutoArrayDelete<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoArrayDelete(CAAutoArrayDelete<T>& that) : ptr_(that.release()) {} // take ownership\n\t\n\t// this becomes an ambiguous call if n == 0\n\tCAAutoArrayDelete(size_t n) : ptr_(new T[n]) {}\n\t\n\t~CAAutoArrayDelete() { free(); }\n\t\n\tvoid alloc(size_t numItems) \n\t{\n\t\tfree();\n\t\tptr_ = new T [numItems];\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoArrayDelete& operator=(CAAutoArrayDelete<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoArrayDelete& operator=(CAAutoArrayDelete& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoArrayDelete& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoArrayDelete& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\n\tbool operator==(CAAutoArrayDelete const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoArrayDelete const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\tdelete [] ptr_;\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoArrayDelete(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoArrayDelete& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoArrayDelete<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\n\n\n\n// convenience function\ntemplate <typename T>\nvoid free(CAAutoFree<T>& p)\n{\n\tp.free();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n#if 0\n// example program showing ownership transfer\n\nCAAutoFree<char> source()\n{\n\t// source allocates and returns ownership to the caller.\n\tconst char* str = \"this is a test\";\n\tsize_t size = strlen(str) + 1;\n\tCAAutoFree<char> captr(size, false);\n\tstrlcpy(captr(), str, size);\n\tprintf(\"source %08X %08X '%s'\\n\", &captr, captr(), captr());\n\treturn captr;\n}\n\nvoid user(CAAutoFree<char> const& captr)\n{\n\t// passed by const reference. user can access the pointer but does not take ownership.\n\tprintf(\"user: %08X %08X '%s'\\n\", &captr, captr(), captr());\n}\n\nvoid sink(CAAutoFree<char> captr)\n{\n\t// passed by value. sink takes ownership and frees the pointer on return.\n\tprintf(\"sink: %08X %08X '%s'\\n\", &captr, captr(), captr());\n}\n\n\nint main (int argc, char * const argv[]) \n{\n\n\tCAAutoFree<char> captr(source());\n\tprintf(\"main captr A %08X %08X\\n\", &captr, captr());\n\tuser(captr);\n\tsink(captr);\n\tprintf(\"main captr B %08X %08X\\n\", &captr, captr());\n    return 0;\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CABitOperations.h",
    "content": "/*\n     File: CABitOperations.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#ifndef _CABitOperations_h_\n#define _CABitOperations_h_\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n    //#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>\n\t#include <CoreFoundation/CFBase.h>\n#else\n//\t#include <MacTypes.h>\n\t#include \"CFBase.h\"\n#endif\n#include <TargetConditionals.h>\n\n// return whether a number is a power of two\ninline UInt32 IsPowerOfTwo(UInt32 x) \n{ \n\treturn (x & (x-1)) == 0;\n}\n\n// count the leading zeros in a word\n// Metrowerks Codewarrior. powerpc native count leading zeros instruction:\n// I think it's safe to remove this ...\n//#define CountLeadingZeroes(x)  ((int)__cntlzw((unsigned int)x))\n\ninline UInt32 CountLeadingZeroes(UInt32 arg)\n{\n// GNUC / LLVM have a builtin\n#if defined(__GNUC__) || defined(__llvm___)\n#if (TARGET_CPU_X86 || TARGET_CPU_X86_64)\n\tif (arg == 0) return 32;\n#endif\t// TARGET_CPU_X86 || TARGET_CPU_X86_64\n\treturn __builtin_clz(arg);\n#elif TARGET_OS_WIN32\n\tUInt32 tmp;\n\t__asm{\n\t\tbsr eax, arg\n\t\tmov ecx, 63\n\t\tcmovz eax, ecx\n\t\txor eax, 31\n\t\tmov tmp, eax\t// this moves the result in tmp to return.\n    }\n\treturn tmp;\n#else\n#error \"Unsupported architecture\"\n#endif\t// defined(__GNUC__)\n}\n// Alias (with different spelling)\n#define CountLeadingZeros CountLeadingZeroes\n\ninline UInt32 CountLeadingZeroesLong(UInt64 arg)\n{\n// GNUC / LLVM have a builtin\n#if defined(__GNUC__) || defined(__llvm___)\n#if (TARGET_CPU_X86 || TARGET_CPU_X86_64)\n\tif (arg == 0) return 64;\n#endif\t// TARGET_CPU_X86 || TARGET_CPU_X86_64\n\treturn __builtin_clzll(arg);\n#elif TARGET_OS_WIN32\n\tUInt32 x = CountLeadingZeroes((UInt32)(arg >> 32));\n\tif(x < 32)\n\t\treturn x;\n\telse\n\t\treturn 32+CountLeadingZeroes((UInt32)arg);\n#else\n#error \"Unsupported architecture\"\n#endif\t// defined(__GNUC__)\n}\n#define CountLeadingZerosLong CountLeadingZeroesLong\n\n// count trailing zeroes\ninline UInt32 CountTrailingZeroes(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(~x & (x-1));\n}\n\n// count leading ones\ninline UInt32 CountLeadingOnes(UInt32 x)\n{\n\treturn CountLeadingZeroes(~x);\n}\n\n// count trailing ones\ninline UInt32 CountTrailingOnes(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x & (~x-1));\n}\n\n// number of bits required to represent x.\ninline UInt32 NumBits(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x);\n}\n\n// base 2 log of next power of two greater or equal to x\ninline UInt32 Log2Ceil(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x - 1);\n}\n\n// base 2 log of next power of two less or equal to x\ninline UInt32 Log2Floor(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x) - 1;\n}\n\n// next power of two greater or equal to x\ninline UInt32 NextPowerOfTwo(UInt32 x)\n{\n\treturn 1 << Log2Ceil(x);\n}\n\n// counting the one bits in a word\ninline UInt32 CountOnes(UInt32 x)\n{\n\t// secret magic algorithm for counting bits in a word.\n    x = x - ((x >> 1) & 0x55555555);\n    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\n    return (((x + (x >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;\n}\n\n// counting the zero bits in a word\ninline UInt32 CountZeroes(UInt32 x)\n{\n\treturn CountOnes(~x);\n}\n\n// return the bit position (0..31) of the least significant bit\ninline UInt32 LSBitPos(UInt32 x)\n{\n\treturn CountTrailingZeroes(x & -(SInt32)x);\n}\n\n// isolate the least significant bit\ninline UInt32 LSBit(UInt32 x)\n{\n\treturn x & -(SInt32)x;\n}\n\n// return the bit position (0..31) of the most significant bit\ninline UInt32 MSBitPos(UInt32 x)\n{\n\treturn 31 - CountLeadingZeroes(x);\n}\n\n// isolate the most significant bit\ninline UInt32 MSBit(UInt32 x)\n{\n\treturn 1 << MSBitPos(x);\n}\n\n// Division optimized for power of 2 denominators\ninline UInt32 DivInt(UInt32 numerator, UInt32 denominator)\n{\n\tif(IsPowerOfTwo(denominator))\n\t\treturn numerator >> (31 - CountLeadingZeroes(denominator));\n\telse\n\t\treturn numerator/denominator;\n}\n\n#endif\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFArray.cpp",
    "content": "/*\n     File: CACFArray.cpp\n Abstract: CACFArray.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CACFArray.h\"\n\n//\tPublicUtility Includes\n#include \"CACFDictionary.h\"\n#include \"CACFNumber.h\"\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFArray\n//=============================================================================\n\nbool\tCACFArray::HasItem(const void* inItem) const\n{\n\tbool theAnswer = false;\n\tif(mCFArray != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFArrayGetCount(mCFArray)};\n\t\ttheAnswer = CFArrayContainsValue(mCFArray, theRange, inItem);\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetIndexOfItem(const void* inItem, UInt32& outIndex) const\n{\n\tbool theAnswer = false;\n\toutIndex = 0;\n\tif(mCFArray != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFArrayGetCount(mCFArray)};\n\t\tCFIndex theIndex = CFArrayGetFirstIndexOfValue(mCFArray, theRange, inItem);\n\t\tif(theIndex != -1)\n\t\t{\n\t\t\ttheAnswer = true;\n\t\t\toutIndex = ToUInt32(theIndex);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetBool(UInt32 inIndex, bool& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inIndex, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFBooleanGetTypeID()))\n\t\t{\n\t\t\toutValue = CFBooleanGetValue(static_cast<CFBooleanRef>(theValue));\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theNumericValue = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theNumericValue);\n\t\t\toutValue = theNumericValue != 0;\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetSInt32(UInt32 inIndex, SInt32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUInt32(UInt32 inIndex, UInt32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetSInt64(UInt32 inIndex, SInt64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUInt64(UInt32 inIndex, UInt64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetFloat32(UInt32 inIndex, Float32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberFloat32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetFloat64(UInt32 inIndex, Float64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberFloat64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::Get4CC(UInt32 inIndex, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inIndex, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\tCFStringRef theString = static_cast<CFStringRef>(theValue);\n\t\t\tif(CFStringGetLength(theString) == 4)\n\t\t\t{\n\t\t\t\tchar theCString[5];\n\t\t\t\tCFStringGetCString(theString, theCString, 5, kCFStringEncodingASCII);\n\t\t\t\toutValue = CFSwapInt32BigToHost(*reinterpret_cast<UInt32*>(theCString));\n\t\t\t}\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetString(UInt32 inIndex, CFStringRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFStringRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetArray(UInt32 inIndex, CFArrayRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFArrayRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetDictionary(UInt32 inIndex, CFDictionaryRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDictionaryRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetData(UInt32 inIndex, CFDataRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDataGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDataRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUUID(UInt32 inIndex, CFUUIDRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFUUIDGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFUUIDRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetCFType(UInt32 inIndex, CFTypeRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && (inIndex < GetNumberItems()))\n\t{\n\t\toutItem = CFArrayGetValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex));\n\t\ttheAnswer = outItem != NULL;\n\t}\n\t\n\treturn theAnswer;\n}\n\nvoid\tCACFArray::GetCACFString(UInt32 inIndex, CACFString& outItem) const\n{\n\toutItem = static_cast<CFStringRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFStringRef>(theItem);\n\t\t}\n\t}\n}\n\nvoid\tCACFArray::GetCACFArray(UInt32 inIndex, CACFArray& outItem) const\n{\n\toutItem = static_cast<CFArrayRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFArrayRef>(theItem);\n\t\t}\n\t}\n}\n\nvoid\tCACFArray::GetCACFDictionary(UInt32 inIndex, CACFDictionary& outItem) const\n{\n\toutItem = static_cast<CFDictionaryRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDictionaryRef>(theItem);\n\t\t}\n\t}\n}\n\nbool\tCACFArray::AppendBool(bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendSInt32(SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendUInt32(UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendSInt64(SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendUInt64(UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendFloat32(Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendFloat64(Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendString(const CFStringRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendArray(const CFArrayRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendDictionary(const CFDictionaryRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendData(const CFDataRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendCFType(const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCFArrayAppendValue(mCFArray, inItem);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertBool(UInt32 inIndex, bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertSInt32(UInt32 inIndex, SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertUInt32(UInt32 inIndex, UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertSInt64(UInt32 inIndex, SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertUInt64(UInt32 inIndex, UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertFloat32(UInt32 inIndex, Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertFloat64(UInt32 inIndex, Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertString(UInt32 inIndex, const CFStringRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertArray(UInt32 inIndex, const CFArrayRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertDictionary(UInt32 inIndex, const CFDictionaryRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertData(UInt32 inIndex, const CFDataRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertCFType(UInt32 inIndex, const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tif(inIndex < GetNumberItems())\n\t\t{\n\t\t\tCFArrayInsertValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex), inItem);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCFArrayAppendValue(mCFArray, inItem);\n\t\t}\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetBool(UInt32 inIndex, bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetSInt32(UInt32 inIndex, SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetUInt32(UInt32 inIndex, UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetSInt64(UInt32 inIndex, SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetUInt64(UInt32 inIndex, UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetFloat32(UInt32 inIndex, Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetFloat64(UInt32 inIndex, Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetString(UInt32 inIndex, const CFStringRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetArray(UInt32 inIndex, const CFArrayRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetDictionary(UInt32 inIndex, const CFDictionaryRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetData(UInt32 inIndex, const CFDataRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetCFType(UInt32 inIndex, const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCFArraySetValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex), inItem);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFArray.h",
    "content": "/*\n     File: CACFArray.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFArray_h__)\n#define __CACFArray_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CoreFoundation.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CoreFoundation.h>\n#endif\n\n#include \"CADebugMacros.h\"\n\n//=============================================================================\n//\tTypes\n//=============================================================================\n\nclass\tCACFDictionary;\nclass\tCACFString;\n\n//=============================================================================\n//\tCACFArray\n//=============================================================================\n\nclass CACFArray\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFArray()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)), mRelease(true), mMutable(true) {}\n\texplicit\t\t\tCACFArray(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(UInt32 inMaxNumberItems, bool inRelease)\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, static_cast<CFIndex>(inMaxNumberItems), &kCFTypeArrayCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(CFArrayRef inCFArray, bool inRelease)\t\t\t\t\t\t\t\t: mCFArray(const_cast<CFMutableArrayRef>(inCFArray)), mRelease(inRelease), mMutable(false) {}\n\t\t\t\t\t\tCACFArray(CFMutableArrayRef inCFArray, bool inRelease)\t\t\t\t\t\t: mCFArray(inCFArray), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(const CACFArray& inArray)\t\t\t\t\t\t\t\t\t\t\t: mCFArray(inArray.mCFArray), mRelease(inArray.mRelease), mMutable(inArray.mMutable) { Retain(); }\n\tCACFArray&\t\t\toperator=(const CACFArray& inArray)\t\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = inArray.mCFArray; mRelease = inArray.mRelease; mMutable = inArray.mMutable; Retain(); return *this; }\n\tCACFArray&\t\t\toperator=(CFArrayRef inCFArray)\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = const_cast<CFMutableArrayRef>(inCFArray); mMutable = false; Retain(); return *this; }\n\tCACFArray&\t\t\toperator=(CFMutableArrayRef inCFArray)\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = inCFArray; mMutable = true; Retain(); return *this; }\n\t\t\t\t\t\t~CACFArray()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); }\n\nprivate:\n\tvoid\t\t\t\tRetain()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFArray != NULL)) { CFRetain(mCFArray); } }\n\tvoid\t\t\t\tRelease()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFArray != NULL)) { CFRelease(mCFArray); } }\n\t\t\n//\tAttributes\npublic:\n\tbool\t\t\t\tIsValid() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray != NULL; }\n\tbool\t\t\t\tIsMutable() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable; }\n\tbool\t\t\t\tCanModify() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable && (mCFArray != NULL); }\n\t\n\tbool\t\t\t\tWillRelease() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mRelease; }\n\tvoid\t\t\t\tShouldRelease(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t\t{ mRelease = inRelease; }\n\t\n\tCFTypeID\t\t\tGetTypeID() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return CFGetTypeID(mCFArray); }\n\t\n\tCFArrayRef\t\t\tGetCFArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\tCFArrayRef\t\t\tCopyCFArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFArray != NULL) { CFRetain(mCFArray); } return mCFArray; }\n\t\n\tCFMutableArrayRef\tGetCFMutableArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\tCFMutableArrayRef\tCopyCFMutableArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFArray != NULL) { CFRetain(mCFArray); } return mCFArray; }\n\tCFPropertyListRef   AsPropertyList() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\n\tvoid\t\t\t\tSetCFMutableArrayFromCopy(CFArrayRef inArray, bool inRelease = true)\t\t{ Release(); mCFArray = CFArrayCreateMutableCopy(NULL, 0, inArray); mMutable = true; mRelease = inRelease; }\n\n//\tItem Operations\npublic:\n\tUInt32\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ UInt32 theAnswer = 0; if(mCFArray != NULL) { theAnswer = ToUInt32(CFArrayGetCount(mCFArray)); } return theAnswer; }\n\tbool\t\t\t\tHasItem(const void* inItem) const;\n\tvoid\t\t\t\tRemoveItem(const void* inItem)\t\t\t\t\t\t\t\t\t\t\t\t{ UInt32 theIndex; if(CanModify() && GetIndexOfItem(inItem, theIndex)) { RemoveItemAtIndex(theIndex); } }\n\tbool\t\t\t\tGetIndexOfItem(const void* inItem, UInt32& outIndex) const;\n\tvoid\t\t\t\tRemoveItemAtIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFArrayRemoveValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex)); } }\n\tvoid\t\t\t\tClear()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFArrayRemoveAllValues(mCFArray); } }\n\tvoid\t\t\t\tSort(CFComparatorFunction inCompareFunction)\t\t\t\t\t\t\t\t{ if(CanModify()) { CFRange theRange = { 0, CFArrayGetCount(mCFArray) }; CFArraySortValues(mCFArray, theRange, inCompareFunction, NULL); } }\n\tvoid\t\t\t\tSortNumbers()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Sort((CFComparatorFunction)CFNumberCompare); }\n\tvoid\t\t\t\tSortStrings()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Sort((CFComparatorFunction)CFStringCompare); }\n\t\n\tbool\t\t\t\tGetBool(UInt32 inIndex, bool& outValue) const;\n\tbool\t\t\t\tGetSInt32(UInt32 inIndex, SInt32& outItem) const;\n\tbool\t\t\t\tGetUInt32(UInt32 inIndex, UInt32& outItem) const;\n\tbool\t\t\t\tGetSInt64(UInt32 inIndex, SInt64& outItem) const;\n\tbool\t\t\t\tGetUInt64(UInt32 inIndex, UInt64& outItem) const;\n\tbool\t\t\t\tGetFloat32(UInt32 inIndex, Float32& outItem) const;\n\tbool\t\t\t\tGetFloat64(UInt32 inIndex, Float64& outItem) const;\n\tbool\t\t\t\tGet4CC(UInt32 inIndex, UInt32& outValue) const;\n\tbool\t\t\t\tGetString(UInt32 inIndex, CFStringRef& outItem) const;\n\tbool\t\t\t\tGetArray(UInt32 inIndex, CFArrayRef& outItem) const;\n\tbool\t\t\t\tGetDictionary(UInt32 inIndex, CFDictionaryRef& outItem) const;\n\tbool\t\t\t\tGetData(UInt32 inIndex, CFDataRef& outItem) const;\n\tbool\t\t\t\tGetUUID(UInt32 inIndex, CFUUIDRef& outItem) const;\n\tbool\t\t\t\tGetCFType(UInt32 inIndex, CFTypeRef& outItem) const;\n\t\n\tvoid\t\t\t\tGetCACFString(UInt32 inIndex, CACFString& outItem) const;\n\tvoid\t\t\t\tGetCACFArray(UInt32 inIndex, CACFArray& outItem) const;\n\tvoid\t\t\t\tGetCACFDictionary(UInt32 inIndex, CACFDictionary& outItem) const;\n\t\n\tbool\t\t\t\tAppendBool(bool inItem);\n\tbool\t\t\t\tAppendSInt32(SInt32 inItem);\n\tbool\t\t\t\tAppendUInt32(UInt32 inItem);\n\tbool\t\t\t\tAppendSInt64(SInt64 inItem);\n\tbool\t\t\t\tAppendUInt64(UInt64 inItem);\n\tbool\t\t\t\tAppendFloat32(Float32 inItem);\n\tbool\t\t\t\tAppendFloat64(Float64 inItem);\n\tbool\t\t\t\tAppendString(const CFStringRef inItem);\n\tbool\t\t\t\tAppendArray(const CFArrayRef inItem);\n\tbool\t\t\t\tAppendDictionary(const CFDictionaryRef inItem);\n\tbool\t\t\t\tAppendData(const CFDataRef inItem);\n\tbool\t\t\t\tAppendCFType(const CFTypeRef inItem);\n\t\n\tbool\t\t\t\tInsertBool(UInt32 inIndex, bool inItem);\n\tbool\t\t\t\tInsertSInt32(UInt32 inIndex, SInt32 inItem);\n\tbool\t\t\t\tInsertUInt32(UInt32 inIndex, UInt32 inItem);\n\tbool\t\t\t\tInsertSInt64(UInt32 inIndex, SInt64 inItem);\n\tbool\t\t\t\tInsertUInt64(UInt32 inIndex, UInt64 inItem);\n\tbool\t\t\t\tInsertFloat32(UInt32 inIndex, Float32 inItem);\n\tbool\t\t\t\tInsertFloat64(UInt32 inIndex, Float64 inItem);\n\tbool\t\t\t\tInsertString(UInt32 inIndex, const CFStringRef inItem);\n\tbool\t\t\t\tInsertArray(UInt32 inIndex, const CFArrayRef inItem);\n\tbool\t\t\t\tInsertDictionary(UInt32 inIndex, const CFDictionaryRef inItem);\n\tbool\t\t\t\tInsertData(UInt32 inIndex, const CFDataRef inItem);\n\tbool\t\t\t\tInsertCFType(UInt32 inIndex, const CFTypeRef inItem);\n\t\n\tbool\t\t\t\tSetBool(UInt32 inIndex, bool inItem);\n\tbool\t\t\t\tSetSInt32(UInt32 inIndex, SInt32 inItem);\n\tbool\t\t\t\tSetUInt32(UInt32 inIndex, UInt32 inItem);\n\tbool\t\t\t\tSetSInt64(UInt32 inIndex, SInt64 inItem);\n\tbool\t\t\t\tSetUInt64(UInt32 inIndex, UInt64 inItem);\n\tbool\t\t\t\tSetFloat32(UInt32 inIndex, Float32 inItem);\n\tbool\t\t\t\tSetFloat64(UInt32 inIndex, Float64 inItem);\n\tbool\t\t\t\tSetString(UInt32 inIndex, const CFStringRef inItem);\n\tbool\t\t\t\tSetArray(UInt32 inIndex, const CFArrayRef inItem);\n\tbool\t\t\t\tSetDictionary(UInt32 inIndex, const CFDictionaryRef inItem);\n\tbool\t\t\t\tSetData(UInt32 inIndex, const CFDataRef inItem);\n\tbool\t\t\t\tSetCFType(UInt32 inIndex, const CFTypeRef inItem);\n\n//\tImplementation\nprivate:\n\tCFMutableArrayRef\tmCFArray;\n\tbool\t\t\t\tmRelease;\n\tbool\t\t\t\tmMutable;\n\t\n\t\t\t\t\t\tCACFArray(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFDictionary.cpp",
    "content": "/*\n     File: CACFDictionary.cpp\n Abstract: CACFDictionary.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CACFDictionary.h\"\n\n//\tPublicUtility Includes\n#include \"CACFArray.h\"\n#include \"CACFNumber.h\"\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFDictionary\n//=============================================================================\n\nbool\tCACFDictionary::HasKey(const CFStringRef inKey) const\n{\n\treturn CFDictionaryContainsKey(mCFDictionary, inKey) != 0;\n}\n\nUInt32\tCACFDictionary::Size () const\n{\n\treturn mCFDictionary ? ToUInt32(CFDictionaryGetCount(mCFDictionary)) : 0;\n}\n\nvoid\tCACFDictionary::GetKeys (const void **keys) const\n{\n\tCFDictionaryGetKeysAndValues(mCFDictionary, keys, NULL);\n}\n\nvoid\tCACFDictionary::GetKeysAndValues (const void **keys, const void **values) const\n{\n\tCFDictionaryGetKeysAndValues(mCFDictionary, keys, values);\n}\n\nbool\tCACFDictionary::GetBool(const CFStringRef inKey, bool& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFBooleanGetTypeID()))\n\t\t{\n\t\t\toutValue = CFBooleanGetValue(static_cast<CFBooleanRef>(theValue));\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theNumericValue = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theNumericValue);\n\t\t\toutValue = theNumericValue != 0;\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetSInt32(const CFStringRef inKey, SInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt32(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetSInt64(const CFStringRef inKey, SInt64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt64(const CFStringRef inKey, UInt64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat32FromString(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<Float32>(CFStringGetDoubleValue(static_cast<CFStringRef>(theValue)));\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt32FromString(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = CFStringGetIntValue(static_cast<CFStringRef>(theValue));\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat32(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat64(const CFStringRef inKey, Float64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFixed32(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theFixed32 = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theFixed32);\n\t\t\t\n\t\t\t//\tthis is a 16.16 value so convert it to a float\n\t\t\tFloat32 theSign = theFixed32 < 0 ? -1.0f : 1.0f;\n\t\t\ttheFixed32 *= (SInt32)theSign;\n\t\t\tFloat32 theWholePart = (theFixed32 & 0x7FFF0000) >> 16;\n\t\t\tFloat32 theFractPart = theFixed32 & 0x0000FFFF;\n\t\t\ttheFractPart /= 65536.0f;\n\t\t\toutValue = theSign * (theWholePart + theFractPart);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFixed64(const CFStringRef inKey, Float64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt64 theFixed64 = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &theFixed64);\n\t\t\toutValue = static_cast<Float64>(theFixed64 >> 32);\n\t\t\toutValue += static_cast<Float64>(theFixed64 & 0x00000000FFFFFFFFLL) / static_cast<Float64>(0x0000000100000000LL);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::Get4CC(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\tCFStringRef theString = static_cast<CFStringRef>(theValue);\n\t\t\tif(CFStringGetLength(theString) == 4)\n\t\t\t{\n\t\t\t\tchar theCString[5];\n\t\t\t\tCFStringGetCString(theString, theCString, 5, kCFStringEncodingASCII);\n\t\t\t\toutValue = CFSwapInt32BigToHost(*reinterpret_cast<UInt32*>(theCString));\n\t\t\t}\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetString(const CFStringRef inKey, CFStringRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFStringRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\t\nbool\tCACFDictionary::GetArray(const CFStringRef inKey, CFArrayRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFArrayRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\t\nbool\tCACFDictionary::GetDictionary(const CFStringRef inKey, CFDictionaryRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDictionaryRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetData(const CFStringRef inKey, CFDataRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDataGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDataRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetCFType(const CFStringRef inKey, CFTypeRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tif(mCFDictionary != NULL)\n\t{\n\t\toutValue = CFDictionaryGetValue(mCFDictionary, inKey);\n\t\ttheAnswer = (outValue != NULL);\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetURL(const CFStringRef inKey, CFURLRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFURLGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFURLRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetCFTypeWithCStringKey(const char* inKey, CFTypeRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tif(mCFDictionary != NULL)\n\t{\n\t\tCACFString theKey(inKey);\n\t\tif(theKey.IsValid())\n\t\t{\n\t\t\ttheAnswer = GetCFType(theKey.GetCFString(), outValue);\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nvoid\tCACFDictionary::GetCACFString(const CFStringRef inKey, CACFString& outValue) const\n{\n\toutValue = static_cast<CFStringRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFStringRef>(theValue);\n\t\t}\n\t}\n}\n\t\nvoid\tCACFDictionary::GetCACFArray(const CFStringRef inKey, CACFArray& outValue) const\n{\n\toutValue = static_cast<CFArrayRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFArrayRef>(theValue);\n\t\t}\n\t}\n}\n\t\nvoid\tCACFDictionary::GetCACFDictionary(const CFStringRef inKey, CACFDictionary& outValue) const\n{\n\toutValue = static_cast<CFDictionaryRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDictionaryRef>(theValue);\n\t\t}\n\t}\n}\n\nbool\tCACFDictionary::AddBool(const CFStringRef inKey, bool inValue)\n{\n\tCACFBoolean theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFBoolean());\n}\n\nbool\tCACFDictionary::AddSInt32(const CFStringRef inKey, SInt32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddUInt32(const CFStringRef inKey, UInt32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddSInt64(const CFStringRef inKey, SInt64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddUInt64(const CFStringRef inKey, UInt64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddFloat32(const CFStringRef inKey, Float32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddFloat64(const CFStringRef inKey, Float64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddNumber(const CFStringRef inKey, const CFNumberRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddString(const CFStringRef inKey, const CFStringRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddArray(const CFStringRef inKey, const CFArrayRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddDictionary(const CFStringRef inKey, const CFDictionaryRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddData(const CFStringRef inKey, const CFDataRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddURL(const CFStringRef inKey, const CFURLRef inValue)\n{\n\treturn AddCFType (inKey, inValue);\n}\n\nbool\tCACFDictionary::AddCFTypeWithCStringKey(const char* inKey, const CFTypeRef inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif (inKey)\n\t{\n\t\tCACFString theKey(inKey);\n\t\tif(theKey.IsValid())\n\t\t{\n\t\t\ttheAnswer = AddCFType(theKey.GetCFString(), inValue);\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::AddCString(const CFStringRef inKey, const char* inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif (inValue)\n\t{\n\t\tCACFString theValue(inValue);\n\t\tif(theValue.IsValid())\n\t\t{\n\t\t\ttheAnswer = AddCFType(inKey, theValue.GetCFString());\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::AddCFType(const CFStringRef inKey, const CFTypeRef inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif(mMutable && (mCFDictionary != NULL) && inValue)\n\t{\n\t\tCFDictionarySetValue(mCFDictionary, inKey, inValue);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFDictionary.h",
    "content": "/*\n     File: CACFDictionary.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFDictionary_h__)\n#define __CACFDictionary_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreFoundation/CoreFoundation.h>\n#else\n\t#include <CoreFoundation.h>\n#endif\n\n//=============================================================================\n//\tTypes\n//=============================================================================\n\nclass\tCACFArray;\nclass\tCACFString;\n\n//=============================================================================\n//\tCACFDictionary\n//=============================================================================\n\nclass CACFDictionary \n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\tCACFDictionary()\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFDictionary(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), mRelease(true), mMutable(true) {}\n\texplicit\t\t\t\tCACFDictionary(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t: mCFDictionary(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\t\tCACFDictionary(CFDictionaryRef inCFDictionary, bool inRelease)\t\t\t: mCFDictionary(const_cast<CFMutableDictionaryRef>(inCFDictionary)), mRelease(inRelease), mMutable(false) {}\n\t\t\t\t\t\t\tCACFDictionary(CFMutableDictionaryRef inCFDictionary, bool inRelease)\t: mCFDictionary(inCFDictionary), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\t\tCACFDictionary(const CACFDictionary& inDictionary)\t\t\t\t\t\t: mCFDictionary(inDictionary.mCFDictionary), mRelease(inDictionary.mRelease), mMutable(inDictionary.mMutable) { Retain(); }\n\tCACFDictionary&\t\t\toperator=(const CACFDictionary& inDictionary)\t\t\t\t\t\t\t{ Release(); mCFDictionary = inDictionary.mCFDictionary; mRelease = inDictionary.mRelease; mMutable = inDictionary.mMutable; Retain(); return *this; } \n\tCACFDictionary&\t\t\toperator=(CFDictionaryRef inDictionary)\t\t\t\t\t\t\t\t\t{ Release(); mCFDictionary = const_cast<CFMutableDictionaryRef>(inDictionary); mMutable = false; Retain(); return *this; } \n\tCACFDictionary&\t\t\toperator=(CFMutableDictionaryRef inDictionary)\t\t\t\t\t\t\t{ Release(); mCFDictionary = inDictionary; mMutable = true; Retain(); return *this; } \n\t\t\t\t\t\t\t~CACFDictionary()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); }\n\nprivate:\n\tvoid\t\t\t\t\tRetain()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFDictionary != NULL)) { CFRetain(mCFDictionary); } }\n\tvoid\t\t\t\t\tRelease()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFDictionary != NULL)) { CFRelease(mCFDictionary); } }\n\t\t\n//\tAttributes\npublic:\n\tbool\t\t\t\t\tIsValid() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary != NULL; }\n\tbool\t\t\t\t\tIsMutable() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable;}\n\tbool\t\t\t\t\tCanModify() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable && (mCFDictionary != NULL); }\n\t\n\tbool\t\t\t\t\tWillRelease() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mRelease; }\n\tvoid\t\t\t\t\tShouldRelease(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t{ mRelease = inRelease; }\n\t\n\tCFDictionaryRef\t\t\tGetDict() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFDictionaryRef\t\t\tGetCFDictionary() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFDictionaryRef\t\t\tCopyCFDictionary() const\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFDictionary != NULL) { CFRetain(mCFDictionary); } return mCFDictionary; }\n\n\tCFMutableDictionaryRef\tGetMutableDict()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFMutableDictionaryRef\tGetCFMutableDictionary() const\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFMutableDictionaryRef\tCopyCFMutableDictionary() const\t\t\t\t\t\t\t\t\t\t\t{ if(mCFDictionary != NULL) { CFRetain(mCFDictionary); } return mCFDictionary; }\n\tvoid\t\t\t\t\tSetCFMutableDictionaryFromCopy(CFDictionaryRef inDictionary, bool inRelease = true)\t\t{ Release(); mCFDictionary = CFDictionaryCreateMutableCopy(NULL, 0, inDictionary); mMutable = true; mRelease = inRelease; }\n\tvoid\t\t\t\t\tSetCFMutableDictionaryToEmpty(bool inRelease = true)\t\t\t\t\t{ Release(); mCFDictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); mMutable = true; mRelease = inRelease; }\n\n\tCFPropertyListRef\t\tAsPropertyList() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tOSStatus\t\t\t\tGetDictIfMutable(CFMutableDictionaryRef& outDict) const\t\t\t\t\t{ OSStatus theAnswer = -1; if(mMutable) { outDict = mCFDictionary; theAnswer = 0; } return theAnswer; }\n\n//\tItem Operations\npublic:\n\tbool\t\t\t\t\tHasKey(const CFStringRef inKey) const;\n\tUInt32\t\t\t\t\tSize() const;\n\tvoid\t\t\t\t\tGetKeys(const void** keys) const;\n\tvoid\t\t\t\t\tGetKeysAndValues (const void **keys, const void **values) const;\n\t\n\tbool\t\t\t\t\tGetBool(const CFStringRef inKey, bool& outValue) const;\n\tbool\t\t\t\t\tGetSInt32(const CFStringRef inKey, SInt32& outValue) const;\n\tbool\t\t\t\t\tGetUInt32(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetUInt32FromString(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetSInt64(const CFStringRef inKey, SInt64& outValue) const;\n\tbool\t\t\t\t\tGetUInt64(const CFStringRef inKey, UInt64& outValue) const;\n\tbool\t\t\t\t\tGetFloat32(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFloat32FromString(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFloat64(const CFStringRef inKey, Float64& outValue) const;\n\tbool\t\t\t\t\tGetFixed32(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFixed64(const CFStringRef inKey, Float64& outValue) const;\n\tbool\t\t\t\t\tGet4CC(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetString(const CFStringRef inKey, CFStringRef& outValue) const;\t\n\tbool\t\t\t\t\tGetArray(const CFStringRef inKey, CFArrayRef& outValue) const;\t\n\tbool\t\t\t\t\tGetDictionary(const CFStringRef inKey, CFDictionaryRef& outValue) const;\t\n\tbool\t\t\t\t\tGetData(const CFStringRef inKey, CFDataRef& outValue) const;\n\tbool\t\t\t\t\tGetCFType(const CFStringRef inKey, CFTypeRef& outValue) const;\n\tbool\t\t\t\t\tGetURL(const CFStringRef inKey, CFURLRef& outValue) const;\n\tbool\t\t\t\t\tGetCFTypeWithCStringKey(const char* inKey, CFTypeRef& outValue) const;\n\n\tvoid\t\t\t\t\tGetCACFString(const CFStringRef inKey, CACFString& outItem) const;\n\tvoid\t\t\t\t\tGetCACFArray(const CFStringRef inKey, CACFArray& outItem) const;\n\tvoid\t\t\t\t\tGetCACFDictionary(const CFStringRef inKey, CACFDictionary& outItem) const;\n\t\n\tbool\t\t\t\t\tAddBool(const CFStringRef inKey, bool inValue);\n\tbool\t\t\t\t\tAddSInt32(const CFStringRef inKey, SInt32 inValue);\n\tbool\t\t\t\t\tAddUInt32(const CFStringRef inKey, UInt32 inValue);\n\tbool\t\t\t\t\tAddSInt64(const CFStringRef inKey, SInt64 inValue);\n\tbool\t\t\t\t\tAddUInt64(const CFStringRef inKey, UInt64 inValue);\n\tbool\t\t\t\t\tAddFloat32(const CFStringRef inKey, Float32 inValue);\n\tbool\t\t\t\t\tAddFloat64(const CFStringRef inKey, Float64 inValue);\n\tbool\t\t\t\t\tAddNumber(const CFStringRef inKey, const CFNumberRef inValue);\n\tbool\t\t\t\t\tAddString(const CFStringRef inKey, const CFStringRef inValue);\n\tbool\t\t\t\t\tAddArray(const CFStringRef inKey, const CFArrayRef inValue);\n\tbool\t\t\t\t\tAddDictionary(const CFStringRef inKey, const CFDictionaryRef inValue);\n\tbool\t\t\t\t\tAddData(const CFStringRef inKey, const CFDataRef inValue);\n\tbool\t\t\t\t\tAddCFType(const CFStringRef inKey, const CFTypeRef inValue);\n\tbool\t\t\t\t\tAddURL(const CFStringRef inKey, const CFURLRef inValue);\n\t\n\tbool\t\t\t\t\tAddCFTypeWithCStringKey(const char* inKey, const CFTypeRef inValue);\n\tbool\t\t\t\t\tAddCString(const CFStringRef inKey, const char* inValue);\n\n\tvoid\t\t\t\t\tRemoveKey(const CFStringRef inKey)\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFDictionaryRemoveValue(mCFDictionary, inKey); } }\n\tvoid\t\t\t\t\tClear()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFDictionaryRemoveAllValues(mCFDictionary); } }\n\t\n\tvoid\t\t\t\t\tShow()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ CFShow(mCFDictionary); }\n\t\n//\tImplementation\nprivate:\n\tCFMutableDictionaryRef \tmCFDictionary;\n\tbool\t\t\t\t\tmRelease;\n\tbool\t\t\t\t\tmMutable;\n\t\n\t\t\t\t\t\t\tCACFDictionary(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif //__CACFDictionary_h__\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFNumber.cpp",
    "content": "/*\n     File: CACFNumber.cpp\n Abstract: CACFNumber.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CACFNumber.h\"\n\n//=============================================================================\n//\tCACFNumber\n//=============================================================================\n\nFloat32\tCACFNumber::GetFixed32() const\n{\n\tSInt32 theFixedValue = GetSInt32();\n\t\n\t//\tthis is a 16.16 value so convert it to a float\n\tFloat32 theSign = theFixedValue < 0 ? -1.0f : 1.0f;\n\ttheFixedValue *= (SInt32)theSign;\n\tFloat32 theWholePart = (theFixedValue & 0x7FFF0000) >> 16;\n\tFloat32 theFractPart = theFixedValue & 0x0000FFFF;\n\ttheFractPart /= 65536.0f;\n\t\n\treturn theSign * (theWholePart + theFractPart);\n}\n\nFloat64\tCACFNumber::GetFixed64() const\n{\n\tSInt64 theFixedValue = GetSInt64();\n\t\n\t//\tthis is a 32.32 value so convert it to a double\n\tFloat64 theSign = theFixedValue < 0 ? -1.0 : 1.0;\n\ttheFixedValue *= (SInt64)theSign;\n\tFloat64 theWholePart = (theFixedValue & 0x7FFFFFFF00000000LL) >> 32;\n\tFloat64 theFractPart = theFixedValue & 0x00000000FFFFFFFFLL;\n\ttheFractPart /= 4294967296.0;\n\t\n\treturn theSign * (theWholePart + theFractPart);\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFNumber.h",
    "content": "/*\n     File: CACFNumber.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFNumber_h__)\n#define __CACFNumber_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CFNumber.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CFNumber.h>\n#endif\n\n//=============================================================================\n//\tCACFBoolean\n//=============================================================================\n\nclass\tCACFBoolean\n{\n//\tConstruction/Destruction\npublic:\n\texplicit\t\tCACFBoolean(CFBooleanRef inCFBoolean) : mCFBoolean(inCFBoolean), mWillRelease(true) {}\n\t\t\t\t\tCACFBoolean(CFBooleanRef inCFBoolean, bool inWillRelease) : mCFBoolean(inCFBoolean), mWillRelease(inWillRelease) {}\n\texplicit\t\tCACFBoolean(bool inValue) : mCFBoolean(inValue ? kCFBooleanTrue : kCFBooleanFalse), mWillRelease(true) { Retain(); }\n\t\t\t\t\t~CACFBoolean() { Release(); }\n\t\t\t\t\tCACFBoolean(const CACFBoolean& inBoolean) : mCFBoolean(inBoolean.mCFBoolean), mWillRelease(inBoolean.mWillRelease) { Retain(); }\n\tCACFBoolean&\toperator=(const CACFBoolean& inBoolean) { Release(); mCFBoolean = inBoolean.mCFBoolean; mWillRelease = inBoolean.mWillRelease; Retain(); return *this; }\n\tCACFBoolean&\toperator=(CFBooleanRef inCFBoolean) { Release(); mCFBoolean = inCFBoolean; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\t\tRetain() { if(mWillRelease && (mCFBoolean != NULL)) { CFRetain(mCFBoolean); } }\n\tvoid\t\t\tRelease() { if(mWillRelease && (mCFBoolean != NULL)) { CFRelease(mCFBoolean); } }\n\t\n\tCFBooleanRef\tmCFBoolean;\n\tbool\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\tIsValid() { return mCFBoolean != NULL; }\n\n//\tValue Access\npublic:\n\tCFBooleanRef\tGetCFBoolean() const { return mCFBoolean; }\n\tCFBooleanRef\tCopyCFBoolean() const { if(mCFBoolean != NULL) { CFRetain(mCFBoolean); } return mCFBoolean; }\n\n\tbool\t\t\tGetBoolean() const { bool theAnswer = false; if(mCFBoolean != NULL) { theAnswer = CFEqual(mCFBoolean, kCFBooleanTrue); } return theAnswer; }\n\t\n\t\t\t\t\tCACFBoolean(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n//=============================================================================\n//\tCACFNumber\n//=============================================================================\n\nclass\tCACFNumber\n{\n//\tConstruction/Destruction\npublic:\n\texplicit\tCACFNumber(CFNumberRef inCFNumber) : mCFNumber(inCFNumber), mWillRelease(true) {}\n\t\t\t\tCACFNumber(CFNumberRef inCFNumber, bool inWillRelease) : mCFNumber(inCFNumber), mWillRelease(inWillRelease) {}\n\t\t\t\tCACFNumber(SInt32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(UInt32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(SInt64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(UInt64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(Float32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberFloat32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(Float64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberFloat64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\t~CACFNumber() { Release(); }\n\t\t\t\tCACFNumber(const CACFNumber& inNumber) : mCFNumber(inNumber.mCFNumber), mWillRelease(inNumber.mWillRelease) { Retain(); }\n\tCACFNumber&\toperator=(const CACFNumber& inNumber) { Release(); mCFNumber = inNumber.mCFNumber; mWillRelease = inNumber.mWillRelease; Retain(); return *this; }\n\tCACFNumber&\toperator=(CFNumberRef inCFNumber) { Release(); mCFNumber = inCFNumber; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\tRetain() { if(mWillRelease && (mCFNumber != NULL)) { CFRetain(mCFNumber); } }\n\tvoid\t\tRelease() { if(mWillRelease && (mCFNumber != NULL)) { CFRelease(mCFNumber); } }\n\t\n\tCFNumberRef\tmCFNumber;\n\tbool\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\tIsValid() const { return mCFNumber != NULL; }\n\n//\tValue Access\npublic:\n\tCFNumberRef\tGetCFNumber() const { return mCFNumber; }\n\tCFNumberRef\tCopyCFNumber() const { if(mCFNumber != NULL) { CFRetain(mCFNumber); } return mCFNumber; }\n\n\tSInt8\t\tGetSInt8() const { SInt8 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt8Type, &theAnswer); } return theAnswer; }\n\tSInt32\t\tGetSInt32() const { SInt32 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt32Type, &theAnswer); } return theAnswer; }\n\tUInt32\t\tGetUInt32() const { UInt32 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt32Type, &theAnswer); } return theAnswer; }\n\tFloat32\t\tGetFloat32() const { Float32 theAnswer = 0.0f; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberFloat32Type, &theAnswer); } return theAnswer; }\n\tFloat32\t\tGetFixed32() const;\n\tFloat64\t\tGetFixed64() const;\n\tSInt64\t\tGetSInt64() const { SInt64 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt64Type, &theAnswer); } return theAnswer; }\n\t\n\t\t\t\tCACFNumber(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFString.cpp",
    "content": "/*\n     File: CACFString.cpp\n Abstract: CACFString.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFString\n//=============================================================================\n\nUInt32\tCACFString::GetStringByteLength(CFStringRef inCFString, CFStringEncoding inEncoding)\n{\n\tCFIndex theAnswer = 0;\n\t\n\tif(inCFString != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFStringGetLength(inCFString) };\n\t\tCFStringGetBytes(inCFString, theRange, inEncoding, 0, false, NULL, 0x7FFFFFFF, &theAnswer);\n\t}\n\t\n\treturn UInt32(theAnswer);\n}\n\nvoid\tCACFString::GetCString(CFStringRef inCFString, char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding)\n{\n\tif(ioStringSize > 0)\n\t{\n\t\tif(inCFString != NULL)\n\t\t{\n\t\t\tCFIndex theLength = 0;\n\t\t\tCFRange theRange = { 0, CFStringGetLength(inCFString) };\n\t\t\tCFStringGetBytes(inCFString, theRange, inEncoding, 0, false, (UInt8*)outString, static_cast<CFIndex>(ioStringSize - 1), &theLength);\n\t\t\toutString[theLength] = 0;\n\t\t\tioStringSize = ToUInt32(theLength) + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutString[0] = 0;\n\t\t\tioStringSize = 1;\n\t\t}\n\t}\n}\n\nvoid\tCACFString::GetUnicodeString(CFStringRef inCFString, UInt16* outString, UInt32& ioStringSize)\n{\n\tif(ioStringSize > 0)\n\t{\n\t\tif(inCFString != NULL)\n\t\t{\n\t\t\tCFRange theStringRange = { 0, CFStringGetLength(inCFString) };\n\t\t\tif(static_cast<UInt32>(theStringRange.length) > ioStringSize)\n\t\t\t{\n\t\t\t\ttheStringRange.length = static_cast<CFIndex>(ioStringSize);\n\t\t\t}\n\t\t\tCFStringGetCharacters(inCFString, theStringRange, outString);\n\t\t\tioStringSize = ToUInt32(theStringRange.length);\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutString[0] = 0;\n\t\t\tioStringSize = 0;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CACFString.h",
    "content": "/*\n     File: CACFString.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFString_h__)\n#define __CACFString_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CADebugMacros.h\"\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CFString.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CFString.h>\n#endif\n\n//=============================================================================\n//\tCACFString\n//\n//\tNotes\n//\t-\tUsing the AssignWithoutRetain() method will fool the static analyzer into thinking that the\n//\t\tCFString being assigned will be leaked. This is because the static analyzer is not smart\n//\t\tenough to understand that the destructor will release the object.\n//=============================================================================\n\nclass\tCACFString\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFString() : mCFString(NULL), mWillRelease(true) {}\n\texplicit\t\t\tCACFString(CFStringRef inCFString) : mCFString(inCFString), mWillRelease(true) {}\n\t\t\t\t\t\tCACFString(const char* inCString) : mCFString(CFStringCreateWithCString(NULL, inCString, kCFStringEncodingASCII)), mWillRelease(true) {}\n\t\t\t\t\t\tCACFString(CFStringRef inCFString, bool inWillRelease) : mCFString(inCFString), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFString(const char* inCString, bool inWillRelease) : mCFString(CFStringCreateWithCString(NULL, inCString, kCFStringEncodingASCII)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFString(const char* inCString, CFStringEncoding inCStringEncoding, bool inWillRelease = true) : mCFString(CFStringCreateWithCString(NULL, inCString, inCStringEncoding)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\t~CACFString() { Release(); }\n\t\t\t\t\t\tCACFString(const CACFString& inString) : mCFString(inString.mCFString), mWillRelease(inString.mWillRelease) { Retain(); }\n\tCACFString&\t\t\toperator=(const CACFString& inString) { if (inString.mCFString != mCFString) { Release(); mCFString = inString.mCFString; mWillRelease = inString.mWillRelease; Retain(); } return *this; }\n\tCACFString&\t\t\toperator=(CFStringRef inCFString) { if (inCFString != mCFString) { Release(); mCFString = inCFString; } mWillRelease = true; Retain(); return *this; }\n\tvoid\t\t\t\tAssignWithoutRetain(CFStringRef inCFString) { if (inCFString != mCFString) { Release(); mCFString = inCFString; } mWillRelease = true; }\n\nprivate:\n\tvoid\t\t\t\tRetain() { if(mWillRelease && (mCFString != NULL)) { CFRetain(mCFString); } }\n\tvoid\t\t\t\tRelease() { if(mWillRelease && (mCFString != NULL)) { CFRelease(mCFString); } }\n\t\n\tCFStringRef\t\t\tmCFString;\n\tbool\t\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\t\tIsValid() const { return mCFString != NULL; }\n\tbool\t\t\t\tIsEqualTo(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringCompare(inString, mCFString, 0) == kCFCompareEqualTo; } return theAnswer; }\n\tbool\t\t\t\tStartsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringHasPrefix(mCFString, inString); } return theAnswer; }\n\tbool\t\t\t\tEndsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringHasSuffix(mCFString, inString); } return theAnswer; }\n\n//\tValue Access\npublic:\n\tCFStringRef\t\t\tGetCFString() const { return mCFString; }\n\tCFStringRef\t\t\tCopyCFString() const { if(mCFString != NULL) { CFRetain(mCFString); } return mCFString; }\n\tconst CFStringRef*\tGetPointerToStorage() const\t{ return &mCFString; }\n\tCFStringRef&\t\tGetStorage() { Release(); mWillRelease = true; return mCFString; }\n\tUInt32\t\t\t\tGetLength() const { UInt32 theAnswer = 0; if(mCFString != NULL) { theAnswer = ToUInt32(CFStringGetLength(mCFString)); } return theAnswer; }\n\tUInt32\t\t\t\tGetByteLength(CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { UInt32 theAnswer = 0; if(mCFString != NULL) { theAnswer = GetStringByteLength(mCFString, inEncoding); } return theAnswer; }\n\tvoid\t\t\t\tGetCString(char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { GetCString(mCFString, outString, ioStringSize, inEncoding); }\n\tvoid\t\t\t\tGetUnicodeString(UInt16* outString, UInt32& ioStringSize) const { GetUnicodeString(mCFString, outString, ioStringSize); }\n\tSInt32\t\t\t\tGetAsInteger() { return GetAsInteger(mCFString); }\n\tFloat64\t\t\t\tGetAsFloat64() { return GetAsFloat64(mCFString); }\n\n\tstatic UInt32\t\tGetStringLength(CFStringRef inCFString)  { UInt32 theAnswer = 0; if(inCFString != NULL) { theAnswer = ToUInt32(CFStringGetLength(inCFString)); } return theAnswer; }\n\tstatic UInt32\t\tGetStringByteLength(CFStringRef inCFString, CFStringEncoding inEncoding = kCFStringEncodingUTF8);\n\tstatic void\t\t\tGetCString(CFStringRef inCFString, char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8);\n\tstatic void\t\t\tGetUnicodeString(CFStringRef inCFString, UInt16* outString, UInt32& ioStringSize);\n\tstatic SInt32\t\tGetAsInteger(CFStringRef inCFString) { SInt32 theAnswer = 0; if(inCFString != NULL){ theAnswer = CFStringGetIntValue(inCFString); } return theAnswer; }\n\tstatic Float64\t\tGetAsFloat64(CFStringRef inCFString) { Float64 theAnswer = 0; if(inCFString != NULL){ theAnswer = CFStringGetDoubleValue(inCFString); } return theAnswer; }\n\t\n};\n\ninline bool\toperator<(const CACFString& x, const CACFString& y) { return CFStringCompare(x.GetCFString(), y.GetCFString(), 0) == kCFCompareLessThan; }\ninline bool\toperator==(const CACFString& x, const CACFString& y) { return CFStringCompare(x.GetCFString(), y.GetCFString(), 0) == kCFCompareEqualTo; }\ninline bool\toperator!=(const CACFString& x, const CACFString& y) { return !(x == y); }\ninline bool\toperator<=(const CACFString& x, const CACFString& y) { return (x < y) || (x == y); }\ninline bool\toperator>=(const CACFString& x, const CACFString& y) { return !(x < y); }\ninline bool\toperator>(const CACFString& x, const CACFString& y) { return !((x < y) || (x == y)); }\n\ninline bool\toperator<(const CACFString& x, CFStringRef y) { return CFStringCompare(x.GetCFString(), y, 0) == kCFCompareLessThan; }\ninline bool\toperator==(const CACFString& x, CFStringRef y) { return CFStringCompare(x.GetCFString(), y, 0) == kCFCompareEqualTo; }\ninline bool\toperator!=(const CACFString& x, CFStringRef y) { return !(x == y); }\ninline bool\toperator<=(const CACFString& x, CFStringRef y) { return (x < y) || (x == y); }\ninline bool\toperator>=(const CACFString& x, CFStringRef y) { return !(x < y); }\ninline bool\toperator>(const CACFString& x, CFStringRef y) { return !((x < y) || (x == y)); }\n\ninline bool\toperator<(CFStringRef x, const CACFString& y) { return CFStringCompare(x, y.GetCFString(), 0) == kCFCompareLessThan; }\ninline bool\toperator==(CFStringRef x, const CACFString& y) { return CFStringCompare(x, y.GetCFString(), 0) == kCFCompareEqualTo; }\ninline bool\toperator!=(CFStringRef x, const CACFString& y) { return !(x == y); }\ninline bool\toperator<=(CFStringRef x, const CACFString& y) { return (x < y) || (x == y); }\ninline bool\toperator>=(CFStringRef x, const CACFString& y) { return !(x < y); }\ninline bool\toperator>(CFStringRef x, const CACFString& y) { return !((x < y) || (x == y)); }\n\n//=============================================================================\n//\tCACFMutableString\n//=============================================================================\n\nclass\tCACFMutableString\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFMutableString() : mCFMutableString(NULL), mWillRelease(true) {}\n\t\t\t\t\t\tCACFMutableString(CFMutableStringRef inCFMutableString, bool inWillRelease = true) : mCFMutableString(inCFMutableString), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFMutableString(CFStringRef inStringToCopy, bool /*inMakeCopy*/, bool inWillRelease = true) : mCFMutableString(CFStringCreateMutableCopy(NULL, 0, inStringToCopy)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFMutableString(const char* inCString, bool inWillRelease = true) : mCFMutableString(NULL), mWillRelease(inWillRelease) { CACFString theString(inCString); mCFMutableString = CFStringCreateMutableCopy(NULL, 0, theString.GetCFString()); }\n\t\t\t\t\t\tCACFMutableString(const char* inCString, CFStringEncoding inCStringEncoding, bool inWillRelease = true) : mCFMutableString(NULL), mWillRelease(inWillRelease) { CACFString theString(inCString, inCStringEncoding); mCFMutableString = CFStringCreateMutableCopy(NULL, 0, theString.GetCFString()); }\n\t\t\t\t\t\t~CACFMutableString() { Release(); }\n\t\t\t\t\t\tCACFMutableString(const CACFMutableString& inString) : mCFMutableString(inString.mCFMutableString), mWillRelease(inString.mWillRelease) { Retain(); }\n\tCACFMutableString&\toperator=(const CACFMutableString& inString) { Release(); mCFMutableString = inString.mCFMutableString; mWillRelease = inString.mWillRelease; Retain(); return *this; }\n\tCACFMutableString&\toperator=(CFMutableStringRef inCFMutableString) { Release(); mCFMutableString = inCFMutableString; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\t\t\tRetain() { if(mWillRelease && (mCFMutableString != NULL)) { CFRetain(mCFMutableString); } }\n\tvoid\t\t\t\tRelease() { if(mWillRelease && (mCFMutableString != NULL)) { CFRelease(mCFMutableString); } }\n\t\n\tCFMutableStringRef\tmCFMutableString;\n\tbool\t\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\t\tIsValid() { return mCFMutableString != NULL; }\n\tbool\t\t\t\tIsEqualTo(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringCompare(inString, mCFMutableString, 0) == kCFCompareEqualTo; } return theAnswer; }\n\tbool\t\t\t\tStartsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringHasPrefix(mCFMutableString, inString); } return theAnswer; }\n\tbool\t\t\t\tEndsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringHasSuffix(mCFMutableString, inString); } return theAnswer; }\n\tvoid\t\t\t\tAppend(CFStringRef inString) { if(mCFMutableString != NULL) { CFStringAppend(mCFMutableString, inString); } }\n\n//\tValue Access\npublic:\n\tCFMutableStringRef\tGetCFMutableString() const { return mCFMutableString; }\n\tCFMutableStringRef\tCopyCFMutableString() const { if(mCFMutableString != NULL) { CFRetain(mCFMutableString); } return mCFMutableString; }\n\tUInt32\t\t\t\tGetLength() const { UInt32 theAnswer = 0; if(mCFMutableString != NULL) { theAnswer = ToUInt32(CFStringGetLength(mCFMutableString)); } return theAnswer; }\n\tUInt32\t\t\t\tGetByteLength(CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { UInt32 theAnswer = 0; if(mCFMutableString != NULL) { theAnswer = CACFString::GetStringByteLength(mCFMutableString, inEncoding); } return theAnswer; }\n\tvoid\t\t\t\tGetCString(char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { CACFString::GetCString(mCFMutableString, outString, ioStringSize, inEncoding); }\n\tvoid\t\t\t\tGetUnicodeString(UInt16* outString, UInt32& ioStringSize) const { CACFString::GetUnicodeString(mCFMutableString, outString, ioStringSize); }\n\tSInt32\t\t\t\tGetAsInteger() { return CACFString::GetAsInteger(mCFMutableString); }\n\tFloat64\t\t\t\tGetAsFloat64() { return CACFString::GetAsFloat64(mCFMutableString); }\n\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugMacros.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  CADebugMacros.cpp\n//  PublicUtility\n//\n//  Copyright (C) 2014 Apple Inc. All Rights Reserved.\n//  Copyright © 2016, 2017, 2020 Kyle Neideck\n//\n//  Original license header follows.\n//\n\n/*\n     File: CADebugMacros.cpp\n Abstract: CADebugMacros.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#include \"CADebugMacros.h\"\n#include <stdio.h>\n#if TARGET_API_MAC_OSX\n\t#include <syslog.h>\n#endif\n\n#if DEBUG\n#include <stdio.h>\n\nvoid\tDebugPrint(const char *fmt, ...)\n{\n\tva_list args;\n\tva_start(args, fmt);\n\tvprintf(fmt, args);\n\tva_end(args);\n}\n#endif // DEBUG\n\nvoid\tLogError(const char *fmt, ...)\n{\n\tva_list args;\n\tva_start(args, fmt);\n    vLogError(fmt, args);\n    va_end(args);\n}\n\nvoid    vLogError(const char *fmt, va_list args)\n{\n#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog\n    printf(\"[ERROR] \");\n    vprintf(fmt, args);\n    printf(\"\\n\");\n#else\n    vsyslog(LOG_ERR, fmt, args);\n#endif\n\n#if DEBUG\n    CADebuggerStop();\n#endif\n}\n\nvoid\tLogWarning(const char *fmt, ...)\n{\n\tva_list args;\n    va_start(args, fmt);\n    vLogWarning(fmt, args);\n    va_end(args);\n}\n\nvoid    vLogWarning(const char *fmt, va_list args)\n{\n#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog\n    printf(\"[WARNING] \");\n    vprintf(fmt, args);\n    printf(\"\\n\");\n#else\n    vsyslog(LOG_WARNING, fmt, args);\n#endif\n\n#if DEBUG\n    //CADebuggerStop(); // TODO: Add a toggle for this to the project file (under \"Preprocessor Macros\"). Default to off.\n#endif\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugMacros.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  CADebugMacros.h\n//  PublicUtility\n//\n//  Copyright (C) 2014 Apple Inc. All Rights Reserved.\n//  Copyright © 2016, 2020 Kyle Neideck\n//\n//  Original license header follows.\n//\n\n/*\n     File: CADebugMacros.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CADebugMacros_h__)\n#define __CADebugMacros_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n#include \"CADebugPrintf.h\"\n\n#include <stdarg.h>\n\n//=============================================================================\n//\tCADebugMacros\n//=============================================================================\n\n//#define\tCoreAudio_StopOnFailure\t\t\t1\n//#define\tCoreAudio_TimeStampMessages\t\t1\n//#define\tCoreAudio_ThreadStampMessages\t1\n//#define\tCoreAudio_FlushDebugMessages\t1\n\n#if TARGET_RT_BIG_ENDIAN\n\t#define\tCA4CCToCString(the4CC)\t\t\t\t\t{ ((char*)&the4CC)[0], ((char*)&the4CC)[1], ((char*)&the4CC)[2], ((char*)&the4CC)[3], 0 }\n\t#define CACopy4CCToCString(theCString, the4CC)\t{ theCString[0] = ((char*)&the4CC)[0]; theCString[1] = ((char*)&the4CC)[1]; theCString[2] = ((char*)&the4CC)[2]; theCString[3] = ((char*)&the4CC)[3]; theCString[4] = 0; }\n#else\n\t#define\tCA4CCToCString(the4CC)\t\t\t\t\t{ ((char*)&the4CC)[3], ((char*)&the4CC)[2], ((char*)&the4CC)[1], ((char*)&the4CC)[0], 0 }\n\t#define CACopy4CCToCString(theCString, the4CC)\t{ theCString[0] = ((char*)&the4CC)[3]; theCString[1] = ((char*)&the4CC)[2]; theCString[2] = ((char*)&the4CC)[1]; theCString[3] = ((char*)&the4CC)[0]; theCString[4] = 0; }\n#endif\n\n//\tThis is a macro that does a sizeof and casts the result to a UInt32. This is useful for all the\n//\tplaces where -wshorten64-32 catches assigning a sizeof expression to a UInt32.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tSizeOf32(X)\t((UInt32)sizeof(X))\n\n//\tThis is a macro that does a offsetof and casts the result to a UInt32. This is useful for all the\n//\tplaces where -wshorten64-32 catches assigning an offsetof expression to a UInt32.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tOffsetOf32(X, Y)\t((UInt32)offsetof(X, Y))\n\n//\tThis macro casts the expression to a UInt32. It is called out specially to allow us to track casts\n//\tthat have been added purely to avert -wshorten64-32 warnings on 64 bit platforms.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tToUInt32(X)\t((UInt32)(X))\n#define\tToSInt32(X)\t((SInt32)(X))\n\n#pragma mark\tBasic Definitions\n\n//\tbasic debugging print routines\n#if\tTARGET_OS_MAC && !TARGET_API_MAC_CARBON\n\textern void DebugStr(const unsigned char* debuggerMsg);\n\t#define\tDebugMessage(msg)\tDebugStr(\"\\p\"msg)\n\t#define DebugMessageN1(msg, N1)\n\t#define DebugMessageN2(msg, N1, N2)\n\t#define DebugMessageN3(msg, N1, N2, N3)\n#else\n\t#if\t(CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)\n\t\t#define\tFlushRtn\t,fflush(DebugPrintfFile)\n\t#else\n\t\t#define\tFlushRtn\n\t#endif\n\n\t#if\t\tCoreAudio_ThreadStampMessages\n\t\t#include <pthread.h>\n\t\t#include \"CAHostTimeBase.h\"\n\t\t#if TARGET_RT_64_BIT\n\t\t\t#define\tDebugPrintfThreadIDFormat\t\"%16p\"\n\t\t#else\n\t\t\t#define\tDebugPrintfThreadIDFormat\t\"%8p\"\n\t\t#endif\n\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(\"%17qd: \" DebugPrintfThreadIDFormat \" \" inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn\n\t#elif\tCoreAudio_TimeStampMessages\n\t\t#include \"CAHostTimeBase.h\"\n\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(\"%17qd: \" inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn\n\t#else\n\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn\n\t#endif\n#endif\n\n#if\tDEBUG || CoreAudio_Debug\n\t// can be used to break into debugger immediately, also see CADebugger\n\t#define BusError()\t\t{ long* p=NULL; *p=0; }\n\n\tvoid\tDebugPrint(const char *fmt, ...);\t// can be used like printf\n\t#ifndef DEBUGPRINT\n\t\t#define DEBUGPRINT(msg) DebugPrint msg\t\t// have to double-parenthesize arglist (see Debugging.h)\n\t#endif\n\t#if VERBOSE\n\t\t#define vprint(msg) DEBUGPRINT(msg)\n\t#else\n\t\t#define vprint(msg)\n\t#endif\n\t\n\t// Original macro keeps its function of turning on and off use of CADebuggerStop() for both asserts and throws.\n\t// For backwards compat, it overrides any setting of the two sub-macros.\n\t#if\tCoreAudio_StopOnFailure\n\t\t#include \"CADebugger.h\"\n\t\t#undef CoreAudio_StopOnAssert\n\t\t#define CoreAudio_StopOnAssert 1\n\t\t#undef CoreAudio_StopOnThrow\n\t\t#define CoreAudio_StopOnThrow 1\n\t\t#define STOP\tCADebuggerStop()\n\t#else\n\t\t#define STOP\n\t#endif\n\n\t#if CoreAudio_StopOnAssert\n\t\t#if !CoreAudio_StopOnFailure\n\t\t\t#include \"CADebugger.h\"\n\t\t\t#define STOP\n\t\t#endif\n\t\t#define __ASSERT_STOP CADebuggerStop()\n\t#else\n\t\t#define __ASSERT_STOP\n\t#endif\n\n\t#if CoreAudio_StopOnThrow\n\t\t#if !CoreAudio_StopOnFailure\n\t\t\t#include \"CADebugger.h\"\n\t\t\t#define STOP\n\t\t#endif\n\t\t#define __THROW_STOP CADebuggerStop()\n\t#else\n\t\t#define __THROW_STOP\n\t#endif\n\n#else\n\n\t#ifndef DEBUGPRINT\n\t\t#define DEBUGPRINT(msg)\n\t#endif\n\t#define vprint(msg)\n\t#define\tSTOP\n\t#define __ASSERT_STOP\n\t#define __THROW_STOP\n#endif\n\n//\tOld-style numbered DebugMessage calls are implemented in terms of DebugMsg() now\n#define\tDebugMessage(msg)\t\t\t\t\t\t\t\t\t\tDebugMsg(msg)\n#define DebugMessageN1(msg, N1)\t\t\t\t\t\t\t\t\tDebugMsg(msg, N1)\n#define DebugMessageN2(msg, N1, N2)\t\t\t\t\t\t\t\tDebugMsg(msg, N1, N2)\n#define DebugMessageN3(msg, N1, N2, N3)\t\t\t\t\t\t\tDebugMsg(msg, N1, N2, N3)\n#define DebugMessageN4(msg, N1, N2, N3, N4)\t\t\t\t\t\tDebugMsg(msg, N1, N2, N3, N4)\n#define DebugMessageN5(msg, N1, N2, N3, N4, N5)\t\t\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5)\n#define DebugMessageN6(msg, N1, N2, N3, N4, N5, N6)\t\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6)\n#define DebugMessageN7(msg, N1, N2, N3, N4, N5, N6, N7)\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7)\n#define DebugMessageN8(msg, N1, N2, N3, N4, N5, N6, N7, N8)\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8)\n#define DebugMessageN9(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9)\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9)\n\n// BGM edit: Added __printflike and va_list versions.\nvoid\tLogError(const char *fmt, ...) __printflike(1, 2);\t\t\t// writes to syslog (and stderr if debugging)\nvoid    vLogError(const char *fmt, va_list args);\nvoid\tLogWarning(const char *fmt, ...) __printflike(1, 2);\t\t// writes to syslog (and stderr if debugging)\nvoid    vLogWarning(const char *fmt, va_list args);\n\n#define\tNO_ACTION\t(void)0\n\n#if\tDEBUG || CoreAudio_Debug\n\n#pragma mark\tDebug Macros\n\n#define\tAssert(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertFileLine(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessageN3(\"%s, line %d: %s\", __FILE__, __LINE__, inMessage);\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %d (%s)\", (int)__Err, __4CC);\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoKernelError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (unsigned int)(inError);\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNotNULL(inPtr, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif((inPtr) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIf(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithAction(inCondition, inAction, inHandler, inMessage)\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULL(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelError(inKernelError, inAction, inHandler, inMessage)\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfError(inError, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %ld (%s)\", (long int)__Err, __4CC);\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNoMessage(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithActionNoMessage(inCondition, inAction, inHandler, inMessage)\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULLNoMessage(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelErrorNoMessage(inKernelError, inAction, inHandler, inMessage)\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfErrorNoMessage(inError, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if defined(__cplusplus)\n\n#define Throw(inException)  __THROW_STOP; throw (inException)\n\n#define\tThrowIf(inCondition, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfNULL(inPointer, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfKernelError(inKernelError, inException, inMessage)\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tint __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %d (%s)\", (int)__Err, __4CC);\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if TARGET_OS_WIN32\n#define\tThrowIfWinError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tHRESULT __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(FAILED(__Err))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Code: %d, Facility: 0x%X\", HRESULT_CODE(__Err), HRESULT_FACILITY(__Err));\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n#endif\n\n#define\tSubclassResponsibility(inMethodName, inException)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMethodName\": Subclasses must implement this method\");\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#endif\t//\tdefined(__cplusplus)\n\n#else\n\n#pragma mark\tRelease Macros\n\n#define\tAssert(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define AssertFileLine(inCondition, inMessage) Assert(inCondition, inMessage)\n\n#define\tAssertNoError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoKernelError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (unsigned int)(inError);\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNotNULL(inPtr, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif((inPtr) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIf(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithAction(inCondition, inAction, inHandler, inMessage)\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULL(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelError(inKernelError, inAction, inHandler, inMessage)\t\t\t\t\\\n\t\t\tif((inKernelError) != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfError(inError, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inError) != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNoMessage(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithActionNoMessage(inCondition, inAction, inHandler, inMessage)\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULLNoMessage(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelErrorNoMessage(inKernelError, inAction, inHandler, inMessage)\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfErrorNoMessage(inError, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if defined(__cplusplus)\n\n#define Throw(inException)  __THROW_STOP; throw (inException)\n\n#define\tThrowIf(inCondition, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfNULL(inPointer, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfKernelError(inKernelError, inException, inMessage)\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tint __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if TARGET_OS_WIN32\n#define\tThrowIfWinError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tHRESULT __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(FAILED(__Err))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n#endif\n\n#define\tSubclassResponsibility(inMethodName, inException)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#endif\t//\tdefined(__cplusplus)\n\n#endif  //  DEBUG || CoreAudio_Debug\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugPrintf.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  CADebugPrintf.cpp\n//  PublicUtility\n//\n//  Copyright (C) 2014 Apple Inc. All Rights Reserved.\n//  Copyright © 2020 Kyle Neideck\n//\n//  Original license header follows.\n//\n\n/*\n     File: CADebugPrintf.cpp\n Abstract: CADebugPrintf.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CADebugPrintf.h\"\n\n#if\tTARGET_OS_WIN32\n\t#include <stdarg.h>\n\t#include <stdio.h>\n\t#include <Windows.h>\n\textern \"C\"\n\tint\tCAWin32DebugPrintf(char* inFormat, ...)\n\t{\n\t\tif (BGMDebugLoggingIsEnabled()) {\n\t\t\tchar theMessage[1024];\n\t\t\tva_list theArguments;\n\t\t\tva_start(theArguments, inFormat);\n\t\t\t_vsnprintf(theMessage, 1024, inFormat, theArguments);\n\t\t\tva_end(theArguments);\n\t\t\tOutputDebugString(theMessage);\n\t\t}\n\n\t\treturn 0;\n\t}\n#endif\n\n#if defined(CoreAudio_UseSideFile)\n\t#include <unistd.h>\n\tFILE* sDebugPrintfSideFile = NULL;\n\textern \"C\"\n\tvoid OpenDebugPrintfSideFile()\n\t{\n\t\tif(sDebugPrintfSideFile == NULL)\n\t\t{\n\t\t\tchar theFileName[1024];\n\t\t\tsnprintf(theFileName, sizeof(theFileName), CoreAudio_UseSideFile, getpid());\n\t\t\tsDebugPrintfSideFile = fopen(theFileName, \"a+\");\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"\\n------------------------------\\n\");\n\t\t}\n\t}\n#endif\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugPrintf.h",
    "content": "/*\n     File: CADebugPrintf.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CADebugPrintf_h__)\n#define __CADebugPrintf_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n#include \"BGMDebugLogging.h\"\n\n//=============================================================================\n//\tMacros to redirect debugging output to various logging services\n//=============================================================================\n\n//#define\tCoreAudio_UseSysLog\t\t1\n//#define\tCoreAudio_UseSideFile\t\"/CoreAudio-%d.txt\"\n\n#if\tTARGET_OS_WIN32\n\t#if defined(__cplusplus)\n\textern \"C\"\n\t#endif\n\textern int CAWin32DebugPrintf(char* inFormat, ...);\n\t#define\tDebugPrintfRtn\t\t\tCAWin32DebugPrintf\n\t#define\tDebugPrintfFile\n\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t#define\tDebugPrintfFileComma\n#else\n\t#if\tCoreAudio_UseSysLog\n\t\t#include <sys/syslog.h>\n\t\t#define\tDebugPrintfRtn\tsyslog\n\t\t#define\tDebugPrintfFile\tLOG_NOTICE\n\t\t#define\tDebugPrintfLineEnding\t\"\"\n\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t#elif defined(CoreAudio_UseSideFile)\n\t\t#include <stdio.h>\n\t\t#if defined(__cplusplus)\n\t\textern \"C\"\n\t\t#endif\n\t\tvoid OpenDebugPrintfSideFile();\n\t\textern FILE* sDebugPrintfSideFile;\n\t\t#define\tDebugPrintfRtn\tfprintf\n\t\t#define\tDebugPrintfFile\t((sDebugPrintfSideFile != NULL) ? sDebugPrintfSideFile : stderr)\n\t\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t#else\n\t\t#include <stdio.h>\n\t\t#define\tDebugPrintfRtn\tfprintf\n\t\t#define\tDebugPrintfFile\tstderr\n\t\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t#endif\n#endif\n\n#define DebugPrintf(inFormat, ...) \\\n\t\tdo { \\\n\t\t\tif (BGMDebugLoggingIsEnabled()) { \\\n                DebugPrintfRtn(DebugPrintfFileComma inFormat DebugPrintfLineEnding, ## __VA_ARGS__); \\\n\t\t\t} \\\n\t\t} while (0)\n\n#endif\n\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugger.cpp",
    "content": "/*\n     File: CADebugger.cpp\n Abstract: CADebugger.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CADebugger.h\"\n\n//=============================================================================\n//\tCADebugger\n//=============================================================================\n\n#if TARGET_API_MAC_OSX\n\n#include <sys/sysctl.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nbool CAIsDebuggerAttached(void)\n{\n\tint\t\t\t\t\tmib[4];\n\tstruct kinfo_proc\tinfo;\n\tsize_t\t\t\t\tsize;\n\n\tmib[0] = CTL_KERN;\n\tmib[1] = KERN_PROC;\n\tmib[2] = KERN_PROC_PID;\n\tmib[3] = getpid();\n\tsize = sizeof(info);\n\tinfo.kp_proc.p_flag = 0;\n\n\tsysctl(mib, 4, &info, &size, NULL, 0);\n\n\treturn (info.kp_proc.p_flag & P_TRACED) == P_TRACED;\n}\n\n#endif\n\nvoid\tCADebuggerStop(void)\n{\n\t#if\tCoreAudio_Debug\n\t\t#if\tTARGET_API_MAC_OSX\n\t\t\tif(CAIsDebuggerAttached())\n\t\t\t{\n\t\t\t\t#if defined(__i386__) || defined(__x86_64__)\n\t\t\t\t\tasm(\"int3\");\n\t\t\t\t#else\n\t\t\t\t\t__builtin_trap();\n\t\t\t\t#endif\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tabort();\n\t\t\t}\n\t\t#else\n\t\t\t__debugbreak();\n\t\t#endif\n\t#endif\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugger.h",
    "content": "/*\n     File: CADebugger.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CADebugger_h__)\n#define __CADebugger_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n//=============================================================================\n//\tCADebugger\n//=============================================================================\n\n// BGM edit: Added extern \"C\" so CADebugger (and headers that include it) can be used in Obj-C.\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if\tTARGET_API_MAC_OSX\n\textern bool CAIsDebuggerAttached(void);\n#endif\nextern void    CADebuggerStop(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAException.h",
    "content": "/*\n     File: CAException.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAException_h__)\n#define __CAException_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n//=============================================================================\n//\tCAException\n//=============================================================================\n\nclass CAException\n{\n\npublic:\n\t\t\t\t\tCAException(OSStatus inError) : mError(inError) {}\n\t\t\t\t\tCAException(const CAException& inException) : mError(inException.mError) {}\n\tCAException&\toperator=(const CAException& inException) { mError = inException.mError; return *this; }\n\t\t\t\t\t~CAException() {}\n\n\tOSStatus\t\tGetError() const { return mError; }\n\t\nprotected:\n\tOSStatus\t\tmError;\n};\n\n#define\tCATry\t\t\t\t\t\t\t\ttry{\n#define CACatch\t\t\t\t\t\t\t\t} catch(...) {}\n#define\tCASwallowException(inExpression)\ttry { inExpression; } catch(...) {}\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioDevice.cpp",
    "content": "/*\n     File: CAHALAudioDevice.cpp\n Abstract: CAHALAudioDevice.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAHALAudioDevice.h\"\n\n//\tPublicUtility Includes\n#include \"CAAutoDisposer.h\"\n#include \"CAHALAudioStream.h\"\n#include \"CAHALAudioSystemObject.h\"\n#include \"CAPropertyAddress.h\"\n\n//==================================================================================================\n//\tCAHALAudioDevice\n//==================================================================================================\n\nCAHALAudioDevice::CAHALAudioDevice(AudioObjectID inAudioDevice)\n:\n\tCAHALAudioObject(inAudioDevice)\n{\n}\n\nCAHALAudioDevice::CAHALAudioDevice(CFStringRef inUID)\n:\n\tCAHALAudioObject(CAHALAudioSystemObject().GetAudioDeviceForUID(inUID))\n{\n}\n\nCAHALAudioDevice::~CAHALAudioDevice()\n{\n}\n\nCFStringRef\tCAHALAudioDevice::CopyDeviceUID() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDeviceUID);\n\treturn GetPropertyData_CFString(theAddress, 0, NULL);\n}\n\nbool\tCAHALAudioDevice::HasModelUID() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyModelUID);\n\treturn HasProperty(theAddress);\n}\n\nCFStringRef\tCAHALAudioDevice::CopyModelUID() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyModelUID);\n\treturn GetPropertyData_CFString(theAddress, 0, NULL);\n}\n\nCFStringRef\tCAHALAudioDevice::CopyConfigurationApplicationBundleID() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyConfigurationApplication);\n\treturn GetPropertyData_CFString(theAddress, 0, NULL);\n}\n\nCFURLRef\tCAHALAudioDevice::CopyIconLocation() const\n{\n\tCFURLRef theAnswer = NULL;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIcon);\n\tUInt32 theSize = sizeof(CFURLRef);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nUInt32\tCAHALAudioDevice::GetTransportType() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyTransportType);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nbool\tCAHALAudioDevice::CanBeDefaultDevice(bool inIsInput, bool inIsSystem) const\n{\n\tCAPropertyAddress theAddress(inIsSystem ? kAudioDevicePropertyDeviceCanBeDefaultSystemDevice : kAudioDevicePropertyDeviceCanBeDefaultDevice, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL) != 0;\n}\n\nbool\tCAHALAudioDevice::HasDevicePlugInStatus() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlugIn);\n\treturn HasProperty(theAddress);\n}\n\nOSStatus\tCAHALAudioDevice::GetDevicePlugInStatus() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlugIn);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nbool\tCAHALAudioDevice::IsAlive() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDeviceIsAlive);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL) != 0;\n}\n\nbool\tCAHALAudioDevice::IsHidden() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIsHidden);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL) != 0;\n}\n\npid_t\tCAHALAudioDevice::GetHogModeOwner() const\n{\n\tpid_t theAnswer = -1;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyHogMode);\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = sizeof(pid_t);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioDevice::IsHogModeSettable() const\n{\n\tbool theAnswer = false;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyHogMode);\n\tif(HasProperty(theAddress))\n\t{\n\t\ttheAnswer = IsPropertySettable(theAddress);\n\t}\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioDevice::TakeHogMode()\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyHogMode);\n\tpid_t thePID = getpid();\n\tif(HasProperty(theAddress))\n\t{\n\t\tSetPropertyData(theAddress, 0, NULL, sizeof(pid_t), &thePID);\n\t}\n\treturn thePID == getpid();\n}\n\nvoid\tCAHALAudioDevice::ReleaseHogMode()\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyHogMode);\n\tif(HasProperty(theAddress))\n\t{\n\t\tpid_t thePID = -1;\n\t\tSetPropertyData(theAddress, 0, NULL, sizeof(pid_t), &thePID);\n\t}\n}\n\nbool\tCAHALAudioDevice::HasPreferredStereoChannels(bool inIsInput) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelsForStereo, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\treturn HasProperty(theAddress);\n}\n\nvoid\tCAHALAudioDevice::GetPreferredStereoChannels(bool inIsInput, UInt32& outLeft, UInt32& outRight) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelsForStereo, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theStereoPair[2] = { 0, 0 };\n\tUInt32 theSize = 2 * sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, theStereoPair);\n\toutLeft = theStereoPair[0];\n\toutRight = theStereoPair[1];\n}\n\nvoid\tCAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, UInt32 inLeft, UInt32 inRight)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelsForStereo, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theStereoPair[2] = { inLeft, inRight };\n\tSetPropertyData(theAddress, 0, NULL, 2 * sizeof(UInt32), theStereoPair);\n}\n\nbool\tCAHALAudioDevice::HasPreferredChannelLayout(bool inIsInput) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelLayout, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\treturn HasProperty(theAddress);\n}\n\nvoid\tCAHALAudioDevice::GetPreferredChannelLayout(bool inIsInput, AudioChannelLayout& outChannelLayout) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelLayout, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theSize = (SizeOf32(AudioChannelLayout) - SizeOf32(AudioChannelDescription)) + GetTotalNumberChannels(inIsInput) * SizeOf32(AudioChannelDescription);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &outChannelLayout);\n}\n\nvoid\tCAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, AudioChannelLayout& inChannelLayout)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPreferredChannelLayout, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theSize = (SizeOf32(AudioChannelLayout) - SizeOf32(AudioChannelDescription)) + GetTotalNumberChannels(inIsInput) * SizeOf32(AudioChannelDescription);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inChannelLayout);\n}\n\nUInt32\tCAHALAudioDevice::GetNumberRelatedAudioDevices() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyRelatedDevices);\n\tUInt32 theAnswer = 0;\n\tif(HasProperty(theAddress))\n\t{\n\t\ttheAnswer = GetPropertyDataSize(theAddress, 0, NULL);\n\t\ttheAnswer /= SizeOf32(AudioObjectID);\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetRelatedAudioDevices(UInt32& ioNumberRelatedDevices, AudioObjectID* outRelatedDevices) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyRelatedDevices);\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = ioNumberRelatedDevices * SizeOf32(AudioObjectID);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, outRelatedDevices);\n\t\tioNumberRelatedDevices = theSize / SizeOf32(AudioObjectID);\n\t}\n\telse\n\t{\n\t\tUInt32 theSize = ioNumberRelatedDevices * SizeOf32(AudioObjectID);\n\t\tmemset(outRelatedDevices, 0, theSize);\n\t\tioNumberRelatedDevices = 0;\n\t}\n}\n\nAudioObjectID\tCAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIndex) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tUInt32 theNumberRelatedDevices = GetNumberRelatedAudioDevices();\n\tif((theNumberRelatedDevices > 0) && (inIndex < theNumberRelatedDevices))\n\t{\n\t\tCAAutoArrayDelete<AudioObjectID> theRelatedDeviceList(theNumberRelatedDevices);\n\t\tGetRelatedAudioDevices(theNumberRelatedDevices, theRelatedDeviceList);\n\t\tif((theNumberRelatedDevices > 0) && (inIndex < theNumberRelatedDevices))\n\t\t{\n\t\t\ttheAnswer = theRelatedDeviceList[inIndex];\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nUInt32\tCAHALAudioDevice::GetNumberStreams(bool inIsInput) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStreams, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theAnswer = GetPropertyDataSize(theAddress, 0, NULL);\n\ttheAnswer /= SizeOf32(AudioObjectID);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetStreams(bool inIsInput, UInt32& ioNumberStreams, AudioObjectID* outStreamList) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStreams, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theSize = ioNumberStreams * SizeOf32(AudioObjectID);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outStreamList);\n\tioNumberStreams = theSize / SizeOf32(AudioObjectID);\n}\n\nAudioObjectID\tCAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 inIndex) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tUInt32 theNumberStreams = GetNumberStreams(inIsInput);\n\tif((theNumberStreams > 0) && (inIndex < theNumberStreams))\n\t{\n\t\tCAAutoArrayDelete<AudioObjectID> theStreamList(theNumberStreams);\n\t\tGetStreams(inIsInput, theNumberStreams, theStreamList);\n\t\tif((theNumberStreams > 0) && (inIndex < theNumberStreams))\n\t\t{\n\t\t\ttheAnswer = theStreamList[inIndex];\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nUInt32\tCAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const\n{\n\tUInt32 theAnswer = 0;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStreamConfiguration, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tUInt32 theSize = GetPropertyDataSize(theAddress, 0, NULL);\n\tCAAutoFree<AudioBufferList> theBufferList(theSize);\n\tGetPropertyData(theAddress, 0, NULL, theSize, theBufferList);\n\tfor(UInt32 theIndex = 0; theIndex < theBufferList->mNumberBuffers; ++theIndex)\n\t{\n\t\ttheAnswer += theBufferList->mBuffers[theIndex].mNumberChannels;\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetCurrentVirtualFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const\n{\n\tioNumberStreams = std::min(ioNumberStreams, GetNumberStreams(inIsInput));\n\tfor(UInt32 theIndex = 0; theIndex < ioNumberStreams; ++theIndex)\n\t{\n\t\tCAHALAudioStream theStream(GetStreamByIndex(inIsInput, theIndex));\n\t\ttheStream.GetCurrentVirtualFormat(outFormats[theIndex]);\n\t}\n}\n\nvoid\tCAHALAudioDevice::GetCurrentPhysicalFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const\n{\n\tioNumberStreams = std::min(ioNumberStreams, GetNumberStreams(inIsInput));\n\tfor(UInt32 theIndex = 0; theIndex < ioNumberStreams; ++theIndex)\n\t{\n\t\tCAHALAudioStream theStream(GetStreamByIndex(inIsInput, theIndex));\n\t\ttheStream.GetCurrentPhysicalFormat(outFormats[theIndex]);\n\t}\n}\n\nbool\tCAHALAudioDevice::IsRunning() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDeviceIsRunning);\n\tUInt32 theAnswer = GetPropertyData_UInt32(theAddress, 0, NULL);\n\treturn theAnswer != 0;\n}\n\nbool\tCAHALAudioDevice::IsRunningSomewhere() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDeviceIsRunningSomewhere);\n\tUInt32 theAnswer = 0;\n\tif(HasProperty(theAddress))\n\t{\n\t\ttheAnswer = GetPropertyData_UInt32(theAddress, 0, NULL);\n\t}\n\treturn theAnswer != 0;\n}\n\nUInt32\tCAHALAudioDevice::GetLatency(bool inIsInput) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyLatency, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nUInt32\tCAHALAudioDevice::GetSafetyOffset(bool inIsInput) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySafetyOffset, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nbool\tCAHALAudioDevice::HasClockDomain() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockDomain);\n\treturn HasProperty(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetClockDomain() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockDomain);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nFloat64\tCAHALAudioDevice::GetActualSampleRate() const\n{\n\tFloat64 theAnswer = 0;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyActualSampleRate);\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = sizeof(Float64);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\telse\n\t{\n\t\ttheAnswer = GetNominalSampleRate();\n\t}\n\treturn theAnswer;\n}\n\nFloat64\tCAHALAudioDevice::GetNominalSampleRate() const\n{\n\tFloat64 theAnswer = 0;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyNominalSampleRate);\n\tUInt32 theSize = sizeof(Float64);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::SetNominalSampleRate(Float64 inSampleRate)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyNominalSampleRate);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float64), &inSampleRate);\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const\n{\n\tUInt32 theAnswer = 0;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyAvailableNominalSampleRates);\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = GetPropertyDataSize(theAddress, 0, NULL);\n\t\ttheAnswer = theSize / SizeOf32(AudioValueRange);\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetAvailableNominalSampleRateRanges(UInt32& ioNumberRanges, AudioValueRange* outRanges) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyAvailableNominalSampleRates);\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = ioNumberRanges * SizeOf32(AudioValueRange);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, outRanges);\n\t\tioNumberRanges = theSize / SizeOf32(AudioValueRange);\n\t}\n\telse\n\t{\n\t\tioNumberRanges = 0;\n\t}\n}\n\nvoid\tCAHALAudioDevice::GetAvailableNominalSampleRateRangeByIndex(UInt32 inIndex, Float64& outMinimum, Float64& outMaximum) const\n{\n\tUInt32 theNumberRanges = GetNumberAvailableNominalSampleRateRanges();\n\tThrowIf(inIndex >= theNumberRanges, CAException(kAudioHardwareIllegalOperationError), \"CAHALAudioDevice::GetAvailableNominalSampleRateRangeByIndex: index out of range\");\n\tCAAutoArrayDelete<AudioValueRange> theRanges(theNumberRanges);\n\tGetAvailableNominalSampleRateRanges(theNumberRanges, theRanges);\n\toutMinimum = theRanges[inIndex].mMinimum;\n\toutMaximum = theRanges[inIndex].mMaximum;\n}\n\nbool\tCAHALAudioDevice::IsValidNominalSampleRate(Float64 inSampleRate) const\n{\n\tbool theAnswer = false;\n\tUInt32 theNumberRanges = GetNumberAvailableNominalSampleRateRanges();\n\tCAAutoArrayDelete<AudioValueRange> theRanges(theNumberRanges);\n\tGetAvailableNominalSampleRateRanges(theNumberRanges, theRanges);\n\tfor(UInt32 theIndex = 0; !theAnswer && (theIndex < theNumberRanges); ++theIndex)\n\t{\n\t\ttheAnswer = (inSampleRate >= theRanges[theIndex].mMinimum) && (inSampleRate <= theRanges[theIndex].mMinimum);\n\t}\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioDevice::IsIOBufferSizeSettable() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyBufferFrameSize);\n\treturn IsPropertySettable(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetIOBufferSize() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyBufferFrameSize);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nvoid\tCAHALAudioDevice::SetIOBufferSize(UInt32 inBufferSize)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyBufferFrameSize);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(UInt32), &inBufferSize);\n}\n\nbool\tCAHALAudioDevice::UsesVariableIOBufferSizes() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyUsesVariableBufferFrameSizes);\n\treturn HasProperty(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetMaximumVariableIOBufferSize() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyUsesVariableBufferFrameSizes);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nbool\tCAHALAudioDevice::HasIOBufferSizeRange() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyBufferFrameSizeRange);\n\treturn HasProperty(theAddress);\n}\n\nvoid\tCAHALAudioDevice::GetIOBufferSizeRange(UInt32& outMinimum, UInt32& outMaximum) const\n{\n\tAudioValueRange theAnswer = { 0, 0 };\n\tCAPropertyAddress theAddress(kAudioDevicePropertyBufferFrameSizeRange);\n\tUInt32 theSize = sizeof(AudioValueRange);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\toutMinimum = static_cast<UInt32>(theAnswer.mMinimum);\n\toutMaximum = static_cast<UInt32>(theAnswer.mMaximum);\n}\n\nAudioDeviceIOProcID\tCAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc inIOProc, void* inClientData)\n{\n\tAudioDeviceIOProcID theAnswer = NULL;\n\tOSStatus theError = AudioDeviceCreateIOProcID(mObjectID, inIOProc, inClientData, &theAnswer);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::CreateIOProcID: got an error creating the IOProc ID\");\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::DestroyIOProcID(AudioDeviceIOProcID inIOProcID)\n{\n\tOSStatus theError = AudioDeviceDestroyIOProcID(mObjectID, inIOProcID);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::DestroyIOProcID: got an error destroying the IOProc ID\");\n}\n\nvoid\tCAHALAudioDevice::StartIOProc(AudioDeviceIOProcID inIOProcID)\n{\n\tOSStatus theError = AudioDeviceStart(mObjectID, inIOProcID);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::StartIOProc: got an error starting an IOProc\");\n}\n\nvoid\tCAHALAudioDevice::StartIOProcAtTime(AudioDeviceIOProcID inIOProcID, AudioTimeStamp& ioStartTime, bool inIsInput, bool inIgnoreHardware)\n{\n\tUInt32 theFlags = 0;\n\tif(inIsInput)\n\t{\n\t\ttheFlags |= kAudioDeviceStartTimeIsInputFlag;\n\t}\n\tif(inIgnoreHardware)\n\t{\n\t\ttheFlags |= kAudioDeviceStartTimeDontConsultDeviceFlag;\n\t}\n\t\n\tOSStatus theError = AudioDeviceStartAtTime(mObjectID, inIOProcID, &ioStartTime, theFlags);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::StartIOProcAtTime: got an error starting an IOProc\");\n}\n\nvoid\tCAHALAudioDevice::StopIOProc(AudioDeviceIOProcID inIOProcID)\n{\n\tOSStatus theError = AudioDeviceStop(mObjectID, inIOProcID);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::StopIOProc: got an error stopping an IOProc\");\n}\n\nvoid\tCAHALAudioDevice::GetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, bool* outStreamUsage) const\n{\n\t//\tmake an AudioHardwareIOProcStreamUsage the right size\n\tUInt32 theNumberStreams = GetNumberStreams(inIsInput);\n\tUInt32 theSize = SizeOf32(void*) + SizeOf32(UInt32) + (theNumberStreams * SizeOf32(UInt32));\n\tCAAutoFree<AudioHardwareIOProcStreamUsage> theStreamUsage(theSize);\n\t\n\t//\tset it up\n\ttheStreamUsage->mIOProc = reinterpret_cast<void*>(inIOProcID);\n\ttheStreamUsage->mNumberStreams = theNumberStreams;\n\t\n\t//\tget the property\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIOProcStreamUsage, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tGetPropertyData(theAddress, 0, NULL, theSize, theStreamUsage);\n\t\n\t//\tfill out the return value\n\tfor(UInt32 theIndex = 0; theIndex < theNumberStreams; ++theIndex)\n\t{\n\t\toutStreamUsage[theIndex] = (theStreamUsage->mStreamIsOn[theIndex] != 0);\n\t}\n}\n\nvoid\tCAHALAudioDevice::SetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, const bool* inStreamUsage)\n{\n\t//\tmake an AudioHardwareIOProcStreamUsage the right size\n\tUInt32 theNumberStreams = GetNumberStreams(inIsInput);\n\tUInt32 theSize = SizeOf32(void*) + SizeOf32(UInt32) + (theNumberStreams * SizeOf32(UInt32));\n\tCAAutoFree<AudioHardwareIOProcStreamUsage> theStreamUsage(theSize);\n\t\n\t//\tset it up\n\ttheStreamUsage->mIOProc = reinterpret_cast<void*>(inIOProcID);\n\ttheStreamUsage->mNumberStreams = theNumberStreams;\n\tfor(UInt32 theIndex = 0; theIndex < theNumberStreams; ++theIndex)\n\t{\n\t\ttheStreamUsage->mStreamIsOn[theIndex] = (inStreamUsage[theIndex] ? 1 : 0);\n\t}\n\t\n\t//\tset the property\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIOProcStreamUsage, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);\n\tSetPropertyData(theAddress, 0, NULL, theSize, theStreamUsage);\n}\n\nFloat32\tCAHALAudioDevice::GetIOCycleUsage() const\n{\n\tFloat32 theAnswer = 0;\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIOCycleUsage);\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::SetIOCycleUsage(Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyIOCycleUsage);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float32), &inValue);\n}\n\nvoid\tCAHALAudioDevice::GetCurrentTime(AudioTimeStamp& outTime)\n{\n\tOSStatus theError = AudioDeviceGetCurrentTime(mObjectID, &outTime);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::GetCurrentTime: got an error getting the current time\");\n}\n\nvoid\tCAHALAudioDevice::TranslateTime(const AudioTimeStamp& inTime, AudioTimeStamp& outTime)\n{\n\tOSStatus theError = AudioDeviceTranslateTime(mObjectID, &inTime, &outTime);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::TranslateTime: got an error translating time\");\n}\n\nvoid\tCAHALAudioDevice::GetNearestStartTime(AudioTimeStamp& ioTime, bool inIsInput, bool inIgnoreHardware)\n{\n\tUInt32 theFlags = 0;\n\tif(inIsInput)\n\t{\n\t\ttheFlags |= kAudioDeviceStartTimeIsInputFlag;\n\t}\n\tif(inIgnoreHardware)\n\t{\n\t\ttheFlags |= kAudioDeviceStartTimeDontConsultDeviceFlag;\n\t}\n\t\n\tOSStatus theError = AudioDeviceGetNearestStartTime(mObjectID, &ioTime, theFlags);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::GetNearestStartTime: got an error getting the start time\");\n}\n\nbool\tCAHALAudioDevice::HasVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeScalar, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::VolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeScalar, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeScalar, inScope, inChannel);\n\tFloat32 theValue = 0.0f;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeDecibels, inScope, inChannel);\n\tFloat32 theValue = 0.0f;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nvoid\tCAHALAudioDevice::SetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeScalar, inScope, inChannel);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float32), &inValue);\n}\n\nvoid\tCAHALAudioDevice::SetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeDecibels, inScope, inChannel);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float32), &inValue);\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeDecibelsToScalar, inScope, inChannel);\n\tFloat32 theValue = inValue;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nFloat32\tCAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyVolumeScalarToDecibels, inScope, inChannel);\n\tFloat32 theValue = inValue;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nbool\tCAHALAudioDevice::HasSubVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeScalar, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::SubVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeScalar, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeScalar, inScope, inChannel);\n\tFloat32 theValue = 0.0f;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeDecibels, inScope, inChannel);\n\tFloat32 theValue = 0.0f;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nvoid\tCAHALAudioDevice::SetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeScalar, inScope, inChannel);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float32), &inValue);\n}\n\nvoid\tCAHALAudioDevice::SetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeDecibels, inScope, inChannel);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(Float32), &inValue);\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeDecibelsToScalar, inScope, inChannel);\n\tFloat32 theValue = inValue;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nFloat32\tCAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubVolumeScalarToDecibels, inScope, inChannel);\n\tFloat32 theValue = inValue;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nbool\tCAHALAudioDevice::HasMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyMute, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::MuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyMute, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nbool\tCAHALAudioDevice::GetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyMute, inScope, inChannel);\n\tUInt32 theValue = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue != 0;\n}\n\nvoid\tCAHALAudioDevice::SetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyMute, inScope, inChannel);\n\tUInt32 theValue = (inValue ? 1 : 0);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n}\n\nbool\tCAHALAudioDevice::HasSoloControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySolo, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::SoloControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySolo, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nbool\tCAHALAudioDevice::GetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySolo, inScope, inChannel);\n\tUInt32 theValue = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue != 0;\n}\n\nvoid\tCAHALAudioDevice::SetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySolo, inScope, inChannel);\n\tUInt32 theValue = (inValue ? 1 : 0);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n}\n\nbool\tCAHALAudioDevice::HasStereoPanControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStereoPan, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::StereoPanControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStereoPan, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nFloat32\tCAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStereoPan, inScope, inChannel);\n\tFloat32 theValue = 0.0f;\n\tUInt32 theSize = sizeof(Float32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue;\n}\n\nvoid\tCAHALAudioDevice::SetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStereoPan, inScope, inChannel);\n\tUInt32 theSize = sizeof(Float32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inValue);\n}\n\nvoid\tCAHALAudioDevice::GetStereoPanControlChannels(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& outLeftChannel, UInt32& outRightChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyStereoPanChannels, inScope, inChannel);\n\tUInt32 theValue[2] = { 0, 0 };\n\tUInt32 theSize = 2 * sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, theValue);\n\toutLeftChannel = theValue[0];\n\toutRightChannel = theValue[1];\n}\n\nbool\tCAHALAudioDevice::HasJackControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyJackIsConnected, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::GetJackControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyJackIsConnected, inScope, inChannel);\n\tUInt32 theValue = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue != 0;\n}\n\nbool\tCAHALAudioDevice::HasSubMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubMute, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::SubMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubMute, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nbool\tCAHALAudioDevice::GetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubMute, inScope, inChannel);\n\tUInt32 theValue = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue != 0;\n}\n\nvoid\tCAHALAudioDevice::SetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertySubMute, inScope, inChannel);\n\tUInt32 theValue = (inValue ? 1 : 0);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n}\n\nbool\tCAHALAudioDevice::HasiSubOwnerControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDriverShouldOwniSub, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::iSubOwnerControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDriverShouldOwniSub, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nbool\tCAHALAudioDevice::GetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDriverShouldOwniSub, inScope, inChannel);\n\tUInt32 theValue = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theValue != 0;\n}\n\nvoid\tCAHALAudioDevice::SetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDriverShouldOwniSub, inScope, inChannel);\n\tUInt32 theValue = (inValue ? 1 : 0);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n}\n\nbool\tCAHALAudioDevice::HasDataSourceControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSource, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::DataSourceControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSource, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSource, inScope, inChannel);\n\tUInt32 theAnswer = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::SetCurrentDataSourceByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSource, inScope, inChannel);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inID);\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSources, inScope, inChannel);\n\tUInt32 theAnswer = 0;\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = GetPropertyDataSize(theAddress, 0, NULL);\n\t\ttheAnswer = theSize / SizeOf32(UInt32);\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberSources, UInt32* outSources) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSources, inScope, inChannel);\n\tUInt32 theNumberSources = std::min(GetNumberAvailableDataSources(inScope, inChannel), ioNumberSources);\n\tUInt32 theSize = theNumberSources * SizeOf32(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outSources);\n\tioNumberSources = theSize / SizeOf32(UInt32);\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const\n{\n\tAudioStreamID theAnswer = 0;\n\tUInt32 theNumberSources = GetNumberAvailableDataSources(inScope, inChannel);\n\tif((theNumberSources > 0) && (inIndex < theNumberSources))\n\t{\n\t\tCAAutoArrayDelete<UInt32> theSourceList(theNumberSources);\n\t\tGetAvailableDataSources(inScope, inChannel, theNumberSources, theSourceList);\n\t\ttheAnswer = theSourceList[inIndex];\n\t}\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyDataSourceNameForIDCFString, inScope, inChannel);\n\tCFStringRef theAnswer = NULL;\n\tAudioValueTranslation theTranslation = { &inID, sizeof(UInt32), &theAnswer, sizeof(CFStringRef) };\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theTranslation);\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioDevice::HasDataDestinationControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestination, inScope, inChannel);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::DataDestinationControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestination, inScope, inChannel);\n\treturn IsPropertySettable(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestination, inScope, inChannel);\n\tUInt32 theAnswer = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::SetCurrentDataDestinationByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestination, inScope, inChannel);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inID);\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestinations, inScope, inChannel);\n\tUInt32 theAnswer = 0;\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = GetPropertyDataSize(theAddress, 0, NULL);\n\t\ttheAnswer = theSize / SizeOf32(UInt32);\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberDestinations, UInt32* outDestinations) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestinations, inScope, inChannel);\n\tUInt32 theNumberDestinations = std::min(GetNumberAvailableDataDestinations(inScope, inChannel), ioNumberDestinations);\n\tUInt32 theSize = theNumberDestinations * SizeOf32(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outDestinations);\n\tioNumberDestinations = theSize / SizeOf32(UInt32);\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const\n{\n\tAudioStreamID theAnswer = 0;\n\tUInt32 theNumberDestinations = GetNumberAvailableDataDestinations(inScope, inChannel);\n\tif((theNumberDestinations > 0) && (inIndex < theNumberDestinations))\n\t{\n\t\tCAAutoArrayDelete<UInt32> theDestinationList(theNumberDestinations);\n\t\tGetAvailableDataDestinations(inScope, inChannel, theNumberDestinations, theDestinationList);\n\t\ttheAnswer = theDestinationList[inIndex];\n\t}\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyPlayThruDestinationNameForIDCFString, inScope, inChannel);\n\tCFStringRef theAnswer = NULL;\n\tAudioValueTranslation theTranslation = { &inID, sizeof(UInt32), &theAnswer, sizeof(CFStringRef) };\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theTranslation);\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioDevice::HasClockSourceControl() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSource);\n\treturn HasProperty(theAddress);\n}\n\nbool\tCAHALAudioDevice::ClockSourceControlIsSettable() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSource);\n\treturn IsPropertySettable(theAddress);\n}\n\nUInt32\tCAHALAudioDevice::GetCurrentClockSourceID() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSource);\n\tUInt32 theAnswer = 0;\n\tUInt32 theSize = sizeof(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::SetCurrentClockSourceByID(UInt32 inID)\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSource);\n\tUInt32 theSize = sizeof(UInt32);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inID);\n}\n\nUInt32\tCAHALAudioDevice::GetNumberAvailableClockSources() const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSources);\n\tUInt32 theAnswer = 0;\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = GetPropertyDataSize(theAddress, 0, NULL);\n\t\ttheAnswer = theSize / SizeOf32(UInt32);\n\t}\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioDevice::GetAvailableClockSources(UInt32& ioNumberSources, UInt32* outSources) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSources);\n\tUInt32 theNumberSources = std::min(GetNumberAvailableClockSources(), ioNumberSources);\n\tUInt32 theSize = theNumberSources * SizeOf32(UInt32);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outSources);\n\tioNumberSources = theSize / SizeOf32(UInt32);\n}\n\nUInt32\tCAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) const\n{\n\tAudioStreamID theAnswer = 0;\n\tUInt32 theNumberSources = GetNumberAvailableClockSources();\n\tif((theNumberSources > 0) && (inIndex < theNumberSources))\n\t{\n\t\tCAAutoArrayDelete<UInt32> theSourceList(theNumberSources);\n\t\tGetAvailableClockSources(theNumberSources, theSourceList);\n\t\ttheAnswer = theSourceList[inIndex];\n\t}\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSourceNameForIDCFString);\n\tCFStringRef theAnswer = NULL;\n\tAudioValueTranslation theTranslation = { &inID, sizeof(UInt32), &theAnswer, sizeof(CFStringRef) };\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theTranslation);\n\treturn theAnswer;\n}\n\nUInt32\tCAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const\n{\n\tCAPropertyAddress theAddress(kAudioDevicePropertyClockSourceKindForID);\n\tUInt32 theAnswer = 0;\n\tAudioValueTranslation theTranslation = { &inID, sizeof(UInt32), &theAnswer, sizeof(UInt32) };\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theTranslation);\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioDevice.h",
    "content": "/*\n     File: CAHALAudioDevice.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAHALAudioDevice_h__)\n#define __CAHALAudioDevice_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSuper Class Includes\n#include \"CAHALAudioObject.h\"\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n//==================================================================================================\n//\tCAHALAudioDevice\n//==================================================================================================\n\nclass CAHALAudioDevice\n:\n\tpublic\tCAHALAudioObject\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCAHALAudioDevice(AudioObjectID inAudioDevice);\n\t\t\t\t\t\tCAHALAudioDevice(CFStringRef inUID);\n\tvirtual\t\t\t\t~CAHALAudioDevice();\n\n//\tGeneral Stuff\npublic:\n\tCFStringRef\t\t\tCopyDeviceUID() const;\n\tbool\t\t\t\tHasModelUID() const;\n\tCFStringRef\t\t\tCopyModelUID() const;\n\tCFStringRef\t\t\tCopyConfigurationApplicationBundleID() const;\n\tCFURLRef\t\t\tCopyIconLocation() const;\n\tUInt32\t\t\t\tGetTransportType() const;\n\tbool\t\t\t\tCanBeDefaultDevice(bool inIsInput, bool inIsSystem) const;\n\tbool\t\t\t\tHasDevicePlugInStatus() const;\n\tOSStatus\t\t\tGetDevicePlugInStatus() const;\n\tbool\t\t\t\tIsAlive() const;\n\tbool\t\t\t\tIsHidden() const;\n\tpid_t\t\t\t\tGetHogModeOwner() const;\n\tbool\t\t\t\tIsHogModeSettable() const;\n\tbool\t\t\t\tTakeHogMode();\n\tvoid\t\t\t\tReleaseHogMode();\n\tbool\t\t\t\tHasPreferredStereoChannels(bool inIsInput) const;\n\tvoid\t\t\t\tGetPreferredStereoChannels(bool inIsInput, UInt32& outLeft, UInt32& outRight) const;\n\tvoid\t\t\t\tSetPreferredStereoChannels(bool inIsInput, UInt32 inLeft, UInt32 inRight);\n\tbool\t\t\t\tHasPreferredChannelLayout(bool inIsInput) const;\n\tvoid\t\t\t\tGetPreferredChannelLayout(bool inIsInput, AudioChannelLayout& outChannelLayout) const;\n\tvoid\t\t\t\tSetPreferredStereoChannels(bool inIsInput, AudioChannelLayout& inChannelLayout);\n\tUInt32\t\t\t\tGetNumberRelatedAudioDevices() const;\n\tvoid\t\t\t\tGetRelatedAudioDevices(UInt32& ioNumberRelatedDevices, AudioObjectID* outRelatedDevices) const;\n\tAudioObjectID\t\tGetRelatedAudioDeviceByIndex(UInt32 inIndex) const;\n\n//\tStream Stuff\npublic:\n\tUInt32\t\t\t\tGetNumberStreams(bool inIsInput) const;\n\tvoid\t\t\t\tGetStreams(bool inIsInput, UInt32& ioNumberStreams, AudioObjectID* outStreamList) const;\n\tAudioObjectID\t\tGetStreamByIndex(bool inIsInput, UInt32 inIndex) const;\n\tUInt32\t\t\t\tGetTotalNumberChannels(bool inIsInput) const;\n\tvoid\t\t\t\tGetCurrentVirtualFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const;\n\tvoid\t\t\t\tGetCurrentPhysicalFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const;\n\t\n//\tIO Stuff\npublic:\n\tbool\t\t\t\tIsRunning() const;\n\tbool\t\t\t\tIsRunningSomewhere() const;\n\tUInt32\t\t\t\tGetLatency(bool inIsInput) const;\n\tUInt32\t\t\t\tGetSafetyOffset(bool inIsInput) const;\n\tbool\t\t\t\tHasClockDomain() const;\n\tUInt32\t\t\t\tGetClockDomain() const;\n\tFloat64\t\t\t\tGetActualSampleRate() const;\n\tFloat64\t\t\t\tGetNominalSampleRate() const;\n\tvoid\t\t\t\tSetNominalSampleRate(Float64 inSampleRate);\n\tUInt32\t\t\t\tGetNumberAvailableNominalSampleRateRanges() const;\n\tvoid\t\t\t\tGetAvailableNominalSampleRateRanges(UInt32& ioNumberRanges, AudioValueRange* outRanges) const;\n\tvoid\t\t\t\tGetAvailableNominalSampleRateRangeByIndex(UInt32 inIndex, Float64& outMinimum, Float64& outMaximum) const;\n\tbool\t\t\t\tIsValidNominalSampleRate(Float64 inSampleRate) const;\n\tbool\t\t\t\tIsIOBufferSizeSettable() const;\n\tUInt32\t\t\t\tGetIOBufferSize() const;\n\tvoid\t\t\t\tSetIOBufferSize(UInt32 inBufferSize);\n\tbool\t\t\t\tUsesVariableIOBufferSizes() const;\n\tUInt32\t\t\t\tGetMaximumVariableIOBufferSize() const;\n\tbool\t\t\t\tHasIOBufferSizeRange() const;\n\tvoid\t\t\t\tGetIOBufferSizeRange(UInt32& outMinimum, UInt32& outMaximum) const;\n\tAudioDeviceIOProcID\tCreateIOProcID(AudioDeviceIOProc inIOProc, void* inClientData);\n\tAudioDeviceIOProcID\tCreateIOProcIDWithBlock(dispatch_queue_t inDispatchQueue, AudioDeviceIOBlock inIOBlock);\n\tvoid\t\t\t\tDestroyIOProcID(AudioDeviceIOProcID inIOProcID);\n\tvoid\t\t\t\tStartIOProc(AudioDeviceIOProcID inIOProcID);\n\tvoid\t\t\t\tStartIOProcAtTime(AudioDeviceIOProcID inIOProcID, AudioTimeStamp& ioStartTime, bool inIsInput, bool inIgnoreHardware);\n\tvoid\t\t\t\tStopIOProc(AudioDeviceIOProcID inIOProcID);\n\tvoid\t\t\t\tGetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, bool* outStreamUsage) const;\n\tvoid\t\t\t\tSetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, const bool* inStreamUsage);\n\tFloat32\t\t\t\tGetIOCycleUsage() const;\n\tvoid\t\t\t\tSetIOCycleUsage(Float32 inValue);\n\t\n//\tTime Operations\npublic:\n\tvoid\t\t\t\tGetCurrentTime(AudioTimeStamp& outTime);\n\tvoid\t\t\t\tTranslateTime(const AudioTimeStamp& inTime, AudioTimeStamp& outTime);\n\tvoid\t\t\t\tGetNearestStartTime(AudioTimeStamp& ioTime, bool inIsInput, bool inIgnoreHardware);\n\n//\tControls\npublic:\n\tbool\t\t\t\tHasVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tFloat32\t\t\t\tGetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tFloat32\t\t\t\tGetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue);\n\tvoid\t\t\t\tSetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue);\n\tFloat32\t\t\t\tGetVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const;\n\tFloat32\t\t\t\tGetVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const;\n\t\n\tbool\t\t\t\tHasSubVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tSubVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tFloat32\t\t\t\tGetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tFloat32\t\t\t\tGetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue);\n\tvoid\t\t\t\tSetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue);\n\tFloat32\t\t\t\tGetSubVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const;\n\tFloat32\t\t\t\tGetSubVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const;\n\t\n\tbool\t\t\t\tHasMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tGetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue);\n\t\n\tbool\t\t\t\tHasSoloControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tSoloControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tGetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue);\n\t\n\tbool\t\t\t\tHasStereoPanControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tStereoPanControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tFloat32\t\t\t\tGetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue);\n\tvoid\t\t\t\tGetStereoPanControlChannels(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& outLeftChannel, UInt32& outRightChannel) const;\n\t\n\tbool\t\t\t\tHasJackControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tGetJackControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\t\n\tbool\t\t\t\tHasSubMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tSubMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tGetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue);\n\t\n\tbool\t\t\t\tHasiSubOwnerControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tiSubOwnerControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tGetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue);\n\n\tbool\t\t\t\tHasDataSourceControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tDataSourceControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tUInt32\t\t\t\tGetCurrentDataSourceID(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetCurrentDataSourceByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID);\n\tUInt32\t\t\t\tGetNumberAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tGetAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberSources, UInt32* outSources) const;\n\tUInt32\t\t\t\tGetAvailableDataSourceByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const;\n\tCFStringRef\t\t\tCopyDataSourceNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const;\n\n\tbool\t\t\t\tHasDataDestinationControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tbool\t\t\t\tDataDestinationControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tUInt32\t\t\t\tGetCurrentDataDestinationID(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tSetCurrentDataDestinationByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID);\n\tUInt32\t\t\t\tGetNumberAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel) const;\n\tvoid\t\t\t\tGetAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberDestinations, UInt32* outDestinations) const;\n\tUInt32\t\t\t\tGetAvailableDataDestinationByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const;\n\tCFStringRef\t\t\tCopyDataDestinationNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const;\n\n\tbool\t\t\t\tHasClockSourceControl() const;\n\tbool\t\t\t\tClockSourceControlIsSettable() const;\n\tUInt32\t\t\t\tGetCurrentClockSourceID() const;\n\tvoid\t\t\t\tSetCurrentClockSourceByID(UInt32 inID);\n\tUInt32\t\t\t\tGetNumberAvailableClockSources() const;\n\tvoid\t\t\t\tGetAvailableClockSources(UInt32& ioNumberSources, UInt32* outSources) const;\n\tUInt32\t\t\t\tGetAvailableClockSourceByIndex(UInt32 inIndex) const;\n\tCFStringRef\t\t\tCopyClockSourceNameForID(UInt32 inID) const;\n\tUInt32\t\t\t\tGetClockSourceKindForID(UInt32 inID) const;\n\t\n};\n\ninline AudioDeviceIOProcID\tCAHALAudioDevice::CreateIOProcIDWithBlock(dispatch_queue_t inDispatchQueue, AudioDeviceIOBlock inIOBlock)\n{\n\tAudioDeviceIOProcID theAnswer = NULL;\n\tOSStatus theError = AudioDeviceCreateIOProcIDWithBlock(&theAnswer, mObjectID, inDispatchQueue, inIOBlock);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioDevice::CreateIOProcIDWithBlock: got an error creating the IOProc ID\");\n\treturn theAnswer;\n}\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioObject.cpp",
    "content": "/*\n     File: CAHALAudioObject.cpp\n Abstract: CAHALAudioObject.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAHALAudioObject.h\"\n\n//\tPublicUtility Includes\n#include \"CAAutoDisposer.h\"\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAPropertyAddress.h\"\n\n//==================================================================================================\n//\tCAHALAudioObject\n//==================================================================================================\n\nCAHALAudioObject::CAHALAudioObject(AudioObjectID inObjectID)\n:\n\tmObjectID(inObjectID)\n{\n}\n\nCAHALAudioObject::~CAHALAudioObject()\n{\n}\n\nAudioObjectID\tCAHALAudioObject::GetObjectID() const\n{\n\treturn mObjectID;\n}\n\nvoid\tCAHALAudioObject::SetObjectID(AudioObjectID inObjectID)\n{\n\tmObjectID = inObjectID;\n}\n\nAudioClassID\tCAHALAudioObject::GetClassID() const\n{\n\t//\tset up the return value\n\tAudioClassID theAnswer = 0;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyClass);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\tUInt32 theSize = sizeof(AudioClassID);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nAudioObjectID\tCAHALAudioObject::GetOwnerObjectID() const\n{\n\t//\tset up the return value\n\tAudioObjectID theAnswer = 0;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyOwner);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(AudioObjectID);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyOwningPlugInBundleID() const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyCreator);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyName() const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyName);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyManufacturer() const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyManufacturer);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyElementName, inScope, inElement);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyCategoryNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyElementCategoryName, inScope, inElement);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nCFStringRef\tCAHALAudioObject::CopyNumberNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const\n{\n\t//\tset up the return value\n\tCFStringRef theAnswer = NULL;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyElementNumberName, inScope, inElement);\n\t\n\t//\tmake sure the property exists\n\tif(HasProperty(theAddress))\n\t{\n\t\t//\tget the property data\n\t\tUInt32 theSize = sizeof(CFStringRef);\n\t\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioObject::ObjectExists(AudioObjectID inObjectID)\n{\n\tBoolean isSettable;\n\tCAPropertyAddress theAddress(kAudioObjectPropertyClass);\n    // BGM edit: Negated the expression returned. Seems to have been a bug.\n    //return (inObjectID == 0) || (AudioObjectIsPropertySettable(inObjectID, &theAddress, &isSettable) != 0);\n    return (inObjectID != kAudioObjectUnknown) && (AudioObjectIsPropertySettable(inObjectID, &theAddress, &isSettable) == kAudioHardwareNoError);\n    // BGM edit end\n}\n\nUInt32\tCAHALAudioObject::GetNumberOwnedObjects(AudioClassID inClass) const\n{\n\t//\tset up the return value\n\tUInt32 theAnswer = 0;\n\t\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyOwnedObjects);\n\t\n\t//\tfigure out the qualifier\n\tUInt32 theQualifierSize = 0;\n\tvoid* theQualifierData = NULL;\n\tif(inClass != 0)\n\t{\n\t\ttheQualifierSize = sizeof(AudioObjectID);\n\t\ttheQualifierData = &inClass;\n\t}\n\t\n\t//\tget the property data size\n\ttheAnswer = GetPropertyDataSize(theAddress, theQualifierSize, theQualifierData);\n\t\n\t//\tcalculate the number of object IDs\n\ttheAnswer /= SizeOf32(AudioObjectID);\n\t\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioObject::GetAllOwnedObjects(AudioClassID inClass, UInt32& ioNumberObjects, AudioObjectID* ioObjectIDs) const\n{\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyOwnedObjects);\n\t\n\t//\tfigure out the qualifier\n\tUInt32 theQualifierSize = 0;\n\tvoid* theQualifierData = NULL;\n\tif(inClass != 0)\n\t{\n\t\ttheQualifierSize = sizeof(AudioObjectID);\n\t\ttheQualifierData = &inClass;\n\t}\n\t\n\t//\tget the property data\n\tUInt32 theDataSize = ioNumberObjects * SizeOf32(AudioClassID);\n\tGetPropertyData(theAddress, theQualifierSize, theQualifierData, theDataSize, ioObjectIDs);\n\t\n\t//\tset the number of object IDs being returned\n\tioNumberObjects = theDataSize / SizeOf32(AudioObjectID);\n}\n\nAudioObjectID\tCAHALAudioObject::GetOwnedObjectByIndex(AudioClassID inClass, UInt32 inIndex)\n{\n\t//\tset up the property address\n\tCAPropertyAddress theAddress(kAudioObjectPropertyOwnedObjects);\n\t\n\t//\tfigure out the qualifier\n\tUInt32 theQualifierSize = 0;\n\tvoid* theQualifierData = NULL;\n\tif(inClass != 0)\n\t{\n\t\ttheQualifierSize = sizeof(AudioObjectID);\n\t\ttheQualifierData = &inClass;\n\t}\n\t\n\t//\tfigure out how much space to allocate\n\tUInt32 theDataSize = GetPropertyDataSize(theAddress, theQualifierSize, theQualifierData);\n\tUInt32 theNumberObjectIDs = theDataSize / SizeOf32(AudioObjectID);\n\t\n\t//\tset up the return value\n\tAudioObjectID theAnswer = 0;\n\t\n\t//\tmaker sure the index is in range\n\tif(inIndex < theNumberObjectIDs)\n\t{\n\t\t//\tallocate it\n\t\tCAAutoArrayDelete<AudioObjectID> theObjectList(theDataSize / sizeof(AudioObjectID));\n\t\t\n\t\t//\tget the property data\n\t\tGetPropertyData(theAddress, theQualifierSize, theQualifierData, theDataSize, theObjectList);\n\t\t\n\t\t//\tget the return value\n\t\ttheAnswer = theObjectList[inIndex];\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCAHALAudioObject::HasProperty(const AudioObjectPropertyAddress& inAddress) const\n{\n\treturn AudioObjectHasProperty(mObjectID, &inAddress);\n}\n\nbool\tCAHALAudioObject::IsPropertySettable(const AudioObjectPropertyAddress& inAddress) const\n{\n\tBoolean isSettable = false;\n\tOSStatus theError = AudioObjectIsPropertySettable(mObjectID, &inAddress, &isSettable);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::IsPropertySettable: got an error getting info about a property\");\n\treturn isSettable != 0;\n}\n\nUInt32\tCAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n\tUInt32 theDataSize = 0;\n\tOSStatus theError = AudioObjectGetPropertyDataSize(mObjectID, &inAddress, inQualifierDataSize, inQualifierData, &theDataSize);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::GetPropertyDataSize: got an error getting the property data size\");\n\treturn theDataSize;\n}\n\nvoid\tCAHALAudioObject::GetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32& ioDataSize, void* outData) const\n{\n\tOSStatus theError = AudioObjectGetPropertyData(mObjectID, &inAddress, inQualifierDataSize, inQualifierData, &ioDataSize, outData);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::GetPropertyData: got an error getting the property data\");\n}\n\nvoid\tCAHALAudioObject::SetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n\tOSStatus theError = AudioObjectSetPropertyData(mObjectID, &inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::SetPropertyData: got an error setting the property data\");\n}\n\nvoid\tCAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)\n{\n\tOSStatus theError = AudioObjectAddPropertyListener(mObjectID, &inAddress, inListenerProc, inClientData);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::AddPropertyListener: got an error adding a property listener\");\n}\n\nvoid\tCAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)\n{\n\tOSStatus theError = AudioObjectRemovePropertyListener(mObjectID, &inAddress, inListenerProc, inClientData);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::RemovePropertyListener: got an error removing a property listener\");\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioObject.h",
    "content": "/*\n     File: CAHALAudioObject.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAHALAudioObject_h__)\n#define __CAHALAudioObject_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudio.h>\n\t#include <CoreFoundation/CoreFoundation.h>\n#else\n\t#include <CoreAudio.h>\n\t#include <CoreFoundation.h>\n#endif\n\n//==================================================================================================\n//\tCAHALAudioObject\n//==================================================================================================\n\nclass CAHALAudioObject\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\t\tCAHALAudioObject(AudioObjectID inObjectID);\n\tvirtual\t\t\t\t\t\t~CAHALAudioObject();\n\n//\tAttributes\npublic:\n\tAudioObjectID\t\t\t\tGetObjectID() const;\n\tvoid\t\t\t\t\t\tSetObjectID(AudioObjectID inObjectID);\n\tAudioClassID\t\t\t\tGetClassID() const;\n\tAudioObjectID\t\t\t\tGetOwnerObjectID() const;\n\tCFStringRef\t\t\t\t\tCopyOwningPlugInBundleID() const;\n\tCFStringRef\t\t\t\t\tCopyName() const;\n\tCFStringRef\t\t\t\t\tCopyManufacturer() const;\n\tCFStringRef\t\t\t\t\tCopyNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const;\n\tCFStringRef\t\t\t\t\tCopyCategoryNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const;\n\tCFStringRef\t\t\t\t\tCopyNumberNameForElement(AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement) const;\n\t\n\tstatic bool\t\t\t\t\tObjectExists(AudioObjectID inObjectID);\n\n//\tOwned Objects\npublic:\n\tUInt32\t\t\t\t\t\tGetNumberOwnedObjects(AudioClassID inClass) const;\n\tvoid\t\t\t\t\t\tGetAllOwnedObjects(AudioClassID inClass, UInt32& ioNumberObjects, AudioObjectID* ioObjectIDs) const;\n\tAudioObjectID\t\t\t\tGetOwnedObjectByIndex(AudioClassID inClass, UInt32 inIndex);\n\t\n//\tProperty Operations\npublic:\n\tbool\t\t\t\t\t\tHasProperty(const AudioObjectPropertyAddress& inAddress) const;\n\tbool\t\t\t\t\t\tIsPropertySettable(const AudioObjectPropertyAddress& inAddress) const;\n\tUInt32\t\t\t\t\t\tGetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const;\n\t\n\tvoid\t\t\t\t\t\tGetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32& ioDataSize, void* outData) const;\n\tvoid\t\t\t\t\t\tSetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData);\n\t\n\tUInt32\t\t\t\t\t\tGetPropertyData_UInt32(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t\t{ UInt32 theAnswer = 0; UInt32 theDataSize = SizeOf32(UInt32); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &theAnswer); return theAnswer; }\n\tvoid\t\t\t\t\t\tSetPropertyData_UInt32(const AudioObjectPropertyAddress& inAddress, UInt32 inValue, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(UInt32), &inValue); }\n\n\tFloat32\t\t\t\t\t\tGetPropertyData_Float32(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t\t{ Float32 theAnswer = 0; UInt32 theDataSize = SizeOf32(Float32); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &theAnswer); return theAnswer; }\n\tvoid\t\t\t\t\t\tSetPropertyData_Float32(const AudioObjectPropertyAddress& inAddress, Float32 inValue, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(Float32), &inValue); }\n\n\tFloat64\t\t\t\t\t\tGetPropertyData_Float64(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t\t{ Float64 theAnswer = 0; UInt32 theDataSize = SizeOf32(Float64); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &theAnswer); return theAnswer; }\n\tvoid\t\t\t\t\t\tSetPropertyData_Float64(const AudioObjectPropertyAddress& inAddress, Float64 inValue, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(Float64), &inValue); }\n\n\tCFTypeRef\t\t\t\t\tGetPropertyData_CFType(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t\t{ CFTypeRef theAnswer = NULL; UInt32 theDataSize = SizeOf32(CFTypeRef); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &theAnswer); return theAnswer; }\n\tvoid\t\t\t\t\t\tSetPropertyData_CFType(const AudioObjectPropertyAddress& inAddress, CFTypeRef inValue, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(CFTypeRef), &inValue); }\n\n\tCFStringRef\t\t\t\t\tGetPropertyData_CFString(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t\t{ CFStringRef theAnswer = NULL; UInt32 theDataSize = SizeOf32(CFStringRef); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &theAnswer); return theAnswer; }\n\tvoid\t\t\t\t\t\tSetPropertyData_CFString(const AudioObjectPropertyAddress& inAddress, CFStringRef inValue, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(CFStringRef), &inValue); }\n\n\ttemplate <class T> void\t\tGetPropertyData_Struct(const AudioObjectPropertyAddress& inAddress, T& outStruct, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t{ UInt32 theDataSize = SizeOf32(T); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, &outStruct); }\n\ttemplate <class T> void\t\tSetPropertyData_Struct(const AudioObjectPropertyAddress& inAddress, T& inStruct, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t\t\t\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, SizeOf32(T), &inStruct); }\n\n\ttemplate <class T> UInt32\tGetPropertyData_ArraySize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t\t\t\t\t\t\t\t\t{ return GetPropertyDataSize(inAddress, inQualifierDataSize, inQualifierData) / SizeOf32(T); }\n\ttemplate <class T> void\t\tGetPropertyData_Array(const AudioObjectPropertyAddress& inAddress, UInt32& ioNumberItems, T* outArray, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL) const\t{ UInt32 theDataSize = ioNumberItems * SizeOf32(T); GetPropertyData(inAddress, inQualifierDataSize, inQualifierData, theDataSize, outArray); ioNumberItems = theDataSize / SizeOf32(T); }\n\ttemplate <class T> void\t\tSetPropertyData_Array(const AudioObjectPropertyAddress& inAddress, UInt32 inNumberItems, T* inArray, UInt32 inQualifierDataSize = 0, const void* inQualifierData = NULL)\t\t\t{ SetPropertyData(inAddress, inQualifierDataSize, inQualifierData, inNumberItems * SizeOf32(T), inArray); }\n\t\n\tvoid\t\t\t\t\t\tAddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData);\n\tvoid\t\t\t\t\t\tRemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData);\n\n\tvoid\t\t\t\t\t\tAddPropertyListenerBlock(const AudioObjectPropertyAddress& inAddress, dispatch_queue_t inDispatchQueue, AudioObjectPropertyListenerBlock inListenerBlock);\n\tvoid\t\t\t\t\t\tRemovePropertyListenerBlock(const AudioObjectPropertyAddress& inAddress, dispatch_queue_t inDispatchQueue, AudioObjectPropertyListenerBlock inListenerBlock);\n\n//\tImplementation\nprotected:\n\tAudioObjectID\t\t\t\tmObjectID;\n\n};\n\ninline void\tCAHALAudioObject::AddPropertyListenerBlock(const AudioObjectPropertyAddress& inAddress, dispatch_queue_t inDispatchQueue, AudioObjectPropertyListenerBlock inListenerBlock)\n{\n\tOSStatus theError = AudioObjectAddPropertyListenerBlock(mObjectID, &inAddress, inDispatchQueue, inListenerBlock);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::AddPropertyListenerBlock: got an error adding a property listener\");\n}\n\ninline void\tCAHALAudioObject::RemovePropertyListenerBlock(const AudioObjectPropertyAddress& inAddress, dispatch_queue_t inDispatchQueue, AudioObjectPropertyListenerBlock inListenerBlock)\n{\n\tOSStatus theError = AudioObjectRemovePropertyListenerBlock(mObjectID, &inAddress, inDispatchQueue, inListenerBlock);\n\tThrowIfError(theError, CAException(theError), \"CAHALAudioObject::RemovePropertyListener: got an error removing a property listener\");\n}\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioStream.cpp",
    "content": "/*\n     File: CAHALAudioStream.cpp\n Abstract: CAHALAudioStream.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAHALAudioStream.h\"\n\n//\tPublicUtility Includes\n#include \"CAAutoDisposer.h\"\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAPropertyAddress.h\"\n\n//==================================================================================================\n//\tCAHALAudioStream\n//==================================================================================================\n\nCAHALAudioStream::CAHALAudioStream(AudioObjectID inAudioStream)\n:\n\tCAHALAudioObject(inAudioStream)\n{\n}\n\nCAHALAudioStream::~CAHALAudioStream()\n{\n}\n\nUInt32\tCAHALAudioStream::GetDirection() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyDirection);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nUInt32\tCAHALAudioStream::GetTerminalType() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyTerminalType);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nUInt32\tCAHALAudioStream::GetStartingChannel() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyStartingChannel);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nUInt32\tCAHALAudioStream::GetLatency() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyLatency);\n\treturn GetPropertyData_UInt32(theAddress, 0, NULL);\n}\n\nvoid\tCAHALAudioStream::GetCurrentVirtualFormat(AudioStreamBasicDescription& outFormat) const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyVirtualFormat);\n\tUInt32 theSize = sizeof(AudioStreamBasicDescription);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &outFormat);\n}\n\nvoid\tCAHALAudioStream::SetCurrentVirtualFormat(const AudioStreamBasicDescription& inFormat)\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyVirtualFormat);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(AudioStreamBasicDescription), &inFormat);\n}\n\nUInt32\tCAHALAudioStream::GetNumberAvailableVirtualFormats() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyAvailableVirtualFormats);\n\tUInt32 theAnswer = GetPropertyDataSize(theAddress, 0, NULL);\n\ttheAnswer /= SizeOf32(AudioStreamRangedDescription);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioStream::GetAvailableVirtualFormats(UInt32& ioNumberFormats, AudioStreamRangedDescription* outFormats) const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyAvailableVirtualFormats);\n\tUInt32 theSize = ioNumberFormats * SizeOf32(AudioStreamRangedDescription);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outFormats);\n\tioNumberFormats = theSize / SizeOf32(AudioStreamRangedDescription);\n}\n\nvoid\tCAHALAudioStream::GetAvailableVirtualFormatByIndex(UInt32 inIndex, AudioStreamRangedDescription& outFormat) const\n{\n\tUInt32 theNumberFormats = GetNumberAvailableVirtualFormats();\n\tif((theNumberFormats > 0) && (inIndex < theNumberFormats))\n\t{\n\t\tCAAutoArrayDelete<AudioStreamRangedDescription> theFormats(theNumberFormats);\n\t\tGetAvailableVirtualFormats(theNumberFormats, theFormats);\n\t\tif((theNumberFormats > 0) && (inIndex < theNumberFormats))\n\t\t{\n\t\t\toutFormat = theFormats[inIndex];\n\t\t}\n\t}\n}\n\nvoid\tCAHALAudioStream::GetCurrentPhysicalFormat(AudioStreamBasicDescription& outFormat) const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyPhysicalFormat);\n\tUInt32 theSize = sizeof(AudioStreamBasicDescription);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &outFormat);\n}\n\nvoid\tCAHALAudioStream::SetCurrentPhysicalFormat(const AudioStreamBasicDescription& inFormat)\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyPhysicalFormat);\n\tSetPropertyData(theAddress, 0, NULL, sizeof(AudioStreamBasicDescription), &inFormat);\n}\n\nUInt32\tCAHALAudioStream::GetNumberAvailablePhysicalFormats() const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyAvailablePhysicalFormats);\n\tUInt32 theAnswer = GetPropertyDataSize(theAddress, 0, NULL);\n\ttheAnswer /= SizeOf32(AudioStreamRangedDescription);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioStream::GetAvailablePhysicalFormats(UInt32& ioNumberFormats, AudioStreamRangedDescription* outFormats) const\n{\n\tCAPropertyAddress theAddress(kAudioStreamPropertyAvailablePhysicalFormats);\n\tUInt32 theSize = ioNumberFormats * SizeOf32(AudioStreamRangedDescription);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outFormats);\n\tioNumberFormats = theSize / SizeOf32(AudioStreamRangedDescription);\n}\n\nvoid\tCAHALAudioStream::GetAvailablePhysicalFormatByIndex(UInt32 inIndex, AudioStreamRangedDescription& outFormat) const\n{\n\tUInt32 theNumberFormats = GetNumberAvailablePhysicalFormats();\n\tif((theNumberFormats > 0) && (inIndex < theNumberFormats))\n\t{\n\t\tCAAutoArrayDelete<AudioStreamRangedDescription> theFormats(theNumberFormats);\n\t\tGetAvailablePhysicalFormats(theNumberFormats, theFormats);\n\t\tif((theNumberFormats > 0) && (inIndex < theNumberFormats))\n\t\t{\n\t\t\toutFormat = theFormats[inIndex];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioStream.h",
    "content": "/*\n     File: CAHALAudioStream.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAHALAudioStream_h__)\n#define __CAHALAudioStream_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSuper Class Includes\n#include \"CAHALAudioObject.h\"\n\n//==================================================================================================\n//\tCAHALAudioStream\n//==================================================================================================\n\nclass CAHALAudioStream\n:\n\tpublic\tCAHALAudioObject\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\tCAHALAudioStream(AudioObjectID inAudioStream);\n\tvirtual\t\t\t~CAHALAudioStream();\n\n//\tAttributes\npublic:\n\tUInt32\t\t\tGetDirection() const;\n\tUInt32\t\t\tGetTerminalType() const;\n\tUInt32\t\t\tGetStartingChannel() const;\n\tUInt32\t\t\tGetLatency() const;\n\n//\tFormat Info\npublic:\n\tvoid\t\t\tGetCurrentVirtualFormat(AudioStreamBasicDescription& outFormat) const;\n\tvoid\t\t\tSetCurrentVirtualFormat(const AudioStreamBasicDescription& inFormat);\n\tUInt32\t\t\tGetNumberAvailableVirtualFormats() const;\n\tvoid\t\t\tGetAvailableVirtualFormats(UInt32& ioNumberFormats, AudioStreamRangedDescription* outFormats) const;\n\tvoid\t\t\tGetAvailableVirtualFormatByIndex(UInt32 inIndex, AudioStreamRangedDescription& outFormat) const;\n\n\tvoid\t\t\tGetCurrentPhysicalFormat(AudioStreamBasicDescription& outFormat) const;\n\tvoid\t\t\tSetCurrentPhysicalFormat(const AudioStreamBasicDescription& inFormat);\n\tUInt32\t\t\tGetNumberAvailablePhysicalFormats() const;\n\tvoid\t\t\tGetAvailablePhysicalFormats(UInt32& ioNumberFormats, AudioStreamRangedDescription* outFormats) const;\n\tvoid\t\t\tGetAvailablePhysicalFormatByIndex(UInt32 inIndex, AudioStreamRangedDescription& outFormat) const;\n\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioSystemObject.cpp",
    "content": "/*\n     File: CAHALAudioSystemObject.cpp\n Abstract: CAHALAudioSystemObject.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAHALAudioSystemObject.h\"\n\n//\tPublicUtility Includes\n#include \"CAAutoDisposer.h\"\n#include \"CACFString.h\"\n#include \"CAHALAudioDevice.h\"\n#include \"CAPropertyAddress.h\"\n\n//==================================================================================================\n//\tCAHALAudioSystemObject\n//==================================================================================================\n\nCAHALAudioSystemObject::CAHALAudioSystemObject()\n:\n\tCAHALAudioObject(kAudioObjectSystemObject)\n{\n}\n\nCAHALAudioSystemObject::~CAHALAudioSystemObject()\n{\n}\n\nUInt32\tCAHALAudioSystemObject::GetNumberAudioDevices() const\n{\n\tCAPropertyAddress theAddress(kAudioHardwarePropertyDevices);\n\tUInt32 theAnswer = GetPropertyDataSize(theAddress, 0, NULL);\n\ttheAnswer /= SizeOf32(AudioObjectID);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioSystemObject::GetAudioDevices(UInt32& ioNumberAudioDevices, AudioObjectID* outAudioDevices) const\n{\n\tCAPropertyAddress theAddress(kAudioHardwarePropertyDevices);\n\tUInt32 theSize = ioNumberAudioDevices * SizeOf32(AudioObjectID);\n\tGetPropertyData(theAddress, 0, NULL, theSize, outAudioDevices);\n\tioNumberAudioDevices = theSize / SizeOf32(AudioObjectID);\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioDeviceAtIndex(UInt32 inIndex) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tUInt32 theNumberDevices = GetNumberAudioDevices();\n\tif((theNumberDevices > 0) && (inIndex < theNumberDevices))\n\t{\n\t\tCAAutoArrayDelete<AudioObjectID> theDeviceList(theNumberDevices);\n\t\tGetAudioDevices(theNumberDevices, theDeviceList);\n\t\tif((theNumberDevices > 0) && (inIndex < theNumberDevices))\n\t\t{\n\t\t\ttheAnswer = theDeviceList[inIndex];\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef inUID) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tAudioValueTranslation theValue = { &inUID, sizeof(CFStringRef), &theAnswer, sizeof(AudioObjectID) };\n\tCAPropertyAddress theAddress(kAudioHardwarePropertyDeviceForUID);\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioSystemObject::LogBasicDeviceInfo()\n{\n\tUInt32 theNumberDevices = GetNumberAudioDevices();\n\tCAAutoArrayDelete<AudioObjectID> theDeviceList(theNumberDevices);\n\tGetAudioDevices(theNumberDevices, theDeviceList);\n\tDebugMessageN1(\"CAHALAudioSystemObject::LogBasicDeviceInfo: %d devices\", (int)theNumberDevices);\n\tfor(UInt32 theDeviceIndex = 0; theDeviceIndex < theNumberDevices; ++theDeviceIndex)\n\t{\n\t\tchar theCString[256];\n\t\tUInt32 theCStringSize = sizeof(theCString);\n\t\tDebugMessageN1(\"CAHALAudioSystemObject::LogBasicDeviceInfo: Device %d\", (int)theDeviceIndex);\n\t\t\n\t\tCAHALAudioDevice theDevice(theDeviceList[theDeviceIndex]);\n\t\tDebugMessageN1(\"CAHALAudioSystemObject::LogBasicDeviceInfo:   Object ID: %d\", (int)theDeviceList[theDeviceIndex]);\n\t\t\n\t\tCACFString theDeviceName(theDevice.CopyName());\n\t\ttheCStringSize = sizeof(theCString);\n\t\ttheDeviceName.GetCString(theCString, theCStringSize);\n\t\tDebugMessageN1(\"CAHALAudioSystemObject::LogBasicDeviceInfo:   Name:      %s\", theCString);\n\t\t\n\t\tCACFString theDeviceUID(theDevice.CopyDeviceUID());\n\t\ttheCStringSize = sizeof(theCString);\n\t\ttheDeviceUID.GetCString(theCString, theCStringSize);\n\t\tDebugMessageN1(\"CAHALAudioSystemObject::LogBasicDeviceInfo:   UID:       %s\", theCString);\n\t}\n}\n\nstatic inline AudioObjectPropertySelector\tCAHALAudioSystemObject_CalculateDefaultDeviceProperySelector(bool inIsInput, bool inIsSystem)\n{\n\tAudioObjectPropertySelector theAnswer = kAudioHardwarePropertyDefaultOutputDevice;\n\tif(inIsInput)\n\t{\n\t\ttheAnswer = kAudioHardwarePropertyDefaultInputDevice;\n\t}\n\telse if(inIsSystem)\n\t{\n\t\ttheAnswer = kAudioHardwarePropertyDefaultSystemOutputDevice;\n\t}\n\treturn theAnswer;\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetDefaultAudioDevice(bool inIsInput, bool inIsSystem) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tCAPropertyAddress theAddress(CAHALAudioSystemObject_CalculateDefaultDeviceProperySelector(inIsInput, inIsSystem));\n\tUInt32 theSize = sizeof(AudioObjectID);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theAnswer);\n\treturn theAnswer;\n}\n\nvoid\tCAHALAudioSystemObject::SetDefaultAudioDevice(bool inIsInput, bool inIsSystem, AudioObjectID inNewDefaultDevice)\n{\n\tCAPropertyAddress theAddress(CAHALAudioSystemObject_CalculateDefaultDeviceProperySelector(inIsInput, inIsSystem));\n\tUInt32 theSize = sizeof(AudioObjectID);\n\tSetPropertyData(theAddress, 0, NULL, theSize, &inNewDefaultDevice);\n}\n\nAudioObjectID\tCAHALAudioSystemObject::GetAudioPlugInForBundleID(CFStringRef inUID) const\n{\n\tAudioObjectID theAnswer = kAudioObjectUnknown;\n\tAudioValueTranslation theValue = { &inUID, sizeof(CFStringRef), &theAnswer, sizeof(AudioObjectID) };\n\tCAPropertyAddress theAddress(kAudioHardwarePropertyPlugInForBundleID);\n\tUInt32 theSize = sizeof(AudioValueTranslation);\n\tGetPropertyData(theAddress, 0, NULL, theSize, &theValue);\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioSystemObject.h",
    "content": "/*\n     File: CAHALAudioSystemObject.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAHALAudioSystemObject_h__)\n#define __CAHALAudioSystemObject_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSuper Class Includes\n#include \"CAHALAudioObject.h\"\n\n//==================================================================================================\n//\tCAHALAudioSystemObject\n//==================================================================================================\n\nclass CAHALAudioSystemObject\n:\n\tpublic\tCAHALAudioObject\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\tCAHALAudioSystemObject();\n\tvirtual\t\t\t~CAHALAudioSystemObject();\n\n//\tAudio Device List Management\npublic:\n\tUInt32\t\t\tGetNumberAudioDevices() const;\n\tvoid\t\t\tGetAudioDevices(UInt32& ioNumberAudioDevices, AudioObjectID* outAudioDevices) const;\n\tAudioObjectID\tGetAudioDeviceAtIndex(UInt32 inIndex) const;\n\tAudioObjectID\tGetAudioDeviceForUID(CFStringRef inUID) const;\n\tvoid\t\t\tLogBasicDeviceInfo();\n\n//\tDefault Device Management\npublic:\n\tAudioObjectID\tGetDefaultAudioDevice(bool inIsInput, bool inIsSystem) const;\n\tvoid\t\t\tSetDefaultAudioDevice(bool inIsInput, bool inIsSystem, AudioObjectID inNewDefaultDevice);\n\t\n//\tPlugIns\npublic:\n\tAudioObjectID\tGetAudioPlugInForBundleID(CFStringRef inBundleID) const;\n\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHostTimeBase.cpp",
    "content": "/*\n     File: CAHostTimeBase.cpp\n Abstract: CAHostTimeBase.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CAHostTimeBase.h\"\n\nFloat64\t\t\tCAHostTimeBase::sFrequency = 0;\nFloat64\t\t\tCAHostTimeBase::sInverseFrequency = 0;\nUInt32\t\t\tCAHostTimeBase::sMinDelta = 0;\nUInt32\t\t\tCAHostTimeBase::sToNanosNumerator = 0;\nUInt32\t\t\tCAHostTimeBase::sToNanosDenominator = 0;\npthread_once_t\tCAHostTimeBase::sIsInited = PTHREAD_ONCE_INIT;\n#if Track_Host_TimeBase\nUInt64\t\t\tCAHostTimeBase::sLastTime = 0;\n#endif\n\n//=============================================================================\n//\tCAHostTimeBase\n//\n//\tThis class provides platform independent access to the host's time base.\n//=============================================================================\n\nvoid\tCAHostTimeBase::Initialize()\n{\n\t//\tget the info about Absolute time\n\t#if TARGET_OS_MAC\n\t\tstruct mach_timebase_info\ttheTimeBaseInfo;\n\t\tmach_timebase_info(&theTimeBaseInfo);\n\t\tsMinDelta = 1;\n\t\tsToNanosNumerator = theTimeBaseInfo.numer;\n\t\tsToNanosDenominator = theTimeBaseInfo.denom;\n\n\t\t//\tthe frequency of that clock is: (sToNanosDenominator / sToNanosNumerator) * 10^9\n\t\tsFrequency = static_cast<Float64>(sToNanosDenominator) / static_cast<Float64>(sToNanosNumerator);\n\t\tsFrequency *= 1000000000.0;\n\t#elif TARGET_OS_WIN32\n\t\tLARGE_INTEGER theFrequency;\n\t\tQueryPerformanceFrequency(&theFrequency);\n\t\tsMinDelta = 1;\n\t\tsToNanosNumerator = 1000000000ULL;\n\t\tsToNanosDenominator = *((UInt64*)&theFrequency);\n\t\tsFrequency = static_cast<Float64>(*((UInt64*)&theFrequency));\n\t#endif\n\tsInverseFrequency = 1.0 / sFrequency;\n\t\n\t#if\tLog_Host_Time_Base_Parameters\n\t\tDebugPrintf(\"Host Time Base Parameters\");\n\t\tDebugPrintf(\" Minimum Delta:          %lu\", (unsigned long)sMinDelta);\n\t\tDebugPrintf(\" Frequency:              %f\", sFrequency);\n\t\tDebugPrintf(\" To Nanos Numerator:     %lu\", (unsigned long)sToNanosNumerator);\n\t\tDebugPrintf(\" To Nanos Denominator:   %lu\", (unsigned long)sToNanosDenominator);\n\t#endif\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAHostTimeBase.h",
    "content": "/*\n     File: CAHostTimeBase.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAHostTimeBase_h__)\n#define __CAHostTimeBase_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <mach/mach_time.h>\n\t#include <pthread.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n\t#include \"WinPThreadDefs.h\"\n#else\n\t#error\tUnsupported operating system\n#endif\n\n#include \"CADebugPrintf.h\"\n\n//=============================================================================\n//\tCAHostTimeBase\n//\n//\tThis class provides platform independent access to the host's time base.\n//=============================================================================\n\n#if CoreAudio_Debug\n//\t#define Log_Host_Time_Base_Parameters\t1\n//\t#define Track_Host_TimeBase\t\t\t\t1\n#endif\n\nclass\tCAHostTimeBase\n{\n\npublic:\n\tstatic UInt64\t\t\tConvertToNanos(UInt64 inHostTime);\n\tstatic UInt64\t\t\tConvertFromNanos(UInt64 inNanos);\n\n\tstatic UInt64\t\t\tGetTheCurrentTime();\n#if TARGET_OS_MAC\n\tstatic UInt64\t\t\tGetCurrentTime() { return GetTheCurrentTime(); }\n#endif\n\tstatic UInt64\t\t\tGetCurrentTimeInNanos();\n\n\tstatic Float64\t\t\tGetFrequency() { pthread_once(&sIsInited, Initialize); return sFrequency; }\n\tstatic Float64\t\t\tGetInverseFrequency() { pthread_once(&sIsInited, Initialize); return sInverseFrequency; }\n\tstatic UInt32\t\t\tGetMinimumDelta() { pthread_once(&sIsInited, Initialize); return sMinDelta; }\n\n\tstatic UInt64\t\t\tAbsoluteHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime);\n\tstatic SInt64\t\t\tHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime);\n\n\tstatic UInt64\t\t\tMultiplyByRatio(UInt64 inMuliplicand, UInt32 inNumerator, UInt32 inDenominator);\n\t\nprivate:\n\tstatic void\t\t\t\tInitialize();\n\t\n\tstatic pthread_once_t\tsIsInited;\n\t\n\tstatic Float64\t\t\tsFrequency;\n\tstatic Float64\t\t\tsInverseFrequency;\n\tstatic UInt32\t\t\tsMinDelta;\n\tstatic UInt32\t\t\tsToNanosNumerator;\n\tstatic UInt32\t\t\tsToNanosDenominator;\n#if Track_Host_TimeBase\n\tstatic UInt64\t\t\tsLastTime;\n#endif\n};\n\ninline UInt64\tCAHostTimeBase::GetTheCurrentTime()\n{\n\tUInt64 theTime = 0;\n\n\t#if TARGET_OS_MAC\n\t\ttheTime = mach_absolute_time();\n\t#elif TARGET_OS_WIN32\n\t\tLARGE_INTEGER theValue;\n\t\tQueryPerformanceCounter(&theValue);\n\t\ttheTime = *((UInt64*)&theValue);\n\t#endif\n\t\n\t#if\tTrack_Host_TimeBase\n\t\tif(sLastTime != 0)\n\t\t{\n\t\t\tif(theTime <= sLastTime)\n\t\t\t{\n\t\t\t\tDebugPrintf(\"CAHostTimeBase::GetTheCurrentTime: the current time is earlier than the last time, now: %qd, then: %qd\", theTime, sLastTime);\n\t\t\t}\n\t\t\tsLastTime = theTime;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsLastTime = theTime;\n\t\t}\n\t#endif\n\n\treturn theTime;\n}\n\ninline UInt64\tCAHostTimeBase::ConvertToNanos(UInt64 inHostTime)\n{\n\tpthread_once(&sIsInited, Initialize);\n\t\n\tUInt64 theAnswer = MultiplyByRatio(inHostTime, sToNanosNumerator, sToNanosDenominator);\n\t#if CoreAudio_Debug\n\t\tif(((sToNanosNumerator > sToNanosDenominator) && (theAnswer < inHostTime)) || ((sToNanosDenominator > sToNanosNumerator) && (theAnswer > inHostTime)))\n\t\t{\n\t\t\tDebugPrintf(\"CAHostTimeBase::ConvertToNanos: The conversion wrapped\");\n\t\t}\n\t#endif\n\t\n\treturn theAnswer;\n}\n\ninline UInt64\tCAHostTimeBase::ConvertFromNanos(UInt64 inNanos)\n{\n\tpthread_once(&sIsInited, Initialize);\n\n\tUInt64 theAnswer = MultiplyByRatio(inNanos, sToNanosDenominator, sToNanosNumerator);\n\t#if CoreAudio_Debug\n\t\tif(((sToNanosDenominator > sToNanosNumerator) && (theAnswer < inNanos)) || ((sToNanosNumerator > sToNanosDenominator) && (theAnswer > inNanos)))\n\t\t{\n\t\t\tDebugPrintf(\"CAHostTimeBase::ConvertFromNanos: The conversion wrapped\");\n\t\t}\n\t#endif\n\n\treturn theAnswer;\n}\n\ninline UInt64\tCAHostTimeBase::GetCurrentTimeInNanos()\n{\n\treturn ConvertToNanos(GetTheCurrentTime());\n}\n\ninline UInt64\tCAHostTimeBase::AbsoluteHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime)\n{\n\tUInt64 theAnswer;\n\t\n\tif(inStartTime <= inEndTime)\n\t{\n\t\ttheAnswer = inEndTime - inStartTime;\n\t}\n\telse\n\t{\n\t\ttheAnswer = inStartTime - inEndTime;\n\t}\n\t\n\treturn ConvertToNanos(theAnswer);\n}\n\ninline SInt64\tCAHostTimeBase::HostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime)\n{\n\tSInt64 theAnswer;\n\tSInt64 theSign = 1;\n\t\n\tif(inStartTime <= inEndTime)\n\t{\n\t\ttheAnswer = static_cast<SInt64>(inEndTime - inStartTime);\n\t}\n\telse\n\t{\n\t\ttheAnswer = static_cast<SInt64>(inStartTime - inEndTime);\n\t\ttheSign = -1;\n\t}\n\t\n\treturn theSign * static_cast<SInt64>(ConvertToNanos(static_cast<UInt64>(theAnswer)));\n}\n\ninline UInt64\tCAHostTimeBase::MultiplyByRatio(UInt64 inMuliplicand, UInt32 inNumerator, UInt32 inDenominator)\n{\n#if TARGET_OS_MAC && TARGET_RT_64_BIT\n\t__uint128_t theAnswer = inMuliplicand;\n#else\n\tlong double theAnswer = inMuliplicand;\n#endif\n\tif(inNumerator != inDenominator)\n\t{\n\t\ttheAnswer *= inNumerator;\n\t\ttheAnswer /= inDenominator;\n\t}\n\treturn static_cast<UInt64>(theAnswer);\n}\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAMutex.cpp",
    "content": "/*\n     File: CAMutex.cpp\n Abstract: CAMutex.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAMutex.h\"\n\n#if TARGET_OS_MAC\n\t#include <errno.h>\n#endif\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAHostTimeBase.h\"\n\n//==================================================================================================\n//\tLogging\n//==================================================================================================\n\n#if CoreAudio_Debug\n//\t#define\tLog_Ownership\t\t1\n//\t#define\tLog_Errors\t\t\t1\n//\t#define Log_LongLatencies\t1\n//\t#define LongLatencyThreshholdNS\t1000000ULL\t// nanoseconds\n#endif\n\n//==================================================================================================\n//\tCAMutex\n//==================================================================================================\n\nCAMutex::CAMutex(const char* inName)\n:\n\tmName(inName),\n\tmOwner(0)\n{\n#if TARGET_OS_MAC\n\tOSStatus theError = pthread_mutex_init(&mMutex, NULL);\n\tThrowIf(theError != 0, CAException(theError), \"CAMutex::CAMutex: Could not init the mutex\");\n\t\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::CAMutex: creating %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n#elif TARGET_OS_WIN32\n\tmMutex = CreateMutex(NULL, false, NULL);\n\tThrowIfNULL(mMutex, CAException(GetLastError()), \"CAMutex::CAMutex: could not create the mutex.\");\n\t\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::CAMutex: creating %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n#endif\n}\n\nCAMutex::~CAMutex()\n{\n#if TARGET_OS_MAC\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::~CAMutex: destroying %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n\tpthread_mutex_destroy(&mMutex);\n#elif TARGET_OS_WIN32\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::~CAMutex: destroying %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n\tif(mMutex != NULL)\n\t{\n\t\tCloseHandle(mMutex);\n\t}\n#endif\n}\n\nbool\tCAMutex::Lock()\n{\n\tbool theAnswer = false;\n\t\n#if TARGET_OS_MAC\n\tpthread_t theCurrentThread = pthread_self();\n\tif(!pthread_equal(theCurrentThread, mOwner))\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Lock: thread %p is locking %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t#endif\n\t\t\n\t\t#if Log_LongLatencies\n\t\t\tUInt64 lockTryTime = CAHostTimeBase::GetCurrentTimeInNanos();\n\t\t#endif\n\t\t\n\t\tOSStatus theError = pthread_mutex_lock(&mMutex);\n\t\tThrowIf(theError != 0, CAException(theError), \"CAMutex::Lock: Could not lock the mutex\");\n\t\tmOwner = theCurrentThread;\n\t\ttheAnswer = true;\n\t\n\t\t#if Log_LongLatencies\n\t\t\tUInt64 lockAcquireTime = CAHostTimeBase::GetCurrentTimeInNanos();\n\t\t\tif (lockAcquireTime - lockTryTime >= LongLatencyThresholdNS)\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"Thread %p took %.6fs to acquire the lock %s\\n\", theCurrentThread, (lockAcquireTime - lockTryTime) * 1.0e-9 /* nanos to seconds */, mName);\n\t\t#endif\n\t\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Lock: thread %p has locked %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner != GetCurrentThreadId())\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Lock: thread %lu is locking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\n\t\tOSStatus theError = WaitForSingleObject(mMutex, INFINITE);\n\t\tThrowIfError(theError, CAException(theError), \"CAMutex::Lock: could not lock the mutex\");\n\t\tmOwner = GetCurrentThreadId();\n\t\ttheAnswer = true;\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Lock: thread %lu has locked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t}\n#endif\n\n\treturn theAnswer;\n}\n\nvoid\tCAMutex::Unlock()\n{\n#if TARGET_OS_MAC\n\tif(pthread_equal(pthread_self(), mOwner))\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Unlock: thread %p is unlocking %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\n\t\tmOwner = 0;\n\t\tOSStatus theError = pthread_mutex_unlock(&mMutex);\n\t\tThrowIf(theError != 0, CAException(theError), \"CAMutex::Unlock: Could not unlock the mutex\");\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Unlock: thread %p has unlocked %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\t}\n\telse\n\t{\n\t\tDebugMessage(\"CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own\");\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner == GetCurrentThreadId())\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Unlock: thread %lu is unlocking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\n\t\tmOwner = 0;\n\t\tbool wasReleased = ReleaseMutex(mMutex);\n\t\tThrowIf(!wasReleased, CAException(GetLastError()), \"CAMutex::Unlock: Could not unlock the mutex\");\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Unlock: thread %lu has unlocked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t}\n\telse\n\t{\n\t\tDebugMessage(\"CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own\");\n\t}\n#endif\n}\n\nbool\tCAMutex::Try(bool& outWasLocked)\n{\n\tbool theAnswer = false;\n\toutWasLocked = false;\n\n#if TARGET_OS_MAC\n\tpthread_t theCurrentThread = pthread_self();\n\tif(!pthread_equal(theCurrentThread, mOwner))\n\t{\n\t\t//\tthis means the current thread doesn't already own the lock\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p is try-locking %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t#endif\n\n\t\t//\tgo ahead and call trylock to see if we can lock it.\n\t\tint theError = pthread_mutex_trylock(&mMutex);\n\t\tif(theError == 0)\n\t\t{\n\t\t\t//\treturn value of 0 means we successfully locked the lock\n\t\t\tmOwner = theCurrentThread;\n\t\t\ttheAnswer = true;\n\t\t\toutWasLocked = true;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p has locked %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse if(theError == EBUSY)\n\t\t{\n\t\t\t//\treturn value of EBUSY means that the lock was already locked by another thread\n\t\t\ttheAnswer = false;\n\t\t\toutWasLocked = false;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p failed to lock %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\tany other return value means something really bad happenned\n\t\t\tThrowIfError(theError, CAException(theError), \"CAMutex::Try: call to pthread_mutex_trylock failed\");\n\t\t}\n\t}\n\telse\n\t{\n\t\t//\tthis means the current thread already owns the lock\n\t\ttheAnswer = true;\n\t\toutWasLocked = false;\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner != GetCurrentThreadId())\n\t{\n\t\t//\tthis means the current thread doesn't own the lock\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu is try-locking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t\t\n\t\t//\ttry to acquire the mutex\n\t\tOSStatus theError = WaitForSingleObject(mMutex, 0);\n\t\tif(theError == WAIT_OBJECT_0)\n\t\t{\n\t\t\t//\tthis means we successfully locked the lock\n\t\t\tmOwner = GetCurrentThreadId();\n\t\t\ttheAnswer = true;\n\t\t\toutWasLocked = true;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu has locked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse if(theError == WAIT_TIMEOUT)\n\t\t{\n\t\t\t//\tthis means that the lock was already locked by another thread\n\t\t\ttheAnswer = false;\n\t\t\toutWasLocked = false;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu failed to lock %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\tany other return value means something really bad happenned\n\t\t\tThrowIfError(theError, CAException(GetLastError()), \"CAMutex::Try: call to lock the mutex failed\");\n\t\t}\n\t}\n\telse\n\t{\n\t\t//\tthis means the current thread already owns the lock\n\t\ttheAnswer = true;\n\t\toutWasLocked = false;\n\t}\n#endif\n\t\n\treturn theAnswer;\n}\n\nbool\tCAMutex::IsFree() const\n{\n\treturn mOwner == 0;\n}\n\nbool\tCAMutex::IsOwnedByCurrentThread() const\n{\n\tbool theAnswer = true;\n\t\n#if TARGET_OS_MAC\n\ttheAnswer = pthread_equal(pthread_self(), mOwner);\n#elif TARGET_OS_WIN32\n\ttheAnswer = (mOwner == GetCurrentThreadId());\n#endif\n\n\treturn theAnswer;\n}\n\n\nCAMutex::Unlocker::Unlocker(CAMutex& inMutex)\n:\tmMutex(inMutex),\n\tmNeedsLock(false)\n{\n\tAssert(mMutex.IsOwnedByCurrentThread(), \"Major problem: Unlocker attempted to unlock a mutex not owned by the current thread!\");\n\n\tmMutex.Unlock();\n\tmNeedsLock = true;\n}\n\nCAMutex::Unlocker::~Unlocker()\n{\n\tif(mNeedsLock)\n\t{\n\t\tmMutex.Lock();\n\t}\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAMutex.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  CAMutex.h\n//  PublicUtility\n//\n//  Copyright (C) 2014 Apple Inc. All Rights Reserved.\n//  Copyright © 2020 Kyle Neideck\n//\n//  Original license header follows.\n//\n\n/*\n     File: CAMutex.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#ifndef __CAMutex_h__\n#define __CAMutex_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n#include \"BGMThreadSafetyAnalysis.h\"\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <pthread.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n#else\n\t#error\tUnsupported operating system\n#endif\n\n//==================================================================================================\n//\tA recursive mutex.\n//==================================================================================================\n\nclass CAPABILITY(\"mutex\") CAMutex\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\tCAMutex(const char* inName);\n\tvirtual\t\t\t~CAMutex();\n\n//\tActions\npublic:\n\tvirtual bool\tLock() ACQUIRE();\n\tvirtual void\tUnlock() RELEASE();\n\tvirtual bool\tTry(bool& outWasLocked) TRY_ACQUIRE(true);\t// returns true if lock is free, false if not\n\t\n\tvirtual bool\tIsFree() const;\n\tvirtual bool\tIsOwnedByCurrentThread() const;\n\t\t\n//\tImplementation\nprotected:\n\tconst char*\t\tmName;\n#if TARGET_OS_MAC\n\tpthread_t\t\tmOwner;\n\tpthread_mutex_t\tmMutex;\n#elif TARGET_OS_WIN32\n\tUInt32\t\t\tmOwner;\n\tHANDLE\t\t\tmMutex;\n#endif\n\n//\tHelper class to manage taking and releasing recursively\npublic:\n\tclass SCOPED_CAPABILITY Locker\n\t{\n\t\n\t//\tConstruction/Destruction\n\tpublic:\n\t\t\t\t\tLocker(CAMutex& inMutex) ACQUIRE(inMutex) : mMutex(&inMutex), mNeedsRelease(false) { mNeedsRelease = mMutex->Lock(); }\n\t\t\t\t\tLocker(CAMutex* inMutex) ACQUIRE(inMutex) : mMutex(inMutex), mNeedsRelease(false) { mNeedsRelease = (mMutex != NULL && mMutex->Lock()); }\n\t\t\t\t\t\t// in this case the mutex can be null\n\t\t\t\t\t~Locker() RELEASE() { if(mNeedsRelease) { mMutex->Unlock(); } }\n\t\n\t\n\tprivate:\n\t\t\t\t\tLocker(const Locker&);\n\t\tLocker&\t\toperator=(const Locker&);\n\t\n\t//\tImplementation\n\tprivate:\n\t\tCAMutex*\tmMutex;\n\t\tbool\t\tmNeedsRelease;\n\t\n\t};\n\n\t// Clang's static analysis doesn't work for unlocker classes. See\n\t// <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-alias-analysis>.\n// Unlocker\n\tclass Unlocker\n\t{\n\tpublic:\n\t\t\t\t\t\tUnlocker(CAMutex& inMutex);\n\t\t\t\t\t\t~Unlocker();\n\t\t\n\tprivate:\n\t\tCAMutex&\tmMutex;\n\t\tbool\t\tmNeedsLock;\n\t\t\n\t\t// Hidden definitions of copy ctor, assignment operator\n\t\tUnlocker(const Unlocker& copy);\t\t\t\t// Not implemented\n\t\tUnlocker& operator=(const Unlocker& copy);\t// Not implemented\n\t};\n\t\n// you can use this with Try - if you take the lock in try, pass in the outWasLocked var\n\tclass SCOPED_CAPABILITY Tryer {\n\t\n\t//\tConstruction/Destruction\n\tpublic:\n\t\tTryer (CAMutex &mutex) TRY_ACQUIRE(true, mutex) : mMutex(mutex), mNeedsRelease(false), mHasLock(false) { mHasLock = mMutex.Try (mNeedsRelease); }\n\t\t~Tryer () RELEASE() { if (mNeedsRelease) mMutex.Unlock(); }\n\t\t\n\t\tbool HasLock () const { return mHasLock; }\n\n\tprivate:\n\t\t\t\t\tTryer(const Tryer&);\n\t\tTryer&\t\toperator=(const Tryer&);\n\n\t//\tImplementation\n\tprivate:\n\t\tCAMutex &\t\tmMutex;\n\t\tbool\t\t\tmNeedsRelease;\n\t\tbool\t\t\tmHasLock;\n\t};\n};\n\n\n#endif // __CAMutex_h__\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAPThread.cpp",
    "content": "/*\n     File: CAPThread.cpp\n Abstract: CAPThread.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CAPThread.h\"\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n//\tSystem Includes\n#if\tTARGET_OS_MAC\n\t#include <mach/mach.h>\n#endif\n\n//\tStandard Library Includes\n#include <stdio.h>\n\n//==================================================================================================\n//\tCAPThread\n//==================================================================================================\n\n// returns the thread's priority as it was last set by the API\n#define CAPTHREAD_SET_PRIORITY\t\t\t\t0\n// returns the thread's priority as it was last scheduled by the Kernel\n#define CAPTHREAD_SCHEDULED_PRIORITY\t\t1\n\n//#define\tLog_SetPriority\t\t\t\t\t\t1\n\nCAPThread::CAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPriority, bool inFixedPriority, bool inAutoDelete, const char* inThreadName)\n:\n#if TARGET_OS_MAC\n\tmPThread(0),\n    mSpawningThreadPriority(getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY)),\n#elif TARGET_OS_WIN32\n\tmThreadHandle(NULL),\n\tmThreadID(0),\n#endif\n\tmThreadRoutine(inThreadRoutine),\n\tmThreadParameter(inParameter),\n\tmPriority(inPriority),\n\tmPeriod(0),\n\tmComputation(0),\n\tmConstraint(0),\n\tmIsPreemptible(true),\n\tmTimeConstraintSet(false),\n\tmFixedPriority(inFixedPriority),\n\tmAutoDelete(inAutoDelete)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\nCAPThread::CAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible, bool inAutoDelete, const char* inThreadName)\n:\n#if TARGET_OS_MAC\n\tmPThread(0),\n    mSpawningThreadPriority(getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY)),\n#elif TARGET_OS_WIN32\n\tmThreadHandle(NULL),\n\tmThreadID(0),\n#endif\n\tmThreadRoutine(inThreadRoutine),\n\tmThreadParameter(inParameter),\n\tmPriority(kDefaultThreadPriority),\n\tmPeriod(inPeriod),\n\tmComputation(inComputation),\n\tmConstraint(inConstraint),\n\tmIsPreemptible(inIsPreemptible),\n\tmTimeConstraintSet(true),\n\tmFixedPriority(false),\n\tmAutoDelete(inAutoDelete)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\nCAPThread::~CAPThread()\n{\n}\n\nUInt32\tCAPThread::GetScheduledPriority()\n{\n#if TARGET_OS_MAC\n    return CAPThread::getScheduledPriority( mPThread, CAPTHREAD_SCHEDULED_PRIORITY );\n#elif TARGET_OS_WIN32\n\tUInt32 theAnswer = 0;\n\tif(mThreadHandle != NULL)\n\t{\n\t\ttheAnswer = GetThreadPriority(mThreadHandle);\n\t}\n\treturn theAnswer;\n#endif\n}\n\nUInt32\tCAPThread::GetScheduledPriority(NativeThread thread)\n{\n#if TARGET_OS_MAC\n    return getScheduledPriority( thread, CAPTHREAD_SCHEDULED_PRIORITY );\n#elif TARGET_OS_WIN32\n\treturn 0;\t// ???\n#endif\n}\n\nvoid\tCAPThread::SetPriority(UInt32 inPriority, bool inFixedPriority)\n{\n\tmPriority = inPriority;\n\tmTimeConstraintSet = false;\n\tmFixedPriority = inFixedPriority;\n#if TARGET_OS_MAC\n\tif(mPThread != 0)\n\t{\n\t\tSetPriority(mPThread, mPriority, mFixedPriority);\n    } \n#elif TARGET_OS_WIN32\n\tif(mThreadID != NULL)\n\t{\n\t\tSetPriority(mThreadID, mPriority, mFixedPriority);\n\t}\n#endif\n}\n\nvoid\tCAPThread::SetPriority(NativeThread inThread, UInt32 inPriority, bool inFixedPriority)\n{\n#if TARGET_OS_MAC\n\tif(inThread != 0)\n\t{\n\t\tkern_return_t theError = 0;\n\t\t\n\t\t//\tset whether or not this is a fixed priority thread\n\t\tif (inFixedPriority)\n\t\t{\n\t\t\tthread_extended_policy_data_t theFixedPolicy = { false };\n\t\t\ttheError = thread_policy_set(pthread_mach_thread_np(inThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT);\n\t\t\tAssertNoKernelError(theError, \"CAPThread::SetPriority: failed to set the fixed-priority policy\");\n\t\t}\n\t\t\n\t\t//\tset the thread's absolute priority which is relative to the priority on which thread_policy_set() is called\n\t\tUInt32 theCurrentThreadPriority = getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY);\n        thread_precedence_policy_data_t thePrecedencePolicy = { static_cast<integer_t>(inPriority - theCurrentThreadPriority) };\n\t\ttheError = thread_policy_set(pthread_mach_thread_np(inThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT);\n        AssertNoKernelError(theError, \"CAPThread::SetPriority: failed to set the precedence policy\");\n\t\t\n\t\t#if\tLog_SetPriority\n\t\t\tDebugMessageN4(\"CAPThread::SetPriority: requsted: %lu spawning: %lu current: %lu assigned: %d\", mPriority, mSpawningThreadPriority, theCurrentThreadPriority, thePrecedencePolicy.importance);\n\t\t#endif\n    } \n#elif TARGET_OS_WIN32\n\tif(inThread != NULL)\n\t{\n\t\tHANDLE hThread = OpenThread(NULL, FALSE, inThread);\n\t\tif(hThread != NULL) {\n\t\t\tSetThreadPriority(hThread, inPriority);\n\t\t\tCloseHandle(hThread);\n\t\t}\n\t}\n#endif\n}\n\nvoid\tCAPThread::SetTimeConstraints(UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible)\n{\n\tmPeriod = inPeriod;\n\tmComputation = inComputation;\n\tmConstraint = inConstraint;\n\tmIsPreemptible = inIsPreemptible;\n\tmTimeConstraintSet = true;\n#if TARGET_OS_MAC\n\tif(mPThread != 0)\n\t{\n\t\tthread_time_constraint_policy_data_t thePolicy;\n\t\tthePolicy.period = mPeriod;\n\t\tthePolicy.computation = mComputation;\n\t\tthePolicy.constraint = mConstraint;\n\t\tthePolicy.preemptible = mIsPreemptible;\n\t\tAssertNoError(thread_policy_set(pthread_mach_thread_np(mPThread), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&thePolicy, THREAD_TIME_CONSTRAINT_POLICY_COUNT), \"CAPThread::SetTimeConstraints: thread_policy_set failed\");\n\t}\n#elif TARGET_OS_WIN32\n\tif(mThreadHandle != NULL)\n\t{\n\t\tSetThreadPriority(mThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);\n\t}\n#endif\n}\n\nvoid\tCAPThread::Start()\n{\n#if TARGET_OS_MAC\n\tAssert(mPThread == 0, \"CAPThread::Start: can't start because the thread is already running\");\n\tif(mPThread == 0)\n\t{\n\t\tOSStatus\t\t\ttheResult;\n\t\tpthread_attr_t\t\ttheThreadAttributes;\n\t\t\n\t\ttheResult = pthread_attr_init(&theThreadAttributes);\n\t\tThrowIf(theResult != 0, CAException(theResult), \"CAPThread::Start: Thread attributes could not be created.\");\n\t\t\n\t\ttheResult = pthread_attr_setdetachstate(&theThreadAttributes, PTHREAD_CREATE_DETACHED);\n\t\tThrowIf(theResult != 0, CAException(theResult), \"CAPThread::Start: A thread could not be created in the detached state.\");\n\t\t\n\t\ttheResult = pthread_create(&mPThread, &theThreadAttributes, (ThreadRoutine)CAPThread::Entry, this);\n\t\tThrowIf(theResult != 0 || !mPThread, CAException(theResult), \"CAPThread::Start: Could not create a thread.\");\n\t\t\n\t\tpthread_attr_destroy(&theThreadAttributes);\n\t\t\n\t}\n#elif TARGET_OS_WIN32\n\tAssert(mThreadID == 0, \"CAPThread::Start: can't start because the thread is already running\");\n\tif(mThreadID == 0)\n\t{\n\t\t//\tclean up the existing thread handle\n\t\tif(mThreadHandle != NULL)\n\t\t{\n\t\t\tCloseHandle(mThreadHandle);\n\t\t\tmThreadHandle = NULL;\n\t\t}\n\t\t\n\t\t//\tcreate a new thread\n\t\tmThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Entry, this, 0, &mThreadID);\n\t\tThrowIf(mThreadHandle == NULL, CAException(GetLastError()), \"CAPThread::Start: Could not create a thread.\");\n\t}\n#endif\n}\n\n#if TARGET_OS_MAC\n\nvoid*\tCAPThread::Entry(CAPThread* inCAPThread)\n{\n\tvoid* theAnswer = NULL;\n\n#if TARGET_OS_MAC\n\tinCAPThread->mPThread = pthread_self();\n#elif TARGET_OS_WIN32\n\t// do we need to do something here?\n#endif\n\t\n#if\t!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)\n\tif(inCAPThread->mThreadName[0] != 0)\n\t{\n\t\tpthread_setname_np(inCAPThread->mThreadName);\n\t}\n#endif\n\n\ttry \n\t{\n\t\tif(inCAPThread->mTimeConstraintSet)\n\t\t{\n\t\t\tinCAPThread->SetTimeConstraints(inCAPThread->mPeriod, inCAPThread->mComputation, inCAPThread->mConstraint, inCAPThread->mIsPreemptible);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tinCAPThread->SetPriority(inCAPThread->mPriority, inCAPThread->mFixedPriority);\n\t\t}\n\n\t\tif(inCAPThread->mThreadRoutine != NULL)\n\t\t{\n\t\t\ttheAnswer = inCAPThread->mThreadRoutine(inCAPThread->mThreadParameter);\n\t\t}\n\t}\n\tcatch (...)\n\t{\n\t\t// what should be done here?\n\t}\n\tinCAPThread->mPThread = 0;\n\tif (inCAPThread->mAutoDelete)\n\t\tdelete inCAPThread;\n\treturn theAnswer;\n}\n\nUInt32 CAPThread::getScheduledPriority(pthread_t inThread, int inPriorityKind)\n{\n    thread_basic_info_data_t\t\t\tthreadInfo;\n\tpolicy_info_data_t\t\t\t\t\tthePolicyInfo;\n\tunsigned int\t\t\t\t\t\tcount;\n\n\tif (inThread == NULL)\n\t\treturn 0;\n    \n    // get basic info\n    count = THREAD_BASIC_INFO_COUNT;\n    thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (thread_info_t)&threadInfo, &count);\n    \n\tswitch (threadInfo.policy) {\n\t\tcase POLICY_TIMESHARE:\n\t\t\tcount = POLICY_TIMESHARE_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (thread_info_t)&(thePolicyInfo.ts), &count);\n            if (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) {\n                return static_cast<UInt32>(thePolicyInfo.ts.cur_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.ts.base_priority);\n            break;\n            \n        case POLICY_FIFO:\n\t\t\tcount = POLICY_FIFO_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (thread_info_t)&(thePolicyInfo.fifo), &count);\n            if ( (thePolicyInfo.fifo.depressed) && (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) ) {\n                return static_cast<UInt32>(thePolicyInfo.fifo.depress_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.fifo.base_priority);\n            break;\n            \n\t\tcase POLICY_RR:\n\t\t\tcount = POLICY_RR_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (thread_info_t)&(thePolicyInfo.rr), &count);\n\t\t\tif ( (thePolicyInfo.rr.depressed) && (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) ) {\n                return static_cast<UInt32>(thePolicyInfo.rr.depress_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.rr.base_priority);\n            break;\n\t}\n    \n    return 0;\n}\n\n#elif TARGET_OS_WIN32\n\nUInt32 WINAPI\tCAPThread::Entry(CAPThread* inCAPThread)\n{\n\tUInt32 theAnswer = 0;\n\n\ttry \n\t{\n\t\tif(inCAPThread->mTimeConstraintSet)\n\t\t{\n\t\t\tinCAPThread->SetTimeConstraints(inCAPThread->mPeriod, inCAPThread->mComputation, inCAPThread->mConstraint, inCAPThread->mIsPreemptible);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tinCAPThread->SetPriority(inCAPThread->mPriority, inCAPThread->mFixedPriority);\n\t\t}\n\n\t\tif(inCAPThread->mThreadRoutine != NULL)\n\t\t{\n\t\t\ttheAnswer = reinterpret_cast<UInt32>(inCAPThread->mThreadRoutine(inCAPThread->mThreadParameter));\n\t\t}\n\t\tinCAPThread->mThreadID = 0;\n\t}\n\tcatch (...)\n\t{\n\t\t// what should be done here?\n\t}\n\tCloseHandle(inCAPThread->mThreadHandle);\n\tinCAPThread->mThreadHandle = NULL;\n\tif (inCAPThread->mAutoDelete)\n\t\tdelete inCAPThread;\n\treturn theAnswer;\n}\n\nextern \"C\"\nBoolean CompareAndSwap(UInt32 inOldValue, UInt32 inNewValue, UInt32* inOldValuePtr)\n{\n\treturn InterlockedCompareExchange((volatile LONG*)inOldValuePtr, inNewValue, inOldValue) == inOldValue;\n}\n\n#endif\n\nvoid\tCAPThread::SetName(const char* inThreadName)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\n#if CoreAudio_Debug\nvoid\tCAPThread::DebugPriority(const char *label)\n{\n#if !TARGET_OS_WIN32\n\tif (mTimeConstraintSet)\n\t\tprintf(\"CAPThread::%s %p: pri=<time constraint>, spawning pri=%d, scheduled pri=%d\\n\", label, this, \n\t\t(int)mSpawningThreadPriority, (mPThread != NULL) ? (int)GetScheduledPriority() : -1);\n\telse\n\t\tprintf(\"CAPThread::%s %p: pri=%d%s, spawning pri=%d, scheduled pri=%d\\n\", label, this, (int)mPriority, mFixedPriority ? \" fixed\" : \"\", \n\t\t(int)mSpawningThreadPriority, (mPThread != NULL) ? (int)GetScheduledPriority() : -1);\n#else\n\tif (mTimeConstraintSet)\n\t{\n\t\tprintf(\"CAPThread::%s %p: pri=<time constraint>, spawning pri=%d, scheduled pri=%d\\n\", label, this, \n\t\t(int)mPriority, (mThreadHandle != NULL) ? (int)GetScheduledPriority() : -1);\n\t}\n\telse\n\t{\n\t\tprintf(\"CAPThread::%s %p: pri=%d%s, spawning pri=%d, scheduled pri=%d\\n\", label, this, (int)mPriority, mFixedPriority ? \" fixed\" : \"\", \n\t\t(int)mPriority, (mThreadHandle != NULL) ? (int)GetScheduledPriority() : -1);\n\t}\n#endif\n}\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAPThread.h",
    "content": "/*\n     File: CAPThread.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPThread_h__)\n#define __CAPThread_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreFoundation/CFBase.h>\n#else\n\t#include <CFBase.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <pthread.h>\n\t#include <unistd.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n#else\n\t#error\tUnsupported operating system\n#endif\n\n//==================================================================================================\n//\tCAPThread\n//\n//\tThis class wraps a pthread and a Win32 thread.\n//\tcaution: long-running fixed priority threads can make the system unresponsive\n//==================================================================================================\n\nclass\tCAPThread\n{\n\n//\tTypes\npublic:\n\ttypedef void*\t\t\t(*ThreadRoutine)(void* inParameter);\n\n//\tConstants\npublic:\n\tenum\n\t{\n#if\tTARGET_OS_MAC\n\t\t\t\t\t\t\tkMinThreadPriority = 1,\n\t\t\t\t\t\t\tkMaxThreadPriority = 63,\n\t\t\t\t\t\t\tkDefaultThreadPriority = 31,\n\t\t\t\t\t\t\tkMaxThreadNameLength = 64\n#elif TARGET_OS_WIN32\n\t\t\t\t\t\t\tkMinThreadPriority = 1,\n\t\t\t\t\t\t\tkMaxThreadPriority = 31,\n\t\t\t\t\t\t\tkDefaultThreadPriority = THREAD_PRIORITY_NORMAL,\n\t\t\t\t\t\t\tkMaxThreadNameLength = 256\n#endif\n\t};\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\tCAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPriority = kDefaultThreadPriority, bool inFixedPriority=false, bool inAutoDelete=false, const char* inThreadName = NULL);\n\t\t\t\t\t\t\tCAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible, bool inAutoDelete=false, const char* inThreadName = NULL);\n\tvirtual\t\t\t\t\t~CAPThread();\n\n//\tProperties\npublic:\n#if TARGET_OS_MAC\n\ttypedef pthread_t\t\tNativeThread;\n\n\tNativeThread\t\t\tGetNativeThread() { return mPThread; }\n\tstatic NativeThread\t\tGetCurrentThread() { return pthread_self(); }\n\tstatic bool\t\t\t\tIsNativeThreadsEqual(NativeThread a, NativeThread b) { return (a==b); }\n\n\tbool\t\t\t\t\toperator==(NativeThread b) { return pthread_equal(mPThread,b); }\n\n\tpthread_t\t\t\t\tGetPThread() const { return mPThread; }\n\tbool\t\t\t\t\tIsCurrentThread() const { return (0 != mPThread) && (pthread_self() == mPThread); }\n\tbool\t\t\t\t\tIsRunning() const { return 0 != mPThread; }\n    static UInt32\t\t\tgetScheduledPriority(pthread_t inThread, int inPriorityKind);\n#elif TARGET_OS_WIN32\n\ttypedef unsigned long\tNativeThread;\n\t\n\tNativeThread\t\t\tGetNativeThread() { return mThreadID; }\n\tstatic NativeThread\t\tGetCurrentThread() { return GetCurrentThreadId(); }\n\tstatic bool\t\t\t\tIsNativeThreadsEqual(NativeThread a, NativeThread b) { return (a==b); }\n\n\tbool\t\t\t\t\toperator ==(NativeThread b) { return (mThreadID==b); }\n\n\tHANDLE\t\t\t\t\tGetThreadHandle() const { return mThreadHandle; }\n\tUInt32\t\t\t\t\tGetThreadID() const { return mThreadID; }\n\tbool\t\t\t\t\tIsCurrentThread() const { return (0 != mThreadID) && (GetCurrentThreadId() == mThreadID); }\n\tbool\t\t\t\t\tIsRunning() const { return 0 != mThreadID; }\n#endif\n\n\tbool\t\t\t\t\tIsTimeShareThread() const { return !mTimeConstraintSet; }\n\tbool\t\t\t\t\tIsTimeConstraintThread() const { return mTimeConstraintSet; }\n\n\tUInt32\t\t\t\t\tGetPriority() const { return mPriority; }\n    UInt32\t\t\t\t\tGetScheduledPriority();\n\tstatic UInt32\t\t\tGetScheduledPriority(NativeThread thread);\n    void\t\t\t\t\tSetPriority(UInt32 inPriority, bool inFixedPriority=false);\n\tstatic void\t\t\t\tSetPriority(NativeThread inThread, UInt32 inPriority, bool inFixedPriority = false);\n\n\tvoid\t\t\t\t\tGetTimeConstraints(UInt32& outPeriod, UInt32& outComputation, UInt32& outConstraint, bool& outIsPreemptible) const { outPeriod = mPeriod; outComputation = mComputation; outConstraint = mConstraint; outIsPreemptible = mIsPreemptible; }\n\tvoid\t\t\t\t\tSetTimeConstraints(UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible);\n\tvoid\t\t\t\t\tClearTimeConstraints() { SetPriority(mPriority); }\n\t\n\tbool\t\t\t\t\tWillAutoDelete() const { return mAutoDelete; }\n\tvoid\t\t\t\t\tSetAutoDelete(bool b) { mAutoDelete = b; }\n\t\n\tvoid\t\t\t\t\tSetName(const char* inThreadName);\n\n#if CoreAudio_Debug\t\n\tvoid\t\t\t\t\tDebugPriority(const char *label);\n#endif\n\n//\tActions\npublic:\n\tvirtual void\t\t\tStart();\n\n//\tImplementation\nprotected:\n#if TARGET_OS_MAC\n\tstatic void*\t\t\tEntry(CAPThread* inCAPThread);\n#elif TARGET_OS_WIN32\n\tstatic UInt32 WINAPI\tEntry(CAPThread* inCAPThread);\n#endif\n\n#if\tTARGET_OS_MAC\n\tpthread_t\t\t\t\tmPThread;\n    UInt32\t\t\t\t\tmSpawningThreadPriority;\n#elif TARGET_OS_WIN32\n\tHANDLE\t\t\t\t\tmThreadHandle;\n\tunsigned long\t\t\tmThreadID;\n#endif\n\tThreadRoutine\t\t\tmThreadRoutine;\n\tvoid*\t\t\t\t\tmThreadParameter;\n\tchar\t\t\t\t\tmThreadName[kMaxThreadNameLength];\n\tUInt32\t\t\t\t\tmPriority;\n\tUInt32\t\t\t\t\tmPeriod;\n\tUInt32\t\t\t\t\tmComputation;\n\tUInt32\t\t\t\t\tmConstraint;\n\tbool\t\t\t\t\tmIsPreemptible;\n\tbool\t\t\t\t\tmTimeConstraintSet;\n    bool\t\t\t\t\tmFixedPriority;\n\tbool\t\t\t\t\tmAutoDelete;\t\t// delete self when thread terminates\n};\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CAPropertyAddress.h",
    "content": "/*\n     File: CAPropertyAddress.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPropertyAddress_h__)\n#define __CAPropertyAddress_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n\n//\tSystem Includes\n#include <CoreAudio/AudioHardware.h>\n\n//  Standard Library Includes\n#include <algorithm>\n#include <functional>\n#include <vector>\n\n//==================================================================================================\n//\tCAPropertyAddress\n//\n//  CAPropertyAddress extends the AudioObjectPropertyAddress structure to C++ including constructors\n//  and other utility operations. Note that there is no defined operator< or operator== because the\n//  presence of wildcards for the fields make comparisons ambiguous without specifying whether or\n//  not to take the wildcards into account. Consequently, if you want to use this struct in an STL\n//  data structure, you'll need to specify the approriate function object explicitly in the template\n//  declaration.\n//==================================================================================================\n\nstruct CAPropertyAddress\n:\n\tpublic AudioObjectPropertyAddress\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCAPropertyAddress()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = 0; mScope = kAudioObjectPropertyScopeGlobal; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = inSelector; mScope = kAudioObjectPropertyScopeGlobal; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector, AudioObjectPropertyScope inScope)\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = inSelector; mScope = inScope; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector, AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement)   : AudioObjectPropertyAddress() { mSelector = inSelector; mScope = inScope; mElement = inElement; }\n\t\t\t\t\t\tCAPropertyAddress(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress(inAddress){}\n\t\t\t\t\t\tCAPropertyAddress(const CAPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress(inAddress){}\n\tCAPropertyAddress&  operator=(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ AudioObjectPropertyAddress::operator=(inAddress); return *this; }\n\tCAPropertyAddress&  operator=(const CAPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ AudioObjectPropertyAddress::operator=(inAddress); return *this; }\n\t\n//  Operations\npublic:\n\tstatic bool\t\t\tIsSameAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t\t{ return (inAddress1.mScope == inAddress2.mScope) && (inAddress1.mSelector == inAddress2.mSelector) && (inAddress1.mElement == inAddress2.mElement); }\n\tstatic bool\t\t\tIsLessThanAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t{ bool theAnswer = false; if(inAddress1.mScope != inAddress2.mScope) { theAnswer = inAddress1.mScope < inAddress2.mScope; } else if(inAddress1.mSelector != inAddress2.mSelector) { theAnswer = inAddress1.mSelector < inAddress2.mSelector; } else { theAnswer = inAddress1.mElement < inAddress2.mElement; } return theAnswer; }\n\tstatic bool\t\t\tIsCongruentSelector(AudioObjectPropertySelector inSelector1, AudioObjectPropertySelector inSelector2)\t\t\t\t\t\t\t\t{ return (inSelector1 == inSelector2) || (inSelector1 == kAudioObjectPropertySelectorWildcard) || (inSelector2 == kAudioObjectPropertySelectorWildcard); }\n\tstatic bool\t\t\tIsCongruentScope(AudioObjectPropertyScope inScope1, AudioObjectPropertyScope inScope2)\t\t\t\t\t\t\t\t\t\t\t\t{ return (inScope1 == inScope2) || (inScope1 == kAudioObjectPropertyScopeWildcard) || (inScope2 == kAudioObjectPropertyScopeWildcard); }\n\tstatic bool\t\t\tIsCongruentElement(AudioObjectPropertyElement inElement1, AudioObjectPropertyElement inElement2)\t\t\t\t\t\t\t\t\t{ return (inElement1 == inElement2) || (inElement1 == kAudioObjectPropertyElementWildcard) || (inElement2 == kAudioObjectPropertyElementWildcard); }\n\tstatic bool\t\t\tIsCongruentAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t{ return IsCongruentScope(inAddress1.mScope, inAddress2.mScope) && IsCongruentSelector(inAddress1.mSelector, inAddress2.mSelector) && IsCongruentElement(inAddress1.mElement, inAddress2.mElement); }\n\tstatic bool\t\t\tIsCongruentLessThanAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t{ bool theAnswer = false; if(!IsCongruentScope(inAddress1.mScope, inAddress2.mScope)) { theAnswer = inAddress1.mScope < inAddress2.mScope; } else if(!IsCongruentSelector(inAddress1.mSelector, inAddress2.mSelector)) { theAnswer = inAddress1.mSelector < inAddress2.mSelector; } else if(!IsCongruentElement(inAddress1.mElement, inAddress2.mElement)) { theAnswer = inAddress1.mElement < inAddress2.mElement; } return theAnswer; }\n};\n\n//==================================================================================================\n//  CAPropertyAddressList\n//\n//  An auto-resizing array of CAPropertyAddress structures.\n//==================================================================================================\n\nclass   CAPropertyAddressList\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressList()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(NULL) {}\n\texplicit\t\t\t\t\t\t\t\tCAPropertyAddressList(void* inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(inToken) {}\n\texplicit\t\t\t\t\t\t\t\tCAPropertyAddressList(uintptr_t inToken)\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(reinterpret_cast<void*>(inToken)) {}\n\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressList(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t: mAddressList(inAddressList.mAddressList), mToken(inAddressList.mToken) {}\n\tCAPropertyAddressList&\t\t\t\t\toperator=(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t\t\t\t{ mAddressList = inAddressList.mAddressList; mToken = inAddressList.mToken; return *this; }\n\t\t\t\t\t\t\t\t\t\t\t~CAPropertyAddressList()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{}\n\n//\tOperations\npublic:\n\tvoid*\t\t\t\t\t\t\t\t\tGetToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mToken; }\n\tvoid\t\t\t\t\t\t\t\t\tSetToken(void* inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mToken = inToken; }\n\t\n\tuintptr_t\t\t\t\t\t\t\t\tGetIntToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return reinterpret_cast<uintptr_t>(mToken); }\n\tvoid\t\t\t\t\t\t\t\t\tSetIntToken(uintptr_t inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mToken = reinterpret_cast<void*>(inToken); }\n\t\n\tAudioObjectID\t\t\t\t\t\t\tGetAudioObjectIDToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return static_cast<AudioObjectID>(reinterpret_cast<uintptr_t>(mToken)); }\n\t\n\tbool\t\t\t\t\t\t\t\t\tIsEmpty() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressList.empty(); }\n\tUInt32\t\t\t\t\t\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return ToUInt32(mAddressList.size()); }\n\tvoid\t\t\t\t\t\t\t\t\tGetItemByIndex(UInt32 inIndex, AudioObjectPropertyAddress& outAddress) const\t\t\t{ if(inIndex < mAddressList.size()) { outAddress = mAddressList.at(inIndex); } }\n\tconst AudioObjectPropertyAddress*\t\tGetItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return &(*mAddressList.begin()); }\n\tAudioObjectPropertyAddress*\t\t\t\tGetItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return &(*mAddressList.begin()); }\n\n\tbool\t\t\t\t\t\t\t\t\tHasItem(const AudioObjectPropertyAddress& inAddress) const\t\t\t\t\t\t\t\t{ AddressList::const_iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), [&inAddress](const CAPropertyAddress& addr) { return CAPropertyAddress::IsCongruentAddress(addr, inAddress); }); return theIterator != mAddressList.end(); }\n\tbool\t\t\t\t\t\t\t\t\tHasExactItem(const AudioObjectPropertyAddress& inAddress) const\t\t\t\t\t\t\t{ AddressList::const_iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), [&inAddress](const CAPropertyAddress& addr) { return CAPropertyAddress::IsSameAddress(addr, inAddress); }); return theIterator != mAddressList.end(); }\n\n\tvoid\t\t\t\t\t\t\t\t\tAppendItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t{ mAddressList.push_back(inAddress); }\n\tvoid\t\t\t\t\t\t\t\t\tAppendUniqueItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t{ if(!HasItem(inAddress)) { mAddressList.push_back(inAddress); } }\n\tvoid\t\t\t\t\t\t\t\t\tAppendUniqueExactItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t{ if(!HasExactItem(inAddress)) { mAddressList.push_back(inAddress); } }\n\tvoid\t\t\t\t\t\t\t\t\tInsertItemAtIndex(UInt32 inIndex, const AudioObjectPropertyAddress& inAddress)\t\t\t{ if(inIndex < mAddressList.size()) { AddressList::iterator theIterator = mAddressList.begin(); std::advance(theIterator, static_cast<int>(inIndex)); mAddressList.insert(theIterator, inAddress); } else { mAddressList.push_back(inAddress); } }\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\tvoid\t\t\t\t\t\t\t\t\tEraseExactItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t{ AddressList::iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), [&inAddress](const CAPropertyAddress& addr) { return CAPropertyAddress::IsSameAddress(addr, inAddress); }); if(theIterator != mAddressList.end()) { mAddressList.erase(theIterator); } }\n#pragma clang diagnostic pop\n\tvoid\t\t\t\t\t\t\t\t\tEraseItemAtIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(inIndex < mAddressList.size()) { AddressList::iterator theIterator = mAddressList.begin(); std::advance(theIterator, static_cast<int>(inIndex)); mAddressList.erase(theIterator); } }\n\tvoid\t\t\t\t\t\t\t\t\tEraseAllItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mAddressList.clear(); }\n\n//  Implementation\nprivate:\n\ttypedef std::vector<CAPropertyAddress>  AddressList;\n\n\tAddressList\t\t\t\t\t\t\t\tmAddressList;\n\tvoid*\t\t\t\t\t\t\t\t\tmToken;\n\t\n};\n\n//==================================================================================================\n//  CAPropertyAddressListVector\n//\n//  An auto-resizing array of CAPropertyAddressList objects.\n//==================================================================================================\n\nclass\tCAPropertyAddressListVector\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressListVector()\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressListVector() {}\n\t\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressListVector(const CAPropertyAddressListVector& inAddressListVector)\t: mAddressListVector(inAddressListVector.mAddressListVector) {}\n\tCAPropertyAddressListVector&\t\t\t\toperator=(const CAPropertyAddressListVector& inAddressListVector)\t\t\t\t\t{ mAddressListVector = inAddressListVector.mAddressListVector; return *this; }\n\t\t\t\t\t\t\t\t\t\t\t\t~CAPropertyAddressListVector()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{}\n\n//\tOperations\npublic:\n\tbool\t\t\t\t\t\t\t\t\t\tIsEmpty() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.empty(); }\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyNonEmptyItems() const;\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyItemsWithAddress(const AudioObjectPropertyAddress& inAddress) const;\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyItemsWithExactAddress(const AudioObjectPropertyAddress& inAddress) const;\n\n\tUInt32\t\t\t\t\t\t\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return ToUInt32(mAddressListVector.size()); }\n\tconst CAPropertyAddressList&\t\t\t\tGetItemByIndex(UInt32 inIndex) const\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.at(inIndex); }\n\tCAPropertyAddressList&\t\t\t\t\t\tGetItemByIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.at(inIndex); }\n\tconst CAPropertyAddressList*\t\t\t\tGetItemByToken(void* inToken) const;\n\tCAPropertyAddressList*\t\t\t\t\t\tGetItemByToken(void* inToken);\n\tconst CAPropertyAddressList*\t\t\t\tGetItemByIntToken(uintptr_t inToken) const;\n\tCAPropertyAddressList*\t\t\t\t\t\tGetItemByIntToken(uintptr_t inToken);\n\t\n\tvoid\t\t\t\t\t\t\t\t\t\tAppendItem(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t\t\t{ mAddressListVector.push_back(inAddressList); }\n\tvoid\t\t\t\t\t\t\t\t\t\tEraseAllItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mAddressListVector.clear(); }\n\t\n//  Implementation\nprivate:\n\ttypedef std::vector<CAPropertyAddressList>\tAddressListVector;\n\n\tAddressListVector\t\t\t\t\t\t\tmAddressListVector;\n\n};\n\ninline bool\tCAPropertyAddressListVector::HasAnyNonEmptyItems() const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = !theIterator->IsEmpty();\n\t}\n\treturn theAnswer;\n}\n\ninline bool\tCAPropertyAddressListVector::HasAnyItemsWithAddress(const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = theIterator->HasItem(inAddress);\n\t}\n\treturn theAnswer;\n}\n\ninline bool\tCAPropertyAddressListVector::HasAnyItemsWithExactAddress(const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = theIterator->HasExactItem(inAddress);\n\t}\n\treturn theAnswer;\n}\n\ninline const CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByToken(void* inToken) const\n{\n\tconst CAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByToken(void* inToken)\n{\n\tCAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline const CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByIntToken(uintptr_t inToken) const\n{\n\tconst CAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetIntToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByIntToken(uintptr_t inToken)\n{\n\tCAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetIntToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\n#endif\n"
  },
  {
    "path": "BGMApp/PublicUtility/CARingBuffer.cpp",
    "content": "/*\n     File: CARingBuffer.cpp\n Abstract: CARingBuffer.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#include \"CARingBuffer.h\"\n#include \"CABitOperations.h\"\n#include \"CAAutoDisposer.h\"\n#include \"CAAtomic.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <algorithm>\n#include <libkern/OSAtomic.h>\n\nCARingBuffer::CARingBuffer() :\n\tmBuffers(NULL), mNumberChannels(0), mCapacityFrames(0), mCapacityBytes(0)\n{\n\n}\n\nCARingBuffer::~CARingBuffer()\n{\n\tDeallocate();\n}\n\n\nvoid\tCARingBuffer::Allocate(int nChannels, UInt32 bytesPerFrame, UInt32 capacityFrames)\n{\n\tDeallocate();\n\t\n\tcapacityFrames = NextPowerOfTwo(capacityFrames);\n\t\n\tmNumberChannels = nChannels;\n\tmBytesPerFrame = bytesPerFrame;\n\tmCapacityFrames = capacityFrames;\n\tmCapacityFramesMask = capacityFrames - 1;\n\tmCapacityBytes = bytesPerFrame * capacityFrames;\n\n\t// put everything in one memory allocation, first the pointers, then the deinterleaved channels\n\tUInt32 allocSize = (mCapacityBytes + sizeof(Byte *)) * nChannels;\n\tByte *p = (Byte *)CA_malloc(allocSize);\n\tmemset(p, 0, allocSize);\n\tmBuffers = (Byte **)p;\n\tp += nChannels * sizeof(Byte *);\n\tfor (int i = 0; i < nChannels; ++i) {\n\t\tmBuffers[i] = p;\n\t\tp += mCapacityBytes;\n\t}\n\t\n\tfor (UInt32 i = 0; i<kGeneralRingTimeBoundsQueueSize; ++i)\n\t{\n\t\tmTimeBoundsQueue[i].mStartTime = 0;\n\t\tmTimeBoundsQueue[i].mEndTime = 0;\n\t\tmTimeBoundsQueue[i].mUpdateCounter = 0;\n\t}\n\tmTimeBoundsQueuePtr = 0;\n}\n\nvoid\tCARingBuffer::Deallocate()\n{\n\tif (mBuffers) {\n\t\tfree(mBuffers);\n\t\tmBuffers = NULL;\n\t}\n\tmNumberChannels = 0;\n\tmCapacityBytes = 0;\n\tmCapacityFrames = 0;\n}\n\ninline void ZeroRange(Byte **buffers, int nchannels, int offset, int nbytes)\n{\n\twhile (--nchannels >= 0) {\n\t\tmemset(*buffers + offset, 0, nbytes);\n\t\t++buffers;\n\t}\n}\n\ninline void StoreABL(Byte **buffers, int destOffset, const AudioBufferList *abl, int srcOffset, int nbytes)\n{\n\tint nchannels = abl->mNumberBuffers;\n\tconst AudioBuffer *src = abl->mBuffers;\n\twhile (--nchannels >= 0) {\n\t\tif (srcOffset > (int)src->mDataByteSize) continue;\n\t\tmemcpy(*buffers + destOffset, (Byte *)src->mData + srcOffset, std::min(nbytes, (int)src->mDataByteSize - srcOffset));\n\t\t++buffers;\n\t\t++src;\n\t}\n}\n\ninline void FetchABL(AudioBufferList *abl, int destOffset, Byte **buffers, int srcOffset, int nbytes)\n{\n\tint nchannels = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nchannels >= 0) {\n\t\tif (destOffset > (int)dest->mDataByteSize) continue;\n\t\tmemcpy((Byte *)dest->mData + destOffset, *buffers + srcOffset, std::min(nbytes, (int)dest->mDataByteSize - destOffset));\n\t\t++buffers;\n\t\t++dest;\n\t}\n}\n\ninline void ZeroABL(AudioBufferList *abl, int destOffset, int nbytes)\n{\n\tint nBuffers = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nBuffers >= 0) {\n\t\tif (destOffset > (int)dest->mDataByteSize) continue;\n\t\tmemset((Byte *)dest->mData + destOffset, 0, std::min(nbytes, (int)dest->mDataByteSize - destOffset));\n\t\t++dest;\n\t}\n}\n\n\nCARingBufferError\tCARingBuffer::Store(const AudioBufferList *abl, UInt32 framesToWrite, SampleTime startWrite)\n{\n\tif (framesToWrite == 0)\n\t\treturn kCARingBufferError_OK;\n\t\n\tif (framesToWrite > mCapacityFrames)\n\t\treturn kCARingBufferError_TooMuch;\t\t// too big!\n\n\tSampleTime endWrite = startWrite + framesToWrite;\n\t\n\tif (startWrite < EndTime()) {\n\t\t// going backwards, throw everything out\n\t\tSetTimeBounds(startWrite, startWrite);\n\t} else if (endWrite - StartTime() <= mCapacityFrames) {\n\t\t// the buffer has not yet wrapped and will not need to\n\t} else {\n\t\t// advance the start time past the region we are about to overwrite\n\t\tSampleTime newStart = endWrite - mCapacityFrames;\t// one buffer of time behind where we're writing\n\t\tSampleTime newEnd = std::max(newStart, EndTime());\n\t\tSetTimeBounds(newStart, newEnd);\n\t}\n\t\n\t// write the new frames\n\tByte **buffers = mBuffers;\n\tint nchannels = mNumberChannels;\n\tint offset0, offset1, nbytes;\n\tSampleTime curEnd = EndTime();\n\t\n\tif (startWrite > curEnd) {\n\t\t// we are skipping some samples, so zero the range we are skipping\n\t\toffset0 = FrameOffset(curEnd);\n\t\toffset1 = FrameOffset(startWrite);\n\t\tif (offset0 < offset1)\n\t\t\tZeroRange(buffers, nchannels, offset0, offset1 - offset0);\n\t\telse {\n\t\t\tZeroRange(buffers, nchannels, offset0, mCapacityBytes - offset0);\n\t\t\tZeroRange(buffers, nchannels, 0, offset1);\n\t\t}\n\t\toffset0 = offset1;\n\t} else {\n\t\toffset0 = FrameOffset(startWrite);\n\t}\n\n\toffset1 = FrameOffset(endWrite);\n\tif (offset0 < offset1)\n\t\tStoreABL(buffers, offset0, abl, 0, offset1 - offset0);\n\telse {\n\t\tnbytes = mCapacityBytes - offset0;\n\t\tStoreABL(buffers, offset0, abl, 0, nbytes);\n\t\tStoreABL(buffers, 0, abl, nbytes, offset1);\n\t}\n\t\n\t// now update the end time\n\tSetTimeBounds(StartTime(), endWrite);\n\t\n\treturn kCARingBufferError_OK;\t// success\n}\n\nvoid\tCARingBuffer::SetTimeBounds(SampleTime startTime, SampleTime endTime)\n{\n\tUInt32 nextPtr = mTimeBoundsQueuePtr + 1;\n\tUInt32 index = nextPtr & kGeneralRingTimeBoundsQueueMask;\n\t\n\tmTimeBoundsQueue[index].mStartTime = startTime;\n\tmTimeBoundsQueue[index].mEndTime = endTime;\n\tmTimeBoundsQueue[index].mUpdateCounter = nextPtr;\n\tCAAtomicCompareAndSwap32Barrier(mTimeBoundsQueuePtr, mTimeBoundsQueuePtr + 1, (SInt32*)&mTimeBoundsQueuePtr);\n}\n\nCARingBufferError\tCARingBuffer::GetTimeBounds(SampleTime &startTime, SampleTime &endTime)\n{\n\tfor (int i=0; i<8; ++i) // fail after a few tries.\n\t{\n\t\tUInt32 curPtr = mTimeBoundsQueuePtr;\n\t\tUInt32 index = curPtr & kGeneralRingTimeBoundsQueueMask;\n\t\tCARingBuffer::TimeBounds* bounds = mTimeBoundsQueue + index;\n\t\t\n\t\tstartTime = bounds->mStartTime;\n\t\tendTime = bounds->mEndTime;\n\t\tUInt32 newPtr = bounds->mUpdateCounter;\n\t\t\n\t\tif (newPtr == curPtr) \n\t\t\treturn kCARingBufferError_OK;\n\t}\n\treturn kCARingBufferError_CPUOverload;\n}\n\nCARingBufferError\tCARingBuffer::ClipTimeBounds(SampleTime& startRead, SampleTime& endRead)\n{\n\tSampleTime startTime, endTime;\n\t\n\tCARingBufferError err = GetTimeBounds(startTime, endTime);\n\tif (err) return err;\n\t\n\tif (startRead > endTime || endRead < startTime) {\n\t\tendRead = startRead;\n\t\treturn kCARingBufferError_OK;\n\t}\n\t\n\tstartRead = std::max(startRead, startTime);\n\tendRead = std::min(endRead, endTime);\n\tendRead = std::max(endRead, startRead);\n\t\t\n\treturn kCARingBufferError_OK;\t// success\n}\n\nCARingBufferError\tCARingBuffer::Fetch(AudioBufferList *abl, UInt32 nFrames, SampleTime startRead)\n{\n\tif (nFrames == 0)\n\t\treturn kCARingBufferError_OK;\n\t\t\n\tstartRead = std::max(0LL, startRead);\n\t\n\tSampleTime endRead = startRead + nFrames;\n\n\tSampleTime startRead0 = startRead;\n\tSampleTime endRead0 = endRead;\n\n\tCARingBufferError err = ClipTimeBounds(startRead, endRead);\n\tif (err) return err;\n\n\tif (startRead == endRead) {\n\t\tZeroABL(abl, 0, nFrames * mBytesPerFrame);\n\t\treturn kCARingBufferError_OK;\n\t}\n\t\n\tSInt32 byteSize = (SInt32)((endRead - startRead) * mBytesPerFrame);\n\t\n\tSInt32 destStartByteOffset = std::max((SInt32)0, (SInt32)((startRead - startRead0) * mBytesPerFrame)); \n\t\t\n\tif (destStartByteOffset > 0) {\n\t\tZeroABL(abl, 0, std::min((SInt32)(nFrames * mBytesPerFrame), destStartByteOffset));\n\t}\n\n\tSInt32 destEndSize = std::max((SInt32)0, (SInt32)(endRead0 - endRead)); \n\tif (destEndSize > 0) {\n\t\tZeroABL(abl, destStartByteOffset + byteSize, destEndSize * mBytesPerFrame);\n\t}\n\t\n\tByte **buffers = mBuffers;\n\tint offset0 = FrameOffset(startRead);\n\tint offset1 = FrameOffset(endRead);\n\tint nbytes;\n\t\n\tif (offset0 < offset1) {\n\t\tnbytes = offset1 - offset0;\n\t\tFetchABL(abl, destStartByteOffset, buffers, offset0, nbytes);\n\t} else {\n\t\tnbytes = mCapacityBytes - offset0;\n\t\tFetchABL(abl, destStartByteOffset, buffers, offset0, nbytes);\n\t\tFetchABL(abl, destStartByteOffset + nbytes, buffers, 0, offset1);\n\t\tnbytes += offset1;\n\t}\n\n\tint nchannels = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nchannels >= 0)\n\t{\n\t\tdest->mDataByteSize = nbytes;\n\t\tdest++;\n\t}\n\n\treturn noErr;\n}\n"
  },
  {
    "path": "BGMApp/PublicUtility/CARingBuffer.h",
    "content": "/*\n     File: CARingBuffer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n\n#ifndef CARingBuffer_Header\n#define CARingBuffer_Header\n\nenum {\n\tkCARingBufferError_OK = 0,\n\tkCARingBufferError_TooMuch = 3, // fetch start time is earlier than buffer start time and fetch end time is later than buffer end time\n\tkCARingBufferError_CPUOverload = 4 // the reader is unable to get enough CPU cycles to capture a consistent snapshot of the time bounds\n};\n\ntypedef SInt32 CARingBufferError;\n\nconst UInt32 kGeneralRingTimeBoundsQueueSize = 32;\nconst UInt32 kGeneralRingTimeBoundsQueueMask = kGeneralRingTimeBoundsQueueSize - 1;\n\nclass CARingBuffer {\npublic:\n\ttypedef SInt64 SampleTime;\n\n\tCARingBuffer();\n\t~CARingBuffer();\n\t\n\tvoid\t\t\t\t\tAllocate(int nChannels, UInt32 bytesPerFrame, UInt32 capacityFrames);\n\t\t\t\t\t\t\t\t// capacityFrames will be rounded up to a power of 2\n\tvoid\t\t\t\t\tDeallocate();\n\t\n\tCARingBufferError\tStore(const AudioBufferList *abl, UInt32 nFrames, SampleTime frameNumber);\n\t\t\t\t\t\t\t// Copy nFrames of data into the ring buffer at the specified sample time.\n\t\t\t\t\t\t\t// The sample time should normally increase sequentially, though gaps\n\t\t\t\t\t\t\t// are filled with zeroes. A sufficiently large gap effectively empties\n\t\t\t\t\t\t\t// the buffer before storing the new data. \n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// If frameNumber is less than the previous frame number, the behavior is undefined.\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Return false for failure (buffer not large enough).\n\t\t\t\t\n\tCARingBufferError\tFetch(AudioBufferList *abl, UInt32 nFrames, SampleTime frameNumber);\n\t\t\t\t\t\t\t\t// will alter mDataByteSize of the buffers\n\t\n\tCARingBufferError\tGetTimeBounds(SampleTime &startTime, SampleTime &endTime);\n\t\nprotected:\n\n\tUInt32\t\t\t\t\tFrameOffset(SampleTime frameNumber) { return (frameNumber & mCapacityFramesMask) * mBytesPerFrame; }\n\n\tCARingBufferError\t\tClipTimeBounds(SampleTime& startRead, SampleTime& endRead);\n\t\n\t// these should only be called from Store.\n\tSampleTime\t\t\t\tStartTime() const { return mTimeBoundsQueue[mTimeBoundsQueuePtr & kGeneralRingTimeBoundsQueueMask].mStartTime; }\n\tSampleTime\t\t\t\tEndTime()   const { return mTimeBoundsQueue[mTimeBoundsQueuePtr & kGeneralRingTimeBoundsQueueMask].mEndTime; }\n\tvoid\t\t\t\t\tSetTimeBounds(SampleTime startTime, SampleTime endTime);\n\t\nprotected:\n\tByte **\t\t\t\t\tmBuffers;\t\t\t\t// allocated in one chunk of memory\n\tint\t\t\t\t\t\tmNumberChannels;\n\tUInt32\t\t\t\t\tmBytesPerFrame;\t\t\t// within one deinterleaved channel\n\tUInt32\t\t\t\t\tmCapacityFrames;\t\t// per channel, must be a power of 2\n\tUInt32\t\t\t\t\tmCapacityFramesMask;\n\tUInt32\t\t\t\t\tmCapacityBytes;\t\t\t// per channel\n\t\n\t// range of valid sample time in the buffer\n\ttypedef struct {\n\t\tvolatile SampleTime\t\tmStartTime;\n\t\tvolatile SampleTime\t\tmEndTime;\n\t\tvolatile UInt32\t\t\tmUpdateCounter;\n\t} TimeBounds;\n\t\n\tCARingBuffer::TimeBounds mTimeBoundsQueue[kGeneralRingTimeBoundsQueueSize];\n\tUInt32 mTimeBoundsQueuePtr;\n};\n\n\n\n#endif\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AbstractDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_AbstractDevice.cpp\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n\n// Self Include\n#include \"BGM_AbstractDevice.h\"\n\n// Local Includes\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CAException.h\"\n#include \"CADebugMacros.h\"\n\n\n#pragma clang assume_nonnull begin\n\nBGM_AbstractDevice::BGM_AbstractDevice(AudioObjectID inObjectID, AudioObjectID inOwnerObjectID)\n:\n    BGM_Object(inObjectID, kAudioDeviceClassID, kAudioObjectClassID, inOwnerObjectID)\n{\n}\n\nBGM_AbstractDevice::~BGM_AbstractDevice()\n{\n}\n\n#pragma mark Property Operations\n\nbool    BGM_AbstractDevice::HasProperty(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress) const\n{\n    bool theAnswer = false;\n    \n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyName:\n        case kAudioObjectPropertyManufacturer:\n        case kAudioDevicePropertyDeviceUID:\n        case kAudioDevicePropertyModelUID:\n        case kAudioDevicePropertyTransportType:\n        case kAudioDevicePropertyRelatedDevices:\n        case kAudioDevicePropertyClockDomain:\n        case kAudioDevicePropertyDeviceIsAlive:\n        case kAudioDevicePropertyDeviceIsRunning:\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n        case kAudioDevicePropertyLatency:\n        case kAudioDevicePropertyStreams:\n        case kAudioObjectPropertyControlList:\n        case kAudioDevicePropertySafetyOffset:\n        case kAudioDevicePropertyNominalSampleRate:\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n        case kAudioDevicePropertyIsHidden:\n        case kAudioDevicePropertyZeroTimeStampPeriod:\n            theAnswer = true;\n            break;\n            \n        default:\n            theAnswer = BGM_Object::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nbool    BGM_AbstractDevice::IsPropertySettable(AudioObjectID inObjectID,\n                                               pid_t inClientPID,\n                                               const AudioObjectPropertyAddress& inAddress) const\n{\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyName:\n        case kAudioObjectPropertyManufacturer:\n        case kAudioDevicePropertyDeviceUID:\n        case kAudioDevicePropertyModelUID:\n        case kAudioDevicePropertyTransportType:\n        case kAudioDevicePropertyRelatedDevices:\n        case kAudioDevicePropertyClockDomain:\n        case kAudioDevicePropertyDeviceIsAlive:\n        case kAudioDevicePropertyDeviceIsRunning:\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n        case kAudioDevicePropertyLatency:\n        case kAudioDevicePropertyStreams:\n        case kAudioObjectPropertyControlList:\n        case kAudioDevicePropertySafetyOffset:\n        case kAudioDevicePropertyNominalSampleRate:\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n        case kAudioDevicePropertyIsHidden:\n        case kAudioDevicePropertyZeroTimeStampPeriod:\n            theAnswer = false;\n            break;\n            \n        default:\n            theAnswer = BGM_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32    BGM_AbstractDevice::GetPropertyDataSize(AudioObjectID inObjectID,\n                                                  pid_t inClientPID,\n                                                  const AudioObjectPropertyAddress& inAddress,\n                                                  UInt32 inQualifierDataSize,\n                                                  const void* __nullable inQualifierData) const\n{\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyName:\n            theAnswer = sizeof(CFStringRef);\n            break;\n\n        case kAudioObjectPropertyManufacturer:\n            theAnswer = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyDeviceUID:\n            theAnswer = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyModelUID:\n            theAnswer = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyTransportType:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyRelatedDevices:\n            theAnswer = sizeof(AudioObjectID);\n            break;\n\n        case kAudioDevicePropertyClockDomain:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceIsAlive:\n            theAnswer = sizeof(AudioClassID);\n            break;\n\n        case kAudioDevicePropertyDeviceIsRunning:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyLatency:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyStreams:\n            theAnswer = 0;\n            break;\n\n        case kAudioObjectPropertyControlList:\n            theAnswer = 0;\n            break;\n\n        case kAudioDevicePropertySafetyOffset:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyNominalSampleRate:\n            theAnswer = sizeof(Float64);\n            break;\n\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n            theAnswer = 0;\n            break;\n\n        case kAudioDevicePropertyIsHidden:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyZeroTimeStampPeriod:\n            theAnswer = sizeof(UInt32);\n            break;\n            \n        default:\n            theAnswer = BGM_Object::GetPropertyDataSize(inObjectID,\n                                                        inClientPID,\n                                                        inAddress,\n                                                        inQualifierDataSize,\n                                                        inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_AbstractDevice::GetPropertyData(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress,\n                                            UInt32 inQualifierDataSize,\n                                            const void* __nullable inQualifierData,\n                                            UInt32 inDataSize,\n                                            UInt32& outDataSize,\n                                            void* outData) const\n{\n    UInt32 theNumberItemsToFetch;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyName:\n        case kAudioObjectPropertyManufacturer:\n        case kAudioDevicePropertyDeviceUID:\n        case kAudioDevicePropertyModelUID:\n        case kAudioDevicePropertyDeviceIsRunning:\n        case kAudioDevicePropertyZeroTimeStampPeriod:\n        case kAudioDevicePropertyNominalSampleRate:\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n            // Should be unreachable. Reaching this point would mean a concrete device has delegated\n            // a required property that can't be handled by this class or its parent, BGM_Object.\n            //\n            // See BGM_Device for info about these properties.\n            //\n            // TODO: Write a test that checks all required properties for each subclass.\n            BGMAssert(false,\n                      \"BGM_AbstractDevice::GetPropertyData: Property %u not handled in subclass\",\n                      inAddress.mSelector);\n            // Throw in release builds.\n            Throw(CAException(kAudioHardwareIllegalOperationError));\n\n        case kAudioDevicePropertyTransportType:\n            // This value represents how the device is attached to the system. This can be\n            // any 32 bit integer, but common values for this property are defined in\n            // <CoreAudio/AudioHardwareBase.h>.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyTransportType for the device\");\n            // Default to virtual device.\n            *reinterpret_cast<UInt32*>(outData) = kAudioDeviceTransportTypeVirtual;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyRelatedDevices:\n            // The related devices property identifies device objects that are very closely\n            // related. Generally, this is for relating devices that are packaged together\n            // in the hardware such as when the input side and the output side of a piece\n            // of hardware can be clocked separately and therefore need to be represented\n            // as separate AudioDevice objects. In such case, both devices would report\n            // that they are related to each other. Note that at minimum, a device is\n            // related to itself, so this list will always be at least one item long.\n\n            // Calculate the number of items that have been requested. Note that this\n            // number is allowed to be smaller than the actual size of the list. In such\n            // case, only that number of items will be returned\n            theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID);\n\n            // Default to only have the one device.\n            if(theNumberItemsToFetch > 1)\n            {\n                theNumberItemsToFetch = 1;\n            }\n\n            // Write the devices' object IDs into the return value.\n            if(theNumberItemsToFetch > 0)\n            {\n                reinterpret_cast<AudioObjectID*>(outData)[0] = GetObjectID();\n            }\n            \n            // Report how much we wrote.\n            outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID);\n            break;\n\n        case kAudioDevicePropertyClockDomain:\n            // This property allows the device to declare what other devices it is\n            // synchronized with in hardware. The way it works is that if two devices have\n            // the same value for this property and the value is not zero, then the two\n            // devices are synchronized in hardware. Note that a device that either can't\n            // be synchronized with others or doesn't know should return 0 for this\n            // property.\n            //\n            // Default to 0.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyClockDomain for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceIsAlive:\n            // Default to alive.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceIsAlive for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 1;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n            // This property returns whether or not the device wants to be able to be the\n            // default device for content. This is the device that iTunes and QuickTime\n            // will use to play their content on and FaceTime will use as it's microphone.\n            // Nearly all devices should allow for this.\n            //\n            // Default to true.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceCanBeDefaultDevice for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 1;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n            // This property returns whether or not the device wants to be the system\n            // default device. This is the device that is used to play interface sounds and\n            // other incidental or UI-related sounds on. Most devices should allow this\n            // although devices with lots of latency may not want to.\n            //\n            // Default to true.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceCanBeDefaultSystemDevice for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 1;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyLatency:\n            // This property returns the presentation latency of the device. Default to 0.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyLatency for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyStreams:\n            // Default to not having any streams.\n            outDataSize = 0;\n            break;\n\n        case kAudioObjectPropertyControlList:\n            // Default to not having any controls.\n            outDataSize = 0;\n            break;\n\n        case kAudioDevicePropertySafetyOffset:\n            // This property returns the how close to now the HAL can read and write. Default to 0.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertySafetyOffset for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyIsHidden:\n            // This returns whether or not the device is visible to clients. Default to not hidden.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_AbstractDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyIsHidden for the device\");\n            *reinterpret_cast<UInt32*>(outData) = 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        default:\n            BGM_Object::GetPropertyData(inObjectID,\n                                        inClientPID,\n                                        inAddress,\n                                        inQualifierDataSize,\n                                        inQualifierData,\n                                        inDataSize,\n                                        outDataSize,\n                                        outData);\n            break;\n    };\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AbstractDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_AbstractDevice.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n#ifndef BGM_Driver__BGM_AbstractDevice\n#define BGM_Driver__BGM_AbstractDevice\n\n// SuperClass Includes\n#include \"BGM_Object.h\"\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_AbstractDevice\n:\n    public BGM_Object\n{\n\n#pragma mark Construction/Destruction\n\nprotected:\n                                BGM_AbstractDevice(AudioObjectID inObjectID,\n                                                   AudioObjectID inOwnerObjectID);\n    virtual                     ~BGM_AbstractDevice();\n\n#pragma mark Property Operations\n\npublic:\n    virtual bool                HasProperty(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress) const;\n    virtual bool                IsPropertySettable(AudioObjectID inObjectID,\n                                                   pid_t inClientPID,\n                                                   const AudioObjectPropertyAddress& inAddress) const;\n    virtual UInt32              GetPropertyDataSize(AudioObjectID inObjectID,\n                                                    pid_t inClientPID,\n                                                    const AudioObjectPropertyAddress& inAddress,\n                                                    UInt32 inQualifierDataSize,\n                                                    const void* __nullable inQualifierData) const;\n    virtual void                GetPropertyData(AudioObjectID inObjectID,\n                                                pid_t inClientPID,\n                                                const AudioObjectPropertyAddress& inAddress,\n                                                UInt32 inQualifierDataSize,\n                                                const void* __nullable inQualifierData,\n                                                UInt32 inDataSize,\n                                                UInt32& outDataSize,\n                                                void* outData) const;\n\n#pragma mark IO Operations\n\npublic:\n    virtual void                StartIO(UInt32 inClientID) = 0;\n    virtual void                StopIO(UInt32 inClientID) = 0;\n\n    virtual void                GetZeroTimeStamp(Float64& outSampleTime,\n                                                 UInt64& outHostTime,\n                                                 UInt64& outSeed) = 0;\n\n    virtual void                WillDoIOOperation(UInt32 inOperationID,\n                                                  bool& outWillDo,\n                                                  bool& outWillDoInPlace) const = 0;\n    virtual void                BeginIOOperation(UInt32 inOperationID,\n                                                 UInt32 inIOBufferFrameSize,\n                                                 const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                                 UInt32 inClientID) = 0;\n    virtual void                DoIOOperation(AudioObjectID inStreamObjectID,\n                                              UInt32 inClientID, UInt32 inOperationID,\n                                              UInt32 inIOBufferFrameSize,\n                                              const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                              void* ioMainBuffer,\n                                              void* __nullable ioSecondaryBuffer) = 0;\n    virtual void                EndIOOperation(UInt32 inOperationID,\n                                               UInt32 inIOBufferFrameSize,\n                                               const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                               UInt32 inClientID) = 0;\n\n#pragma mark Implementation\n\npublic:\n    virtual CFStringRef         CopyDeviceUID() const = 0;\n    virtual void                AddClient(const AudioServerPlugInClientInfo* inClientInfo) = 0;\n    virtual void                RemoveClient(const AudioServerPlugInClientInfo* inClientInfo) = 0;\n    virtual void                PerformConfigChange(UInt64 inChangeAction,\n                                                    void* __nullable inChangeInfo) = 0;\n    virtual void                AbortConfigChange(UInt64 inChangeAction,\n                                                  void* __nullable inChangeInfo) = 0;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGM_Driver__BGM_AbstractDevice */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AudibleState.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_AudibleState.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//  Copyright © 2016 Josh Junon\n//\n\n// Self Include\n#include \"BGM_AudibleState.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wsign-conversion\"\n#include \"CAAtomic.h\"\n#pragma clang diagnostic pop\n\n// STL Includes\n#include <algorithm>  // For std::min and std::max.\n\n\n// TODO: This is just the first value I tried.\nstatic const Float32 kSampleVolumeMarginRaw = 0.0001f;\n\nBGM_AudibleState::BGM_AudibleState()\n:\n    mState(kBGMDeviceIsSilent),\n    mSampleTimes({0, 0, 0, 0})\n{\n}\n\nBGMDeviceAudibleState   BGM_AudibleState::GetState() const noexcept\n{\n    CAMemoryBarrier();  // Probably unnecessary.\n    return mState;\n}\n\nvoid    BGM_AudibleState::Reset() noexcept\n{\n    mState = kBGMDeviceIsSilent;\n\n    mSampleTimes.latestSilent = 0;\n    mSampleTimes.latestAudibleNonMusic = 0;\n    mSampleTimes.latestSilentMusic = 0;\n    mSampleTimes.latestAudibleMusic = 0;\n}\n\nvoid    BGM_AudibleState::UpdateWithClientIO(bool inClientIsMusicPlayer,\n                                             UInt32 inIOBufferFrameSize,\n                                             Float64 inOutputSampleTime,\n                                             const Float32* inBuffer)\n{\n    // Update the sample times of the most recent audible music, silent music and audible non-music\n    // samples we've received.\n\n    Float64 endFrameSampleTime = inOutputSampleTime + inIOBufferFrameSize - 1;\n\n    if(inClientIsMusicPlayer)\n    {\n        if(BufferIsAudible(inIOBufferFrameSize, inBuffer))\n        {\n            mSampleTimes.latestAudibleMusic = std::max(mSampleTimes.latestAudibleMusic,\n                                                       endFrameSampleTime);\n        }\n        else\n        {\n            mSampleTimes.latestSilentMusic = std::max(mSampleTimes.latestSilentMusic,\n                                                      endFrameSampleTime);\n        }\n    }\n    else if(endFrameSampleTime > mSampleTimes.latestAudibleNonMusic &&  // Don't bother checking the\n                                                                        // buffer if it won't change\n                                                                        // anything.\n            BufferIsAudible(inIOBufferFrameSize, inBuffer))\n    {\n        mSampleTimes.latestAudibleNonMusic = std::max(mSampleTimes.latestAudibleNonMusic,\n                                                      endFrameSampleTime);\n    }\n}\n\nbool    BGM_AudibleState::UpdateWithMixedIO(UInt32 inIOBufferFrameSize,\n                                            Float64 inOutputSampleTime,\n                                            const Float32* inBuffer)\n{\n    // Update the sample time of the most recent silent sample we've received. (The music player\n    // client is not considered separate for the latest silent sample.)\n\n    bool audible = BufferIsAudible(inIOBufferFrameSize, inBuffer);\n\n    // The sample time of the last frame we're looking at.\n    Float64 endFrameSampleTime = inOutputSampleTime + inIOBufferFrameSize - 1;\n\n    if(!audible)\n    {\n        mSampleTimes.latestSilent = std::max(mSampleTimes.latestSilent, endFrameSampleTime);\n    }\n\n    return RecalculateState(endFrameSampleTime);\n}\n\nbool    BGM_AudibleState::RecalculateState(Float64 inEndFrameSampleTime)\n{\n    Float64 sinceLatestSilent = inEndFrameSampleTime - mSampleTimes.latestSilent;\n    Float64 sinceLatestMusicSilent = inEndFrameSampleTime - mSampleTimes.latestSilentMusic;\n    Float64 sinceLatestAudible = inEndFrameSampleTime - mSampleTimes.latestAudibleNonMusic;\n    Float64 sinceLatestMusicAudible = inEndFrameSampleTime - mSampleTimes.latestAudibleMusic;\n\n    bool didChangeState = false;\n\n    // Update mState\n\n    // Change from silent/silentExceptMusic to audible\n    if(mState != kBGMDeviceIsAudible &&\n       sinceLatestSilent >= kDeviceAudibleStateMinChangedFramesForUpdate &&\n       // Check that non-music audio is currently playing\n       sinceLatestAudible <= 0 && mSampleTimes.latestAudibleNonMusic != 0)\n    {\n        DebugMsg(\"BGM_AudibleState::RecalculateState: Changing \"\n                 \"kAudioDeviceCustomPropertyDeviceAudibleState to audible\");\n        mState = kBGMDeviceIsAudible;\n        CAMemoryBarrier();\n        didChangeState = true;\n    }\n    // Change from silent to silentExceptMusic\n    else if(((mState == kBGMDeviceIsSilent &&\n              sinceLatestMusicSilent >= kDeviceAudibleStateMinChangedFramesForUpdate) ||\n             // ...or from audible to silentExceptMusic\n             (mState == kBGMDeviceIsAudible &&\n              sinceLatestAudible >= kDeviceAudibleStateMinChangedFramesForUpdate &&\n              sinceLatestMusicSilent >= kDeviceAudibleStateMinChangedFramesForUpdate)) &&\n            // In case we haven't seen any music samples yet (either audible or silent), check that\n            // music is currently playing\n            sinceLatestMusicAudible <= 0 && mSampleTimes.latestAudibleMusic != 0)\n    {\n        DebugMsg(\"BGM_AudibleState::RecalculateState: Changing \"\n                 \"kAudioDeviceCustomPropertyDeviceAudibleState to silent except music\");\n        mState = kBGMDeviceIsSilentExceptMusic;\n        CAMemoryBarrier();\n        didChangeState = true;\n    }\n    // Change from audible/silentExceptMusic to silent\n    else if(mState != kBGMDeviceIsSilent &&\n            sinceLatestAudible >= kDeviceAudibleStateMinChangedFramesForUpdate &&\n            sinceLatestMusicAudible >= kDeviceAudibleStateMinChangedFramesForUpdate)\n    {\n        DebugMsg(\"BGM_AudibleState::RecalculateState: Changing \"\n                 \"kAudioDeviceCustomPropertyDeviceAudibleState to silent\");\n        mState = kBGMDeviceIsSilent;\n        CAMemoryBarrier();\n        didChangeState = true;\n    }\n\n    return didChangeState;\n}\n\n// static\nbool    BGM_AudibleState::BufferIsAudible(UInt32 inIOBufferFrameSize, const Float32* inBuffer)\n{\n    // Check each frame to see if any are audible. This could be much more accurate, but seems to\n    // work well enough for now.\n    //\n    // The trade-off here is between pausing the music player at the wrong time and unpausing it at\n    // the wrong time. If a short sound (e.g. a UI alert) plays but has a long, barely-audible tail,\n    // we might not detect the silence quickly enough and pause the music player. Similarly, if\n    // we've paused the music player and there's a period of near-silence in the new audio, we might\n    // unpause the music and briefly interrupt the new audio.\n    //\n    // A fairly long period of silence before unpausing the music player isn't a big problem, which\n    // means BGMApp can wait much longer before unpausing than before pausing. So this function errs\n    // toward considering the buffer silent, which helps BGMApp ignore short sounds.\n    if(inIOBufferFrameSize > 0)\n    {\n        // Bounds for the left channel samples.\n        Float32 firstSampleLLower = inBuffer[0] - kSampleVolumeMarginRaw;\n        Float32 firstSampleLUpper = inBuffer[0] + kSampleVolumeMarginRaw;\n        // Bounds for the right channel samples.\n        Float32 firstSampleRLower = inBuffer[1] - kSampleVolumeMarginRaw;\n        Float32 firstSampleRUpper = inBuffer[1] + kSampleVolumeMarginRaw;\n\n        for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2)\n        {\n            bool audibleL =\n                    (inBuffer[i] < firstSampleLLower) || (inBuffer[i] > firstSampleLUpper);\n            bool audibleR =\n                    (inBuffer[i + 1] < firstSampleRLower) || (inBuffer[i + 1] > firstSampleRUpper);\n\n            if(audibleL || audibleR)\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AudibleState.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_AudibleState.h\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n//  Inspects a stream of audio data and reports whether it's silent, silent except for the user's\n//  music player, or audible.\n//\n//  See kAudioDeviceCustomPropertyDeviceAudibleState and the BGMDeviceAudibleState enum in\n//  BGM_Types.h for more info.\n//\n//  Not thread-safe.\n//\n\n#ifndef BGMDriver__BGM_AudibleState\n#define BGMDriver__BGM_AudibleState\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// System Includes\n#include <MacTypes.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_AudibleState\n{\n\npublic:\n                                BGM_AudibleState();\n\n    /*!\n     @return The current audible state of the device, to be used as the value of the\n             kAudioDeviceCustomPropertyDeviceAudibleState property.\n     */\n    BGMDeviceAudibleState       GetState() const noexcept;\n\n    /*! Set the audible state back to kBGMDeviceIsSilent and ignore all previous IO. */\n    void                        Reset() noexcept;\n    \n    /*!\n     Read an audio buffer sent by a single device client (i.e. a process playing audio) and update\n     the audible state. The update will only affect the return value of GetState after the next\n     call to UpdateWithMixedIO, when all IO for the cycle has been read.\n\n     Real-time safe. Not thread safe.\n     */\n    void                        UpdateWithClientIO(bool inClientIsMusicPlayer,\n                                                   UInt32 inIOBufferFrameSize,\n                                                   Float64 inOutputSampleTime,\n                                                   const Float32* inBuffer);\n    /*!\n     Read a fully mixed audio buffer and update the audible state. All client (unmixed) buffers for\n     the same cycle must be read with UpdateWithClientIO before calling this function.\n\n     Real-time safe. Not thread safe.\n\n     @return True if the audible state changed.\n     */\n    bool                        UpdateWithMixedIO(UInt32 inIOBufferFrameSize,\n                                                  Float64 inOutputSampleTime,\n                                                  const Float32* inBuffer);\n\nprivate:\n    bool                        RecalculateState(Float64 inEndFrameSampleTime);\n\n    static bool                 BufferIsAudible(UInt32 inIOBufferFrameSize,\n                                                const Float32* inBuffer);\n\nprivate:\n    BGMDeviceAudibleState       mState;\n\n    struct\n    {\n        Float64                 latestAudibleNonMusic;\n        Float64                 latestSilent;\n        Float64                 latestAudibleMusic;\n        Float64                 latestSilentMusic;\n    }                           mSampleTimes;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_AudibleState */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Control.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Control.cpp\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n\n// Self Include\n#include \"BGM_Control.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n// System Includes\n#include <CoreAudio/AudioHardwareBase.h>\n\n\n#pragma clang assume_nonnull begin\n\nBGM_Control::BGM_Control(AudioObjectID inObjectID,\n                         AudioClassID inClassID,\n                         AudioClassID inBaseClassID,\n                         AudioObjectID inOwnerObjectID,\n                         AudioObjectPropertyScope inScope,\n                         AudioObjectPropertyElement inElement)\n:\n    BGM_Object(inObjectID, inClassID, inBaseClassID, inOwnerObjectID),\n    mScope(inScope),\n    mElement(inElement)\n{\n}\n\nbool    BGM_Control::HasProperty(AudioObjectID inObjectID,\n                                 pid_t inClientPID,\n                                 const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioControlPropertyScope:\n        case kAudioControlPropertyElement:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Object::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nbool    BGM_Control::IsPropertySettable(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioControlPropertyScope:\n        case kAudioControlPropertyElement:\n            theAnswer = false;\n            break;\n\n        default:\n            theAnswer = BGM_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32  BGM_Control::GetPropertyDataSize(AudioObjectID inObjectID,\n                                         pid_t inClientPID,\n                                         const AudioObjectPropertyAddress& inAddress,\n                                         UInt32 inQualifierDataSize,\n                                         const void* inQualifierData) const\n{\n    CheckObjectID(inObjectID);\n\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioControlPropertyScope:\n            theAnswer = sizeof(AudioObjectPropertyScope);\n            break;\n\n        case kAudioControlPropertyElement:\n            theAnswer = sizeof(AudioObjectPropertyElement);\n            break;\n\n        default:\n            theAnswer = BGM_Object::GetPropertyDataSize(inObjectID,\n                                                        inClientPID,\n                                                        inAddress,\n                                                        inQualifierDataSize,\n                                                        inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_Control::GetPropertyData(AudioObjectID inObjectID,\n                                     pid_t inClientPID,\n                                     const AudioObjectPropertyAddress& inAddress,\n                                     UInt32 inQualifierDataSize,\n                                     const void* inQualifierData,\n                                     UInt32 inDataSize,\n                                     UInt32& outDataSize,\n                                     void* outData) const\n{\n    CheckObjectID(inObjectID);\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioControlPropertyScope:\n            // This property returns the scope that the control is attached to.\n            ThrowIf(inDataSize < sizeof(AudioObjectPropertyScope),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Control::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioControlPropertyScope for the control\");\n            *reinterpret_cast<AudioObjectPropertyScope*>(outData) = mScope;\n            outDataSize = sizeof(AudioObjectPropertyScope);\n            break;\n\n        case kAudioControlPropertyElement:\n            // This property returns the element that the control is attached to.\n            ThrowIf(inDataSize < sizeof(AudioObjectPropertyElement),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Control::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioControlPropertyElement for the control\");\n            *reinterpret_cast<AudioObjectPropertyElement*>(outData) = mElement;\n            outDataSize = sizeof(AudioObjectPropertyElement);\n            break;\n\n        default:\n            BGM_Object::GetPropertyData(inObjectID,\n                                        inClientPID,\n                                        inAddress,\n                                        inQualifierDataSize,\n                                        inQualifierData,\n                                        inDataSize,\n                                        outDataSize,\n                                        outData);\n            break;\n    };\n}\n\nvoid    BGM_Control::CheckObjectID(AudioObjectID inObjectID) const\n{\n    ThrowIf(inObjectID == kAudioObjectUnknown || inObjectID != GetObjectID(),\n            CAException(kAudioHardwareBadObjectError),\n            \"BGM_Control::CheckObjectID: wrong audio object ID for the control\");\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Control.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Control.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  An AudioObject that represents a user-controllable aspect of a device or stream, such as volume\n//  or balance.\n//\n\n#ifndef BGMDriver__BGM_Control\n#define BGMDriver__BGM_Control\n\n// Superclass Includes\n#include \"BGM_Object.h\"\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_Control\n:\n    public BGM_Object\n{\n\nprotected:\n                        BGM_Control(AudioObjectID inObjectID,\n                                    AudioClassID inClassID,\n                                    AudioClassID inBaseClassID,\n                                    AudioObjectID inOwnerObjectID,\n                                    AudioObjectPropertyScope inScope =\n                                            kAudioObjectPropertyScopeOutput,\n                                    AudioObjectPropertyElement inElement =\n                                            kAudioObjectPropertyElementMaster);\n\n#pragma mark Property Operations\n\npublic:\n    virtual bool        HasProperty(AudioObjectID inObjectID,\n                                    pid_t inClientPID,\n                                    const AudioObjectPropertyAddress& inAddress) const;\n    virtual bool        IsPropertySettable(AudioObjectID inObjectID,\n                                           pid_t inClientPID,\n                                           const AudioObjectPropertyAddress& inAddress) const;\n    virtual UInt32      GetPropertyDataSize(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress,\n                                            UInt32 inQualifierDataSize,\n                                            const void* inQualifierData) const;\n    virtual void        GetPropertyData(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress,\n                                        UInt32 inQualifierDataSize,\n                                        const void* inQualifierData,\n                                        UInt32 inDataSize,\n                                        UInt32& outDataSize,\n                                        void* outData) const;\n\n#pragma mark Implementation\n\nprotected:\n    void                CheckObjectID(AudioObjectID inObjectID) const;\n\nprotected:\n    const AudioObjectPropertyScope      mScope;\n    const AudioObjectPropertyElement    mElement;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_Control */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Device.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Device.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017, 2019, 2025 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//  Copyright © 2019 Gordon Childs\n//  Copyright © 2020 Aleksey Yurkevich\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_Device.cpp from Apple's SimpleAudioDriver Plug-In sample code. Also uses a few sections from Apple's\n//  NullAudio.c sample code (found in the same sample project).\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n\n// Self Include\n#include \"BGM_Device.h\"\n\n// Local Includes\n#include \"BGM_PlugIn.h\"\n#include \"BGM_XPCHelper.h\"\n#include \"BGM_Utils.h\"\n\n// PublicUtility Includes\n#include \"CADispatchQueue.h\"\n#include \"CAException.h\"\n#include \"CACFArray.h\"\n#include \"CACFDictionary.h\"\n#include \"CACFString.h\"\n#include \"CADebugMacros.h\"\n#include \"CAHostTimeBase.h\"\n\n// STL Includes\n#include <stdexcept>\n\n// System Includes\n#include <CoreAudio/AudioHardwareBase.h>\n\n\n#pragma mark Construction/Destruction\n\npthread_once_t\t\t\t\tBGM_Device::sStaticInitializer = PTHREAD_ONCE_INIT;\nBGM_Device*\t\t\t\t\tBGM_Device::sInstance = nullptr;\nBGM_Device*\t\t\t\t\tBGM_Device::sUISoundsInstance = nullptr;\n\nBGM_Device&\tBGM_Device::GetInstance()\n{\n    pthread_once(&sStaticInitializer, StaticInitializer);\n    return *sInstance;\n}\n\nBGM_Device&\tBGM_Device::GetUISoundsInstance()\n{\n    pthread_once(&sStaticInitializer, StaticInitializer);\n    return *sUISoundsInstance;\n}\n\nvoid\tBGM_Device::StaticInitializer()\n{\n    try\n    {\n        // The main instance, usually referred to in the code as \"BGMDevice\". This is the device\n        // that appears in System Preferences as \"Background Music\".\n        sInstance = new BGM_Device(kObjectID_Device,\n                                   CFSTR(kDeviceName),\n\t\t\t\t\t\t\t\t   CFSTR(kBGMDeviceUID),\n\t\t\t\t\t\t\t\t   CFSTR(kBGMDeviceModelUID),\n                                   kObjectID_Stream_Input,\n                                   kObjectID_Stream_Output,\n\t\t\t\t\t\t\t\t   kObjectID_Volume_Output_Master,\n\t\t\t\t\t\t\t\t   kObjectID_Mute_Output_Master);\n        sInstance->Activate();\n\n        // The instance for system (UI) sounds.\n        sUISoundsInstance = new BGM_Device(kObjectID_Device_UI_Sounds,\n\t\t\t\t\t\t\t\t\t\t   CFSTR(kDeviceName_UISounds),\n\t\t\t\t\t\t\t\t\t\t   CFSTR(kBGMDeviceUID_UISounds),\n\t\t\t\t\t\t\t\t\t\t   CFSTR(kBGMDeviceModelUID_UISounds),\n                                           kObjectID_Stream_Input_UI_Sounds,\n                                           kObjectID_Stream_Output_UI_Sounds,\n                                           kObjectID_Volume_Output_Master_UI_Sounds,\n                                           kAudioObjectUnknown);  // No mute control.\n\n        // Set up the UI sounds device's volume control.\n        BGM_VolumeControl& theUISoundsVolumeControl = sUISoundsInstance->mVolumeControl;\n        // Default to full volume.\n        theUISoundsVolumeControl.SetVolumeScalar(1.0f);\n        // Make the volume curve a bit steeper than the default.\n        theUISoundsVolumeControl.GetVolumeCurve().SetTransferFunction(CAVolumeCurve::kPow4Over1Curve);\n        // Apply the volume to the device's output stream. The main instance of BGM_Device doesn't\n        // apply volume to its audio because BGMApp changes the real output device's volume directly\n        // instead.\n        theUISoundsVolumeControl.SetWillApplyVolumeToAudio(true);\n\n        sUISoundsInstance->Activate();\n    }\n    catch(...)\n    {\n        DebugMsg(\"BGM_Device::StaticInitializer: failed to create the devices\");\n\n        delete sInstance;\n        sInstance = nullptr;\n\n        delete sUISoundsInstance;\n        sUISoundsInstance = nullptr;\n    }\n}\n\nBGM_Device::BGM_Device(AudioObjectID inObjectID,\n\t\t\t\t\t   const CFStringRef __nonnull inDeviceName,\n\t\t\t\t\t   const CFStringRef __nonnull inDeviceUID,\n\t\t\t\t\t   const CFStringRef __nonnull inDeviceModelUID,\n                       AudioObjectID inInputStreamID,\n                       AudioObjectID inOutputStreamID,\n\t\t\t\t\t   AudioObjectID inOutputVolumeControlID,\n\t\t\t\t\t   AudioObjectID inOutputMuteControlID)\n:\n\tBGM_AbstractDevice(inObjectID, kAudioObjectPlugInObject),\n\tmStateMutex(\"Device State\"),\n\tmIOMutex(\"Device IO\"),\n\tmDeviceName(inDeviceName),\n\tmDeviceUID(inDeviceUID),\n\tmDeviceModelUID(inDeviceModelUID),\n    mWrappedAudioEngine(nullptr),\n    mClients(inObjectID, &mTaskQueue),\n    mInputStream(inInputStreamID, inObjectID, false, kSampleRateDefault),\n    mOutputStream(inOutputStreamID, inObjectID, false, kSampleRateDefault),\n    mAudibleState(),\n    mVolumeControl(inOutputVolumeControlID, GetObjectID()),\n    mMuteControl(inOutputMuteControlID, GetObjectID())\n{\n    // Initialises the loopback clock with the default sample rate and, if there is one, sets the wrapped device to the same sample rate\n    SetSampleRate(kSampleRateDefault, true);\n}\n\nBGM_Device::~BGM_Device()\n{\n}\n\nvoid\tBGM_Device::Activate()\n{\n\tCAMutex::Locker theStateLocker(mStateMutex);\n\n\t//\tOpen the connection to the driver and initialize things.\n\t//_HW_Open();\n\n\tmInputStream.Activate();\n\tmOutputStream.Activate();\n\n\tif(mVolumeControl.GetObjectID() != kAudioObjectUnknown)\n\t{\n\t\tmVolumeControl.Activate();\n\t}\n\n    if(mMuteControl.GetObjectID() != kAudioObjectUnknown)\n\t{\n\t\tmMuteControl.Activate();\n\t}\n\t\n\t//\tCall the super-class, which just marks the object as active\n\tBGM_AbstractDevice::Activate();\n}\n\nvoid\tBGM_Device::Deactivate()\n{\n\t//\tWhen this method is called, the object is basically dead, but we still need to be thread\n\t//\tsafe. In this case, we also need to be safe vs. any IO threads, so we need to take both\n\t//\tlocks.\n\tCAMutex::Locker theStateLocker(mStateMutex);\n\tCAMutex::Locker theIOLocker(mIOMutex);\n\n    // Mark the device's sub-objects inactive.\n\tmInputStream.Deactivate();\n\tmOutputStream.Deactivate();\n    mVolumeControl.Deactivate();\n    mMuteControl.Deactivate();\n\n\t//\tmark the object inactive by calling the super-class\n\tBGM_AbstractDevice::Deactivate();\n\t\n\t//\tclose the connection to the driver\n\t//_HW_Close();\n}\n\nvoid    BGM_Device::InitLoopback()\n{\n    // Calculate the number of host clock ticks per frame for our loopback clock.\n    mLoopbackTime.hostTicksPerFrame = CAHostTimeBase::GetFrequency() / mLoopbackSampleRate;\n    \n    //  Allocate (or re-allocate) the loopback buffer.\n    //  2 channels * 32-bit float = bytes in each frame\n    //  Pass 1 for nChannels because it's going to be storing interleaved audio, which means we\n    //  don't need a separate buffer for each channel.\n\tmLoopbackRingBuffer.Allocate(1, 2 * sizeof(Float32), kLoopbackRingBufferFrameSize);\n}\n\n#pragma mark Property Operations\n\nbool\tBGM_Device::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\t//\tThis object owns several API-level objects. So the first thing to do is to figure out\n\t//\twhich object this request is really for. Note that mObjectID is an invariant as this\n\t//\tdriver's structure does not change dynamically. It will always have the parts it has.\n\tbool theAnswer = false;\n\n\tif(inObjectID == mObjectID)\n\t{\n\t\ttheAnswer = Device_HasProperty(inObjectID, inClientPID, inAddress);\n\t}\n    else\n\t{\n\t\ttheAnswer = GetOwnedObjectByID(inObjectID).HasProperty(inObjectID, inClientPID, inAddress);\n\t}\n\n\treturn theAnswer;\n}\n\nbool\tBGM_Device::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\n\tif(inObjectID == mObjectID)\n\t{\n\t\ttheAnswer = Device_IsPropertySettable(inObjectID, inClientPID, inAddress);\n\t}\n\telse\n\t{\n\t\ttheAnswer = GetOwnedObjectByID(inObjectID).IsPropertySettable(inObjectID, inClientPID, inAddress);\n\t}\n\n\treturn theAnswer;\n}\n\nUInt32\tBGM_Device::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n\tUInt32 theAnswer = 0;\n\n\tif(inObjectID == mObjectID)\n\t{\n\t\ttheAnswer = Device_GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData);\n\t}\n\telse\n\t{\n\t\ttheAnswer = GetOwnedObjectByID(inObjectID).GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData);\n\t}\n\n\treturn theAnswer;\n}\n\nvoid\tBGM_Device::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const\n{\n    ThrowIfNULL(outData, std::runtime_error(\"!outData\"), \"BGM_Device::GetPropertyData: !outData\");\n    \n\tif(inObjectID == mObjectID)\n\t{\n\t\tDevice_GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);\n\t}\n\telse\n\t{\n\t\tGetOwnedObjectByID(inObjectID).GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);\n\t}\n}\n\nvoid\tBGM_Device::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n    ThrowIfNULL(inData, std::runtime_error(\"no data\"), \"BGM_Device::SetPropertyData: no data\");\n    \n\tif(inObjectID == mObjectID)\n\t{\n\t\tDevice_SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);\n\t}\n\telse\n    {\n        GetOwnedObjectByID(inObjectID).SetPropertyData(inObjectID,\n                                                       inClientPID,\n                                                       inAddress,\n                                                       inQualifierDataSize,\n                                                       inQualifierData,\n                                                       inDataSize,\n                                                       inData);\n\t\tif(IsStreamID(inObjectID))\n\t\t{\n            // When one of the stream's sample rate changes, set the new sample rate for both\n            // streams and the device. The streams check the new format before this point but don't\n            // change until the device tells them to, as it has to get the host to pause IO first.\n            if(inAddress.mSelector == kAudioStreamPropertyVirtualFormat ||\n               inAddress.mSelector == kAudioStreamPropertyPhysicalFormat)\n            {\n                const AudioStreamBasicDescription* theNewFormat =\n                    reinterpret_cast<const AudioStreamBasicDescription*>(inData);\n                RequestSampleRate(theNewFormat->mSampleRate);\n            }\n\t\t}\n\t}\n}\n\n#pragma mark Device Property Operations\n\nbool\tBGM_Device::Device_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\t//\tFor each object, this driver implements all the required properties plus a few extras that\n\t//\tare useful but not required. There is more detailed commentary about each property in the\n\t//\tDevice_GetPropertyData() method.\n\t\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n\t{\n        case kAudioDevicePropertyStreams:\n        case kAudioDevicePropertyIcon:\n        case kAudioObjectPropertyCustomPropertyInfoList:\n        case kAudioDeviceCustomPropertyDeviceAudibleState:\n        case kAudioDeviceCustomPropertyMusicPlayerProcessID:\n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n        case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:\n        case kAudioDeviceCustomPropertyAppVolumes:\n        case kAudioDeviceCustomPropertyEnabledOutputControls:\n\t\t\ttheAnswer = true;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioDevicePropertyLatency:\n\t\tcase kAudioDevicePropertySafetyOffset:\n\t\tcase kAudioDevicePropertyPreferredChannelsForStereo:\n\t\tcase kAudioDevicePropertyPreferredChannelLayout:\n\t\tcase kAudioDevicePropertyDeviceCanBeDefaultDevice:\n\t\tcase kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n\t\t\ttheAnswer = (inAddress.mScope == kAudioObjectPropertyScopeInput) || (inAddress.mScope == kAudioObjectPropertyScopeOutput);\n\t\t\tbreak;\n\t\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_AbstractDevice::HasProperty(inObjectID, inClientPID, inAddress);\n\t\t\tbreak;\n\t};\n\treturn theAnswer;\n}\n\nbool\tBGM_Device::Device_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\t//\tFor each object, this driver implements all the required properties plus a few extras that\n\t//\tare useful but not required. There is more detailed commentary about each property in the\n\t//\tDevice_GetPropertyData() method.\n\t\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n    {\n\t\tcase kAudioDevicePropertyStreams:\n\t\tcase kAudioDevicePropertyLatency:\n\t\tcase kAudioDevicePropertySafetyOffset:\n        case kAudioDevicePropertyPreferredChannelsForStereo:\n        case kAudioDevicePropertyPreferredChannelLayout:\n\t\tcase kAudioDevicePropertyDeviceCanBeDefaultDevice:\n\t\tcase kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n        case kAudioDevicePropertyIcon:\n        case kAudioObjectPropertyCustomPropertyInfoList:\n        case kAudioDeviceCustomPropertyDeviceAudibleState:\n        case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:\n\t\t\ttheAnswer = false;\n\t\t\tbreak;\n            \n        case kAudioDevicePropertyNominalSampleRate:\n        case kAudioDeviceCustomPropertyMusicPlayerProcessID:\n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n        case kAudioDeviceCustomPropertyAppVolumes:\n        case kAudioDeviceCustomPropertyEnabledOutputControls:\n\t\t\ttheAnswer = true;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_AbstractDevice::IsPropertySettable(inObjectID, inClientPID, inAddress);\n\t\t\tbreak;\n\t};\n\treturn theAnswer;\n}\n\nUInt32\tBGM_Device::Device_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n\t//\tFor each object, this driver implements all the required properties plus a few extras that\n\t//\tare useful but not required. There is more detailed commentary about each property in the\n\t//\tDevice_GetPropertyData() method.\n\t\n\tUInt32 theAnswer = 0;\n\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyOwnedObjects:\n            {\n                switch(inAddress.mScope)\n                {\n                    case kAudioObjectPropertyScopeGlobal:\n                        theAnswer = GetNumberOfSubObjects() * sizeof(AudioObjectID);\n                        break;\n                        \n                    case kAudioObjectPropertyScopeInput:\n                        theAnswer = kNumberOfInputSubObjects * sizeof(AudioObjectID);\n                        break;\n                        \n                    case kAudioObjectPropertyScopeOutput:\n                        theAnswer = kNumberOfOutputStreams * sizeof(AudioObjectID);\n                        theAnswer += GetNumberOfOutputControls() * sizeof(AudioObjectID);\n                        break;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n                };\n            }\n\t\t\tbreak;\n\n        case kAudioDevicePropertyStreams:\n            {\n                switch(inAddress.mScope)\n                {\n                    case kAudioObjectPropertyScopeGlobal:\n                        theAnswer = kNumberOfStreams * sizeof(AudioObjectID);\n                        break;\n                        \n                    case kAudioObjectPropertyScopeInput:\n                        theAnswer = kNumberOfInputStreams * sizeof(AudioObjectID);\n                        break;\n                        \n                    case kAudioObjectPropertyScopeOutput:\n                        theAnswer = kNumberOfOutputStreams * sizeof(AudioObjectID);\n                        break;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n                };\n            }\n\t\t\tbreak;\n\n        case kAudioObjectPropertyControlList:\n            theAnswer = GetNumberOfOutputControls() * sizeof(AudioObjectID);\n            break;\n\n\t\tcase kAudioDevicePropertyAvailableNominalSampleRates:\n\t\t\ttheAnswer = 1 * sizeof(AudioValueRange);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyPreferredChannelsForStereo:\n\t\t\ttheAnswer = 2 * sizeof(UInt32);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyPreferredChannelLayout:\n\t\t\ttheAnswer = offsetof(AudioChannelLayout, mChannelDescriptions) + (2 * sizeof(AudioChannelDescription));\n\t\t\tbreak;\n\n        case kAudioDevicePropertyIcon:\n            theAnswer = sizeof(CFURLRef);\n            break;\n            \n        case kAudioObjectPropertyCustomPropertyInfoList:\n            theAnswer = sizeof(AudioServerPlugInCustomPropertyInfo) * 6;\n            break;\n            \n        case kAudioDeviceCustomPropertyDeviceAudibleState:\n            theAnswer = sizeof(CFNumberRef);\n            break;\n\n        case kAudioDeviceCustomPropertyMusicPlayerProcessID:\n            theAnswer = sizeof(CFPropertyListRef);\n\t\t\tbreak;\n            \n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n            theAnswer = sizeof(CFStringRef);\n            break;\n            \n        case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:\n            theAnswer = sizeof(CFBooleanRef);\n            break;\n            \n        case kAudioDeviceCustomPropertyAppVolumes:\n            theAnswer = sizeof(CFPropertyListRef);\n            break;\n\n        case kAudioDeviceCustomPropertyEnabledOutputControls:\n            theAnswer = sizeof(CFArrayRef);\n            break;\n\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_AbstractDevice::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData);\n\t\t\tbreak;\n\t};\n\n\treturn theAnswer;\n}\n\nvoid\tBGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const\n{\n\t//\tFor each object, this driver implements all the required properties plus a few extras that\n\t//\tare useful but not required.\n\t//\tAlso, since most of the data that will get returned is static, there are few instances where\n\t//\tit is necessary to lock the state mutex.\n\n\tUInt32 theNumberItemsToFetch;\n\tUInt32 theItemIndex;\n\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyName:\n\t\t\t//\tThis is the human readable name of the device. Note that in this case we return a\n\t\t\t//\tvalue that is a key into the localizable strings in this bundle. This allows us to\n\t\t\t//\treturn a localized name for the device.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioObjectPropertyName for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = mDeviceName;\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyManufacturer:\n\t\t\t//\tThis is the human readable name of the maker of the plug-in. Note that in this case\n\t\t\t//\twe return a value that is a key into the localizable strings in this bundle. This\n\t\t\t//\tallows us to return a localized name for the manufacturer.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioObjectPropertyManufacturer for the device\");\n\t\t\t*reinterpret_cast<CFStringRef*>(outData) = CFSTR(kDeviceManufacturerName);\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\t\t//\tCalculate the number of items that have been requested. Note that this\n\t\t\t//\tnumber is allowed to be smaller than the actual size of the list. In such\n\t\t\t//\tcase, only that number of items will be returned\n\t\t\ttheNumberItemsToFetch = inDataSize / sizeof(AudioObjectID);\n\t\t\t\n\t\t\t//\tThe device owns its streams and controls. Note that what is returned here\n\t\t\t//\tdepends on the scope requested.\n\t\t\tswitch(inAddress.mScope)\n\t\t\t{\n\t\t\t\tcase kAudioObjectPropertyScopeGlobal:\n\t\t\t\t\t//\tglobal scope means return all objects\n                    {\n                        CAMutex::Locker theStateLocker(mStateMutex);\n\n                        if(theNumberItemsToFetch > GetNumberOfSubObjects())\n                        {\n                            theNumberItemsToFetch = GetNumberOfSubObjects();\n                        }\n\n                        //\tfill out the list with as many objects as requested, which is everything\n                        if(theNumberItemsToFetch > 0)\n                        {\n                            reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStream.GetObjectID();\n                        }\n\n                        if(theNumberItemsToFetch > 1)\n                        {\n                            reinterpret_cast<AudioObjectID*>(outData)[1] = mOutputStream.GetObjectID();\n                        }\n\n                        // If at least one of the controls is enabled, and there's room, return one.\n\t\t\t\t\t\tif(theNumberItemsToFetch > 2)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif(mVolumeControl.IsActive())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[2] = mVolumeControl.GetObjectID();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if(mMuteControl.IsActive())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[2] = mMuteControl.GetObjectID();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If both controls are enabled, and there's room, return the mute control as well.\n                        if(theNumberItemsToFetch > 3 && mVolumeControl.IsActive() && mMuteControl.IsActive())\n                        {\n\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[3] = mMuteControl.GetObjectID();\n                        }\n                    }\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n\t\t\t\tcase kAudioObjectPropertyScopeInput:\n\t\t\t\t\t//\tinput scope means just the objects on the input side\n\t\t\t\t\tif(theNumberItemsToFetch > kNumberOfInputSubObjects)\n\t\t\t\t\t{\n\t\t\t\t\t\ttheNumberItemsToFetch = kNumberOfInputSubObjects;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t//\tfill out the list with the right objects\n\t\t\t\t\tif(theNumberItemsToFetch > 0)\n\t\t\t\t\t{\n                        reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStream.GetObjectID();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n\t\t\t\tcase kAudioObjectPropertyScopeOutput:\n\t\t\t\t\t//\toutput scope means just the objects on the output side\n                    {\n                        CAMutex::Locker theStateLocker(mStateMutex);\n\n                        if(theNumberItemsToFetch > GetNumberOfOutputControls())\n                        {\n                            theNumberItemsToFetch = GetNumberOfOutputControls();\n                        }\n\n                        //\tfill out the list with the right objects\n                        if(theNumberItemsToFetch > 0)\n                        {\n                            reinterpret_cast<AudioObjectID*>(outData)[0] = mOutputStream.GetObjectID();\n                        }\n\n\t\t\t\t\t\t// If at least one of the controls is enabled, and there's room, return one.\n\t\t\t\t\t\tif(theNumberItemsToFetch > 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif(mVolumeControl.IsActive())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[1] = mVolumeControl.GetObjectID();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if(mMuteControl.IsActive())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[1] = mMuteControl.GetObjectID();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If both controls are enabled, and there's room, return the mute control as well.\n\t\t\t\t\t\tif(theNumberItemsToFetch > 2 && mVolumeControl.IsActive() && mMuteControl.IsActive())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[2] = mMuteControl.GetObjectID();\n\t\t\t\t\t\t}\n                    }\n\t\t\t\t\tbreak;\n\t\t\t};\n\t\t\t\n\t\t\t//\treport how much we wrote\n\t\t\toutDataSize = theNumberItemsToFetch * sizeof(AudioObjectID);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyDeviceUID:\n\t\t\t//\tThis is a CFString that is a persistent token that can identify the same\n\t\t\t//\taudio device across boot sessions. Note that two instances of the same\n\t\t\t//\tdevice must have different values for this property.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceUID for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = mDeviceUID;\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyModelUID:\n\t\t\t//\tThis is a CFString that is a persistent token that can identify audio\n\t\t\t//\tdevices that are the same kind of device. Note that two instances of the\n\t\t\t//\tsave device must have the same value for this property.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyModelUID for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = mDeviceModelUID;\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n            \n\t\tcase kAudioDevicePropertyDeviceIsRunning:\n\t\t\t//\tThis property returns whether or not IO is running for the device.\n            ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceIsRunning for the device\");\n            *reinterpret_cast<UInt32*>(outData) = mClients.ClientsRunningIO() ? 1 : 0;\n            outDataSize = sizeof(UInt32);\n\t\t\tbreak;\n\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n            // See BGM_AbstractDevice::GetPropertyData.\n\t\t\t//\n\t\t\t// We don't allow the UI Sounds instance of BGM_Device to be set as the default device\n\t\t\t// so that it doesn't appear in the list of devices, which would just be confusing to\n\t\t\t// users. (And it wouldn't make sense to set it as the default device anyway.)\n\t\t\t//\n\t\t\t// Instead, BGMApp sets the UI Sounds device as the \"system default\" (see\n\t\t\t// kAudioDevicePropertyDeviceCanBeDefaultSystemDevice) so apps will use it for\n\t\t\t// UI-related sounds.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Device::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceCanBeDefaultDevice for the device\");\n            // TODO: Add a field for this and set it in BGM_Device::StaticInitializer so we don't\n            //       have to handle a specific instance differently here.\n            *reinterpret_cast<UInt32*>(outData) = (GetObjectID() == kObjectID_Device_UI_Sounds ? 0 : 1);\n            outDataSize = sizeof(UInt32);\n            break;\n\n\t\tcase kAudioDevicePropertyStreams:\n\t\t\t//\tCalculate the number of items that have been requested. Note that this\n\t\t\t//\tnumber is allowed to be smaller than the actual size of the list. In such\n\t\t\t//\tcase, only that number of items will be returned\n\t\t\ttheNumberItemsToFetch = inDataSize / sizeof(AudioObjectID);\n\t\t\t\n\t\t\t//\tNote that what is returned here depends on the scope requested.\n\t\t\tswitch(inAddress.mScope)\n\t\t\t{\n\t\t\t\tcase kAudioObjectPropertyScopeGlobal:\n\t\t\t\t\t//\tglobal scope means return all streams\n\t\t\t\t\tif(theNumberItemsToFetch > kNumberOfStreams)\n\t\t\t\t\t{\n\t\t\t\t\t\ttheNumberItemsToFetch = kNumberOfStreams;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t//\tfill out the list with as many objects as requested\n\t\t\t\t\tif(theNumberItemsToFetch > 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[0] = mInputStream.GetObjectID();\n\t\t\t\t\t}\n\t\t\t\t\tif(theNumberItemsToFetch > 1)\n\t\t\t\t\t{\n\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[1] = mOutputStream.GetObjectID();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n\t\t\t\tcase kAudioObjectPropertyScopeInput:\n\t\t\t\t\t//\tinput scope means just the objects on the input side\n\t\t\t\t\tif(theNumberItemsToFetch > kNumberOfInputStreams)\n\t\t\t\t\t{\n\t\t\t\t\t\ttheNumberItemsToFetch = kNumberOfInputStreams;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t//\tfill out the list with as many objects as requested\n\t\t\t\t\tif(theNumberItemsToFetch > 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[0] = mInputStream.GetObjectID();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n\t\t\t\tcase kAudioObjectPropertyScopeOutput:\n\t\t\t\t\t//\toutput scope means just the objects on the output side\n\t\t\t\t\tif(theNumberItemsToFetch > kNumberOfOutputStreams)\n\t\t\t\t\t{\n\t\t\t\t\t\ttheNumberItemsToFetch = kNumberOfOutputStreams;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t//\tfill out the list with as many objects as requested\n\t\t\t\t\tif(theNumberItemsToFetch > 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treinterpret_cast<AudioObjectID*>(outData)[0] = mOutputStream.GetObjectID();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t};\n\t\t\t\n\t\t\t//\treport how much we wrote\n\t\t\toutDataSize = theNumberItemsToFetch * sizeof(AudioObjectID);\n\t\t\tbreak;\n\n\t\tcase kAudioObjectPropertyControlList:\n            {\n                //\tCalculate the number of items that have been requested. Note that this\n                //\tnumber is allowed to be smaller than the actual size of the list, in which\n                //\tcase only that many items will be returned.\n                theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID);\n                if(theNumberItemsToFetch > 2)\n                {\n                    theNumberItemsToFetch = 2;\n                }\n\n                UInt32 theNumberOfItemsFetched = 0;\n\n                CAMutex::Locker theStateLocker(mStateMutex);\n                \n                //\tfill out the list with as many objects as requested\n                if(theNumberItemsToFetch > 0)\n                {\n\t\t\t\t\tif(mVolumeControl.IsActive())\n                    {\n                        reinterpret_cast<AudioObjectID*>(outData)[0] = mVolumeControl.GetObjectID();\n                        theNumberOfItemsFetched++;\n                    }\n                    else if(mMuteControl.IsActive())\n                    {\n                        reinterpret_cast<AudioObjectID*>(outData)[0] = mMuteControl.GetObjectID();\n                        theNumberOfItemsFetched++;\n                    }\n                }\n\n                if(theNumberItemsToFetch > 1 && mVolumeControl.IsActive() && mMuteControl.IsActive())\n                {\n                    reinterpret_cast<AudioObjectID*>(outData)[1] = mMuteControl.GetObjectID();\n                    theNumberOfItemsFetched++;\n                }\n                \n                //\treport how much we wrote\n                outDataSize = theNumberOfItemsFetched * sizeof(AudioObjectID);\n            }\n\t\t\tbreak;\n\n        // TODO: Should we return the real kAudioDevicePropertyLatency and/or\n        //       kAudioDevicePropertySafetyOffset for the real/wrapped output device?\n        //       If so, should we also add on the extra latency added by Background Music? \n\n\t\tcase kAudioDevicePropertyNominalSampleRate:\n\t\t\t//\tThis property returns the nominal sample rate of the device.\n            ThrowIf(inDataSize < sizeof(Float64),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyNominalSampleRate for the device\");\n\n            *reinterpret_cast<Float64*>(outData) = GetSampleRate();\n            outDataSize = sizeof(Float64);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyAvailableNominalSampleRates:\n\t\t\t//\tThis returns all nominal sample rates the device supports as an array of\n\t\t\t//\tAudioValueRangeStructs. Note that for discrete sampler rates, the range\n\t\t\t//\twill have the minimum value equal to the maximum value.\n            //\n            //  BGMDevice supports any sample rate so it can be set to match the output\n            //  device when in loopback mode.\n\t\t\t\n\t\t\t//\tCalculate the number of items that have been requested. Note that this\n\t\t\t//\tnumber is allowed to be smaller than the actual size of the list. In such\n\t\t\t//\tcase, only that number of items will be returned\n\t\t\ttheNumberItemsToFetch = inDataSize / sizeof(AudioValueRange);\n\t\t\t\n\t\t\t//\tclamp it to the number of items we have\n\t\t\tif(theNumberItemsToFetch > 1)\n\t\t\t{\n\t\t\t\ttheNumberItemsToFetch = 1;\n\t\t\t}\n\t\t\t\n\t\t\t//\tfill out the return array\n\t\t\tif(theNumberItemsToFetch > 0)\n\t\t\t{\n                // 0 would cause divide-by-zero errors in other BGM_Device functions (and\n                // wouldn't make sense anyway).\n                ((AudioValueRange*)outData)[0].mMinimum = 1.0;\n                // Just in case DBL_MAX would cause problems in a client for some reason,\n                // use an arbitrary very large number instead. (It wouldn't make sense to\n                // actually set the sample rate this high, but I don't know what a\n                // reasonable maximum would be.)\n                ((AudioValueRange*)outData)[0].mMaximum = 1000000000.0;\n\t\t\t}\n\t\t\t\n\t\t\t//\treport how much we wrote\n\t\t\toutDataSize = theNumberItemsToFetch * sizeof(AudioValueRange);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyPreferredChannelsForStereo:\n\t\t\t//\tThis property returns which two channels to use as left/right for stereo\n\t\t\t//\tdata by default. Note that the channel numbers are 1-based.\n\t\t\tThrowIf(inDataSize < (2 * sizeof(UInt32)), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyPreferredChannelsForStereo for the device\");\n\t\t\t((UInt32*)outData)[0] = 1;\n\t\t\t((UInt32*)outData)[1] = 2;\n\t\t\toutDataSize = 2 * sizeof(UInt32);\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyPreferredChannelLayout:\n\t\t\t//\tThis property returns the default AudioChannelLayout to use for the device\n\t\t\t//\tby default. For this device, we return a stereo ACL.\n\t\t\t{\n\t\t\t\tUInt32 theACLSize = offsetof(AudioChannelLayout, mChannelDescriptions) + (2 * sizeof(AudioChannelDescription));\n\t\t\t\tThrowIf(inDataSize < theACLSize, CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyPreferredChannelLayout for the device\");\n\t\t\t\t((AudioChannelLayout*)outData)->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;\n\t\t\t\t((AudioChannelLayout*)outData)->mChannelBitmap = 0;\n\t\t\t\t((AudioChannelLayout*)outData)->mNumberChannelDescriptions = 2;\n\t\t\t\tfor(theItemIndex = 0; theItemIndex < 2; ++theItemIndex)\n\t\t\t\t{\n\t\t\t\t\t((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mChannelLabel = kAudioChannelLabel_Left + theItemIndex;\n\t\t\t\t\t((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mChannelFlags = 0;\n\t\t\t\t\t((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[0] = 0;\n\t\t\t\t\t((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[1] = 0;\n\t\t\t\t\t((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[2] = 0;\n\t\t\t\t}\n\t\t\t\toutDataSize = theACLSize;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase kAudioDevicePropertyZeroTimeStampPeriod:\n\t\t\t//\tThis property returns how many frames the HAL should expect to see between\n\t\t\t//\tsuccessive sample times in the zero time stamps this device provides.\n\t\t\tThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyZeroTimeStampPeriod for the device\");\n\t\t\t*reinterpret_cast<UInt32*>(outData) = kLoopbackRingBufferFrameSize;\n\t\t\toutDataSize = sizeof(UInt32);\n            break;\n            \n        case kAudioDevicePropertyIcon:\n            {\n                // This property is a CFURL that points to the device's icon in the plugin's resource bundle\n                ThrowIf(inDataSize < sizeof(CFURLRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyIcon for the device\");\n                \n                CFBundleRef theBundle = CFBundleGetBundleWithIdentifier(BGM_PlugIn::GetInstance().GetBundleID());\n                ThrowIf(theBundle == NULL, CAException(kAudioHardwareUnspecifiedError), \"BGM_Device::Device_GetPropertyData: could not get the plugin bundle for kAudioDevicePropertyIcon\");\n                \n                CFURLRef theURL = CFBundleCopyResourceURL(theBundle, CFSTR(\"DeviceIcon.icns\"), NULL, NULL);\n                ThrowIf(theURL == NULL, CAException(kAudioHardwareUnspecifiedError), \"BGM_Device::Device_GetPropertyData: could not get the URL for kAudioDevicePropertyIcon\");\n                \n                *reinterpret_cast<CFURLRef*>(outData) = theURL;\n                outDataSize = sizeof(CFURLRef);\n            }\n            break;\n            \n        case kAudioObjectPropertyCustomPropertyInfoList:\n            theNumberItemsToFetch = inDataSize / sizeof(AudioServerPlugInCustomPropertyInfo);\n            \n            //\tclamp it to the number of items we have\n            if(theNumberItemsToFetch > 6)\n            {\n                theNumberItemsToFetch = 6;\n            }\n            \n            if(theNumberItemsToFetch > 0)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[0].mSelector = kAudioDeviceCustomPropertyAppVolumes;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[0].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[0].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n            if(theNumberItemsToFetch > 1)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[1].mSelector = kAudioDeviceCustomPropertyMusicPlayerProcessID;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[1].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[1].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n            if(theNumberItemsToFetch > 2)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[2].mSelector = kAudioDeviceCustomPropertyMusicPlayerBundleID;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[2].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFString;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[2].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n            if(theNumberItemsToFetch > 3)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[3].mSelector = kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[3].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[3].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n            if(theNumberItemsToFetch > 4)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[4].mSelector = kAudioDeviceCustomPropertyDeviceAudibleState;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[4].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[4].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n            if(theNumberItemsToFetch > 5)\n            {\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[5].mSelector = kAudioDeviceCustomPropertyEnabledOutputControls;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[5].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                ((AudioServerPlugInCustomPropertyInfo*)outData)[5].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;\n            }\n\n            outDataSize = theNumberItemsToFetch * sizeof(AudioServerPlugInCustomPropertyInfo);\n            break;\n            \n        case kAudioDeviceCustomPropertyDeviceAudibleState:\n            {\n                ThrowIf(inDataSize < sizeof(CFNumberRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyDeviceAudibleState for the device\");\n\n                // The audible state is read without locking to avoid priority inversions on the IO threads.\n                BGMDeviceAudibleState theAudibleState = mAudibleState.GetState();\n                *reinterpret_cast<CFNumberRef*>(outData) =\n                        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &theAudibleState);\n                outDataSize = sizeof(CFNumberRef);\n            }\n            break;\n            \n        case kAudioDeviceCustomPropertyMusicPlayerProcessID:\n            {\n                ThrowIf(inDataSize < sizeof(CFNumberRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyMusicPlayerProcessID for the device\");\n                CAMutex::Locker theStateLocker(mStateMutex);\n                pid_t pid = mClients.GetMusicPlayerProcessIDProperty();\n                *reinterpret_cast<CFNumberRef*>(outData) = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);\n                outDataSize = sizeof(CFNumberRef);\n            }\n            break;\n            \n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n            {\n                ThrowIf(inDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyMusicPlayerBundleID for the device\");\n                CAMutex::Locker theStateLocker(mStateMutex);\n                *reinterpret_cast<CFStringRef*>(outData) = mClients.CopyMusicPlayerBundleIDProperty();\n                outDataSize = sizeof(CFStringRef);\n            }\n            break;\n            \n        case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:\n            ThrowIf(inDataSize < sizeof(CFBooleanRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp for the device\");\n            *reinterpret_cast<CFBooleanRef*>(outData) = mClients.ClientsOtherThanBGMAppRunningIO() ? kCFBooleanTrue : kCFBooleanFalse;\n            outDataSize = sizeof(CFBooleanRef);\n            break;\n            \n        case kAudioDeviceCustomPropertyAppVolumes:\n            {\n                ThrowIf(inDataSize < sizeof(CFArrayRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyAppVolumes for the device\");\n                CAMutex::Locker theStateLocker(mStateMutex);\n                *reinterpret_cast<CFArrayRef*>(outData) = mClients.CopyClientRelativeVolumesAsAppVolumes().GetCFArray();\n                outDataSize = sizeof(CFArrayRef);\n            }\n            break;\n\n        case kAudioDeviceCustomPropertyEnabledOutputControls:\n            {\n                ThrowIf(inDataSize < sizeof(CFArrayRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyEnabledOutputControls for the device\");\n                CACFArray theEnabledControls(2, true);\n\n\t\t\t\t{\n\t\t\t\t\tCAMutex::Locker theStateLocker(mStateMutex);\n\t\t\t\t\ttheEnabledControls.AppendCFType(mVolumeControl.IsActive() ? kCFBooleanTrue : kCFBooleanFalse);\n\t\t\t\t\ttheEnabledControls.AppendCFType(mMuteControl.IsActive() ? kCFBooleanTrue : kCFBooleanFalse);\n\t\t\t\t}\n\n                *reinterpret_cast<CFArrayRef*>(outData) = theEnabledControls.CopyCFArray();\n                outDataSize = sizeof(CFArrayRef);\n            }\n            break;\n\n\t\tdefault:\n\t\t\tBGM_AbstractDevice::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);\n\t\t\tbreak;\n\t};\n}\n\n// Validates inAppVolumes for the kAudioDeviceCustomPropertyAppVolumes property as described in\n// BGM_Types.h. Throws CAException(kAudioHardwareIllegalOperationError) if invalid.\nstatic void ValidateAppVolumesProperty(const CACFArray& inAppVolumes)\n{\n    UInt32 theCount = inAppVolumes.GetNumberItems();\n\n    for(UInt32 i = 0; i < theCount; i++)\n    {\n        // Each element must be a CFDictionary.\n        CFTypeRef theElement = nullptr;\n        bool didGetValue = inAppVolumes.GetCFType(i, theElement);\n        ThrowIf(!didGetValue || theElement == nullptr,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGM_Device::ValidateAppVolumesProperty: Could not get element from array\");\n        ThrowIf(CFGetTypeID(theElement) != CFDictionaryGetTypeID(),\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGM_Device::ValidateAppVolumesProperty: Element is not a CFDictionary\");\n\n        CACFDictionary theDict(static_cast<CFDictionaryRef>(theElement), false);\n\n        // Check for ProcessID. Must be a CFNumber if present.\n        CFTypeRef thePIDValue = nullptr;\n        bool hasPID = theDict.GetCFType(CFSTR(kBGMAppVolumesKey_ProcessID), thePIDValue);\n        if(hasPID && thePIDValue != nullptr)\n        {\n            ThrowIf(CFGetTypeID(thePIDValue) != CFNumberGetTypeID(),\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_Device::ValidateAppVolumesProperty: ProcessID is not a CFNumber\");\n        }\n\n        // Check for BundleID. Must be a CFString if present.\n        CFTypeRef theBundleIDValue = nullptr;\n        bool hasBundleID = theDict.GetCFType(CFSTR(kBGMAppVolumesKey_BundleID), theBundleIDValue);\n        if(hasBundleID && theBundleIDValue != nullptr)\n        {\n            ThrowIf(CFGetTypeID(theBundleIDValue) != CFStringGetTypeID(),\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_Device::ValidateAppVolumesProperty: BundleID is not a CFString\");\n        }\n\n        // At least one of ProcessID or BundleID must be present.\n        ThrowIf(!hasPID && !hasBundleID,\n                CAException(kAudioHardwareIllegalOperationError),\n                \"BGM_Device::ValidateAppVolumesProperty: Neither ProcessID nor BundleID present\");\n\n        // Check RelativeVolume. Must be a CFNumber in [0, 100] if present.\n        SInt32 theVolume;\n        bool hasVolume = theDict.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theVolume);\n        if(hasVolume)\n        {\n            ThrowIf(theVolume < kAppRelativeVolumeMinRawValue ||\n                    theVolume > kAppRelativeVolumeMaxRawValue,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_Device::ValidateAppVolumesProperty: RelativeVolume out of range\");\n        }\n\n        // Check PanPosition. Must be a CFNumber in [-100, 100] if present.\n        SInt32 thePan;\n        bool hasPan = theDict.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePan);\n        if(hasPan)\n        {\n            ThrowIf(thePan < kAppPanLeftRawValue || thePan > kAppPanRightRawValue,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_Device::ValidateAppVolumesProperty: PanPosition out of range\");\n        }\n    }\n}\n\nvoid\tBGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n\tswitch(inAddress.mSelector)\n\t{\n        case kAudioDevicePropertyNominalSampleRate:\n            ThrowIf(inDataSize < sizeof(Float64),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDevicePropertyNominalSampleRate\");\n            RequestSampleRate(*reinterpret_cast<const Float64*>(inData));\n            break;\n            \n        case kAudioDeviceCustomPropertyMusicPlayerProcessID:\n            {\n                ThrowIf(inDataSize < sizeof(CFNumberRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDeviceCustomPropertyMusicPlayerProcessID\");\n                \n                CFNumberRef pidRef = *reinterpret_cast<const CFNumberRef*>(inData);\n                \n                ThrowIf(pidRef == NULL, CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: null reference given for kAudioDeviceCustomPropertyMusicPlayerProcessID\");\n                ThrowIf(CFGetTypeID(pidRef) != CFNumberGetTypeID(), CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: CFType given for kAudioDeviceCustomPropertyMusicPlayerProcessID was not a CFNumber\");\n                \n                // Get the pid out of the CFNumber we received\n                // (Not using CACFNumber::GetSInt32 here because it would return 0 if CFNumberGetValue didn't write to our\n                // pid variable, and we want that to be an error.)\n                pid_t pid = INT_MIN;\n                // CFNumberGetValue docs: \"If the conversion is lossy, or the value is out of range, false is returned.\"\n                Boolean success = CFNumberGetValue(pidRef, kCFNumberIntType, &pid);\n                \n                ThrowIf(!success, CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: probable error from CFNumberGetValue when reading pid for kAudioDeviceCustomPropertyMusicPlayerProcessID\");\n                \n                CAMutex::Locker theStateLocker(mStateMutex);\n                \n                bool propertyWasChanged = false;\n                \n                try\n                {\n                    propertyWasChanged = mClients.SetMusicPlayer(pid);\n                }\n                catch(BGM_InvalidClientPIDException)\n                {\n                    Throw(CAException(kAudioHardwareIllegalOperationError));\n                }\n                \n                if(propertyWasChanged)\n                {\n                    // Send notification\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false,\t^{\n                        AudioObjectPropertyAddress theChangedProperties[] = { kBGMMusicPlayerProcessIDAddress, kBGMMusicPlayerBundleIDAddress };\n                        BGM_PlugIn::Host_PropertiesChanged(inObjectID, 2, theChangedProperties);\n                    });\n                }\n            }\n            break;\n            \n        case kAudioDeviceCustomPropertyMusicPlayerBundleID:\n            {\n                ThrowIf(inDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDeviceCustomPropertyMusicPlayerBundleID\");\n            \n                CFStringRef theBundleIDRef = *reinterpret_cast<const CFStringRef*>(inData);\n                \n                ThrowIfNULL(theBundleIDRef, CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: kAudioDeviceCustomPropertyMusicPlayerBundleID cannot be set to NULL\");\n                ThrowIf(CFGetTypeID(theBundleIDRef) != CFStringGetTypeID(), CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: CFType given for kAudioDeviceCustomPropertyMusicPlayerBundleID was not a CFString\");\n                \n                CAMutex::Locker theStateLocker(mStateMutex);\n                \n                CFRetain(theBundleIDRef);\n                CACFString bundleID(theBundleIDRef);\n                \n                bool propertyWasChanged = mClients.SetMusicPlayer(bundleID);\n                \n                if(propertyWasChanged)\n                {\n                    // Send notification\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false,\t^{\n                        AudioObjectPropertyAddress theChangedProperties[] = { kBGMMusicPlayerBundleIDAddress, kBGMMusicPlayerProcessIDAddress };\n                        BGM_PlugIn::Host_PropertiesChanged(inObjectID, 2, theChangedProperties);\n                    });\n                }\n            }\n            break;\n            \n        case kAudioDeviceCustomPropertyAppVolumes:\n            {\n                ThrowIf(inDataSize < sizeof(CFArrayRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDeviceCustomPropertyAppVolumes\");\n                \n                CFArrayRef arrayRef = *reinterpret_cast<const CFArrayRef*>(inData);\n\n                ThrowIfNULL(arrayRef, CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: kAudioDeviceCustomPropertyAppVolumes cannot be set to NULL\");\n                ThrowIf(CFGetTypeID(arrayRef) != CFArrayGetTypeID(), CAException(kAudioHardwareIllegalOperationError), \"BGM_Device::Device_SetPropertyData: CFType given for kAudioDeviceCustomPropertyAppVolumes was not a CFArray\");\n                \n                CACFArray array(arrayRef, false);\n\n                ValidateAppVolumesProperty(array);\n\n                bool propertyWasChanged = false;\n\n\t\t\t\tCAMutex::Locker theStateLocker(mStateMutex);\n\n\t\t\t\ttry\n                {\n                    propertyWasChanged = mClients.SetClientsRelativeVolumes(array);\n                }\n                catch(BGM_InvalidClientRelativeVolumeException)\n                {\n                    Throw(CAException(kAudioHardwareIllegalOperationError));\n                }\n\n                if(propertyWasChanged)\n                {\n                    // Send notification\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false,\t^{\n                        AudioObjectPropertyAddress theChangedProperties[] = { kBGMAppVolumesAddress };\n                        BGM_PlugIn::Host_PropertiesChanged(inObjectID, 1, theChangedProperties);\n                    });\n                }\n            }\n            break;\n\n        case kAudioDeviceCustomPropertyEnabledOutputControls:\n            {\n                ThrowIf(inDataSize < sizeof(CFArrayRef),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Device::Device_SetPropertyData: wrong size for the data for \"\n                        \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n\n                CFArrayRef theEnabledControlsRef = *reinterpret_cast<const CFArrayRef*>(inData);\n\n                ThrowIfNULL(theEnabledControlsRef,\n                            CAException(kAudioHardwareIllegalOperationError),\n                            \"BGM_Device::Device_SetPropertyData: null reference given for \"\n                            \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n                ThrowIf(CFGetTypeID(theEnabledControlsRef) != CFArrayGetTypeID(),\n                        CAException(kAudioHardwareIllegalOperationError),\n                        \"BGM_Device::Device_SetPropertyData: CFType given for \"\n                        \"kAudioDeviceCustomPropertyEnabledOutputControls was not a CFArray\");\n\n                CACFArray theEnabledControls(theEnabledControlsRef, false);\n\n                ThrowIf(theEnabledControls.GetNumberItems() != 2,\n                        CAException(kAudioHardwareIllegalOperationError),\n                        \"BGM_Device::Device_SetPropertyData: Expected the CFArray given for \"\n                        \"kAudioDeviceCustomPropertyEnabledOutputControls to have exactly 2 elements\");\n\n                bool theVolumeControlEnabled;\n                bool didGetBool = theEnabledControls.GetBool(kBGMEnabledOutputControlsIndex_Volume,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t theVolumeControlEnabled);\n                ThrowIf(!didGetBool,\n                        CAException(kAudioHardwareIllegalOperationError),\n                        \"BGM_Device::Device_SetPropertyData: Expected CFBoolean for volume elem of \"\n                        \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n\n                bool theMuteControlEnabled;\n                didGetBool = theEnabledControls.GetBool(kBGMEnabledOutputControlsIndex_Mute,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttheMuteControlEnabled);\n                ThrowIf(!didGetBool,\n                        CAException(kAudioHardwareIllegalOperationError),\n                        \"BGM_Device::Device_SetPropertyData: Expected CFBoolean for mute elem of \"\n                        \"kAudioDeviceCustomPropertyEnabledOutputControls\");\n\n                RequestEnabledControls(theVolumeControlEnabled, theMuteControlEnabled);\n            }\n            break;\n\n\t\tdefault:\n\t\t\tBGM_AbstractDevice::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);\n\t\t\tbreak;\n    };\n}\n\n#pragma mark IO Operations\n\nvoid\tBGM_Device::StartIO(UInt32 inClientID)\n{\n    bool clientIsBGMApp, bgmAppHasClientRegistered;\n    \n    {\n        CAMutex::Locker theStateLocker(mStateMutex);\n        \n        // An overview of the process this function is part of:\n        //   - A client starts IO.\n        //   - The plugin host (the HAL) calls the StartIO function in BGM_PlugInInterface, which calls this function.\n        //   - BGMDriver sends a message to BGMApp telling it to start the (real) audio hardware.\n        //   - BGMApp starts the hardware and, after the hardware is ready, replies to BGMDriver's message.\n        //   - BGMDriver lets the host know that it's ready to do IO by returning from StartIO.\n        \n        // Update our client data.\n        //\n        // We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation\n        // also add this task to the queue and the updates should be done in order.\n        bool didStartIO = mTaskQueue.QueueSync_StartClientIO(&mClients, inClientID);\n        \n        // We only tell the hardware to start if this is the first time IO has been started.\n        if(didStartIO)\n        {\n            kern_return_t theError = _HW_StartIO();\n            ThrowIfKernelError(theError,\n                               CAException(theError),\n                               \"BGM_Device::StartIO: Failed to start because of an error calling down to the driver.\");\n        }\n        \n        clientIsBGMApp = mClients.IsBGMApp(inClientID);\n        bgmAppHasClientRegistered = mClients.BGMAppHasClientRegistered();\n    }\n    \n    // We only return from StartIO after BGMApp is ready to pass the audio through to the output device. That way\n    // the HAL doesn't start sending us data before BGMApp can play it, which would mean we'd have to either drop\n    // frames or increase latency.\n    if(!clientIsBGMApp && bgmAppHasClientRegistered)\n    {\n        DebugMsg(\"BGM_Device::StartIO: StartBGMAppPlayThroughSync.\");\n        UInt64 theXPCError = StartBGMAppPlayThroughSync(GetObjectID() == kObjectID_Device_UI_Sounds);\n        \n        switch(theXPCError)\n        {\n            case kBGMXPC_Success:\n                DebugMsg(\"BGM_Device::StartIO: Ready for IO.\");\n                break;\n        \n            case kBGMXPC_MessageFailure:\n                // This most likely means BGMXPCHelper isn't installed or has crashed. IO will probably still work,\n                // but we may drop frames while the audio hardware starts up.\n                LogWarning(\"BGM_Device::StartIO: Couldn't reach BGMApp via XPC. Attempting to start IO anyway.\");\n                break;\n                       \n           case kBGMXPC_Timeout:\n               // XPC timeout. IO will probably still work,\n               // but we may drop frames while the audio hardware starts up.\n               LogWarning(\"BGM_Device::StartIO: Couldn't reach BGMApp via XPC (timeout). Attempting to start IO anyway.\");\n               break;\n\n            case kBGMXPC_ReturningEarlyError:\n                // This can (and might always) happen when the user changes output device in BGMApp while IO is running.\n                // See BGMAudioDeviceManager::startPlayThroughSync and BGMPlayThrough::WaitForOutputDeviceToStart.\n                LogWarning(\"BGM_Device::StartIO: BGMApp was busy, so BGMDriver has to return from StartIO early.\");\n                break;\n                \n            default:\n                DebugMsg(\"BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu\", theXPCError);\n                LogError(\"BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu\", theXPCError);\n                //Throw(CAException(kAudioHardwareNotRunningError));\n        }\n    }\n}\n\nvoid\tBGM_Device::StopIO(UInt32 inClientID)\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n    \n    // Update our client data.\n    //\n    // We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation also\n    // add this task to the queue and the updates should be done in order.\n    bool didStopIO = mTaskQueue.QueueSync_StopClientIO(&mClients, inClientID);\n\t\n\t//\twe tell the hardware to stop if this is the last stop call\n\tif(didStopIO)\n\t{\n\t\t_HW_StopIO();\n\t}\n}\n\nvoid\tBGM_Device::GetZeroTimeStamp(Float64& outSampleTime, UInt64& outHostTime, UInt64& outSeed)\n{\n\t// accessing the buffers requires holding the IO mutex\n\tCAMutex::Locker theIOLocker(mIOMutex);\n    \n    if(mWrappedAudioEngine != NULL)\n    {\n    }\n    else\n    {\n        // Without a wrapped device, we base our timing on the host. This is mostly from Apple's NullAudio.c sample code\n    \tUInt64 theCurrentHostTime;\n    \tFloat64 theHostTicksPerRingBuffer;\n    \tFloat64 theHostTickOffset;\n    \tUInt64 theNextHostTime;\n    \t\n    \t//\tget the current host time\n        theCurrentHostTime = CAHostTimeBase::GetTheCurrentTime();\n    \t\n    \t//\tcalculate the next host time\n    \ttheHostTicksPerRingBuffer = mLoopbackTime.hostTicksPerFrame * kLoopbackRingBufferFrameSize;\n    \ttheHostTickOffset = static_cast<Float64>(mLoopbackTime.numberTimeStamps + 1) * theHostTicksPerRingBuffer;\n    \ttheNextHostTime = mLoopbackTime.anchorHostTime + static_cast<UInt64>(theHostTickOffset);\n    \t\n    \t//\tgo to the next time if the next host time is less than the current time\n    \tif(theNextHostTime <= theCurrentHostTime)\n    \t{\n            mLoopbackTime.numberTimeStamps++;\n    \t}\n    \t\n    \t//\tset the return values\n    \toutSampleTime = mLoopbackTime.numberTimeStamps * kLoopbackRingBufferFrameSize;\n    \toutHostTime = static_cast<UInt64>(mLoopbackTime.anchorHostTime + (static_cast<Float64>(mLoopbackTime.numberTimeStamps) * theHostTicksPerRingBuffer));\n        // TODO: I think we should increment outSeed whenever this device switches to/from having a wrapped engine\n    \toutSeed = 1;\n    }\n}\n\nvoid\tBGM_Device::WillDoIOOperation(UInt32 inOperationID, bool& outWillDo, bool& outWillDoInPlace) const\n{\n\tswitch(inOperationID)\n\t{\n        case kAudioServerPlugInIOOperationThread:\n        case kAudioServerPlugInIOOperationReadInput:\n        case kAudioServerPlugInIOOperationProcessOutput:\n\t\tcase kAudioServerPlugInIOOperationWriteMix:\n\t\t\toutWillDo = true;\n\t\t\toutWillDoInPlace = true;\n\t\t\tbreak;\n\n        case kAudioServerPlugInIOOperationProcessMix:\n            outWillDo = mVolumeControl.WillApplyVolumeToAudioRT();\n            outWillDoInPlace = true;\n            break;\n\n\t\tcase kAudioServerPlugInIOOperationCycle:\n        case kAudioServerPlugInIOOperationConvertInput:\n        case kAudioServerPlugInIOOperationProcessInput:\n\t\tcase kAudioServerPlugInIOOperationMixOutput:\n\t\tcase kAudioServerPlugInIOOperationConvertMix:\n\t\tdefault:\n\t\t\toutWillDo = false;\n\t\t\toutWillDoInPlace = true;\n\t\t\tbreak;\n\t\t\t\n\t};\n}\n\nvoid\tBGM_Device::BeginIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, UInt32 inClientID)\n{\n\t#pragma unused(inIOBufferFrameSize, inIOCycleInfo)\n    \n    if(inOperationID == kAudioServerPlugInIOOperationThread)\n    {\n        // Update this client's IO state and send notifications if that changes the value of\n        // kAudioDeviceCustomPropertyDeviceIsRunning or\n        // kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp. We have to do this here\n        // as well as in StartIO because the HAL only calls StartIO/StopIO with the first/last clients.\n        //\n        // We perform the update async because it isn't real-time safe, but we can't just dispatch it with\n        // dispatch_async because that isn't real-time safe either. (Apparently even constructing a block\n        // isn't.)\n        //\n        // We don't have to hold the IO mutex here because mTaskQueue and mClients don't change and\n        // adding a task to mTaskQueue is thread safe.\n        mTaskQueue.QueueAsync_StartClientIO(&mClients, inClientID);\n    }\n}\n\nvoid\tBGM_Device::DoIOOperation(AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer)\n{\n    #pragma unused(inStreamObjectID, ioSecondaryBuffer)\n    \n\tswitch(inOperationID)\n\t{\n\t\tcase kAudioServerPlugInIOOperationReadInput:\n            {\n                CAMutex::Locker theIOLocker(mIOMutex);\n\n                // Copy the audio data out of our ring buffer.\n                //\n                // Take the IO mutex because, in testing, not taking it seemed to make this function\n                // occasionally miss its deadline and cause an audio glitch. It's hard to be sure\n                // that was actually the cause, but it's probably not worth the risk anyway.\n                //\n                // If an IO operation misses its deadline, the host will log this message:\n                //     Audio IO Overload inputs: '<private>' outputs: '<private>' cause: 'Unknown'\n                //     prewarming: no recovering: no\n                ReadInputData(inIOBufferFrameSize,\n                              inIOCycleInfo.mInputTime.mSampleTime,\n                              ioMainBuffer);\n            }\n\t\t\tbreak;\n            \n        case kAudioServerPlugInIOOperationProcessOutput:\n            {\n                bool theClientIsMusicPlayer = mClients.IsMusicPlayerRT(inClientID);\n                \n                CAMutex::Locker theIOLocker(mIOMutex);\n                // Called in this IO operation so we can get the music player client's data separately\n\t\t\t\tmAudibleState.UpdateWithClientIO(theClientIsMusicPlayer,\n\t\t\t\t\t\t\t\t\t\t\t\t inIOBufferFrameSize,\n\t\t\t\t\t\t\t\t\t\t\t\t inIOCycleInfo.mOutputTime.mSampleTime,\n\t\t\t\t\t\t\t\t\t\t\t\t reinterpret_cast<const Float32*>(ioMainBuffer));\n            }\n            ApplyClientRelativeVolume(inClientID, inIOBufferFrameSize, ioMainBuffer);\n            break;\n\n        case kAudioServerPlugInIOOperationProcessMix:\n            {\n                // Check the arguments.\n                ThrowIfNULL(ioMainBuffer,\n                            CAException(kAudioHardwareIllegalOperationError),\n                            \"BGM_Device::DoIOOperation: Buffer for \"\n                                    \"kAudioServerPlugInIOOperationProcessMix must not be null\");\n\n                CAMutex::Locker theIOLocker(mIOMutex);\n\n                // We ask to do this IO operation so this device can apply its own volume to the\n                // stream. Currently, only the UI sounds device does.\n                mVolumeControl.ApplyVolumeToAudioRT(reinterpret_cast<Float32*>(ioMainBuffer),\n                                                    inIOBufferFrameSize);\n            }\n            break;\n\n        case kAudioServerPlugInIOOperationWriteMix:\n            {\n                CAMutex::Locker theIOLocker(mIOMutex);\n\n                bool didChangeState =\n                        mAudibleState.UpdateWithMixedIO(\n                                inIOBufferFrameSize,\n                                inIOCycleInfo.mOutputTime.mSampleTime,\n                                reinterpret_cast<const Float32*>(ioMainBuffer));\n\n                if(didChangeState)\n                {\n                    // Send notifications.\n                    mTaskQueue.QueueAsync_SendPropertyNotification(\n\t\t\t\t\t\t\tkAudioDeviceCustomPropertyDeviceAudibleState, GetObjectID());\n                }\n\n                // Copy the audio data into our ring buffer.\n                WriteOutputData(inIOBufferFrameSize,\n                                inIOCycleInfo.mOutputTime.mSampleTime,\n                                ioMainBuffer);\n            }\n\t\t\tbreak;\n\n\t\tdefault:\n            // Note that this will only log the error in debug builds.\n\t\t\tDebugMsg(\"BGM_Device::DoIOOperation: Unexpected IO operation: %u\", inOperationID);\n\t\t\tbreak;\n\t};\n}\n\nvoid\tBGM_Device::EndIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, UInt32 inClientID)\n{\n    #pragma unused(inIOBufferFrameSize, inIOCycleInfo)\n\n    if(inOperationID == kAudioServerPlugInIOOperationThread)\n    {\n        // Tell BGM_Clients that this client has stopped IO. Queued async because we have to be real-time safe here.\n        //\n        // We don't have to hold the IO mutex here because mTaskQueue and mClients don't change and adding a task to\n        // mTaskQueue is thread safe.\n        mTaskQueue.QueueAsync_StopClientIO(&mClients, inClientID);\n    }\n}\n\nvoid\tBGM_Device::ReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, void* outBuffer)\n{\n    // Wrap the provided buffer in an AudioBufferList.\n    AudioBufferList abl = {\n        .mNumberBuffers = 1,\n        .mBuffers[0] = {\n            .mNumberChannels = 2,\n            // Each frame is 2 Float32 samples (one per channel). The number of frames * the number\n            // of bytes per frame = the size of outBuffer in bytes.\n            .mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2),\n            .mData = outBuffer\n        }\n    };\n\n    // Copy the audio data from our ring buffer into the provided buffer.\n    CARingBufferError err =\n            mLoopbackRingBuffer.Fetch(&abl,\n                                      inIOBufferFrameSize,\n                                      static_cast<CARingBuffer::SampleTime>(inSampleTime));\n\n    // Handle errors.\n    switch (err)\n    {\n        case kCARingBufferError_CPUOverload:\n            // Write silence to the buffer.\n            memset(outBuffer, 0, abl.mBuffers[0].mDataByteSize);\n            break;\n        case kCARingBufferError_TooMuch:\n            // Should be impossible, but handle it just in case. Write silence to the buffer and\n            // return an error code.\n            memset(outBuffer, 0, abl.mBuffers[0].mDataByteSize);\n            Throw(CAException(kAudioHardwareIllegalOperationError));\n        case kCARingBufferError_OK:\n            break;\n        default:\n            throw CAException(kAudioHardwareUnspecifiedError);\n    }\n}\n\nvoid\tBGM_Device::WriteOutputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, const void* inBuffer)\n{\n    // Wrap the provided buffer in an AudioBufferList.\n    AudioBufferList abl = {\n        .mNumberBuffers = 1,\n        .mBuffers[0] = {\n            .mNumberChannels = 2,\n            // Each frame is 2 Float32 samples (one per channel). The number of frames * the number\n            // of bytes per frame = the size of inBuffer in bytes.\n            .mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2),\n            .mData = const_cast<void *>(inBuffer)\n        }\n    };\n\n    // Copy the audio data from the provided buffer into our ring buffer.\n    CARingBufferError err =\n            mLoopbackRingBuffer.Store(&abl,\n                                      inIOBufferFrameSize,\n                                      static_cast<CARingBuffer::SampleTime>(inSampleTime));\n\n    // Return an error code if we failed to store the data. (But ignore CPU overload, which would be\n    // temporary.)\n    if (err != kCARingBufferError_OK && err != kCARingBufferError_CPUOverload)\n    {\n        Throw(CAException(err));\n    }\n}\n\nvoid\tBGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferFrameSize, void* ioBuffer) const\n{\n    Float32* theBuffer = reinterpret_cast<Float32*>(ioBuffer);\n    Float32 theRelativeVolume = mClients.GetClientRelativeVolumeRT(inClientID);\n    \n    auto thePanPositionInt = mClients.GetClientPanPositionRT(inClientID);\n    Float32 thePanPosition = static_cast<Float32>(thePanPositionInt) / 100.0f;\n    \n    // TODO When we get around to supporting devices with more than two channels it would be worth looking into\n    //      kAudioFormatProperty_PanningMatrix and kAudioFormatProperty_BalanceFade in AudioFormat.h.\n    \n    // TODO precompute matrix coefficients w/ volume and do everything in one pass\n    \n    // Apply balance w/ crossfeed to the frames in the buffer.\n    // Expect samples interleaved, starting with left\n    if (thePanPosition > 0.0f) {\n        for (UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2) {\n            auto L = i;\n            auto R = i + 1;\n            \n            theBuffer[R] = theBuffer[R] + theBuffer[L] * thePanPosition;\n            theBuffer[L] = theBuffer[L] * (1 - thePanPosition);\n        }\n    } else if (thePanPosition < 0.0f) {\n        for (UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2) {\n            auto L = i;\n            auto R = i + 1;\n            \n            theBuffer[L] = theBuffer[L] + theBuffer[R] * (-thePanPosition);\n            theBuffer[R] = theBuffer[R] * (1 + thePanPosition);\n        }\n    }\n\n    if(theRelativeVolume != 1.0f)\n    {\n        for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++)\n        {\n            Float32 theAdjustedSample = theBuffer[i] * theRelativeVolume;\n            \n            // Clamp to [-1, 1].\n            // (This way is roughly 6 times faster than using std::min and std::max because the compiler can vectorize the loop.)\n            const Float32 theAdjustedSampleClippedBelow = theAdjustedSample < -1.0f ? -1.0f : theAdjustedSample;\n            theBuffer[i] = theAdjustedSampleClippedBelow > 1.0f ? 1.0f : theAdjustedSampleClippedBelow;\n        }\n    }\n}\n\n#pragma mark Accessors\n\nvoid    BGM_Device::RequestEnabledControls(bool inVolumeEnabled, bool inMuteEnabled)\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    bool changeVolume = (mVolumeControl.IsActive() != inVolumeEnabled);\n    bool changeMute = (mMuteControl.IsActive() != inMuteEnabled);\n\n    if(changeVolume)\n    {\n        DebugMsg(\"BGM_Device::RequestEnabledControls: %s volume control\",\n                 (inVolumeEnabled ? \"Enabling\" : \"Disabling\"));\n        mPendingOutputVolumeControlEnabled = inVolumeEnabled;\n    }\n\n    if(changeMute)\n    {\n        DebugMsg(\"BGM_Device::RequestEnabledControls: %s mute control\",\n                 (inMuteEnabled ? \"Enabling\" : \"Disabling\"));\n        mPendingOutputMuteControlEnabled = inMuteEnabled;\n    }\n\n    if(changeVolume || changeMute)\n    {\n        // Ask the host to stop IO (and whatever else) so we can safely update the device's list of\n        // controls. See RequestDeviceConfigurationChange in AudioServerPlugIn.h.\n        AudioObjectID theDeviceObjectID = GetObjectID();\n        UInt64 action = static_cast<UInt64>(ChangeAction::SetEnabledControls);\n        \n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false,\t^{\n            BGM_PlugIn::Host_RequestDeviceConfigurationChange(theDeviceObjectID, action, nullptr);\n        });\n    }\n}\n\nFloat64\tBGM_Device::GetSampleRate() const\n{\n    // The sample rate is guarded by the state lock. Note that we don't need to take the IO lock.\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    Float64 theSampleRate;\n\n    // Report the sample rate from the wrapped device if we have one. Note that _HW_GetSampleRate\n    // the device's nominal sample rate, not one calculated from its timestamps.\n    if(mWrappedAudioEngine == nullptr)\n    {\n        theSampleRate = mLoopbackSampleRate;\n    }\n    else\n    {\n        theSampleRate = _HW_GetSampleRate();\n    }\n\n    return theSampleRate;\n}\n\nvoid\tBGM_Device::RequestSampleRate(Float64 inRequestedSampleRate)\n{\n    // Changing the sample rate needs to be handled via the RequestConfigChange/PerformConfigChange\n    // machinery. See RequestDeviceConfigurationChange in AudioServerPlugIn.h.\n\n\t// We try to support any sample rate a real output device might.\n    ThrowIf(inRequestedSampleRate < 1.0,\n            CAException(kAudioDeviceUnsupportedFormatError),\n            \"BGM_Device::RequestSampleRate: unsupported sample rate\");\n\n    DebugMsg(\"BGM_Device::RequestSampleRate: Sample rate change requested: %f\",\n             inRequestedSampleRate);\n\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    if(inRequestedSampleRate != GetSampleRate())  // Check the sample rate will actually be changed.\n    {\n        mPendingSampleRate = inRequestedSampleRate;\n\n        // Dispatch this so the change can happen asynchronously.\n        auto requestSampleRate = ^{\n\t\t\tUInt64 action = static_cast<UInt64>(ChangeAction::SetSampleRate);\n            BGM_PlugIn::Host_RequestDeviceConfigurationChange(GetObjectID(), action, nullptr);\n        };\n\n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, requestSampleRate);\n    }\n}\n\nBGM_Object&  BGM_Device::GetOwnedObjectByID(AudioObjectID inObjectID)\n{\n\t// C++ is weird. See \"Avoid Duplication in const and Non-const Member Functions\" in Item 3 of Effective C++.\n\treturn const_cast<BGM_Object&>(static_cast<const BGM_Device&>(*this).GetOwnedObjectByID(inObjectID));\n}\n\nconst BGM_Object&  BGM_Device::GetOwnedObjectByID(AudioObjectID inObjectID) const\n{\n\tif(inObjectID == mInputStream.GetObjectID())\n\t{\n\t\treturn mInputStream;\n\t}\n\telse if(inObjectID == mOutputStream.GetObjectID())\n\t{\n\t\treturn mOutputStream;\n\t}\n\telse if(inObjectID == mVolumeControl.GetObjectID())\n\t{\n\t\treturn mVolumeControl;\n\t}\n\telse if(inObjectID == mMuteControl.GetObjectID())\n\t{\n\t\treturn mMuteControl;\n\t}\n\telse\n\t{\n\t\tLogError(\"BGM_Device::GetOwnedObjectByID: Unknown object ID. inObjectID = %u\", inObjectID);\n\t\tThrow(CAException(kAudioHardwareBadObjectError));\n\t}\n}\n\nUInt32\tBGM_Device::GetNumberOfSubObjects() const\n{\n\treturn kNumberOfInputSubObjects + GetNumberOfOutputSubObjects();\n}\n\nUInt32\tBGM_Device::GetNumberOfOutputSubObjects() const\n{\n\treturn kNumberOfOutputStreams + GetNumberOfOutputControls();\n}\n\nUInt32\tBGM_Device::GetNumberOfOutputControls() const\n{\n\tCAMutex::Locker theStateLocker(mStateMutex);\n\n\tUInt32 theAnswer = 0;\n\n\tif(mVolumeControl.IsActive())\n\t{\n\t\ttheAnswer++;\n\t}\n\n\tif(mMuteControl.IsActive())\n\t{\n\t\ttheAnswer++;\n\t}\n\n    return theAnswer;\n}\n\nvoid    BGM_Device::SetEnabledControls(bool inVolumeEnabled, bool inMuteEnabled)\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    if(mVolumeControl.IsActive() != inVolumeEnabled)\n    {\n        DebugMsg(\"BGM_Device::SetEnabledControls: %s the volume control\",\n                 inVolumeEnabled ? \"Enabling\" : \"Disabling\");\n\n        if(inVolumeEnabled)\n\t\t{\n\t\t\tmVolumeControl.Activate();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmVolumeControl.Deactivate();\n\t\t}\n    }\n\n    if(mMuteControl.IsActive() != inMuteEnabled)\n    {\n        DebugMsg(\"BGM_Device::SetEnabledControls: %s the mute control\",\n                 inMuteEnabled ? \"Enabling\" : \"Disabling\");\n\n        if(inMuteEnabled)\n\t\t{\n\t\t\tmMuteControl.Activate();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmMuteControl.Deactivate();\n\t\t}\n    }\n}\n\nvoid BGM_Device::SetSampleRate(Float64 inSampleRate, bool force)\n{\n    // We try to support any sample rate a real output device might.\n    ThrowIf(inSampleRate < 1.0,\n            CAException(kAudioDeviceUnsupportedFormatError),\n            \"BGM_Device::SetSampleRate: unsupported sample rate\");\n\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    Float64 theCurrentSampleRate = GetSampleRate();\n\n    if((inSampleRate != theCurrentSampleRate) || force)  // Check whether we need to change it.\n    {\n        DebugMsg(\"BGM_Device::SetSampleRate: Changing the sample rate from %f to %f\",\n                 theCurrentSampleRate,\n                 inSampleRate);\n\n        // Update the sample rate on the wrapped device if we have one.\n        if(mWrappedAudioEngine != nullptr)\n        {\n            kern_return_t theError = _HW_SetSampleRate(inSampleRate);\n            ThrowIfKernelError(theError,\n                               CAException(kAudioHardwareUnspecifiedError),\n                               \"BGM_Device::SetSampleRate: Error setting the sample rate on the \"\n                               \"wrapped audio device.\");\n        }\n\n        // Update the sample rate for loopback.\n        mLoopbackSampleRate = inSampleRate;\n        InitLoopback();\n\n        // Update the streams.\n        mInputStream.SetSampleRate(inSampleRate);\n        mOutputStream.SetSampleRate(inSampleRate);\n    }\n    else\n    {\n        DebugMsg(\"BGM_Device::SetSampleRate: The sample rate is already set to %f\", inSampleRate);\n    }\n}\n\nbool    BGM_Device::IsStreamID(AudioObjectID inObjectID) const noexcept\n{\n    return (inObjectID == mInputStream.GetObjectID()) || (inObjectID == mOutputStream.GetObjectID());\n}\n\n#pragma mark Hardware Accessors\n\n// TODO: Out of laziness, some of these hardware functions do more than their names suggest\n\nvoid\tBGM_Device::_HW_Open()\n{\n}\n\nvoid\tBGM_Device::_HW_Close()\n{\n}\n\nkern_return_t\tBGM_Device::_HW_StartIO()\n{\n\tBGMAssert(mStateMutex.IsOwnedByCurrentThread(),\n              \"BGM_Device::_HW_StartIO: Called without taking the state mutex\");\n\n    if(mWrappedAudioEngine != nullptr)\n    {\n    }\n    \n    // Reset the loopback timing values\n    mLoopbackTime.numberTimeStamps = 0;\n    mLoopbackTime.anchorHostTime = CAHostTimeBase::GetTheCurrentTime();\n    // ...and the most-recent audible/silent sample times. mAudibleState is usually guarded by the\n\t// IO mutex, but we haven't started IO yet (and this function can only be called by one thread\n\t// at a time).\n\tBGMAssert(mIOMutex.IsFree(), \"BGM_Device::_HW_StartIO: IO mutex taken before starting IO\");\n    mAudibleState.Reset();\n    \n    return KERN_SUCCESS;\n}\n\nvoid\tBGM_Device::_HW_StopIO()\n{\n    if(mWrappedAudioEngine != NULL)\n    {\n    }\n}\n\nFloat64\tBGM_Device::_HW_GetSampleRate() const\n{\n    // This function should only be called when wrapping a device.\n    ThrowIf(mWrappedAudioEngine == nullptr,\n            CAException(kAudioHardwareUnspecifiedError),\n            \"BGM_Device::_HW_GetSampleRate: No wrapped audio device\");\n\n    return mWrappedAudioEngine->GetSampleRate();\n}\n\nkern_return_t\tBGM_Device::_HW_SetSampleRate(Float64 inNewSampleRate)\n{\n    // This function should only be called when wrapping a device.\n    ThrowIf(mWrappedAudioEngine == nullptr,\n            CAException(kAudioHardwareUnspecifiedError),\n            \"BGM_Device::_HW_SetSampleRate: No wrapped audio device\");\n\n    return mWrappedAudioEngine->SetSampleRate(inNewSampleRate);\n}\n\nUInt32\tBGM_Device::_HW_GetRingBufferFrameSize() const\n{\n    return (mWrappedAudioEngine != NULL) ? mWrappedAudioEngine->GetSampleBufferFrameSize() : 0;\n}\n\n#pragma mark Implementation\n\nvoid\tBGM_Device::AddClient(const AudioServerPlugInClientInfo* inClientInfo)\n{\n    DebugMsg(\"BGM_Device::AddClient: Adding client %u (%s)\",\n             inClientInfo->mClientID,\n             (inClientInfo->mBundleID == NULL ?\n                 \"no bundle ID\" :\n                 CFStringGetCStringPtr(inClientInfo->mBundleID, kCFStringEncodingUTF8)));\n    \n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    mClients.AddClient(inClientInfo);\n}\n\nvoid\tBGM_Device::RemoveClient(const AudioServerPlugInClientInfo* inClientInfo)\n{\n    DebugMsg(\"BGM_Device::RemoveClient: Removing client %u (%s)\",\n             inClientInfo->mClientID,\n             CFStringGetCStringPtr(inClientInfo->mBundleID, kCFStringEncodingUTF8));\n    \n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    // If we're removing BGMApp, reenable all of BGMDevice's controls.\n    if(mClients.IsBGMApp(inClientInfo->mClientID))\n    {\n        RequestEnabledControls(true, true);\n    }\n\n    mClients.RemoveClient(inClientInfo->mClientID);\n}\n\nvoid\tBGM_Device::PerformConfigChange(UInt64 inChangeAction, void* inChangeInfo)\n{\n\t#pragma unused(inChangeInfo)\n    DebugMsg(\"BGM_Device::PerformConfigChange: inChangeAction = %llu\", inChangeAction);\n\n    // Apply a change requested with BGM_PlugIn::Host_RequestDeviceConfigurationChange. See\n    // PerformDeviceConfigurationChange in AudioServerPlugIn.h.\n\n    switch(static_cast<ChangeAction>(inChangeAction))\n    {\n        case ChangeAction::SetSampleRate:\n            SetSampleRate(mPendingSampleRate);\n            break;\n\n        case ChangeAction::SetEnabledControls:\n            SetEnabledControls(mPendingOutputVolumeControlEnabled,\n                               mPendingOutputMuteControlEnabled);\n            break;\n    }\n}\n\nvoid\tBGM_Device::AbortConfigChange(UInt64 inChangeAction, void* inChangeInfo)\n{\n\t#pragma unused(inChangeAction, inChangeInfo)\n\t\n\t//\tthis device doesn't need to do anything special if a change request gets aborted\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Device.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Device.h\n//  BGMDriver\n//\n//  Copyright © 2016, 2017, 2019 Kyle Neideck\n//  Copyright © 2019 Gordon Childs\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_Device.h from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n\n#ifndef BGMDriver__BGM_Device\n#define BGMDriver__BGM_Device\n\n// SuperClass Includes\n#include \"BGM_AbstractDevice.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_WrappedAudioEngine.h\"\n#include \"BGM_Clients.h\"\n#include \"BGM_TaskQueue.h\"\n#include \"BGM_AudibleState.h\"\n#include \"BGM_Stream.h\"\n#include \"BGM_VolumeControl.h\"\n#include \"BGM_MuteControl.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n#include \"CAVolumeCurve.h\"\n#include \"CARingBuffer.h\"\n\n// System Includes\n#include <CoreFoundation/CoreFoundation.h>\n#include <pthread.h>\n\n\nclass BGM_Device\n:\n\tpublic BGM_AbstractDevice\n{\n\n#pragma mark Construction/Destruction\n    \npublic:\n    static BGM_Device&\t\t\tGetInstance();\n    static BGM_Device&\t\t\tGetUISoundsInstance();\n    \nprivate:\n    static void\t\t\t\t\tStaticInitializer();\n\nprotected:\n                                BGM_Device(AudioObjectID inObjectID,\n\t\t\t\t\t\t\t\t\t\t   const CFStringRef __nonnull inDeviceName,\n                                           const CFStringRef __nonnull inDeviceUID,\n\t\t\t\t\t\t\t\t\t\t   const CFStringRef __nonnull inDeviceModelUID,\n                                           AudioObjectID inInputStreamID,\n                                           AudioObjectID inOutputStreamID,\n                                           AudioObjectID inOutputVolumeControlID,\n\t\t\t\t\t\t\t\t\t\t   AudioObjectID inOutputMuteControlID);\n    virtual\t\t\t\t\t\t~BGM_Device();\n    \n    virtual void\t\t\t\tActivate();\n    virtual void\t\t\t\tDeactivate();\n    \nprivate:\n    void                        InitLoopback();\n\t\n#pragma mark Property Operations\n    \npublic:\n\tvirtual bool\t\t\t\tHasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual bool\t\t\t\tIsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual UInt32\t\t\t\tGetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData) const;\n\tvirtual void\t\t\t\tGetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* __nonnull outData) const;\n\tvirtual void\t\t\t\tSetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, const void* __nonnull inData);\n\n#pragma mark Device Property Operations\n    \nprivate:\n\tbool\t\t\t\t\t\tDevice_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tbool\t\t\t\t\t\tDevice_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tUInt32\t\t\t\t\t\tDevice_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData) const;\n\tvoid\t\t\t\t\t\tDevice_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* __nonnull outData) const;\n\tvoid\t\t\t\t\t\tDevice_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, const void* __nonnull inData);\n\n#pragma mark IO Operations\n    \npublic:\n\tvoid\t\t\t\t\t\tStartIO(UInt32 inClientID);\n\tvoid\t\t\t\t\t\tStopIO(UInt32 inClientID);\n    \n\tvoid\t\t\t\t\t\tGetZeroTimeStamp(Float64& outSampleTime, UInt64& outHostTime, UInt64& outSeed);\n\t\n\tvoid\t\t\t\t\t\tWillDoIOOperation(UInt32 inOperationID, bool& outWillDo, bool& outWillDoInPlace) const;\n\tvoid\t\t\t\t\t\tBeginIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, UInt32 inClientID);\n\tvoid\t\t\t\t\t\tDoIOOperation(AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, void* __nonnull ioMainBuffer, void* __nullable ioSecondaryBuffer);\n\tvoid\t\t\t\t\t\tEndIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, UInt32 inClientID);\n\nprivate:\n\tvoid\t\t\t\t\t\tReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, void* __nonnull outBuffer);\n    void\t\t\t\t\t\tWriteOutputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, const void* __nonnull inBuffer);\n    void                        ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferFrameSize, void* __nonnull inBuffer) const;\n\n#pragma mark Accessors\n\npublic:\n\t/*!\n\t Enable or disable the device's volume and/or mute controls. This function is async because it\n\t has to ask the host to stop IO for the device before the controls can be enabled/disabled.\n\n\t See BGM_Device::PerformConfigChange and RequestDeviceConfigurationChange in AudioServerPlugIn.h.\n\t */\n    void                        RequestEnabledControls(bool inVolumeEnabled, bool inMuteEnabled);\n\n    Float64\t\t\t\t\t\tGetSampleRate() const;\n    void                        RequestSampleRate(Float64 inRequestedSampleRate);\n\nprivate:\n\t/*!\n     @return The Audio Object that has the ID inObjectID and belongs to this device.\n     @throws CAException if there is no such Audio Object.\n     */\n\tconst BGM_Object&\t\t    GetOwnedObjectByID(AudioObjectID inObjectID) const;\n\tBGM_Object&                 GetOwnedObjectByID(AudioObjectID inObjectID);\n\n\t/*! @return The number of Audio Objects belonging to this device, e.g. streams and controls. */\n\tUInt32 \t\t\t\t\t\tGetNumberOfSubObjects() const;\n\t/*! @return The number of Audio Objects with output scope belonging to this device. */\n    UInt32 \t\t\t\t\t\tGetNumberOfOutputSubObjects() const;\n\t/*!\n\t @return The number of control Audio Objects with output scope belonging to this device, e.g.\n\t         output volume and mute controls.\n\t */\n    UInt32 \t\t\t\t\t\tGetNumberOfOutputControls() const;\n    /*!\n     Enable or disable the device's volume and/or mute controls.\n\n     Private because (after initialisation) this can only be called after asking the host to stop IO\n     for the device. See BGM_Device::RequestEnabledControls, BGM_Device::PerformConfigChange and\n     RequestDeviceConfigurationChange in AudioServerPlugIn.h.\n     */\n    void                        SetEnabledControls(bool inVolumeEnabled, bool inMuteEnabled);\n    /*!\n     Set the device's sample rate.\n\n     Private because (after initialisation) this can only be called after asking the host to stop IO\n     for the device. See BGM_Device::RequestEnabledControls, BGM_Device::PerformConfigChange and\n     RequestDeviceConfigurationChange in AudioServerPlugIn.h.\n\n     @param inNewSampleRate The sample rate.\n     @param force If true, set the sample rate on the device even if it's currently set to\n                  inNewSampleRate.\n     @throws CAException if inNewSampleRate < 1 or if applying the sample rate to one of the streams\n             fails.\n     */\n    void                        SetSampleRate(Float64 inNewSampleRate, bool force = false);\n\n    /*! @return True if inObjectID is the ID of one of this device's streams. */\n    inline bool                 IsStreamID(AudioObjectID inObjectID) const noexcept;\n\n#pragma mark Hardware Accessors\n    \nprivate:\n\tvoid\t\t\t\t\t\t_HW_Open();\n\tvoid\t\t\t\t\t\t_HW_Close();\n\tkern_return_t\t\t\t\t_HW_StartIO();\n\tvoid\t\t\t\t\t\t_HW_StopIO();\n\tFloat64\t\t\t\t\t\t_HW_GetSampleRate() const;\n\tkern_return_t\t\t\t\t_HW_SetSampleRate(Float64 inNewSampleRate);\n\tUInt32\t\t\t\t\t\t_HW_GetRingBufferFrameSize() const;\n\n#pragma mark Implementation\n    \npublic:\n    CFStringRef __nonnull\t\tCopyDeviceUID() const { return mDeviceUID; }\n    void                        AddClient(const AudioServerPlugInClientInfo* __nonnull inClientInfo);\n    void                        RemoveClient(const AudioServerPlugInClientInfo* __nonnull inClientInfo);\n    /*!\n     Apply a change requested with BGM_PlugIn::Host_RequestDeviceConfigurationChange. See\n     PerformDeviceConfigurationChange in AudioServerPlugIn.h.\n     */\n\tvoid\t\t\t\t\t\tPerformConfigChange(UInt64 inChangeAction, void* __nullable inChangeInfo);\n    /*! Cancel a change requested with BGM_PlugIn::Host_RequestDeviceConfigurationChange. */\n\tvoid\t\t\t\t\t\tAbortConfigChange(UInt64 inChangeAction, void* __nullable inChangeInfo);\n\nprivate:\n    static pthread_once_t\t\tsStaticInitializer;\n    static BGM_Device* __nonnull    sInstance;\n    static BGM_Device* __nonnull    sUISoundsInstance;\n    \n    #define kDeviceName                 \"Background Music\"\n    #define kDeviceName_UISounds        \"Background Music (UI Sounds)\"\n    #define kDeviceManufacturerName     \"Background Music contributors\"\n\n\tconst CFStringRef __nonnull\tmDeviceName;\n\tconst CFStringRef __nonnull mDeviceUID;\n\tconst CFStringRef __nonnull mDeviceModelUID;\n\n\tenum\n\t{\n\t\t// The number of global/output sub-objects varies because the controls can be disabled.\n\t\t\t\t\t\t\t\tkNumberOfInputSubObjects\t\t\t= 1,\n\n\t\t\t\t\t\t\t\tkNumberOfStreams\t\t\t\t\t= 2,\n\t\t\t\t\t\t\t\tkNumberOfInputStreams\t\t\t\t= 1,\n\t\t\t\t\t\t\t\tkNumberOfOutputStreams\t\t\t\t= 1\n\t};\n\n    CAMutex                     mStateMutex;\n    CAMutex\t\t\t\t\t\tmIOMutex;\n    \n    const Float64               kSampleRateDefault = 44100.0;\n    // Before we can change sample rate, the host has to stop the device. The new sample rate is\n    // stored here while it does.\n    Float64                     mPendingSampleRate = kSampleRateDefault;\n    \n    BGM_WrappedAudioEngine* __nullable mWrappedAudioEngine;\n    \n    BGM_TaskQueue               mTaskQueue;\n    \n    BGM_Clients                 mClients;\n    \n    #define kLoopbackRingBufferFrameSize    16384\n    Float64                     mLoopbackSampleRate;\n    CARingBuffer                mLoopbackRingBuffer;\n\n    // TODO: a comment explaining why we need a clock for loopback-only mode\n    struct {\n        Float64\t\t\t\t\thostTicksPerFrame = 0.0;\n        UInt64\t\t\t\t\tnumberTimeStamps  = 0;\n        UInt64\t\t\t\t\tanchorHostTime    = 0;\n    }                           mLoopbackTime;\n\t\n    BGM_Stream                  mInputStream;\n    BGM_Stream                  mOutputStream;\n\n    BGM_AudibleState            mAudibleState;\n\n    enum class ChangeAction : UInt64\n    {\n        SetSampleRate,\n        SetEnabledControls\n    };\n\n    BGM_VolumeControl\t\t\tmVolumeControl;\n\tBGM_MuteControl\t\t\t\tmMuteControl;\n    bool                        mPendingOutputVolumeControlEnabled = true;\n    bool                        mPendingOutputMuteControlEnabled   = true;\n\n};\n\n#endif /* BGMDriver__BGM_Device */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_MuteControl.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_MuteControl.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_MuteControl.h\"\n\n// Local Includes\n#include \"BGM_PlugIn.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CADispatchQueue.h\"\n\n\n#pragma clang assume_nonnull begin\n\n#pragma mark Construction/Destruction\n\nBGM_MuteControl::BGM_MuteControl(AudioObjectID inObjectID,\n                                 AudioObjectID inOwnerObjectID,\n                                 AudioObjectPropertyScope inScope,\n                                 AudioObjectPropertyElement inElement)\n:\n    BGM_Control(inObjectID,\n                kAudioMuteControlClassID,\n                kAudioBooleanControlClassID,\n                inOwnerObjectID,\n                inScope,\n                inElement),\n    mMutex(\"Mute Control\"),\n    mMuted(false)\n{\n}\n\n#pragma mark Property Operations\n\nbool    BGM_MuteControl::HasProperty(AudioObjectID inObjectID,\n                                     pid_t inClientPID,\n                                     const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioBooleanControlPropertyValue:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Control::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nbool    BGM_MuteControl::IsPropertySettable(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioBooleanControlPropertyValue:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Control::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32  BGM_MuteControl::GetPropertyDataSize(AudioObjectID inObjectID,\n                                             pid_t inClientPID,\n                                             const AudioObjectPropertyAddress& inAddress,\n                                             UInt32 inQualifierDataSize,\n                                             const void* inQualifierData) const\n{\n    CheckObjectID(inObjectID);\n\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioBooleanControlPropertyValue:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        default:\n            theAnswer = BGM_Control::GetPropertyDataSize(inObjectID,\n                                                         inClientPID,\n                                                         inAddress,\n                                                         inQualifierDataSize,\n                                                         inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_MuteControl::GetPropertyData(AudioObjectID inObjectID,\n                                         pid_t inClientPID,\n                                         const AudioObjectPropertyAddress& inAddress,\n                                         UInt32 inQualifierDataSize,\n                                         const void* inQualifierData,\n                                         UInt32 inDataSize,\n                                         UInt32& outDataSize,\n                                         void* outData) const\n{\n    CheckObjectID(inObjectID);\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioBooleanControlPropertyValue:\n            // This returns the mute value of the control.\n            {\n                ThrowIf(inDataSize < sizeof(UInt32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_MuteControl::GetPropertyData: not enough space for the return value \"\n                        \"of kAudioBooleanControlPropertyValue for the mute control\");\n\n                CAMutex::Locker theLocker(mMutex);\n\n                // Non-zero for true, which means audio is being muted.\n                *reinterpret_cast<UInt32*>(outData) = mMuted ? 1 : 0;\n                outDataSize = sizeof(UInt32);\n            }\n            break;\n\n        default:\n            BGM_Control::GetPropertyData(inObjectID,\n                                         inClientPID,\n                                         inAddress,\n                                         inQualifierDataSize,\n                                         inQualifierData,\n                                         inDataSize,\n                                         outDataSize,\n                                         outData);\n            break;\n    };\n}\n\nvoid    BGM_MuteControl::SetPropertyData(AudioObjectID inObjectID,\n                                         pid_t inClientPID,\n                                         const AudioObjectPropertyAddress& inAddress,\n                                         UInt32 inQualifierDataSize,\n                                         const void* inQualifierData,\n                                         UInt32 inDataSize,\n                                         const void* inData)\n{\n    CheckObjectID(inObjectID);\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioBooleanControlPropertyValue:\n            {\n                ThrowIf(inDataSize < sizeof(UInt32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_MuteControl::SetPropertyData: wrong size for the data for \"\n                        \"kAudioBooleanControlPropertyValue\");\n\n                CAMutex::Locker theLocker(mMutex);\n\n                // Non-zero for true, meaning audio will be muted.\n                bool theNewMuted = (*reinterpret_cast<const UInt32*>(inData) != 0);\n\n                if(mMuted != theNewMuted)\n                {\n                    mMuted = theNewMuted;\n\n                    // Send notifications.\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n                        AudioObjectPropertyAddress theChangedProperty[1];\n                        theChangedProperty[0] = {\n                                kAudioBooleanControlPropertyValue, mScope, mElement\n                        };\n\n                        BGM_PlugIn::Host_PropertiesChanged(inObjectID, 1, theChangedProperty);\n                    });\n                }\n            }\n            break;\n\n        default:\n            BGM_Control::SetPropertyData(inObjectID,\n                                         inClientPID,\n                                         inAddress,\n                                         inQualifierDataSize,\n                                         inQualifierData,\n                                         inDataSize,\n                                         inData);\n            break;\n    };\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_MuteControl.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_MuteControl.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n#ifndef BGMDriver__BGM_MuteControl\n#define BGMDriver__BGM_MuteControl\n\n// Superclass Includes\n#include \"BGM_Control.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n\n// System Includes\n#include <MacTypes.h>\n#include <CoreAudio/CoreAudio.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_MuteControl\n:\n    public BGM_Control\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                              BGM_MuteControl(AudioObjectID inObjectID,\n                                              AudioObjectID inOwnerObjectID,\n                                              AudioObjectPropertyScope inScope =\n                                                      kAudioObjectPropertyScopeOutput,\n                                              AudioObjectPropertyElement inElement =\n                                                      kAudioObjectPropertyElementMaster);\n\n#pragma mark Property Operations\n\n    bool                      HasProperty(AudioObjectID inObjectID,\n                                          pid_t inClientPID,\n                                          const AudioObjectPropertyAddress& inAddress) const;\n\n    bool                      IsPropertySettable(AudioObjectID inObjectID,\n                                                 pid_t inClientPID,\n                                                 const AudioObjectPropertyAddress& inAddress) const;\n\n    UInt32                    GetPropertyDataSize(AudioObjectID inObjectID,\n                                                  pid_t inClientPID,\n                                                  const AudioObjectPropertyAddress& inAddress,\n                                                  UInt32 inQualifierDataSize,\n                                                  const void* inQualifierData) const;\n\n    void                      GetPropertyData(AudioObjectID inObjectID,\n                                              pid_t inClientPID,\n                                              const AudioObjectPropertyAddress& inAddress,\n                                              UInt32 inQualifierDataSize,\n                                              const void* inQualifierData,\n                                              UInt32 inDataSize,\n                                              UInt32& outDataSize,\n                                              void* outData) const;\n\n    void                      SetPropertyData(AudioObjectID inObjectID,\n                                              pid_t inClientPID,\n                                              const AudioObjectPropertyAddress& inAddress,\n                                              UInt32 inQualifierDataSize,\n                                              const void* inQualifierData,\n                                              UInt32 inDataSize,\n                                              const void* inData);\n\n#pragma mark Implementation\n\nprivate:\n    CAMutex                   mMutex;\n    bool                      mMuted;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_MuteControl */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_NullDevice.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_NullDevice.cpp\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_NullDevice.h\"\n\n// Local Includes\n#include \"BGM_PlugIn.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAPropertyAddress.h\"\n#include \"CADispatchQueue.h\"\n#include \"CAHostTimeBase.h\"\n\n\n#pragma clang assume_nonnull begin\n\nstatic const Float64 kSampleRate = 44100.0;\nstatic const UInt32 kZeroTimeStampPeriod = 10000;  // Arbitrary.\n\n#pragma mark Construction/Destruction\n\npthread_once_t     BGM_NullDevice::sStaticInitializer = PTHREAD_ONCE_INIT;\nBGM_NullDevice*    BGM_NullDevice::sInstance = nullptr;\n\nBGM_NullDevice&    BGM_NullDevice::GetInstance()\n{\n    pthread_once(&sStaticInitializer, StaticInitializer);\n    return *sInstance;\n}\n\nvoid    BGM_NullDevice::StaticInitializer()\n{\n    try\n    {\n        sInstance = new BGM_NullDevice;\n        // Note that we leave the device inactive initially. BGMApp will activate it when needed.\n    }\n    catch(...)\n    {\n        DebugMsg(\"BGM_NullDevice::StaticInitializer: Failed to create the device\");\n        delete sInstance;\n        sInstance = nullptr;\n    }\n}\n\nBGM_NullDevice::BGM_NullDevice()\n:\n    BGM_AbstractDevice(kObjectID_Device_Null, kAudioObjectPlugInObject),\n    mStateMutex(\"Null Device State\"),\n    mIOMutex(\"Null Device IO\"),\n    mStream(kObjectID_Stream_Null, kObjectID_Device_Null, false, kSampleRate)\n{\n}\n\nBGM_NullDevice::~BGM_NullDevice()\n{\n}\n\nvoid    BGM_NullDevice::Activate()\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    if(!IsActive())\n    {\n        // Call the super-class, which just marks the object as active.\n        BGM_AbstractDevice::Activate();\n\n        // Calculate the number of host clock ticks per frame for this device's clock.\n        mHostTicksPerFrame = CAHostTimeBase::GetFrequency() / kSampleRate;\n\n        SendDeviceIsAlivePropertyNotifications();\n    }\n}\n\nvoid    BGM_NullDevice::Deactivate()\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    if(IsActive())\n    {\n        CAMutex::Locker theIOLocker(mIOMutex);\n\n        // Mark the object inactive by calling the super-class.\n        BGM_AbstractDevice::Deactivate();\n\n        SendDeviceIsAlivePropertyNotifications();\n    }\n}\n\nvoid    BGM_NullDevice::SendDeviceIsAlivePropertyNotifications()\n{\n    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n        AudioObjectPropertyAddress theChangedProperties[] = {\n            CAPropertyAddress(kAudioDevicePropertyDeviceIsAlive)\n        };\n\n        BGM_PlugIn::Host_PropertiesChanged(GetObjectID(), 1, theChangedProperties);\n    });\n}\n\n#pragma mark Property Operations\n\nbool    BGM_NullDevice::HasProperty(AudioObjectID inObjectID,\n                                    pid_t inClientPID,\n                                    const AudioObjectPropertyAddress& inAddress) const\n{\n    if(inObjectID == mStream.GetObjectID())\n    {\n        return mStream.HasProperty(inObjectID, inClientPID, inAddress);\n    }\n\n    bool theAnswer = false;\n    switch(inAddress.mSelector)\n    {\n        case kAudioDevicePropertyDeviceCanBeDefaultDevice:\n        case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_AbstractDevice::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n    return theAnswer;\n}\n\nbool    BGM_NullDevice::IsPropertySettable(AudioObjectID inObjectID,\n                                           pid_t inClientPID,\n                                           const AudioObjectPropertyAddress& inAddress) const\n{\n    // Forward stream properties.\n    if(inObjectID == mStream.GetObjectID())\n    {\n        return mStream.IsPropertySettable(inObjectID, inClientPID, inAddress);\n    }\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        default:\n            theAnswer = BGM_AbstractDevice::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32    BGM_NullDevice::GetPropertyDataSize(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress,\n                                            UInt32 inQualifierDataSize,\n                                            const void* __nullable inQualifierData) const\n{\n    // Forward stream properties.\n    if(inObjectID == mStream.GetObjectID())\n    {\n        return mStream.GetPropertyDataSize(inObjectID,\n                                           inClientPID,\n                                           inAddress,\n                                           inQualifierDataSize,\n                                           inQualifierData);\n    }\n\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioDevicePropertyStreams:\n            theAnswer = 1 * sizeof(AudioObjectID);\n            break;\n\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n            theAnswer = 1 * sizeof(AudioValueRange);\n            break;\n\n        default:\n            theAnswer = BGM_AbstractDevice::GetPropertyDataSize(inObjectID,\n                                                                inClientPID,\n                                                                inAddress,\n                                                                inQualifierDataSize,\n                                                                inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_NullDevice::GetPropertyData(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress,\n                                        UInt32 inQualifierDataSize,\n                                        const void* __nullable inQualifierData,\n                                        UInt32 inDataSize,\n                                        UInt32& outDataSize,\n                                        void* outData) const\n{\n    // Forward stream properties.\n    if(inObjectID == mStream.GetObjectID())\n    {\n        return mStream.GetPropertyData(inObjectID,\n                                       inClientPID,\n                                       inAddress,\n                                       inQualifierDataSize,\n                                       inQualifierData,\n                                       inDataSize,\n                                       outDataSize,\n                                       outData);\n    }\n\n    // See BGM_Device::Device_GetPropertyData for more information about these properties.\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyName:\n            ThrowIf(inDataSize < sizeof(AudioObjectID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioObjectPropertyName for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = CFSTR(kNullDeviceName);\n            outDataSize = sizeof(CFStringRef);\n            break;\n\n        case kAudioObjectPropertyManufacturer:\n            ThrowIf(inDataSize < sizeof(AudioObjectID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioObjectPropertyManufacturer for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = CFSTR(kNullDeviceManufacturerName);\n            outDataSize = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyDeviceUID:\n            ThrowIf(inDataSize < sizeof(AudioObjectID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceUID for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = CFSTR(kBGMNullDeviceUID);\n            outDataSize = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyModelUID:\n            ThrowIf(inDataSize < sizeof(AudioObjectID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyModelUID for the device\");\n            *reinterpret_cast<CFStringRef*>(outData) = CFSTR(kBGMNullDeviceModelUID);\n            outDataSize = sizeof(CFStringRef);\n            break;\n\n        case kAudioDevicePropertyDeviceIsAlive:\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyDeviceIsAlive for the device\");\n            *reinterpret_cast<UInt32*>(outData) = IsActive() ? 1 : 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioDevicePropertyDeviceIsRunning:\n            {\n                ThrowIf(inDataSize < sizeof(UInt32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                        \"kAudioDevicePropertyDeviceIsRunning for the device\");\n                CAMutex::Locker theStateLocker(mStateMutex);\n                // 1 means the device is running, i.e. doing IO.\n                *reinterpret_cast<UInt32*>(outData) = (mClientsDoingIO > 0) ? 1 : 0;\n                outDataSize = sizeof(UInt32);\n            }\n            break;\n\n        case kAudioDevicePropertyStreams:\n            if(inDataSize >= sizeof(AudioObjectID) &&\n               (inAddress.mScope == kAudioObjectPropertyScopeGlobal ||\n                inAddress.mScope == kAudioObjectPropertyScopeOutput))\n            {\n                // Return the ID of this device's stream.\n                reinterpret_cast<AudioObjectID*>(outData)[0] = kObjectID_Stream_Null;\n                // Report how much we wrote.\n                outDataSize = 1 * sizeof(AudioObjectID);\n            }\n            else\n            {\n                // Return nothing if we don't have a stream of the given scope or there's no room\n                // for the response.\n                outDataSize = 0;\n            }\n            break;\n\n        case kAudioDevicePropertyNominalSampleRate:\n            ThrowIf(inDataSize < sizeof(Float64),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyNominalSampleRate for the device\");\n            *reinterpret_cast<Float64*>(outData) = kSampleRate;\n            outDataSize = sizeof(Float64);\n            break;\n\n        case kAudioDevicePropertyAvailableNominalSampleRates:\n            // Check we were given space to return something.\n            if((inDataSize / sizeof(AudioValueRange)) >= 1)\n            {\n                // This device doesn't support changing the sample rate.\n                reinterpret_cast<AudioValueRange*>(outData)[0].mMinimum = kSampleRate;\n                reinterpret_cast<AudioValueRange*>(outData)[0].mMaximum = kSampleRate;\n                outDataSize = sizeof(AudioValueRange);\n            }\n            else\n            {\n                outDataSize = 0;\n            }\n            break;\n\n        case kAudioDevicePropertyZeroTimeStampPeriod:\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_NullDevice::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioDevicePropertyZeroTimeStampPeriod for the device\");\n            *reinterpret_cast<UInt32*>(outData) = kZeroTimeStampPeriod;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        default:\n            BGM_AbstractDevice::GetPropertyData(inObjectID,\n                                                inClientPID,\n                                                inAddress,\n                                                inQualifierDataSize,\n                                                inQualifierData,\n                                                inDataSize,\n                                                outDataSize,\n                                                outData);\n            break;\n    };\n}\n\nvoid    BGM_NullDevice::SetPropertyData(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress,\n                                        UInt32 inQualifierDataSize,\n                                        const void* inQualifierData,\n                                        UInt32 inDataSize,\n                                        const void* inData)\n{\n    // This device doesn't have any settable properties, so just pass stream properties along.\n    if(inObjectID == mStream.GetObjectID())\n    {\n        mStream.SetPropertyData(inObjectID,\n                                inClientPID,\n                                inAddress,\n                                inQualifierDataSize,\n                                inQualifierData,\n                                inDataSize,\n                                inData);\n    }\n    else if(inObjectID == GetObjectID())\n    {\n        BGM_AbstractDevice::SetPropertyData(inObjectID,\n                                            inClientPID,\n                                            inAddress,\n                                            inQualifierDataSize,\n                                            inQualifierData,\n                                            inDataSize,\n                                            inData);\n    }\n    else\n    {\n        Throw(CAException(kAudioHardwareBadObjectError));\n    }\n}\n\n#pragma mark IO Operations\n\n\nvoid    BGM_NullDevice::StartIO(UInt32 inClientID)\n{\n    #pragma unused (inClientID)\n\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    if(mClientsDoingIO == 0)\n    {\n        // Reset the clock.\n        mNumberTimeStamps = 0;\n        mAnchorHostTime = CAHostTimeBase::GetTheCurrentTime();\n\n        // Send notifications.\n        DebugMsg(\"BGM_NullDevice::StartIO: Sending kAudioDevicePropertyDeviceIsRunning\");\n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n            AudioObjectPropertyAddress theChangedProperty[] = {\n                CAPropertyAddress(kAudioDevicePropertyDeviceIsRunning)\n            };\n            BGM_PlugIn::Host_PropertiesChanged(kObjectID_Device_Null, 1, theChangedProperty);\n        });\n    }\n\n    mClientsDoingIO++;\n}\n\nvoid    BGM_NullDevice::StopIO(UInt32 inClientID)\n{\n    #pragma unused (inClientID)\n\n    CAMutex::Locker theStateLocker(mStateMutex);\n\n    ThrowIf(mClientsDoingIO == 0,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGM_NullDevice::StopIO: Underflowed mClientsDoingIO\");\n\n    mClientsDoingIO--;\n\n    if(mClientsDoingIO == 0)\n    {\n        // Send notifications.\n        DebugMsg(\"BGM_NullDevice::StopIO: Sending kAudioDevicePropertyDeviceIsRunning\");\n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n            AudioObjectPropertyAddress theChangedProperty[] = {\n                CAPropertyAddress(kAudioDevicePropertyDeviceIsRunning)\n            };\n            BGM_PlugIn::Host_PropertiesChanged(kObjectID_Device_Null, 1, theChangedProperty);\n        });\n    }\n}\n\nvoid    BGM_NullDevice::GetZeroTimeStamp(Float64& outSampleTime,\n                                         UInt64& outHostTime,\n                                         UInt64& outSeed)\n{\n    CAMutex::Locker theIOLocker(mIOMutex);\n\n    // Not sure whether there's actually any point to implementing this. The documentation says that\n    // clockless devices don't need to, but if the device doesn't have\n    // kAudioDevicePropertyZeroTimeStampPeriod the HAL seems to reject it. So we give it a simple\n    // clock similar to the loopback clock in BGM_Device.\n    UInt64 theCurrentHostTime = CAHostTimeBase::GetTheCurrentTime();\n\n    // Calculate the next host time.\n    Float64 theHostTicksPerPeriod = mHostTicksPerFrame * static_cast<Float64>(kZeroTimeStampPeriod);\n    Float64 theHostTickOffset = static_cast<Float64>(mNumberTimeStamps + 1) * theHostTicksPerPeriod;\n    UInt64 theNextHostTime = mAnchorHostTime + static_cast<UInt64>(theHostTickOffset);\n\n    // Go to the next period if the next host time is less than the current time.\n    if(theNextHostTime <= theCurrentHostTime)\n    {\n        mNumberTimeStamps++;\n    }\n\n    Float64 theHostTicksSinceAnchor =\n        (static_cast<Float64>(mNumberTimeStamps) * theHostTicksPerPeriod);\n\n    // Set the return values.\n    outSampleTime = mNumberTimeStamps * kZeroTimeStampPeriod;\n    outHostTime = static_cast<UInt64>(mAnchorHostTime + theHostTicksSinceAnchor);\n    outSeed = 1;\n}\n\nvoid    BGM_NullDevice::WillDoIOOperation(UInt32 inOperationID,\n                                          bool& outWillDo,\n                                          bool& outWillDoInPlace) const\n{\n    switch(inOperationID)\n    {\n        case kAudioServerPlugInIOOperationWriteMix:\n            outWillDo = true;\n            outWillDoInPlace = true;\n            break;\n\n        default:\n            outWillDo = false;\n            outWillDoInPlace = true;\n            break;\n            \n    };\n}\n\nvoid    BGM_NullDevice::DoIOOperation(AudioObjectID inStreamObjectID,\n                                      UInt32 inClientID,\n                                      UInt32 inOperationID,\n                                      UInt32 inIOBufferFrameSize,\n                                      const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                      void* ioMainBuffer,\n                                      void* __nullable ioSecondaryBuffer)\n{\n    #pragma unused (inStreamObjectID, inClientID, inOperationID, inIOCycleInfo, inIOBufferFrameSize)\n    #pragma unused (ioMainBuffer, ioSecondaryBuffer)\n    // Ignore the audio data.\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_NullDevice.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_NullDevice.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  A device with one output stream that ignores any audio played on that stream.\n//\n//  If we change BGMDevice's controls list, to match the output device set in BGMApp, we need to\n//  change the OS X default device so other programs (including the OS X audio UI) will update\n//  themselves. We could just change to the real output device and change back, but that could have\n//  side effects the user wouldn't expect. For example, an app the user had muted might be unmuted\n//  for a short period.\n//\n//  Instead, BGMApp temporarily enables this device and uses it to toggle the default device. This\n//  device is disabled at all other times so it can be hidden from the user. (We can't just use\n//  kAudioDevicePropertyIsHidden because hidden devices can't be default and the HAL doesn't seem to\n//  let devices change kAudioDevicePropertyIsHidden after setting it initially.)\n//\n//  It might be worth eventually having a virtual device for each real output device, but this is\n//  simpler and seems to work well enough for now.\n//\n\n#ifndef BGMDriver__BGM_NullDevice\n#define BGMDriver__BGM_NullDevice\n\n// SuperClass Includes\n#include \"BGM_AbstractDevice.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Stream.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n\n// System Includes\n#include <pthread.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_NullDevice\n:\n    public BGM_AbstractDevice\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n    static BGM_NullDevice&      GetInstance();\n\nprivate:\n    static void                 StaticInitializer();\n\nprotected:\n                                BGM_NullDevice();\n    virtual                     ~BGM_NullDevice();\n\npublic:\n    virtual void                Activate();\n    virtual void                Deactivate();\n\nprivate:\n    void                        SendDeviceIsAlivePropertyNotifications();\n\n#pragma mark Property Operations\n\npublic:\n    bool                        HasProperty(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress) const;\n    bool                        IsPropertySettable(AudioObjectID inObjectID,\n                                                   pid_t inClientPID,\n                                                   const AudioObjectPropertyAddress& inAddress) const;\n    UInt32                      GetPropertyDataSize(AudioObjectID inObjectID,\n                                                    pid_t inClientPID,\n                                                    const AudioObjectPropertyAddress& inAddress,\n                                                    UInt32 inQualifierDataSize,\n                                                    const void* __nullable inQualifierData) const;\n    void                        GetPropertyData(AudioObjectID inObjectID,\n                                                pid_t inClientPID,\n                                                const AudioObjectPropertyAddress& inAddress,\n                                                UInt32 inQualifierDataSize,\n                                                const void* __nullable inQualifierData,\n                                                UInt32 inDataSize,\n                                                UInt32& outDataSize,\n                                                void* outData) const;\n    void                        SetPropertyData(AudioObjectID inObjectID,\n                                                pid_t inClientPID,\n                                                const AudioObjectPropertyAddress& inAddress,\n                                                UInt32 inQualifierDataSize,\n                                                const void* inQualifierData,\n                                                UInt32 inDataSize,\n                                                const void* inData);\n\n#pragma mark IO Operations\n\npublic:\n    void                        StartIO(UInt32 inClientID);\n    void                        StopIO(UInt32 inClientID);\n\n    void                        GetZeroTimeStamp(Float64& outSampleTime,\n                                                 UInt64& outHostTime,\n                                                 UInt64& outSeed);\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-parameter\"\n    void                        WillDoIOOperation(UInt32 inOperationID,\n                                                  bool& outWillDo,\n                                                  bool& outWillDoInPlace) const;\n    void                        BeginIOOperation(UInt32 inOperationID,\n                                                 UInt32 inIOBufferFrameSize,\n                                                 const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                                 UInt32 inClientID)\n                                    { /* No-op */ };\n    void                        DoIOOperation(AudioObjectID inStreamObjectID,\n                                              UInt32 inClientID,\n                                              UInt32 inOperationID,\n                                              UInt32 inIOBufferFrameSize,\n                                              const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                              void* ioMainBuffer,\n                                              void* __nullable ioSecondaryBuffer);\n    void                        EndIOOperation(UInt32 inOperationID,\n                                               UInt32 inIOBufferFrameSize,\n                                               const AudioServerPlugInIOCycleInfo& inIOCycleInfo,\n                                               UInt32 inClientID)\n                                    { /* No-op */ };\n\n#pragma mark Implementation\n\npublic:\n    CFStringRef                 CopyDeviceUID() const\n                                    { return CFSTR(kBGMNullDeviceUID); };\n\n    void                        AddClient(const AudioServerPlugInClientInfo* inClientInfo)\n                                    { /* No-op */ };\n    void                        RemoveClient(const AudioServerPlugInClientInfo* inClientInfo)\n                                    { /* No-op */ };\n    void                        PerformConfigChange(UInt64 inChangeAction,\n                                                    void* __nullable inChangeInfo)\n                                    { /* No-op */ };\n    void                        AbortConfigChange(UInt64 inChangeAction,\n                                                  void* __nullable inChangeInfo)\n                                    { /* No-op */ };\n#pragma clang diagnostic pop\n\nprivate:\n    static pthread_once_t       sStaticInitializer;\n    static BGM_NullDevice*      sInstance;\n\n    #define kNullDeviceName     \"Background Music Null Device\"\n    #define kNullDeviceManufacturerName \\\n                                \"Background Music contributors\"\n\n    CAMutex                     mStateMutex;\n    CAMutex                     mIOMutex;\n\n    BGM_Stream                  mStream;\n\n    UInt32                      mClientsDoingIO    = 0;\n\n    Float64                     mHostTicksPerFrame = 0.0;\n    UInt64                      mNumberTimeStamps  = 0;\n    UInt64                      mAnchorHostTime    = 0;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_NullDevice */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Object.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Object.cpp\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_Object.cpp from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n//  Similarly to BGM_Object.h, this file hasn't been changed much from SA_Object.cpp, except to\n//  remove the SA_ObjectMap class.\n//\n\n//\tSelf Include\n#include \"BGM_Object.h\"\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n\n//==================================================================================================\n#pragma mark -\n#pragma mark BGM_Object\n//==================================================================================================\n\n#pragma mark Construction/Destruction\n\nBGM_Object::BGM_Object(AudioObjectID inObjectID, AudioClassID inClassID, AudioClassID inBaseClassID, AudioObjectID inOwnerObjectID)\n:\n\tmObjectID(inObjectID),\n\tmClassID(inClassID),\n\tmBaseClassID(inBaseClassID),\n\tmOwnerObjectID(inOwnerObjectID),\n\tmIsActive(false)\n{\n}\n\nvoid\tBGM_Object::Activate()\n{\n\tmIsActive = true;\n}\n\nvoid\tBGM_Object::Deactivate()\n{\n\tmIsActive = false;\n}\n\nBGM_Object::~BGM_Object()\n{\n}\n\n#pragma mark Property Operations\n\nbool\tBGM_Object::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\t#pragma unused(inObjectID, inClientPID)\n\t\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyBaseClass:\n\t\tcase kAudioObjectPropertyClass:\n\t\tcase kAudioObjectPropertyOwner:\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\t\ttheAnswer = true;\n\t\t\tbreak;\n\t};\n\treturn theAnswer;\n}\n\nbool\tBGM_Object::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\t#pragma unused(inObjectID, inClientPID)\n\t\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyBaseClass:\n\t\tcase kAudioObjectPropertyClass:\n\t\tcase kAudioObjectPropertyOwner:\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\t\ttheAnswer = false;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\tThrow(CAException(kAudioHardwareUnknownPropertyError));\n\t};\n\treturn theAnswer;\n}\n\nUInt32\tBGM_Object::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n\t#pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData)\n\t\n\tUInt32 theAnswer = 0;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyBaseClass:\n\t\tcase kAudioObjectPropertyClass:\n\t\t\ttheAnswer = sizeof(AudioClassID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwner:\n\t\t\ttheAnswer = sizeof(AudioObjectID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\t\ttheAnswer = 0;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\tThrow(CAException(kAudioHardwareUnknownPropertyError));\n\t};\n\treturn theAnswer;\n}\n\nvoid\tBGM_Object::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const\n{\n\t#pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData)\n\t\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyBaseClass:\n\t\t\t//\tThis is the AudioClassID of the base class of this object. This is an invariant.\n\t\t\tThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyBaseClass\");\n\t\t\t*reinterpret_cast<AudioClassID*>(outData) = mBaseClassID;\n\t\t\toutDataSize = sizeof(AudioClassID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyClass:\n\t\t\t//\tThis is the AudioClassID of the class of this object. This is an invariant.\n\t\t\tThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyClass\");\n\t\t\t*reinterpret_cast<AudioClassID*>(outData) = mClassID;\n\t\t\toutDataSize = sizeof(AudioClassID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwner:\n\t\t\t//\tThe AudioObjectID of the object that owns this object. This is an invariant.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_Object::GetPropertyData: not enough space for the return value of kAudioObjectPropertyOwner\");\n\t\t\t*reinterpret_cast<AudioClassID*>(outData) = mOwnerObjectID;\n\t\t\toutDataSize = sizeof(AudioObjectID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\t\t//\tThis is an array of AudioObjectIDs for the objects owned by this object. By default,\n\t\t\t//\tobjects don't own any other objects. This is an invariant by default, but an object\n\t\t\t//\tthat can contain other objects will likely need to do some synchronization to access\n\t\t\t//\tthis property.\n\t\t\toutDataSize = 0;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\tThrow(CAException(kAudioHardwareUnknownPropertyError));\n\t};\n}\n\nvoid\tBGM_Object::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n\t#pragma unused(inObjectID, inClientPID, inQualifierDataSize, inQualifierData, inDataSize, inData)\n\t\n\tswitch(inAddress.mSelector)\n\t{\n\t\tdefault:\n\t\t\tThrow(CAException(kAudioHardwareUnknownPropertyError));\n\t};\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Object.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Object.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_Object.h from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n//  The base class for our classes that represent audio objects. (See AudioServerPlugIn.h for a\n//  quick explanation of audio objects.)\n// \n//  This is a stripped down version of SA_Object.h. Unlike the sample code plugin that SA_Object.h\n//  is from, we have a fixed set of audio objects. So we've removed the code that handled\n//  growing/shrinking that set.\n//\n\n#ifndef __BGMDriver__BGM_Object__\n#define __BGMDriver__BGM_Object__\n\n//\tSystem Includes\n#include <CoreAudio/AudioServerPlugIn.h>\n\n\n//  TODO: Update the large comment below to reflect what was removed from SA_Object.h? It hasn't\n//        been changed at all so far, except to replace \"SA_Object\" with \"BGM_Object\".\n\n//==================================================================================================\n//\tBGM_Object\n//\n//\tThis is the base class for objects managed by BGM_ObjectMap. It's only job is to ensure that\n//\tobjects of this type have the proper external semantics for a reference counted object. This\n//\tmeans that the destructor is protected so that these objects cannot be deleted directly. Also,\n//\tthese objects many not make a copy of another object or be assigned from another object. Note\n//\tthat the reference count of the object is tracked and owned by the BGM_ObjectMap.\n//\n//\tThese objects provide RTTI information tied to the constants describing the HAL's API class\n//\thierarchy as described in the headers. The class ID and base class IDs passed in to the\n//\tconstructor must operate with the semantics described in AudioObjectBase.h where the base class\n//\thas to always be one of the standard classes. The class ID can be a custom value or a standard\n//\tvalue however. If it is a standard value, the base class should be the proper standard base\n//\tclass. So for example, a standard volume control object will say that it's class is\n//\tkAudioVolumeControlClassID and that its base class is kAudioLevelControlClassID. In the case of\n//\ta custom boolean control, it would say that it's class is a custom value like 'MYBL' and that\n//\tits base class is kAudioBooleanControlClassID.\n//\n//\tSubclasses of this class must implement Activate(). This method is called after an object has\n//\tbeen constructed and inserted into the object map. Until Activate() is called, a constructed\n//\tobject may not do anything active such as sending/receiving notifications or creating other\n//\tobjects. Active operations may be performed in the Activate() method proper however. Note that\n//\tActivate() is called prior to any references to the object being handed out. As such, it does\n//\tnot need to worry about being thread safe while Activate() is in progress.\n//\n//\tSubclasses of this class must also implement Deactivate(). This method is called when the object\n//\tis at the end of its lifecycle. Once Deactivate() has been called, the object may no longer\n//\tperform active operations, including Deactivating other objects. This is based on the notion that\n//\tall the objects have a definite point at which they are considered dead to the outside world.\n//\tFor example, an AudioDevice object is dead if it's hardware is unplugged. The point of death is\n//\tthe notification the owner of the device gets to signal that it has been unplugged. Note that it\n//\tis both normal and expected that a dead object might still have outstanding references. Thus, an\n//\tobject has to put in some care to do the right thing when these zombie references are used. The\n//\tbest thing to do is to just have those queries return appropriate errors.\n//\n//\tDeactivate() itself needs to be thread safe with respect to other operations taking place on the\n//\tobject. This also means taking care to handle the Deactivation of owned objects. For example, an\n//\tAudioDevice object will almost always own one or more AudioStream objects. If the stream is in a\n//\tseparate lock domain from it's owning device, then the device has to be very careful about how\n//\tit deactivates the stream such that it doesn't try to lock the stream's lock while holding the\n//\tdevice's lock which will inevitably lead to a deadlock situation. There are two reasonable\n//\tapproaches to dealing with this kind of situation. The first is to just not get into it by\n//\tmaking the device share a lock domain with all it's owned objects like streams and controls. The\n//\tother approach is to use dispatch queues to make the work of Deactivating owned objects take\n//\tplace outside of the device's lock domain. For example, if the device needs to deactivate a\n//\tstream, it can remove the stream from any tracking in the device object and then dispatch\n//\tasynchronously the Deactivate() call on the stream and the release of the reference the device\n//\thas on the stream.\n//\n//\tNote that both Activate() and Deactivate() are called by objects at large. Typically,\n//\tActivate() is called by the creator of the object, usually right after the object has been\n//\tallocated. Deactivate() will usually be called by the owner of the object upon recognizing that\n//\tthe object is dead to the outside world. Going back to the example of an AudioDevice getting\n//\tunplugged, the Deactivate() method will be called by whomever receives the notification about\n//\tthe hardware going away, which is often the owner of the object.\n//\n//\tThis class also defines methods to implement the portion of the\n//\tAudioServerPlugInDriverInterface that deals with properties. The five methods all have the same\n//\tbasic arguments and semantics. The class also provides the implementation for\n//\tthe minimum required properties for all AudioObjects. There is a detailed commentary about each\n//\tspecific property in the GetPropertyData() method.\n//\n//\tIt is important that a thread retain and hold a reference while it is using an BGM_Object and \n//\tthat the reference be released promptly when the thread is finished using the object. By\n//\tassuming this, a BGM_Object can minimize the amount of locking it needs to do. In particular,\n//\tpurely static or invariant data can be handled without any locking at all.\n//==================================================================================================\n\nclass BGM_Object\n{\n\n#pragma mark Construction/Destruction\n    \npublic:\n\t\t\t\t\t\tBGM_Object(AudioObjectID inObjectID, AudioClassID inClassID, AudioClassID inBaseClassID, AudioObjectID inOwnerObjectID);\n\t\t\t\t\t\n\tvirtual void\t\tActivate();\n\tvirtual void\t\tDeactivate();\n\nprotected:\n\tvirtual\t\t\t\t~BGM_Object();\n\nprivate:\n\t\t\t\t\t\tBGM_Object(const BGM_Object&);\n\tBGM_Object&\t\t\toperator=(const BGM_Object&);\n\n#pragma mark Attributes\n    \npublic:\n\tAudioObjectID\t\tGetObjectID() const\t\t\t{ return mObjectID; }\n\tvoid*\t\t\t\tGetObjectIDAsPtr() const\t{ uintptr_t thePtr = mObjectID; return reinterpret_cast<void*>(thePtr); }\n\tAudioClassID\t\tGetClassID() const\t\t\t{ return mClassID; }\n\tAudioClassID\t\tGetBaseClassID() const\t\t{ return mBaseClassID; }\n\tAudioObjectID\t\tGetOwnerObjectID() const\t{ return mOwnerObjectID; }\n\tbool\t\t\t\tIsActive() const\t\t\t{ return mIsActive; }\n\n#pragma mark Property Operations\n    \npublic:\n\tvirtual bool\t\tHasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual bool\t\tIsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual UInt32\t\tGetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const;\n\tvirtual void\t\tGetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const;\n\tvirtual void\t\tSetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData);\n\n#pragma mark Implementation\n    \nprotected:\n\tAudioObjectID\t\tmObjectID;\n\tAudioClassID\t\tmClassID;\n\tAudioClassID\t\tmBaseClassID;\n\tAudioObjectID\t\tmOwnerObjectID;\n\tbool\t\t\t\tmIsActive;\n\n};\n\n#endif /* __BGMDriver__BGM_Object__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_PlugIn.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_PlugIn.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n\n//\tSelf Include\n#include \"BGM_PlugIn.h\"\n\n//  Local Includes\n#include \"BGM_Device.h\"\n#include \"BGM_NullDevice.h\"\n\n//  PublicUtility Includes\n#include \"CAException.h\"\n#include \"CADebugMacros.h\"\n#include \"CAPropertyAddress.h\"\n#include \"CADispatchQueue.h\"\n\n\n#pragma mark Construction/Destruction\n\npthread_once_t\t\t\t\tBGM_PlugIn::sStaticInitializer = PTHREAD_ONCE_INIT;\nBGM_PlugIn*\t\t\t\t\tBGM_PlugIn::sInstance = NULL;\nAudioServerPlugInHostRef\tBGM_PlugIn::sHost = NULL;\n\nBGM_PlugIn& BGM_PlugIn::GetInstance()\n{\n    pthread_once(&sStaticInitializer, StaticInitializer);\n    return *sInstance;\n}\n\nvoid\tBGM_PlugIn::StaticInitializer()\n{\n    try\n    {\n        sInstance = new BGM_PlugIn;\n        sInstance->Activate();\n    }\n    catch(...)\n    {\n        DebugMsg(\"BGM_PlugIn::StaticInitializer: failed to create the plug-in\");\n        delete sInstance;\n        sInstance = NULL;\n    }\n}\n\nBGM_PlugIn::BGM_PlugIn()\n:\n\tBGM_Object(kAudioObjectPlugInObject, kAudioPlugInClassID, kAudioObjectClassID, 0),\n\tmMutex(\"BGM_PlugIn\")\n{\n}\n\nBGM_PlugIn::~BGM_PlugIn()\n{\n}\n\nvoid\tBGM_PlugIn::Deactivate()\n{\n\tCAMutex::Locker theLocker(mMutex);\n\tBGM_Object::Deactivate();\n    // TODO:\n\t//_RemoveAllDevices();\n}\n\n#pragma mark Property Operations\n\nbool\tBGM_PlugIn::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyManufacturer:\n\t\tcase kAudioPlugInPropertyDeviceList:\n\t\tcase kAudioPlugInPropertyTranslateUIDToDevice:\n        case kAudioPlugInPropertyResourceBundle:\n        case kAudioObjectPropertyCustomPropertyInfoList:\n        case kAudioPlugInCustomPropertyNullDeviceActive:\n\t\t\ttheAnswer = true;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_Object::HasProperty(inObjectID, inClientPID, inAddress);\n\t};\n\treturn theAnswer;\n}\n\nbool\tBGM_PlugIn::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyManufacturer:\n\t\tcase kAudioPlugInPropertyDeviceList:\n\t\tcase kAudioPlugInPropertyTranslateUIDToDevice:\n        case kAudioPlugInPropertyResourceBundle:\n        case kAudioObjectPropertyCustomPropertyInfoList:\n\t\t\ttheAnswer = false;\n\t\t\tbreak;\n\n        case kAudioPlugInCustomPropertyNullDeviceActive:\n            theAnswer = true;\n            break;\n\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);\n\t};\n\treturn theAnswer;\n}\n\nUInt32\tBGM_PlugIn::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const\n{\n\tUInt32 theAnswer = 0;\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyManufacturer:\n\t\t\ttheAnswer = sizeof(CFStringRef);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwnedObjects:\n\t\tcase kAudioPlugInPropertyDeviceList:\n            // The plug-in owns the main BGM_Device, the instance of BGM_Device that handles UI\n            // sounds and, if it's enabled, the null device.\n            theAnswer = (BGM_NullDevice::GetInstance().IsActive() ? 3 : 2) * sizeof(AudioObjectID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioPlugInPropertyTranslateUIDToDevice:\n\t\t\ttheAnswer = sizeof(AudioObjectID);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioPlugInPropertyResourceBundle:\n\t\t\ttheAnswer = sizeof(CFStringRef);\n\t\t\tbreak;\n\n        case kAudioObjectPropertyCustomPropertyInfoList:\n            theAnswer = sizeof(AudioServerPlugInCustomPropertyInfo);\n            break;\n\n        case kAudioPlugInCustomPropertyNullDeviceActive:\n            theAnswer = sizeof(CFBooleanRef);\n            break;\n\t\t\n\t\tdefault:\n\t\t\ttheAnswer = BGM_Object::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData);\n\t};\n\treturn theAnswer;\n}\n\nvoid\tBGM_PlugIn::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const\n{\n\tswitch(inAddress.mSelector)\n\t{\n\t\tcase kAudioObjectPropertyManufacturer:\n\t\t\t//\tThis is the human readable name of the maker of the plug-in.\n\t\t\tThrowIf(inDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_PlugIn::GetPropertyData: not enough space for the return value of kAudioObjectPropertyManufacturer\");\n\t\t\t*reinterpret_cast<CFStringRef*>(outData) = CFSTR(\"Background Music contributors\");\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioObjectPropertyOwnedObjects:\n            // Fall through because this plug-in object only owns the devices.\n\t\tcase kAudioPlugInPropertyDeviceList:\n            {\n    \t\t\tAudioObjectID* theReturnedDeviceList = reinterpret_cast<AudioObjectID*>(outData);\n                if(inDataSize >= 3 * sizeof(AudioObjectID))\n                {\n                    if(BGM_NullDevice::GetInstance().IsActive())\n                    {\n                        theReturnedDeviceList[0] = kObjectID_Device;\n                        theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;\n                        theReturnedDeviceList[2] = kObjectID_Device_Null;\n                        \n                        //\tsay how much we returned\n                        outDataSize = 3 * sizeof(AudioObjectID);\n                    }\n                    else\n                    {\n                        theReturnedDeviceList[0] = kObjectID_Device;\n                        theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;\n\n                        //\tsay how much we returned\n                        outDataSize = 2 * sizeof(AudioObjectID);\n                    }\n                }\n                else if(inDataSize >= 2 * sizeof(AudioObjectID))\n                {\n                    theReturnedDeviceList[0] = kObjectID_Device;\n                    theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;\n\n                    //\tsay how much we returned\n                    outDataSize = 2 * sizeof(AudioObjectID);\n                }\n                else if(inDataSize >= sizeof(AudioObjectID))\n                {\n                    theReturnedDeviceList[0] = kObjectID_Device;\n                    outDataSize = sizeof(AudioObjectID);\n                }\n                else\n                {\n                    outDataSize = 0;\n                }\n            }\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioPlugInPropertyTranslateUIDToDevice:\n            {\n                //\tThis property translates the UID passed in the qualifier as a CFString into the\n                //\tAudioObjectID for the device the UID refers to or kAudioObjectUnknown if no device\n                //\thas the UID.\n                ThrowIf(inQualifierDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), \"BGM_PlugIn::GetPropertyData: the qualifier size is too small for kAudioPlugInPropertyTranslateUIDToDevice\");\n                ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_PlugIn::GetPropertyData: not enough space for the return value of kAudioPlugInPropertyTranslateUIDToDevice\");\n\n                CFStringRef theUID = *reinterpret_cast<const CFStringRef*>(inQualifierData);\n                AudioObjectID* outID = reinterpret_cast<AudioObjectID*>(outData);\n\n                if(CFEqual(theUID, BGM_Device::GetInstance().CopyDeviceUID()))\n                {\n                    DebugMsg(\"BGM_PlugIn::GetPropertyData: Returning BGMDevice for \"\n                             \"kAudioPlugInPropertyTranslateUIDToDevice\");\n                    *outID = kObjectID_Device;\n                }\n                else if(CFEqual(theUID, BGM_Device::GetUISoundsInstance().CopyDeviceUID()))\n                {\n                    DebugMsg(\"BGM_PlugIn::GetPropertyData: Returning BGMUISoundsDevice for \"\n                             \"kAudioPlugInPropertyTranslateUIDToDevice\");\n                    *outID = kObjectID_Device_UI_Sounds;\n                }\n                else if(BGM_NullDevice::GetInstance().IsActive() &&\n                        CFEqual(theUID, BGM_NullDevice::GetInstance().CopyDeviceUID()))\n                {\n                    DebugMsg(\"BGM_PlugIn::GetPropertyData: Returning null device for \"\n                             \"kAudioPlugInPropertyTranslateUIDToDevice\");\n                    *outID = kObjectID_Device_Null;\n                }\n                else\n                {\n                    LogWarning(\"BGM_PlugIn::GetPropertyData: Returning kAudioObjectUnknown for \"\n                               \"kAudioPlugInPropertyTranslateUIDToDevice\");\n                    *outID = kAudioObjectUnknown;\n                }\n\n                outDataSize = sizeof(AudioObjectID);\n            }\n\t\t\tbreak;\n\t\t\t\n\t\tcase kAudioPlugInPropertyResourceBundle:\n\t\t\t//\tThe resource bundle is a path relative to the path of the plug-in's bundle.\n\t\t\t//\tTo specify that the plug-in bundle itself should be used, we just return the\n\t\t\t//\tempty string.\n\t\t\tThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), \"BGM_GetPlugInPropertyData: not enough space for the return value of kAudioPlugInPropertyResourceBundle\");\n\t\t\t*reinterpret_cast<CFStringRef*>(outData) = CFSTR(\"\");\n\t\t\toutDataSize = sizeof(CFStringRef);\n\t\t\tbreak;\n\n        case kAudioObjectPropertyCustomPropertyInfoList:\n            if(inDataSize >= sizeof(AudioServerPlugInCustomPropertyInfo))\n            {\n                AudioServerPlugInCustomPropertyInfo* outCustomProperties =\n                    reinterpret_cast<AudioServerPlugInCustomPropertyInfo*>(outData);\n\n                outCustomProperties[0].mSelector = kAudioPlugInCustomPropertyNullDeviceActive;\n                outCustomProperties[0].mPropertyDataType =\n                    kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;\n                outCustomProperties[0].mQualifierDataType =\n                    kAudioServerPlugInCustomPropertyDataTypeNone;\n\n                outDataSize = sizeof(AudioServerPlugInCustomPropertyInfo);\n            }\n            else\n            {\n                outDataSize = 0;\n            }\n            break;\n\n        case kAudioPlugInCustomPropertyNullDeviceActive:\n            ThrowIf(inDataSize < sizeof(CFBooleanRef),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_PlugIn::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioPlugInCustomPropertyNullDeviceActive\");\n            *reinterpret_cast<CFBooleanRef*>(outData) =\n                BGM_NullDevice::GetInstance().IsActive() ? kCFBooleanTrue : kCFBooleanFalse;\n            outDataSize = sizeof(CFBooleanRef);\n            break;\n\n\t\tdefault:\n\t\t\tBGM_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);\n\t\t\tbreak;\n\t};\n}\n\nvoid\tBGM_PlugIn::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n\tswitch(inAddress.mSelector)\n\t{\n        case kAudioPlugInCustomPropertyNullDeviceActive:\n            {\n                ThrowIf(inDataSize < sizeof(CFBooleanRef),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_PlugIn::SetPropertyData: wrong size for the data for \"\n                        \"kAudioPlugInCustomPropertyNullDeviceActive\");\n\n                CFBooleanRef theIsActiveRef = *reinterpret_cast<const CFBooleanRef*>(inData);\n\n                ThrowIfNULL(theIsActiveRef,\n                            CAException(kAudioHardwareIllegalOperationError),\n                            \"BGM_PlugIn::SetPropertyData: null reference given for \"\n                            \"kAudioPlugInCustomPropertyNullDeviceActive\");\n                ThrowIf(CFGetTypeID(theIsActiveRef) != CFBooleanGetTypeID(),\n                        CAException(kAudioHardwareIllegalOperationError),\n                        \"BGM_PlugIn::SetPropertyData: CFType given for \"\n                        \"kAudioPlugInCustomPropertyNullDeviceActive was not a CFBoolean\");\n\n                bool theIsActive = CFBooleanGetValue(theIsActiveRef);\n\n                if(theIsActive != BGM_NullDevice::GetInstance().IsActive())\n                {\n                    // Activate/deactivate the Null Device. We only make it active for a short\n                    // period, while changing output device in BGMApp, so it can be hidden from the\n                    // user.\n                    if(theIsActive)\n                    {\n                        DebugMsg(\"BGM_PlugIn::SetPropertyData: Activating null device\");\n                        BGM_NullDevice::GetInstance().Activate();\n                    }\n                    else\n                    {\n                        DebugMsg(\"BGM_PlugIn::SetPropertyData: Deactivating null device\");\n                        BGM_NullDevice::GetInstance().Deactivate();\n                    }\n\n                    // Send notifications.\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n                        AudioObjectPropertyAddress theChangedProperties[] = {\n                            CAPropertyAddress(kAudioObjectPropertyOwnedObjects),\n                            CAPropertyAddress(kAudioPlugInPropertyDeviceList)\n                        };\n\n                        Host_PropertiesChanged(GetObjectID(), 2, theChangedProperties);\n                    });\n                }\n            }\n            break;\n            \n\t\tdefault:\n\t\t\tBGM_Object::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);\n\t\t\tbreak;\n\t};\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_PlugIn.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_PlugIn.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_PlugIn.h from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n\n#ifndef __BGMDriver__BGM_PlugIn__\n#define __BGMDriver__BGM_PlugIn__\n\n// SuperClass Includes\n#include \"BGM_Object.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n\n\nclass BGM_PlugIn\n:\n\tpublic BGM_Object\n{\n\n#pragma mark Construction/Destruction\n    \npublic:\n    static BGM_PlugIn&\t\t\t\tGetInstance();\n\nprotected:\n                                    BGM_PlugIn();\n\tvirtual\t\t\t\t\t\t\t~BGM_PlugIn();\n    virtual void\t\t\t\t\tDeactivate();\n    \nprivate:\n    static void\t\t\t\t\t\tStaticInitializer();\n    \n#pragma mark Host Access\n    \npublic:\n\tstatic void\t\t\t\t\t\tSetHost(AudioServerPlugInHostRef inHost)\t{ sHost = inHost; }\n\t\n\tstatic void\t\t\t\t\t\tHost_PropertiesChanged(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[])\t{ if(sHost != NULL) { sHost->PropertiesChanged(sHost, inObjectID, inNumberAddresses, inAddresses); } }\n\tstatic void\t\t\t\t\t\tHost_RequestDeviceConfigurationChange(AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)\t\t\t{ if(sHost != NULL) { sHost->RequestDeviceConfigurationChange(sHost, inDeviceObjectID, inChangeAction, inChangeInfo); } }\n\n#pragma mark Property Operations\n    \npublic:\n\tvirtual bool\t\t\t\t\tHasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual bool\t\t\t\t\tIsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;\n\tvirtual UInt32\t\t\t\t\tGetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const;\n\tvirtual void\t\t\t\t\tGetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const;\n\tvirtual void\t\t\t\t\tSetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData);\n\n#pragma mark Implementation\n    \npublic:\n    const CFStringRef               GetBundleID() const { return CFSTR(kBGMDriverBundleID); }\n    \nprivate:\n    CAMutex\t\t\t\t\t\t\tmMutex;\n    \n    static pthread_once_t\t\t\tsStaticInitializer;\n    static BGM_PlugIn*\t\t\t\tsInstance;\n\tstatic AudioServerPlugInHostRef\tsHost;\n\n};\n\n#endif /* __BGMDriver__BGM_PlugIn__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_PlugInInterface.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_PlugInInterface.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n//  Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.\n//  https://developer.apple.com/library/mac/samplecode/AudioDriverExamples\n//\n\n//\tSystem Includes\n#include <CoreAudio/AudioServerPlugIn.h>\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n//  Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Object.h\"\n#include \"BGM_PlugIn.h\"\n#include \"BGM_Device.h\"\n#include \"BGM_NullDevice.h\"\n\n\n#pragma mark COM Prototypes\n\n//\tEntry points for the COM methods\nextern \"C\" void*\tBGM_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID);\nstatic HRESULT\t\tBGM_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface);\nstatic ULONG\t\tBGM_AddRef(void* inDriver);\nstatic ULONG\t\tBGM_Release(void* inDriver);\nstatic OSStatus\t\tBGM_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerPlugInHostRef inHost);\nstatic OSStatus\t\tBGM_CreateDevice(AudioServerPlugInDriverRef inDriver, CFDictionaryRef inDescription, const AudioServerPlugInClientInfo* inClientInfo, AudioObjectID* outDeviceObjectID);\nstatic OSStatus\t\tBGM_DestroyDevice(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID);\nstatic OSStatus\t\tBGM_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo);\nstatic OSStatus\t\tBGM_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo);\nstatic OSStatus\t\tBGM_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo);\nstatic OSStatus\t\tBGM_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo);\nstatic Boolean\t\tBGM_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress);\nstatic OSStatus\t\tBGM_IsPropertySettable(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable);\nstatic OSStatus\t\tBGM_GetPropertyDataSize(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize);\nstatic OSStatus\t\tBGM_GetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32* outDataSize, void* outData);\nstatic OSStatus\t\tBGM_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData);\nstatic OSStatus\t\tBGM_StartIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID);\nstatic OSStatus\t\tBGM_StopIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID);\nstatic OSStatus\t\tBGM_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed);\nstatic OSStatus\t\tBGM_WillDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace);\nstatic OSStatus\t\tBGM_BeginIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo);\nstatic OSStatus\t\tBGM_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer);\nstatic OSStatus\t\tBGM_EndIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo);\n\n#pragma mark The COM Interface\n\nstatic AudioServerPlugInDriverInterface\tgAudioServerPlugInDriverInterface =\n{\n\tNULL,\n\tBGM_QueryInterface,\n\tBGM_AddRef,\n\tBGM_Release,\n\tBGM_Initialize,\n\tBGM_CreateDevice,\n\tBGM_DestroyDevice,\n\tBGM_AddDeviceClient,\n\tBGM_RemoveDeviceClient,\n\tBGM_PerformDeviceConfigurationChange,\n\tBGM_AbortDeviceConfigurationChange,\n\tBGM_HasProperty,\n\tBGM_IsPropertySettable,\n\tBGM_GetPropertyDataSize,\n\tBGM_GetPropertyData,\n\tBGM_SetPropertyData,\n\tBGM_StartIO,\n\tBGM_StopIO,\n\tBGM_GetZeroTimeStamp,\n\tBGM_WillDoIOOperation,\n\tBGM_BeginIOOperation,\n\tBGM_DoIOOperation,\n\tBGM_EndIOOperation\n};\nstatic AudioServerPlugInDriverInterface*\tgAudioServerPlugInDriverInterfacePtr\t= &gAudioServerPlugInDriverInterface;\nstatic AudioServerPlugInDriverRef\t\t\tgAudioServerPlugInDriverRef\t\t\t\t= &gAudioServerPlugInDriverInterfacePtr;\nstatic UInt32\t\t\t\t\t\t\t\tgAudioServerPlugInDriverRefCount\t\t= 1;\n\n// TODO: This name is a bit misleading because the devices are actually owned by the plug-in.\nstatic BGM_Object& BGM_LookUpOwnerObject(AudioObjectID inObjectID)\n{\n    switch(inObjectID)\n    {\n        case kObjectID_PlugIn:\n            return BGM_PlugIn::GetInstance();\n            \n        case kObjectID_Device:\n        case kObjectID_Stream_Input:\n        case kObjectID_Stream_Output:\n        case kObjectID_Volume_Output_Master:\n        case kObjectID_Mute_Output_Master:\n            return BGM_Device::GetInstance();\n\n        case kObjectID_Device_UI_Sounds:\n        case kObjectID_Stream_Input_UI_Sounds:\n        case kObjectID_Stream_Output_UI_Sounds:\n        case kObjectID_Volume_Output_Master_UI_Sounds:\n            return BGM_Device::GetUISoundsInstance();\n\n        case kObjectID_Device_Null:\n        case kObjectID_Stream_Null:\n            return BGM_NullDevice::GetInstance();\n    }\n    \n    DebugMsg(\"BGM_LookUpOwnerObject: unknown object\");\n    Throw(CAException(kAudioHardwareBadObjectError));\n}\n\nstatic BGM_AbstractDevice& BGM_LookUpDevice(AudioObjectID inObjectID)\n{\n    switch(inObjectID)\n    {\n        case kObjectID_Device:\n            return BGM_Device::GetInstance();\n\n        case kObjectID_Device_UI_Sounds:\n            return BGM_Device::GetUISoundsInstance();\n\n        case kObjectID_Device_Null:\n            return BGM_NullDevice::GetInstance();\n    }\n\n    DebugMsg(\"BGM_LookUpDevice: unknown device\");\n    Throw(CAException(kAudioHardwareBadDeviceError));\n}\n\n#pragma mark Factory\n\nextern \"C\"\nvoid*\tBGM_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID)\n{\n\t//\tThis is the CFPlugIn factory function. Its job is to create the implementation for the given\n\t//\ttype provided that the type is supported. Because this driver is simple and all its\n\t//\tinitialization is handled via static initialization when the bundle is loaded, all that\n\t//\tneeds to be done is to return the AudioServerPlugInDriverRef that points to the driver's\n\t//\tinterface. A more complicated driver would create any base line objects it needs to satisfy\n\t//\tthe IUnknown methods that are used to discover that actual interface to talk to the driver.\n\t//\tThe majority of the driver's initialization should be handled in the Initialize() method of\n\t//\tthe driver's AudioServerPlugInDriverInterface.\n\t\n\t#pragma unused(inAllocator)\n    \n    void* theAnswer = NULL;\n    if(CFEqual(inRequestedTypeUUID, kAudioServerPlugInTypeUUID))\n    {\n\t\ttheAnswer = gAudioServerPlugInDriverRef;\n        \n        BGM_PlugIn::GetInstance();\n    }\n    return theAnswer;\n}\n\n#pragma mark Inheritence\n\nstatic HRESULT\tBGM_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface)\n{\n\t//\tThis function is called by the HAL to get the interface to talk to the plug-in through.\n\t//\tAudioServerPlugIns are required to support the IUnknown interface and the\n\t//\tAudioServerPlugInDriverInterface. As it happens, all interfaces must also provide the\n\t//\tIUnknown interface, so we can always just return the single interface we made with\n\t//\tgAudioServerPlugInDriverInterfacePtr regardless of which one is asked for.\n\n\tHRESULT theAnswer = 0;\n\tCFUUIDRef theRequestedUUID = NULL;\n\t\n\ttry\n\t{\n\t\t//\tvalidate the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_QueryInterface: bad driver reference\");\n\t\tThrowIfNULL(outInterface, CAException(kAudioHardwareIllegalOperationError), \"BGM_QueryInterface: no place to store the returned interface\");\n\n    \t//\tmake a CFUUIDRef from inUUID\n    \ttheRequestedUUID = CFUUIDCreateFromUUIDBytes(NULL, inUUID);\n    \tThrowIf(theRequestedUUID == NULL, CAException(kAudioHardwareIllegalOperationError), \"BGM_QueryInterface: failed to create the CFUUIDRef\");\n\n\t\t//\tAudioServerPlugIns only support two interfaces, IUnknown (which has to be supported by all\n\t\t//\tCFPlugIns) and AudioServerPlugInDriverInterface (which is the actual interface the HAL will\n\t\t//\tuse).\n\t\tThrowIf(!CFEqual(theRequestedUUID, IUnknownUUID) && !CFEqual(theRequestedUUID, kAudioServerPlugInDriverInterfaceUUID), CAException(E_NOINTERFACE), \"BGM_QueryInterface: requested interface is unsupported\");\n\t\tThrowIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, CAException(E_NOINTERFACE), \"BGM_QueryInterface: the ref count is maxxed out\");\n\t\t\n\t\t//\tdo the work\n\t\t++gAudioServerPlugInDriverRefCount;\n\t\t*outInterface = gAudioServerPlugInDriverRef;\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n    \n    if(theRequestedUUID != NULL)\n    {\n    \tCFRelease(theRequestedUUID);\n    }\n\t\t\n\treturn theAnswer;\n}\n\nstatic ULONG\tBGM_AddRef(void* inDriver)\n{\n\t//\tThis call returns the resulting reference count after the increment.\n\t\n\tULONG theAnswer = 0;\n\t\n\t//\tcheck the arguments\n\tFailIf(inDriver != gAudioServerPlugInDriverRef, Done, \"BGM_AddRef: bad driver reference\");\n\tFailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, \"BGM_AddRef: out of references\");\n\n\t//\tincrement the refcount\n\t++gAudioServerPlugInDriverRefCount;\n\ttheAnswer = gAudioServerPlugInDriverRefCount;\n\nDone:\n\treturn theAnswer;\n}\n\nstatic ULONG\tBGM_Release(void* inDriver)\n{\n\t//\tThis call returns the resulting reference count after the decrement.\n\n\tULONG theAnswer = 0;\n\t\n\t//\tcheck the arguments\n\tFailIf(inDriver != gAudioServerPlugInDriverRef, Done, \"BGM_Release: bad driver reference\");\n\tFailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, \"BGM_Release: out of references\");\n\n\t//\tdecrement the refcount\n\t//\tNote that we don't do anything special if the refcount goes to zero as the HAL\n\t//\twill never fully release a plug-in it opens. We keep managing the refcount so that\n\t//\tthe API semantics are correct though.\n\t--gAudioServerPlugInDriverRefCount;\n\ttheAnswer = gAudioServerPlugInDriverRefCount;\n\nDone:\n\treturn theAnswer;\n}\n\n#pragma mark Basic Operations\n\nstatic OSStatus\tBGM_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerPlugInHostRef inHost)\n{\n\t//\tThe job of this method is, as the name implies, to get the driver initialized. One specific\n\t//\tthing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used\n\t//\tlater. Note that when this call returns, the HAL will scan the various lists the driver\n\t//\tmaintains (such as the device list) to get the initial set of objects the driver is\n\t//\tpublishing. So, there is no need to notifiy the HAL about any objects created as part of the\n\t//\texecution of this method.\n\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t// Check the arguments.\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_Initialize: bad driver reference\");\n\t\t\n\t\t// Store the AudioServerPlugInHostRef.\n\t\tBGM_PlugIn::GetInstance().SetHost(inHost);\n        \n        // Init/activate the devices.\n        BGM_Device::GetInstance();\n        BGM_Device::GetUISoundsInstance();\n        BGM_NullDevice::GetInstance();\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_CreateDevice(AudioServerPlugInDriverRef inDriver, CFDictionaryRef inDescription, const AudioServerPlugInClientInfo* inClientInfo, AudioObjectID* outDeviceObjectID)\n{\n\t//\tThis method is used to tell a driver that implements the Transport Manager semantics to\n\t//\tcreate an AudioEndpointDevice from a set of AudioEndpoints. Since this driver is not a\n\t//\tTransport Manager, we just return kAudioHardwareUnsupportedOperationError.\n\t\n\t#pragma unused(inDriver, inDescription, inClientInfo, outDeviceObjectID)\n\t\n\treturn kAudioHardwareUnsupportedOperationError;\n}\n\nstatic OSStatus\tBGM_DestroyDevice(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID)\n{\n\t//\tThis method is used to tell a driver that implements the Transport Manager semantics to\n\t//\tdestroy an AudioEndpointDevice. Since this driver is not a Transport Manager, we just check\n\t//\tthe arguments and return kAudioHardwareUnsupportedOperationError.\n\t\n\t#pragma unused(inDriver, inDeviceObjectID)\n\t\n\treturn kAudioHardwareUnsupportedOperationError;\n}\n\nstatic OSStatus\tBGM_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo)\n{\n\t//\tThis method is used to inform the driver about a new client that is using the given device.\n\t//\tThis allows the device to act differently depending on who the client is.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t// Check the arguments.\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_AddDeviceClient: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_AddDeviceClient: unknown device\");\n\t\t\n\t\t// Inform the device.\n        BGM_LookUpDevice(inDeviceObjectID).AddClient(inClientInfo);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n    catch(BGM_InvalidClientException)\n    {\n        theAnswer = kAudioHardwareIllegalOperationError;\n    }\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo)\n{\n\t//\tThis method is used to inform the driver about a client that is no longer using the given\n\t//\tdevice.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t// Check the arguments.\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_RemoveDeviceClient: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_RemoveDeviceClient: unknown device\");\n\t\t\n        // Inform the device.\n        BGM_LookUpDevice(inDeviceObjectID).RemoveClient(inClientInfo);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n    }\n    catch(BGM_InvalidClientException)\n    {\n        theAnswer = kAudioHardwareIllegalOperationError;\n    }\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)\n{\n\t//\tThis method is called to tell the device that it can perform the configuration change that it\n\t//\thad requested via a call to the host method, RequestDeviceConfigurationChange(). The\n\t//\targuments, inChangeAction and inChangeInfo are the same as what was passed to\n\t//\tRequestDeviceConfigurationChange().\n\t//\n\t//\tThe HAL guarantees that IO will be stopped while this method is in progress. The HAL will\n\t//\talso handle figuring out exactly what changed for the non-control related properties. This\n\t//\tmeans that the only notifications that would need to be sent here would be for either\n\t//\tcustom properties the HAL doesn't know about or for controls.\n\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_PerformDeviceConfigurationChange: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_PerformDeviceConfigurationChange: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).PerformConfigChange(inChangeAction, inChangeInfo);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)\n{\n\t//\tThis method is called to tell the driver that a request for a config change has been denied.\n\t//\tThis provides the driver an opportunity to clean up any state associated with the request.\n\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_PerformDeviceConfigurationChange: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_PerformDeviceConfigurationChange: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).AbortConfigChange(inChangeAction, inChangeInfo);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\n#pragma mark Property Operations\n\nstatic Boolean\tBGM_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress)\n{\n\t//\tThis method returns whether or not the given object has the given property.\n\t\n\tBoolean theAnswer = false;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_HasProperty: bad driver reference\");\n\t\tThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), \"BGM_HasProperty: no address\");\n\t\t\n\t\ttheAnswer = BGM_LookUpOwnerObject(inObjectID).HasProperty(inObjectID, inClientProcessID, *inAddress);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = false;\n\t}\n\tcatch(...)\n\t{\n\t\tLogError(\"BGM_PlugInInterface::BGM_HasProperty: unknown exception. (object: %u, address: %u)\",\n\t\t\t\t inObjectID,\n\t\t\t\t inAddress ? inAddress->mSelector : 0);\n\t\ttheAnswer = false;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_IsPropertySettable(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable)\n{\n\t//\tThis method returns whether or not the given property on the object can have its value\n\t//\tchanged.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_IsPropertySettable: bad driver reference\");\n\t\tThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), \"BGM_IsPropertySettable: no address\");\n\t\tThrowIfNULL(outIsSettable, CAException(kAudioHardwareIllegalOperationError), \"BGM_IsPropertySettable: no place to put the return value\");\n        \n        BGM_Object& theAudioObject = BGM_LookUpOwnerObject(inObjectID);\n\t\tif(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress))\n\t\t{\n\t\t\t*outIsSettable = theAudioObject.IsPropertySettable(inObjectID, inClientProcessID, *inAddress);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttheAnswer = kAudioHardwareUnknownPropertyError;\n\t\t}\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tLogError(\"BGM_PlugInInterface::BGM_IsPropertySettable: unknown exception. (object: %u, address: %u)\",\n\t\t\t\t inObjectID,\n\t\t\t\t inAddress ? inAddress->mSelector : 0);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_GetPropertyDataSize(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize)\n{\n\t//\tThis method returns the byte size of the property's data.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_GetPropertyDataSize: bad driver reference\");\n\t\tThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), \"BGM_GetPropertyDataSize: no address\");\n\t\tThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), \"BGM_GetPropertyDataSize: no place to put the return value\");\n        \n        BGM_Object& theAudioObject = BGM_LookUpOwnerObject(inObjectID);\n\t\tif(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress))\n\t\t{\n\t\t\t*outDataSize = theAudioObject.GetPropertyDataSize(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttheAnswer = kAudioHardwareUnknownPropertyError;\n\t\t}\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tLogError(\"BGM_PlugInInterface::BGM_GetPropertyDataSize: unknown exception. (object: %u, address: %u)\",\n\t\t\t\t inObjectID,\n\t\t\t\t inAddress ? inAddress->mSelector : 0);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_GetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32* outDataSize, void* outData)\n{\n\t//\tThis method fetches the data for a given property\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_GetPropertyData: bad driver reference\");\n\t\tThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), \"BGM_GetPropertyData: no address\");\n\t\tThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), \"BGM_GetPropertyData: no place to put the return value size\");\n\t\tThrowIfNULL(outData, CAException(kAudioHardwareIllegalOperationError), \"BGM_GetPropertyData: no place to put the return value\");\n\t\t\n        BGM_Object& theAudioObject = BGM_LookUpOwnerObject(inObjectID);\n\t\tif(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress))\n\t\t{\n\t\t\ttheAudioObject.GetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, *outDataSize, outData);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttheAnswer = kAudioHardwareUnknownPropertyError;\n\t\t}\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tLogError(\"BGM_PlugInInterface::BGM_GetPropertyData: unknown exception. (object: %u, address: %u)\",\n                 inObjectID,\n\t\t\t\t inAddress ? inAddress->mSelector : 0);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)\n{\n\t//\tThis method changes the value of the given property\n\n\tOSStatus theAnswer = 0;\n\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), \"BGM_SetPropertyData: bad driver reference\");\n        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), \"BGM_SetPropertyData: no address\");\n        ThrowIfNULL(inData, CAException(kAudioHardwareIllegalOperationError), \"BGM_SetPropertyData: no data\");\n\t\t\n        BGM_Object& theAudioObject = BGM_LookUpOwnerObject(inObjectID);\n\t\tif(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress))\n\t\t{\n\t\t\tif(theAudioObject.IsPropertySettable(inObjectID, inClientProcessID, *inAddress))\n\t\t\t{\n\t\t\t\ttheAudioObject.SetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ttheAnswer = kAudioHardwareUnsupportedOperationError;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttheAnswer = kAudioHardwareUnknownPropertyError;\n\t\t}\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tLogError(\"BGM_PlugInInterface::BGM_SetPropertyData: unknown exception. (object: %u, address: %u)\",\n\t\t\t\t inObjectID,\n\t\t\t\t inAddress ? inAddress->mSelector : 0);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\n#pragma mark IO Operations\n\nstatic OSStatus\tBGM_StartIO(AudioServerPlugInDriverRef inDriver,\n                            AudioObjectID inDeviceObjectID,\n                            UInt32 inClientID)\n{\n\t//\tThis call tells the device that IO is starting for the given client. When this routine\n\t//\treturns, the device's clock is running and it is ready to have data read/written. It is\n\t//\timportant to note that multiple clients can have IO running on the device at the same time.\n\t//\tSo, work only needs to be done when the first client starts. All subsequent starts simply\n\t//\tincrement the counter.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_StartIO: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_StartIO: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n        BGM_LookUpDevice(inDeviceObjectID).StartIO(inClientID);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_StopIO(AudioServerPlugInDriverRef inDriver,\n                           AudioObjectID inDeviceObjectID,\n                           UInt32 inClientID)\n{\n\t//\tThis call tells the device that the client has stopped IO. The driver can stop the hardware\n\t//\tonce all clients have stopped.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_StopIO: bad driver reference\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_StopIO: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).StopIO(inClientID);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver,\n                                     AudioObjectID inDeviceObjectID,\n                                     UInt32 inClientID,\n                                     Float64* outSampleTime,\n                                     UInt64* outHostTime,\n                                     UInt64* outSeed)\n{\n    #pragma unused(inClientID)\n\t//\tThis method returns the current zero time stamp for the device. The HAL models the timing of\n\t//\ta device as a series of time stamps that relate the sample time to a host time. The zero\n\t//\ttime stamps are spaced such that the sample times are the value of\n\t//\tkAudioDevicePropertyZeroTimeStampPeriod apart. This is often modeled using a ring buffer\n\t//\twhere the zero time stamp is updated when wrapping around the ring buffer.\n\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_GetZeroTimeStamp: bad driver reference\");\n\t\tThrowIfNULL(outSampleTime,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_GetZeroTimeStamp: no place to put the sample time\");\n\t\tThrowIfNULL(outHostTime,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_GetZeroTimeStamp: no place to put the host time\");\n\t\tThrowIfNULL(outSeed,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_GetZeroTimeStamp: no place to put the seed\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_GetZeroTimeStamp: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).GetZeroTimeStamp(*outSampleTime, *outHostTime, *outSeed);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_WillDoIOOperation(AudioServerPlugInDriverRef inDriver,\n                                      AudioObjectID inDeviceObjectID,\n                                      UInt32 inClientID,\n                                      UInt32 inOperationID,\n                                      Boolean* outWillDo,\n                                      Boolean* outWillDoInPlace)\n{\n\t#pragma unused(inClientID)\n\t//\tThis method returns whether or not the device will do a given IO operation.\n\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_WillDoIOOperation: bad driver reference\");\n\t\tThrowIfNULL(outWillDo,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_WillDoIOOperation: no place to put the will-do return value\");\n\t\tThrowIfNULL(outWillDoInPlace,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_WillDoIOOperation: no place to put the in-place return value\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_WillDoIOOperation: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tbool willDo = false;\n\t\tbool willDoInPlace = false;\n\t\tBGM_LookUpDevice(inDeviceObjectID).WillDoIOOperation(inOperationID, willDo, willDoInPlace);\n\t\t\n\t\t//\tset the return values\n\t\t*outWillDo = willDo;\n\t\t*outWillDoInPlace = willDoInPlace;\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_BeginIOOperation(AudioServerPlugInDriverRef inDriver,\n                                     AudioObjectID inDeviceObjectID,\n                                     UInt32 inClientID,\n                                     UInt32 inOperationID,\n                                     UInt32 inIOBufferFrameSize,\n                                     const AudioServerPlugInIOCycleInfo* inIOCycleInfo)\n{\n\t// This is called at the beginning of an IO operation.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_BeginIOOperation: bad driver reference\");\n\t\tThrowIfNULL(inIOCycleInfo,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_BeginIOOperation: no cycle info\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_BeginIOOperation: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).BeginIOOperation(inOperationID,\n                                                            inIOBufferFrameSize,\n                                                            *inIOCycleInfo,\n                                                            inClientID);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tDebugMsg(\"BGM_PlugInInterface::BGM_BeginIOOperation: unknown exception. (device: %s, operation: %u)\",\n\t\t\t\t (inDeviceObjectID == kObjectID_Device ? \"BGMDevice\" : \"other\"),\n\t\t\t\t inOperationID);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_DoIOOperation(AudioServerPlugInDriverRef inDriver,\n                                  AudioObjectID inDeviceObjectID,\n                                  AudioObjectID inStreamObjectID,\n                                  UInt32 inClientID,\n                                  UInt32 inOperationID,\n                                  UInt32 inIOBufferFrameSize,\n                                  const AudioServerPlugInIOCycleInfo* inIOCycleInfo,\n                                  void* ioMainBuffer,\n                                  void* ioSecondaryBuffer)\n{\n\t//\tThis is called to actually perform a given IO operation.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_EndIOOperation: bad driver reference\");\n\t\tThrowIfNULL(inIOCycleInfo,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_EndIOOperation: no cycle info\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_EndIOOperation: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).DoIOOperation(inStreamObjectID,\n                                                         inClientID,\n                                                         inOperationID,\n                                                         inIOBufferFrameSize,\n                                                         *inIOCycleInfo,\n                                                         ioMainBuffer,\n                                                         ioSecondaryBuffer);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tDebugMsg(\"BGM_PlugInInterface::BGM_DoIOOperation: unknown exception. (device: %s, operation: %u)\",\n\t\t\t\t (inDeviceObjectID == kObjectID_Device ? \"BGMDevice\" : \"other\"),\n\t\t\t\t inOperationID);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\n\treturn theAnswer;\n}\n\nstatic OSStatus\tBGM_EndIOOperation(AudioServerPlugInDriverRef inDriver,\n                                   AudioObjectID inDeviceObjectID,\n                                   UInt32 inClientID,\n                                   UInt32 inOperationID,\n                                   UInt32 inIOBufferFrameSize,\n                                   const AudioServerPlugInIOCycleInfo* inIOCycleInfo)\n{\n\t// This is called at the end of an IO operation.\n\t\n\tOSStatus theAnswer = 0;\n\t\n\ttry\n\t{\n\t\t//\tcheck the arguments\n\t\tThrowIf(inDriver != gAudioServerPlugInDriverRef,\n                CAException(kAudioHardwareBadObjectError),\n                \"BGM_EndIOOperation: bad driver reference\");\n\t\tThrowIfNULL(inIOCycleInfo,\n                    CAException(kAudioHardwareIllegalOperationError),\n                    \"BGM_EndIOOperation: no cycle info\");\n\t\tThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,\n                CAException(kAudioHardwareBadDeviceError),\n                \"BGM_EndIOOperation: unknown device\");\n\t\t\n\t\t//\ttell the device to do the work\n\t\tBGM_LookUpDevice(inDeviceObjectID).EndIOOperation(inOperationID,\n                                                          inIOBufferFrameSize,\n                                                          *inIOCycleInfo,\n                                                          inClientID);\n\t}\n\tcatch(const CAException& inException)\n\t{\n\t\ttheAnswer = inException.GetError();\n\t}\n\tcatch(...)\n\t{\n\t\tDebugMsg(\"BGM_PlugInInterface::BGM_EndIOOperation: unknown exception. (device: %s, operation: %u)\",\n\t\t\t\t (inDeviceObjectID == kObjectID_Device ? \"BGMDevice\" : \"other\"),\n\t\t\t\t inOperationID);\n\t\ttheAnswer = kAudioHardwareUnspecifiedError;\n\t}\n\t\n\treturn theAnswer;\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Stream.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//  BGM_Stream.cpp\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n\n// Self Include\n#include \"BGM_Stream.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n#include \"BGM_Device.h\"\n#include \"BGM_PlugIn.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAPropertyAddress.h\"\n#include \"CADispatchQueue.h\"\n\n\n#pragma clang assume_nonnull begin\n\nBGM_Stream::BGM_Stream(AudioObjectID inObjectID,\n                       AudioDeviceID inOwnerDeviceID,\n                       bool inIsInput,\n                       Float64 inSampleRate,\n                       UInt32 inStartingChannel)\n:\n    BGM_Object(inObjectID, kAudioStreamClassID, kAudioObjectClassID, inOwnerDeviceID),\n    mStateMutex(inIsInput ? \"Input Stream State\" : \"Output Stream State\"),\n    mIsInput(inIsInput),\n    mIsStreamActive(false),\n    mSampleRate(inSampleRate),\n    mStartingChannel(inStartingChannel)\n{\n}\n\nBGM_Stream::~BGM_Stream()\n{\n}\n\nbool    BGM_Stream::HasProperty(AudioObjectID inObjectID,\n                                pid_t inClientPID,\n                                const AudioObjectPropertyAddress& inAddress) const\n{\n    //    For each object, this driver implements all the required properties plus a few extras that\n    //    are useful but not required. There is more detailed commentary about each property in the\n    //    GetPropertyData() method.\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioStreamPropertyIsActive:\n        case kAudioStreamPropertyDirection:\n        case kAudioStreamPropertyTerminalType:\n        case kAudioStreamPropertyStartingChannel:\n        case kAudioStreamPropertyLatency:\n        case kAudioStreamPropertyVirtualFormat:\n        case kAudioStreamPropertyPhysicalFormat:\n        case kAudioStreamPropertyAvailableVirtualFormats:\n        case kAudioStreamPropertyAvailablePhysicalFormats:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Object::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nbool    BGM_Stream::IsPropertySettable(AudioObjectID inObjectID,\n                                       pid_t inClientPID,\n                                       const AudioObjectPropertyAddress& inAddress) const\n{\n    // For each object, this driver implements all the required properties plus a few extras that\n    // are useful but not required. There is more detailed commentary about each property in the\n    // GetPropertyData() method.\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioStreamPropertyDirection:\n        case kAudioStreamPropertyTerminalType:\n        case kAudioStreamPropertyStartingChannel:\n        case kAudioStreamPropertyLatency:\n        case kAudioStreamPropertyAvailableVirtualFormats:\n        case kAudioStreamPropertyAvailablePhysicalFormats:\n            theAnswer = false;\n            break;\n\n        case kAudioStreamPropertyIsActive:\n        case kAudioStreamPropertyVirtualFormat:\n        case kAudioStreamPropertyPhysicalFormat:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32    BGM_Stream::GetPropertyDataSize(AudioObjectID inObjectID,\n                                          pid_t inClientPID,\n                                          const AudioObjectPropertyAddress& inAddress,\n                                          UInt32 inQualifierDataSize,\n                                          const void* __nullable inQualifierData) const\n{\n    // For each object, this driver implements all the required properties plus a few extras that\n    // are useful but not required. There is more detailed commentary about each property in the\n    // GetPropertyData() method.\n\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioStreamPropertyIsActive:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyDirection:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyTerminalType:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyStartingChannel:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyLatency:\n            theAnswer = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyVirtualFormat:\n        case kAudioStreamPropertyPhysicalFormat:\n            theAnswer = sizeof(AudioStreamBasicDescription);\n            break;\n            \n        case kAudioStreamPropertyAvailableVirtualFormats:\n        case kAudioStreamPropertyAvailablePhysicalFormats:\n            theAnswer = 1 * sizeof(AudioStreamRangedDescription);\n            break;\n            \n        default:\n            theAnswer = BGM_Object::GetPropertyDataSize(inObjectID,\n                                                        inClientPID,\n                                                        inAddress,\n                                                        inQualifierDataSize,\n                                                        inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_Stream::GetPropertyData(AudioObjectID inObjectID,\n                                    pid_t inClientPID,\n                                    const AudioObjectPropertyAddress& inAddress,\n                                    UInt32 inQualifierDataSize,\n                                    const void* __nullable inQualifierData,\n                                    UInt32 inDataSize,\n                                    UInt32& outDataSize,\n                                    void* outData) const\n{\n    // Since most of the data that will get returned is static, there are few instances where it is\n    // necessary to lock the state mutex.\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioObjectPropertyBaseClass:\n            // The base class for kAudioStreamClassID is kAudioObjectClassID\n            ThrowIf(inDataSize < sizeof(AudioClassID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                    \"value of kAudioObjectPropertyBaseClass for the stream\");\n            *reinterpret_cast<AudioClassID*>(outData) = kAudioObjectClassID;\n            outDataSize = sizeof(AudioClassID);\n            break;\n\n        case kAudioObjectPropertyClass:\n            // Streams are of the class kAudioStreamClassID\n            ThrowIf(inDataSize < sizeof(AudioClassID),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                    \"value of kAudioObjectPropertyClass for the stream\");\n            *reinterpret_cast<AudioClassID*>(outData) = kAudioStreamClassID;\n            outDataSize = sizeof(AudioClassID);\n            break;\n\n        case kAudioObjectPropertyOwner:\n            // A stream's owner is a device object.\n            {\n                ThrowIf(inDataSize < sizeof(AudioObjectID),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                        \"value of kAudioObjectPropertyOwner for the stream\");\n\n                // Lock the state mutex to create a memory barrier, just in case a subclass ever\n                // allows mOwnerObjectID to be modified.\n                CAMutex::Locker theStateLocker(mStateMutex);\n\n                // Return the requested value.\n                *reinterpret_cast<AudioObjectID*>(outData) = mOwnerObjectID;\n                outDataSize = sizeof(AudioObjectID);\n            }\n            break;\n\n        case kAudioStreamPropertyIsActive:\n            // This property tells the device whether or not the given stream is going to\n            // be used for IO. Note that we need to take the state lock to examine this\n            // value.\n            {\n                ThrowIf(inDataSize < sizeof(UInt32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                        \"value of kAudioStreamPropertyIsActive for the stream\");\n\n                // Take the state lock.\n                CAMutex::Locker theStateLocker(mStateMutex);\n\n                // Return the requested value.\n                *reinterpret_cast<UInt32*>(outData) = mIsStreamActive;\n                outDataSize = sizeof(UInt32);\n            }\n            break;\n\n        case kAudioStreamPropertyDirection:\n            // This returns whether the stream is an input or output stream.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioStreamPropertyDirection for the stream\");\n            *reinterpret_cast<UInt32*>(outData) = mIsInput ? 1 : 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyTerminalType:\n            // This returns a value that indicates what is at the other end of the stream\n            // such as a speaker or headphones or a microphone.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioStreamPropertyTerminalType for the stream\");\n            *reinterpret_cast<UInt32*>(outData) =\n                mIsInput ? kAudioStreamTerminalTypeMicrophone : kAudioStreamTerminalTypeSpeaker;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyStartingChannel:\n            // This property returns the absolute channel number for the first channel in\n            // the stream. For example, if a device has two output streams with two\n            // channels each, then the starting channel number for the first stream is 1\n            // and the starting channel number for the second stream is 3.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                    \"value of kAudioStreamPropertyStartingChannel for the stream\");\n            *reinterpret_cast<UInt32*>(outData) = mStartingChannel;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyLatency:\n            // This property returns any additional presentation latency the stream has.\n            ThrowIf(inDataSize < sizeof(UInt32),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                    \"value of kAudioStreamPropertyLatency for the stream\");\n            *reinterpret_cast<UInt32*>(outData) = 0;\n            outDataSize = sizeof(UInt32);\n            break;\n\n        case kAudioStreamPropertyVirtualFormat:\n        case kAudioStreamPropertyPhysicalFormat:\n            // This returns the current format of the stream in an AudioStreamBasicDescription.\n            // For devices that don't override the mix operation, the virtual format has to be the\n            // same as the physical format.\n            {\n                ThrowIf(inDataSize < sizeof(AudioStreamBasicDescription),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Stream::GetPropertyData: not enough space for the return \"\n                        \"value of kAudioStreamPropertyVirtualFormat for the stream\");\n\n                // This particular device always vends 32-bit native endian floats\n                AudioStreamBasicDescription* outASBD =\n                    reinterpret_cast<AudioStreamBasicDescription*>(outData);\n\n                // Our streams have the same sample rate as the device they belong to.\n                outASBD->mSampleRate = mSampleRate;\n                outASBD->mFormatID = kAudioFormatLinearPCM;\n                outASBD->mFormatFlags =\n                    kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;\n                outASBD->mBytesPerPacket = 8;\n                outASBD->mFramesPerPacket = 1;\n                outASBD->mBytesPerFrame = 8;\n                outASBD->mChannelsPerFrame = 2;\n                outASBD->mBitsPerChannel = 32;\n\n                outDataSize = sizeof(AudioStreamBasicDescription);\n            }\n            break;\n\n        case kAudioStreamPropertyAvailableVirtualFormats:\n        case kAudioStreamPropertyAvailablePhysicalFormats:\n            // This returns an array of AudioStreamRangedDescriptions that describe what\n            // formats are supported.\n            if((inDataSize / sizeof(AudioStreamRangedDescription)) >= 1)\n            {\n                AudioStreamRangedDescription* outASRD =\n                    reinterpret_cast<AudioStreamRangedDescription*>(outData);\n\n                outASRD[0].mFormat.mSampleRate = mSampleRate;\n                outASRD[0].mFormat.mFormatID = kAudioFormatLinearPCM;\n                outASRD[0].mFormat.mFormatFlags =\n                    kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;\n                outASRD[0].mFormat.mBytesPerPacket = 8;\n                outASRD[0].mFormat.mFramesPerPacket = 1;\n                outASRD[0].mFormat.mBytesPerFrame = 8;\n                outASRD[0].mFormat.mChannelsPerFrame = 2;\n                outASRD[0].mFormat.mBitsPerChannel = 32;\n                // These match kAudioDevicePropertyAvailableNominalSampleRates.\n                outASRD[0].mSampleRateRange.mMinimum = 1.0;\n                outASRD[0].mSampleRateRange.mMaximum = 1000000000.0;\n\n                // Report how much we wrote.\n                outDataSize = sizeof(AudioStreamRangedDescription);\n            }\n            else\n            {\n                outDataSize = 0;\n            }\n            break;\n\n        default:\n            BGM_Object::GetPropertyData(inObjectID,\n                                        inClientPID,\n                                        inAddress,\n                                        inQualifierDataSize,\n                                        inQualifierData,\n                                        inDataSize,\n                                        outDataSize,\n                                        outData);\n            break;\n    };\n}\n\nvoid    BGM_Stream::SetPropertyData(AudioObjectID inObjectID,\n                                    pid_t inClientPID,\n                                    const AudioObjectPropertyAddress& inAddress,\n                                    UInt32 inQualifierDataSize,\n                                    const void* __nullable inQualifierData,\n                                    UInt32 inDataSize,\n                                    const void* inData)\n{\n    // There is more detailed commentary about each property in the GetPropertyData() method.\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioStreamPropertyIsActive:\n            {\n                // Changing the active state of a stream doesn't affect IO or change the structure\n                // so we can just save the state and send the notification.\n                ThrowIf(inDataSize != sizeof(UInt32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Stream::SetPropertyData: wrong size for the data for \"\n                        \"kAudioStreamPropertyIsActive\");\n                bool theNewIsActive = *reinterpret_cast<const UInt32*>(inData) != 0;\n\n                CAMutex::Locker theStateLocker(mStateMutex);\n\n                if(mIsStreamActive != theNewIsActive)\n                {\n                    mIsStreamActive = theNewIsActive;\n\n                    // Send the notification.\n                    CADispatchQueue::GetGlobalSerialQueue().Dispatch(false,\t^{\n                        AudioObjectPropertyAddress theProperty[] = {\n                            CAPropertyAddress(kAudioStreamPropertyIsActive)\n                        };\n                        BGM_PlugIn::Host_PropertiesChanged(inObjectID, 1, theProperty);\n                    });\n                }\n            }\n            break;\n\n        case kAudioStreamPropertyVirtualFormat:\n        case kAudioStreamPropertyPhysicalFormat:\n            {\n                // The device that owns the stream handles changing the stream format, as it needs\n                // to be handled via the RequestConfigChange/PerformConfigChange machinery. The\n                // stream only needs to validate the format at this point.\n                //\n                // Note that because our devices only supports 2 channel 32 bit float data, the only\n                // thing that can change is the sample rate.\n                ThrowIf(inDataSize != sizeof(AudioStreamBasicDescription),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_Stream::SetPropertyData: wrong size for the data for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n\n                const AudioStreamBasicDescription* theNewFormat =\n                    reinterpret_cast<const AudioStreamBasicDescription*>(inData);\n\n                ThrowIf(theNewFormat->mFormatID != kAudioFormatLinearPCM,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported format ID for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mFormatFlags !=\n                            (kAudioFormatFlagIsFloat |\n                             kAudioFormatFlagsNativeEndian |\n                             kAudioFormatFlagIsPacked),\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported format flags for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mBytesPerPacket != 8,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported bytes per packet for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mFramesPerPacket != 1,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported frames per packet for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mBytesPerFrame != 8,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported bytes per frame for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mChannelsPerFrame != 2,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported channels per frame for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mBitsPerChannel != 32,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported bits per channel for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n                ThrowIf(theNewFormat->mSampleRate < 1.0,\n                        CAException(kAudioDeviceUnsupportedFormatError),\n                        \"BGM_Stream::SetPropertyData: unsupported sample rate for \"\n                        \"kAudioStreamPropertyPhysicalFormat\");\n            }\n            break;\n\n        default:\n            BGM_Object::SetPropertyData(inObjectID,\n                                        inClientPID,\n                                        inAddress,\n                                        inQualifierDataSize,\n                                        inQualifierData,\n                                        inDataSize,\n                                        inData);\n            break;\n    };\n}\n\n#pragma mark Accessors\n\nvoid    BGM_Stream::SetSampleRate(Float64 inSampleRate)\n{\n    CAMutex::Locker theStateLocker(mStateMutex);\n    mSampleRate = inSampleRate;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Stream.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//  BGM_Stream.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n//  An input or output audio stream. Each stream belongs to a device (see BGM_AbstractDevice), which\n//  in turn belongs to a plug-in (see BGM_PlugIn).\n//\n//  This class only handles the stream's HAL properties, i.e. the metadata about the stream, not the\n//  audio data itself.\n//\n\n#ifndef BGMDriver__BGM_Stream\n#define BGMDriver__BGM_Stream\n\n// SuperClass Includes\n#include \"BGM_Object.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n\n// System Includes\n#include <CoreAudio/AudioHardwareBase.h>\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_Stream\n:\n    public BGM_Object\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                                BGM_Stream(AudioObjectID inObjectID,\n                                           AudioObjectID inOwnerDeviceID,\n                                           bool inIsInput,\n                                           Float64 inSampleRate,\n                                           UInt32 inStartingChannel = 1);\n    virtual                     ~BGM_Stream();\n\n#pragma mark Property Operations\n\npublic:\n    bool                        HasProperty(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress) const;\n    bool                        IsPropertySettable(AudioObjectID inObjectID,\n                                                   pid_t inClientPID,\n                                                   const AudioObjectPropertyAddress& inAddress) const;\n    UInt32                      GetPropertyDataSize(AudioObjectID inObjectID,\n                                                    pid_t inClientPID,\n                                                    const AudioObjectPropertyAddress& inAddress,\n                                                    UInt32 inQualifierDataSize,\n                                                    const void* __nullable inQualifierData) const;\n    void                        GetPropertyData(AudioObjectID inObjectID,\n                                                pid_t inClientPID,\n                                                const AudioObjectPropertyAddress& inAddress,\n                                                UInt32 inQualifierDataSize,\n                                                const void* __nullable inQualifierData,\n                                                UInt32 inDataSize,\n                                                UInt32& outDataSize,\n                                                void* outData) const;\n    void                        SetPropertyData(AudioObjectID inObjectID,\n                                                pid_t inClientPID,\n                                                const AudioObjectPropertyAddress& inAddress,\n                                                UInt32 inQualifierDataSize,\n                                                const void* __nullable inQualifierData,\n                                                UInt32 inDataSize,\n                                                const void* inData);\n\n#pragma mark Accessors\n\n    void                        SetSampleRate(Float64 inSampleRate);\n\nprivate:\n    CAMutex                     mStateMutex;\n\n    bool                        mIsInput;\n    Float64                     mSampleRate;\n    /*! True if the stream is enabled and doing IO. See kAudioStreamPropertyIsActive. */\n    bool                        mIsStreamActive;\n    /*! \n     The absolute channel number for the first channel in the stream. For example, if a device has\n     two output streams with two channels each, then the starting channel number for the first \n     stream is 1 and the starting channel number for the second stream is 3. See \n     kAudioStreamPropertyStartingChannel.\n     */\n    UInt32                      mStartingChannel;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_Stream */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_TaskQueue.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_TaskQueue.cpp\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_TaskQueue.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n#include \"BGM_PlugIn.h\"\n#include \"BGM_Clients.h\"\n#include \"BGM_ClientMap.h\"\n#include \"BGM_ClientTasks.h\"\n\n// PublicUtility Includes\n#include \"CAException.h\"\n#include \"CADebugMacros.h\"\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wsign-conversion\"\n#include \"CAAtomic.h\"\n#pragma clang diagnostic pop\n\n// System Includes\n#include <mach/mach_init.h>\n#include <mach/mach_time.h>\n#include <mach/task.h>\n\n\n#pragma clang assume_nonnull begin\n\n#pragma mark Construction/destruction\n\nBGM_TaskQueue::BGM_TaskQueue()\n:\n    // The inline documentation for thread_time_constraint_policy.period says \"A value of 0 indicates that there is no\n    // inherent periodicity in the computation\". So I figure setting the period to 0 means the scheduler will take as long\n    // as it wants to wake our real-time thread, which is fine for us, but once it has only other real-time threads can\n    // preempt us. (And that's only if they won't make our computation take longer than kRealTimeThreadMaximumComputationNs).\n    mRealTimeThread(&BGM_TaskQueue::RealTimeThreadProc,\n                    this,\n                    /* inPeriod = */ 0,\n                    NanosToAbsoluteTime(kRealTimeThreadNominalComputationNs),\n                    NanosToAbsoluteTime(kRealTimeThreadMaximumComputationNs),\n                    /* inIsPreemptible = */ true),\n    mNonRealTimeThread(&BGM_TaskQueue::NonRealTimeThreadProc, this)\n{\n    // Init the semaphores\n    auto createSemaphore = [] () {\n        semaphore_t theSemaphore;\n        kern_return_t theError = semaphore_create(mach_task_self(), &theSemaphore, SYNC_POLICY_FIFO, 0);\n        \n        BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::BGM_TaskQueue\", \"semaphore_create\", theError);\n        \n        ThrowIf(theSemaphore == SEMAPHORE_NULL,\n                CAException(kAudioHardwareUnspecifiedError),\n                \"BGM_TaskQueue::BGM_TaskQueue: Could not create semaphore\");\n        \n        return theSemaphore;\n    };\n    \n    mRealTimeThreadWorkQueuedSemaphore = createSemaphore();\n    mNonRealTimeThreadWorkQueuedSemaphore = createSemaphore();\n    mRealTimeThreadSyncTaskCompletedSemaphore = createSemaphore();\n    mNonRealTimeThreadSyncTaskCompletedSemaphore = createSemaphore();\n    \n    // Pre-allocate enough tasks in mNonRealTimeThreadTasksFreeList that the real-time threads should never have to\n    // allocate memory when adding a task to the non-realtime queue.\n    for(UInt32 i = 0; i < kNonRealTimeThreadTaskBufferSize; i++)\n    {\n        BGM_Task* theTask = new BGM_Task;\n        mNonRealTimeThreadTasksFreeList.push_NA(theTask);\n    }\n    \n    // Start the worker threads\n    mRealTimeThread.Start();\n    mNonRealTimeThread.Start();\n}\n\nBGM_TaskQueue::~BGM_TaskQueue()\n{\n    // Join the worker threads\n    BGMLogAndSwallowExceptionsMsg(\"BGM_TaskQueue::~BGM_TaskQueue\", \"QueueSync\", ([&] {\n        QueueSync(kBGMTaskStopWorkerThread, /* inRunOnRealtimeThread = */ true);\n        QueueSync(kBGMTaskStopWorkerThread, /* inRunOnRealtimeThread = */ false);\n    }));\n\n    // Destroy the semaphores\n    auto destroySemaphore = [] (semaphore_t inSemaphore) {\n        kern_return_t theError = semaphore_destroy(mach_task_self(), inSemaphore);\n        \n        BGM_Utils::LogIfMachError(\"BGM_TaskQueue::~BGM_TaskQueue\", \"semaphore_destroy\", theError);\n    };\n    \n    destroySemaphore(mRealTimeThreadWorkQueuedSemaphore);\n    destroySemaphore(mNonRealTimeThreadWorkQueuedSemaphore);\n    destroySemaphore(mRealTimeThreadSyncTaskCompletedSemaphore);\n    destroySemaphore(mNonRealTimeThreadSyncTaskCompletedSemaphore);\n    \n    BGM_Task* theTask;\n    \n    // Delete the tasks in the non-realtime tasks free list\n    while((theTask = mNonRealTimeThreadTasksFreeList.pop_atomic()) != NULL)\n    {\n        delete theTask;\n    }\n    \n    // Delete any tasks left on the non-realtime queue that need to be\n    while((theTask = mNonRealTimeThreadTasks.pop_atomic()) != NULL)\n    {\n        if(!theTask->IsSync())\n        {\n            delete theTask;\n        }\n    }\n}\n\n//static\nUInt32  BGM_TaskQueue::NanosToAbsoluteTime(UInt32 inNanos)\n{\n    // Converts a duration from nanoseconds to absolute time (i.e. number of bus cycles). Used for calculating\n    // the real-time thread's time constraint policy.\n    \n    mach_timebase_info_data_t theTimebaseInfo;\n    mach_timebase_info(&theTimebaseInfo);\n    \n    Float64 theTicksPerNs = static_cast<Float64>(theTimebaseInfo.denom) / theTimebaseInfo.numer;\n    return static_cast<UInt32>(inNanos * theTicksPerNs);\n}\n\n#pragma mark Task queueing\n\nvoid    BGM_TaskQueue::QueueSync_SwapClientShadowMaps(BGM_ClientMap* inClientMap)\n{\n    // TODO: Is there any reason to use uintptr_t when we pass pointers to tasks like this? I can't think of any\n    //       reason for a system to have (non-function) pointers larger than 64-bit, so I figure they should fit.\n    //\n    //       From http://en.cppreference.com/w/cpp/language/reinterpret_cast:\n    //       \"A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed\n    //        to have its original value [...]\"\n    QueueSync(kBGMTaskSwapClientShadowMaps, /* inRunOnRealtimeThread = */ true, reinterpret_cast<UInt64>(inClientMap));\n}\n\nvoid    BGM_TaskQueue::QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty, AudioObjectID inDeviceID)\n{\n    DebugMsg(\"BGM_TaskQueue::QueueAsync_SendPropertyNotification: Queueing property notification. inProperty=%u inDeviceID=%u\",\n             inProperty,\n             inDeviceID);\n    BGM_Task theTask(kBGMTaskSendPropertyNotification, /* inIsSync = */ false, inProperty, inDeviceID);\n    QueueOnNonRealtimeThread(theTask);\n}\n\nbool    BGM_TaskQueue::Queue_UpdateClientIOState(bool inSync, BGM_Clients* inClients, UInt32 inClientID, bool inDoingIO)\n{\n    DebugMsg(\"BGM_TaskQueue::Queue_UpdateClientIOState: Queueing %s %s\",\n             (inDoingIO ? \"kBGMTaskStartClientIO\" : \"kBGMTaskStopClientIO\"),\n             (inSync ? \"synchronously\" : \"asynchronously\"));\n    \n    BGM_TaskID theTaskID = (inDoingIO ? kBGMTaskStartClientIO : kBGMTaskStopClientIO);\n    UInt64 theClientsPtrArg = reinterpret_cast<UInt64>(inClients);\n    UInt64 theClientIDTaskArg = static_cast<UInt64>(inClientID);\n    \n    if(inSync)\n    {\n        return QueueSync(theTaskID, false, theClientsPtrArg, theClientIDTaskArg);\n    }\n    else\n    {\n        BGM_Task theTask(theTaskID, /* inIsSync = */ false, theClientsPtrArg, theClientIDTaskArg);\n        QueueOnNonRealtimeThread(theTask);\n        \n        // This method's return value isn't used when queueing async, because we can't know what it should be yet.\n        return false;\n    }\n}\n\nUInt64    BGM_TaskQueue::QueueSync(BGM_TaskID inTaskID, bool inRunOnRealtimeThread, UInt64 inTaskArg1, UInt64 inTaskArg2)\n{\n    DebugMsg(\"BGM_TaskQueue::QueueSync: Queueing task synchronously to be processed on the %s thread. inTaskID=%d inTaskArg1=%llu inTaskArg2=%llu\",\n             (inRunOnRealtimeThread ? \"realtime\" : \"non-realtime\"),\n             inTaskID,\n             inTaskArg1,\n             inTaskArg2);\n    \n    // Create the task\n    BGM_Task theTask(inTaskID, /* inIsSync = */ true, inTaskArg1, inTaskArg2);\n    \n    // Add the task to the queue\n    TAtomicStack<BGM_Task>& theTasks = (inRunOnRealtimeThread ? mRealTimeThreadTasks : mNonRealTimeThreadTasks);\n    theTasks.push_atomic(&theTask);\n    \n    // Wake the worker thread so it'll process the task. (Note that semaphore_signal has an implicit barrier.)\n    kern_return_t theError = semaphore_signal(inRunOnRealtimeThread ? mRealTimeThreadWorkQueuedSemaphore : mNonRealTimeThreadWorkQueuedSemaphore);\n    BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::QueueSync\", \"semaphore_signal\", theError);\n    \n    // Wait until the task has been processed.\n    //\n    // The worker thread signals all threads waiting on this semaphore when it finishes a task. The comments in WorkerThreadProc\n    // explain why we have to check the condition in a loop here.\n    bool didLogTimeoutMessage = false;\n    while(!theTask.IsComplete())\n    {\n        semaphore_t theTaskCompletedSemaphore =\n            inRunOnRealtimeThread ? mRealTimeThreadSyncTaskCompletedSemaphore : mNonRealTimeThreadSyncTaskCompletedSemaphore;\n        // TODO: Because the worker threads use semaphore_signal_all instead of semaphore_signal, a thread can miss the signal if\n        //       it isn't waiting at the right time. Using a timeout for now as a temporary fix so threads don't get stuck here.\n        theError = semaphore_timedwait(theTaskCompletedSemaphore,\n                                       (mach_timespec_t){ 0, kRealTimeThreadMaximumComputationNs * 4 });\n        \n        if(theError == KERN_OPERATION_TIMED_OUT)\n        {\n            if(!didLogTimeoutMessage && inRunOnRealtimeThread)\n            {\n                DebugMsg(\"BGM_TaskQueue::QueueSync: Task %d taking longer than expected.\", theTask.GetTaskID());\n                didLogTimeoutMessage = true;\n            }\n        }\n        else\n        {\n            BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::QueueSync\", \"semaphore_timedwait\", theError);\n        }\n        \n        CAMemoryBarrier();\n    }\n    \n    if(didLogTimeoutMessage)\n    {\n        DebugMsg(\"BGM_TaskQueue::QueueSync: Late task %d finished.\", theTask.GetTaskID());\n    }\n    \n    if(theTask.GetReturnValue() != INT64_MAX)\n    {\n        DebugMsg(\"BGM_TaskQueue::QueueSync: Task %d returned %llu.\", theTask.GetTaskID(), theTask.GetReturnValue());\n    }\n    \n    return theTask.GetReturnValue();\n}\n\nvoid   BGM_TaskQueue::QueueOnNonRealtimeThread(BGM_Task inTask)\n{\n    // Add the task to our task list\n    BGM_Task* freeTask = mNonRealTimeThreadTasksFreeList.pop_atomic();\n    \n    if(freeTask == NULL)\n    {\n        LogWarning(\"BGM_TaskQueue::QueueOnNonRealtimeThread: No pre-allocated tasks left in the free list. Allocating new task.\");\n        freeTask = new BGM_Task;\n    }\n    \n    *freeTask = inTask;\n    \n    mNonRealTimeThreadTasks.push_atomic(freeTask);\n    \n    // Signal the worker thread to process the task. (Note that semaphore_signal has an implicit barrier.)\n    kern_return_t theError = semaphore_signal(mNonRealTimeThreadWorkQueuedSemaphore);\n    BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::QueueOnNonRealtimeThread\", \"semaphore_signal\", theError);\n}\n\n#pragma mark Worker threads\n\nvoid    BGM_TaskQueue::AssertCurrentThreadIsRTWorkerThread(const char* inCallerMethodName)\n{\n#if DEBUG  // This Assert macro always checks the condition, even in release builds if the compiler doesn't optimise it away\n    if(!mRealTimeThread.IsCurrentThread())\n    {\n        DebugMsg(\"%s should only be called on the realtime worker thread.\", inCallerMethodName);\n        __ASSERT_STOP;  // TODO: Figure out a better way to assert with a formatted message\n    }\n    \n    Assert(mRealTimeThread.IsTimeConstraintThread(), \"mRealTimeThread should be in a time-constraint priority band.\");\n#else\n    #pragma unused (inCallerMethodName)\n#endif\n}\n\n//static\nvoid* __nullable    BGM_TaskQueue::RealTimeThreadProc(void* inRefCon)\n{\n    DebugMsg(\"BGM_TaskQueue::RealTimeThreadProc: The realtime worker thread has started\");\n    \n    BGM_TaskQueue* refCon = static_cast<BGM_TaskQueue*>(inRefCon);\n    refCon->WorkerThreadProc(refCon->mRealTimeThreadWorkQueuedSemaphore,\n                             refCon->mRealTimeThreadSyncTaskCompletedSemaphore,\n                             &refCon->mRealTimeThreadTasks,\n                             NULL,\n                             [&] (BGM_Task* inTask) { return refCon->ProcessRealTimeThreadTask(inTask); });\n    \n    return NULL;\n}\n\n//static\nvoid* __nullable    BGM_TaskQueue::NonRealTimeThreadProc(void* inRefCon)\n{\n    DebugMsg(\"BGM_TaskQueue::NonRealTimeThreadProc: The non-realtime worker thread has started\");\n    \n    BGM_TaskQueue* refCon = static_cast<BGM_TaskQueue*>(inRefCon);\n    refCon->WorkerThreadProc(refCon->mNonRealTimeThreadWorkQueuedSemaphore,\n                             refCon->mNonRealTimeThreadSyncTaskCompletedSemaphore,\n                             &refCon->mNonRealTimeThreadTasks,\n                             &refCon->mNonRealTimeThreadTasksFreeList,\n                             [&] (BGM_Task* inTask) { return refCon->ProcessNonRealTimeThreadTask(inTask); });\n    \n    return NULL;\n}\n\nvoid    BGM_TaskQueue::WorkerThreadProc(semaphore_t inWorkQueuedSemaphore, semaphore_t inSyncTaskCompletedSemaphore, TAtomicStack<BGM_Task>* inTasks, TAtomicStack2<BGM_Task>* __nullable inFreeList, std::function<bool(BGM_Task*)> inProcessTask)\n{\n    bool theThreadShouldStop = false;\n    \n    while(!theThreadShouldStop)\n    {\n        // Wait until a thread signals that it's added tasks to the queue.\n        //\n        // Note that we don't have to hold any lock before waiting. If the semaphore is signalled before we begin waiting we'll\n        // still get the signal after we do.\n        kern_return_t theError = semaphore_wait(inWorkQueuedSemaphore);\n        BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::WorkerThreadProc\", \"semaphore_wait\", theError);\n        \n        // Fetch the tasks from the queue.\n        //\n        // The tasks need to be processed in the order they were added to the queue. Since pop_all_reversed is atomic, other threads\n        // can't add new tasks while we're reading, which would mix up the order.\n        BGM_Task* theTask = inTasks->pop_all_reversed();\n        \n        while(theTask != NULL &&\n              !theThreadShouldStop)  // Stop processing tasks if we're shutting down\n        {\n            BGM_Task* theNextTask = theTask->mNext;\n            \n            BGMAssert(!theTask->IsComplete(),\n                      \"BGM_TaskQueue::WorkerThreadProc: Cannot process already completed task (ID %d)\",\n                      theTask->GetTaskID());\n            \n            BGMAssert(theTask != theNextTask,\n                      \"BGM_TaskQueue::WorkerThreadProc: BGM_Task %p (ID %d) was added to %s multiple times. arg1=%llu arg2=%llu\",\n                      theTask,\n                      theTask->GetTaskID(),\n                      (inTasks == &mRealTimeThreadTasks ? \"mRealTimeThreadTasks\" : \"mNonRealTimeThreadTasks\"),\n                      theTask->GetArg1(),\n                      theTask->GetArg2());\n            \n            // Process the task\n            theThreadShouldStop = inProcessTask(theTask);\n            \n            // If the task was queued synchronously, let the thread that queued it know we're finished\n            if(theTask->IsSync())\n            {\n                // Marking the task as completed allows QueueSync to return, which means it's possible for theTask to point to\n                // invalid memory after this point.\n                CAMemoryBarrier();\n                theTask->MarkCompleted();\n                \n                // Signal any threads waiting for their task to be processed.\n                //\n                // We use semaphore_signal_all instead of semaphore_signal to avoid a race condition in QueueSync. It's possible\n                // for threads calling QueueSync to wait on the semaphore in an order different to the order of the tasks they just\n                // added to the queue. So after each task is completed we have every waiting thread check if it was theirs.\n                //\n                // Note that semaphore_signal_all has an implicit barrier.\n                theError = semaphore_signal_all(inSyncTaskCompletedSemaphore);\n                BGM_Utils::ThrowIfMachError(\"BGM_TaskQueue::WorkerThreadProc\", \"semaphore_signal_all\", theError);\n            }\n            else if(inFreeList != NULL)\n            {\n                // After completing an async task, move it to the free list so the memory can be reused\n                inFreeList->push_atomic(theTask);\n            }\n            \n            theTask = theNextTask;\n        }\n    }\n}\n\nbool    BGM_TaskQueue::ProcessRealTimeThreadTask(BGM_Task* inTask)\n{\n    AssertCurrentThreadIsRTWorkerThread(\"BGM_TaskQueue::ProcessRealTimeThreadTask\");\n    \n    switch(inTask->GetTaskID())\n    {\n        case kBGMTaskStopWorkerThread:\n            DebugMsg(\"BGM_TaskQueue::ProcessRealTimeThreadTask: Stopping\");\n            // Return that the thread should stop itself\n            return true;\n            \n        case kBGMTaskSwapClientShadowMaps:\n            {\n                DebugMsg(\"BGM_TaskQueue::ProcessRealTimeThreadTask: Swapping the shadow maps in BGM_ClientMap\");\n                BGM_ClientMap* theClientMap = reinterpret_cast<BGM_ClientMap*>(inTask->GetArg1());\n                BGM_ClientTasks::SwapInShadowMapsRT(theClientMap);\n            }\n            break;\n            \n        default:\n            Assert(false, \"BGM_TaskQueue::ProcessRealTimeThreadTask: Unexpected task ID\");\n            break;\n    }\n    \n    return false;\n}\n\nbool    BGM_TaskQueue::ProcessNonRealTimeThreadTask(BGM_Task* inTask)\n{\n#if DEBUG  // This Assert macro always checks the condition, if for some reason the compiler doesn't optimise it away, even in release builds\n    Assert(mNonRealTimeThread.IsCurrentThread(), \"ProcessNonRealTimeThreadTask should only be called on the non-realtime worker thread.\");\n    Assert(mNonRealTimeThread.IsTimeShareThread(), \"mNonRealTimeThread should not be in a time-constraint priority band.\");\n#endif\n    \n    switch(inTask->GetTaskID())\n    {\n        case kBGMTaskStopWorkerThread:\n            DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Stopping\");\n            // Return that the thread should stop itself\n            return true;\n            \n        case kBGMTaskStartClientIO:\n            DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Processing kBGMTaskStartClientIO\");\n            try\n            {\n                BGM_Clients* theClients = reinterpret_cast<BGM_Clients*>(inTask->GetArg1());\n                bool didStartIO = BGM_ClientTasks::StartIONonRT(theClients, static_cast<UInt32>(inTask->GetArg2()));\n                inTask->SetReturnValue(didStartIO);\n            }\n            // TODO: Catch the other types of exceptions BGM_ClientTasks::StartIONonRT can throw here as well. Set the task's return\n            //       value (rather than rethrowing) so the exceptions can be handled if the task was queued sync. Then\n            //       QueueSync_StartClientIO can throw some exception and BGM_StartIO can return an appropriate error code to the\n            //       HAL, instead of the driver just crashing.\n            //\n            //       Do the same for the kBGMTaskStopClientIO case below. And should we set a return value in the catch block for\n            //       BGM_InvalidClientException as well, so it can also be rethrown in QueueSync_StartClientIO and then handled?\n            catch(BGM_InvalidClientException)\n            {\n                DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Ignoring BGM_InvalidClientException thrown by StartIONonRT. %s\",\n                         \"It's possible the client was removed before this task was processed.\");\n            }\n            break;\n\n        case kBGMTaskStopClientIO:\n            DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Processing kBGMTaskStopClientIO\");\n            try\n            {\n                BGM_Clients* theClients = reinterpret_cast<BGM_Clients*>(inTask->GetArg1());\n                bool didStopIO = BGM_ClientTasks::StopIONonRT(theClients, static_cast<UInt32>(inTask->GetArg2()));\n                inTask->SetReturnValue(didStopIO);\n            }\n            catch(BGM_InvalidClientException)\n            {\n                DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Ignoring BGM_InvalidClientException thrown by StopIONonRT. %s\",\n                         \"It's possible the client was removed before this task was processed.\");\n            }\n            break;\n            \n        case kBGMTaskSendPropertyNotification:\n            DebugMsg(\"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Processing kBGMTaskSendPropertyNotification\");\n            {\n                AudioObjectPropertyAddress thePropertyAddress[] = {\n                    { static_cast<UInt32>(inTask->GetArg1()), kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster } };\n                BGM_PlugIn::Host_PropertiesChanged(static_cast<AudioObjectID>(inTask->GetArg2()), 1, thePropertyAddress);\n            }\n            break;\n            \n        default:\n            Assert(false, \"BGM_TaskQueue::ProcessNonRealTimeThreadTask: Unexpected task ID\");\n            break;\n    }\n    \n    return false;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_TaskQueue.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_TaskQueue.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n#ifndef __BGMDriver__BGM_TaskQueue__\n#define __BGMDriver__BGM_TaskQueue__\n\n// PublicUtility Includes\n#include \"CAPThread.h\"\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wsign-conversion\"\n#include \"CAAtomicStack.h\"\n#pragma clang diagnostic pop\n\n// STL Includes\n#include <functional>\n\n// System Includes\n#include <mach/semaphore.h>\n#include <CoreAudio/AudioHardware.h>\n\n\n// Forward declarations\nclass BGM_Clients;\nclass BGM_ClientMap;\n\n\n#pragma clang assume_nonnull begin\n\n//==================================================================================================\n//\tBGM_TaskQueue\n//\n//  This class has two worker threads, one with real-time priority and one with default priority,\n//  that tasks can be dispatched to. The two main use cases are dispatching work from a real-time\n//  thread to be done async, and dispatching work from a non-real-time thread that needs to run on\n//  a real-time thread to avoid priority inversions.\n//==================================================================================================\n\nclass BGM_TaskQueue\n{\n    \nprivate:\n    enum BGM_TaskID {\n        kBGMTaskUninitialized,\n        kBGMTaskStopWorkerThread,\n        \n        // Realtime thread only\n        kBGMTaskSwapClientShadowMaps,\n        \n        // Non-realtime thread only\n        kBGMTaskStartClientIO,\n        kBGMTaskStopClientIO,\n        kBGMTaskSendPropertyNotification\n    };\n    \n    class BGM_Task\n    {\n    public:\n                                        BGM_Task(BGM_TaskID inTaskID = kBGMTaskUninitialized, bool inIsSync = false, UInt64 inArg1 = 0, UInt64 inArg2 = 0) : mNext(NULL), mTaskID(inTaskID), mIsSync(inIsSync), mArg1(inArg1), mArg2(inArg2) { };\n        \n        BGM_TaskID                      GetTaskID() { return mTaskID; }\n        \n        // True if the thread that queued this task is blocking until the task is completed\n        bool                            IsSync() { return mIsSync; }\n        \n        UInt64                          GetArg1() { return mArg1; }\n        UInt64                          GetArg2() { return mArg2; }\n        \n        UInt64                          GetReturnValue() { return mReturnValue; }\n        void                            SetReturnValue(UInt64 inReturnValue) { mReturnValue = inReturnValue; }\n        \n        bool                            IsComplete() { return mIsComplete; }\n        void                            MarkCompleted() { mIsComplete = true; }\n        \n        // Used by TAtomicStack\n        BGM_Task* __nullable &          next() { return mNext; }\n        BGM_Task* __nullable            mNext;\n        \n    private:\n        BGM_TaskID                      mTaskID;\n        bool                            mIsSync;\n        UInt64                          mArg1;\n        UInt64                          mArg2;\n        UInt64                          mReturnValue = INT64_MAX;\n        bool                            mIsComplete = false;\n    };\n    \npublic:\n                                        BGM_TaskQueue();\n                                        ~BGM_TaskQueue();\n                                        // Disallow copying\n                                        BGM_TaskQueue(const BGM_TaskQueue&) = delete;\n                                        BGM_TaskQueue& operator=(const BGM_TaskQueue&) = delete;\n    \nprivate:\n    static UInt32                       NanosToAbsoluteTime(UInt32 inNanos);\n    \npublic:\n    void                                QueueSync_SwapClientShadowMaps(BGM_ClientMap* inClientMap);\n    \n    // Sends a property changed notification to the BGMDevice host. Assumes the scope and element are kAudioObjectPropertyScopeGlobal and\n    // kAudioObjectPropertyElementMaster because currently those are the only ones we use.\n    void                                QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty, AudioObjectID inDeviceID);\n    \n    // Set/unset a client's is-doing-IO flag\n    \n    inline bool                         QueueSync_StartClientIO(BGM_Clients* inClients, UInt32 inClientID) { return Queue_UpdateClientIOState(true, inClients, inClientID, true); }\n    inline bool                         QueueSync_StopClientIO(BGM_Clients* inClients, UInt32 inClientID) { return Queue_UpdateClientIOState(true, inClients, inClientID, false); }\n    \n    inline void                         QueueAsync_StartClientIO(BGM_Clients* inClients, UInt32 inClientID) { Queue_UpdateClientIOState(false, inClients, inClientID, true); }\n    inline void                         QueueAsync_StopClientIO(BGM_Clients* inClients, UInt32 inClientID) { Queue_UpdateClientIOState(false, inClients, inClientID, false); }\n    \nprivate:\n    bool                                Queue_UpdateClientIOState(bool inSync, BGM_Clients* inClients, UInt32 inClientID, bool inDoingIO);\n    \n    UInt64                              QueueSync(BGM_TaskID inTaskID, bool inRunOnRealtimeThread, UInt64 inTaskArg1 = 0, UInt64 inTaskArg2 = 0);\n    \n    void                                QueueOnNonRealtimeThread(BGM_Task inTask);\n    \npublic:\n    void                                AssertCurrentThreadIsRTWorkerThread(const char* inCallerMethodName);\n    \nprivate:\n    static void* __nullable             RealTimeThreadProc(void* inRefCon);\n    static void* __nullable             NonRealTimeThreadProc(void* inRefCon);\n    \n    void                                WorkerThreadProc(semaphore_t inWorkQueuedSemaphore, semaphore_t inSyncTaskCompletedSemaphore, TAtomicStack<BGM_Task>* inTasks, TAtomicStack2<BGM_Task>* __nullable inFreeList, std::function<bool(BGM_Task*)> inProcessTask);\n    \n    // These return true when the thread should be stopped\n    bool                                ProcessRealTimeThreadTask(BGM_Task* inTask);\n    bool                                ProcessNonRealTimeThreadTask(BGM_Task* inTask);\n    \nprivate:\n    // The worker threads that perform the queued tasks\n    CAPThread                           mRealTimeThread;\n    CAPThread                           mNonRealTimeThread;\n    \n    // The approximate amount of time we'll need whenever our real-time thread is scheduled. This is currently just\n    // set to the minimum (see sched_prim.c) because our real-time tasks do very little work.\n    //\n    // TODO: Would it be better to specify these in absolute time, which would make them relative to the system's bus\n    //       speed? Or even calculate them from the system's CPU/RAM speed? Note that none of our tasks actually have\n    //       a deadline (though that might change). They just have to run with real-time priority to avoid causing\n    //       priority inversions on the IO thread.\n    static const UInt32                 kRealTimeThreadNominalComputationNs = 50 * NSEC_PER_USEC;\n    // The maximum amount of time the real-time thread can take to finish its computation after being scheduled.\n    static const UInt32                 kRealTimeThreadMaximumComputationNs = 60 * NSEC_PER_USEC;\n    \n    // We use Mach semaphores for communication with the worker threads because signalling them is real-time safe.\n    \n    // Signalled to tell the worker threads when there are tasks for them process.\n    semaphore_t                         mRealTimeThreadWorkQueuedSemaphore;\n    semaphore_t                         mNonRealTimeThreadWorkQueuedSemaphore;\n    // Signalled when a worker thread completes a task, if the thread that queued that task is blocking on it.\n    semaphore_t                         mRealTimeThreadSyncTaskCompletedSemaphore;\n    semaphore_t                         mNonRealTimeThreadSyncTaskCompletedSemaphore;\n    \n    // When a task is queued we add it to one of these, depending on which worker thread it will run on. Using\n    // TAtomicStack lets us safely add and remove tasks on real-time threads.\n    //\n    // We use TAtomicStack rather than TAtomicStack2 because we need pop_all_reversed() to make sure we process the\n    // tasks in order. (It might have been better to use OSAtomicFifoEnqueue/OSAtomicFifoDequeue, but I only\n    // recently found out about them.)\n    TAtomicStack<BGM_Task>              mRealTimeThreadTasks;\n    TAtomicStack<BGM_Task>              mNonRealTimeThreadTasks;\n    \n    // The number of tasks to pre-allocate and add to the non-realtime task free list. Should be large enough that\n    // the free list is never emptied. (At least not while IO could be running.)\n    static const UInt32                 kNonRealTimeThreadTaskBufferSize = 512;\n    // Realtime threads can't safely allocate memory, so when they queue a task the memory for it comes from this\n    // free list. We pre-allocate as many tasks as they should ever need in the constructor. (But if the free list\n    // runs out of tasks somehow the realtime thread will allocate a new one.)\n    //\n    // There's a similar free list used in Apple's CAThreadSafeList.h.\n    //\n    // We can use TAtomicStack2 instead of TAtomicStack because we never call pop_all on the free list.\n    TAtomicStack2<BGM_Task>             mNonRealTimeThreadTasksFreeList;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* __BGMDriver__BGM_TaskQueue__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_VolumeControl.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_VolumeControl.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//  Copyright (C) 2013 Apple Inc. All Rights Reserved.\n//\n\n// Self Include\n#include \"BGM_VolumeControl.h\"\n\n// Local Includes\n#include \"BGM_PlugIn.h\"\n\n// PublicUtility Includes\n#include \"CAException.h\"\n#include \"CADebugMacros.h\"\n#include \"CADispatchQueue.h\"\n#include \"BGM_Utils.h\"\n\n// STL Includes\n#include <algorithm>\n\n// System Includes\n#include <CoreAudio/AudioHardwareBase.h>\n#include <Accelerate/Accelerate.h>\n\n\n#pragma clang assume_nonnull begin\n\n#pragma mark Construction/Destruction\n\nBGM_VolumeControl::BGM_VolumeControl(AudioObjectID inObjectID,\n                                     AudioObjectID inOwnerObjectID,\n                                     AudioObjectPropertyScope inScope,\n                                     AudioObjectPropertyElement inElement)\n:\n    BGM_Control(inObjectID,\n                kAudioVolumeControlClassID,\n                kAudioLevelControlClassID,\n                inOwnerObjectID,\n                inScope,\n                inElement),\n    mMutex(\"Volume Control\"),\n    mVolumeRaw(kDefaultMinRawVolume),\n    mAmplitudeGain(0.0f),\n    mMinVolumeRaw(kDefaultMinRawVolume),\n    mMaxVolumeRaw(kDefaultMaxRawVolume),\n    mMinVolumeDb(kDefaultMinDbVolume),\n    mMaxVolumeDb(kDefaultMaxDbVolume),\n    mWillApplyVolumeToAudio(false)\n{\n    // Setup the volume curve with the one range\n    mVolumeCurve.AddRange(mMinVolumeRaw, mMaxVolumeRaw, mMinVolumeDb, mMaxVolumeDb);\n}\n\n#pragma mark Property Operations\n\nbool    BGM_VolumeControl::HasProperty(AudioObjectID inObjectID,\n                                       pid_t inClientPID,\n                                       const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioLevelControlPropertyScalarValue:\n        case kAudioLevelControlPropertyDecibelValue:\n        case kAudioLevelControlPropertyDecibelRange:\n        case kAudioLevelControlPropertyConvertScalarToDecibels:\n        case kAudioLevelControlPropertyConvertDecibelsToScalar:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Control::HasProperty(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nbool    BGM_VolumeControl::IsPropertySettable(AudioObjectID inObjectID,\n                                              pid_t inClientPID,\n                                              const AudioObjectPropertyAddress& inAddress) const\n{\n    CheckObjectID(inObjectID);\n\n    bool theAnswer = false;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioLevelControlPropertyDecibelRange:\n        case kAudioLevelControlPropertyConvertScalarToDecibels:\n        case kAudioLevelControlPropertyConvertDecibelsToScalar:\n            theAnswer = false;\n            break;\n\n        case kAudioLevelControlPropertyScalarValue:\n        case kAudioLevelControlPropertyDecibelValue:\n            theAnswer = true;\n            break;\n\n        default:\n            theAnswer = BGM_Control::IsPropertySettable(inObjectID, inClientPID, inAddress);\n            break;\n    };\n\n    return theAnswer;\n}\n\nUInt32  BGM_VolumeControl::GetPropertyDataSize(AudioObjectID inObjectID,\n                                               pid_t inClientPID,\n                                               const AudioObjectPropertyAddress& inAddress,\n                                               UInt32 inQualifierDataSize,\n                                               const void* inQualifierData) const\n{\n    CheckObjectID(inObjectID);\n\n    UInt32 theAnswer = 0;\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioLevelControlPropertyScalarValue:\n            theAnswer = sizeof(Float32);\n            break;\n\n        case kAudioLevelControlPropertyDecibelValue:\n            theAnswer = sizeof(Float32);\n            break;\n\n        case kAudioLevelControlPropertyDecibelRange:\n            theAnswer = sizeof(AudioValueRange);\n            break;\n\n        case kAudioLevelControlPropertyConvertScalarToDecibels:\n            theAnswer = sizeof(Float32);\n            break;\n\n        case kAudioLevelControlPropertyConvertDecibelsToScalar:\n            theAnswer = sizeof(Float32);\n            break;\n\n        default:\n            theAnswer = BGM_Control::GetPropertyDataSize(inObjectID,\n                                                         inClientPID,\n                                                         inAddress,\n                                                         inQualifierDataSize,\n                                                         inQualifierData);\n            break;\n    };\n\n    return theAnswer;\n}\n\nvoid    BGM_VolumeControl::GetPropertyData(AudioObjectID inObjectID,\n                                           pid_t inClientPID,\n                                           const AudioObjectPropertyAddress& inAddress,\n                                           UInt32 inQualifierDataSize,\n                                           const void* inQualifierData,\n                                           UInt32 inDataSize,\n                                           UInt32& outDataSize,\n                                           void* outData) const\n{\n    CheckObjectID(inObjectID);\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioLevelControlPropertyScalarValue:\n            // This returns the value of the control in the normalized range of 0 to 1.\n            {\n                ThrowIf(inDataSize < sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::GetPropertyData: not enough space for the return value \"\n                        \"of kAudioLevelControlPropertyScalarValue for the volume control\");\n\n                CAMutex::Locker theLocker(mMutex);\n\n                *reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToScalar(mVolumeRaw);\n                outDataSize = sizeof(Float32);\n            }\n            break;\n\n        case kAudioLevelControlPropertyDecibelValue:\n            // This returns the dB value of the control.\n            {\n                ThrowIf(inDataSize < sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::GetPropertyData: not enough space for the return value \"\n                        \"of kAudioLevelControlPropertyDecibelValue for the volume control\");\n\n                CAMutex::Locker theLocker(mMutex);\n\n                *reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToDB(mVolumeRaw);\n                outDataSize = sizeof(Float32);\n            }\n            break;\n\n        case kAudioLevelControlPropertyDecibelRange:\n            // This returns the dB range of the control.\n            ThrowIf(inDataSize < sizeof(AudioValueRange),\n                    CAException(kAudioHardwareBadPropertySizeError),\n                    \"BGM_VolumeControl::GetPropertyData: not enough space for the return value of \"\n                    \"kAudioLevelControlPropertyDecibelRange for the volume control\");\n            reinterpret_cast<AudioValueRange*>(outData)->mMinimum = mVolumeCurve.GetMinimumDB();\n            reinterpret_cast<AudioValueRange*>(outData)->mMaximum = mVolumeCurve.GetMaximumDB();\n            outDataSize = sizeof(AudioValueRange);\n            break;\n\n        case kAudioLevelControlPropertyConvertScalarToDecibels:\n            // This takes the scalar value in outData and converts it to dB.\n            {\n                ThrowIf(inDataSize < sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::GetPropertyData: not enough space for the return value \"\n                        \"of kAudioLevelControlPropertyConvertScalarToDecibels for the volume \"\n                        \"control\");\n\n                // clamp the value to be between 0 and 1\n                Float32 theVolumeValue = *reinterpret_cast<Float32*>(outData);\n                theVolumeValue = std::min(1.0f, std::max(0.0f, theVolumeValue));\n\n                // do the conversion\n                *reinterpret_cast<Float32*>(outData) =\n                        mVolumeCurve.ConvertScalarToDB(theVolumeValue);\n\n                // report how much we wrote\n                outDataSize = sizeof(Float32);\n            }\n            break;\n\n        case kAudioLevelControlPropertyConvertDecibelsToScalar:\n            // This takes the dB value in outData and converts it to scalar.\n            {\n                ThrowIf(inDataSize < sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::GetPropertyData: not enough space for the return value \"\n                        \"of kAudioLevelControlPropertyConvertDecibelsToScalar for the volume \"\n                        \"control\");\n\n                // clamp the value to be between mMinVolumeDb and mMaxVolumeDb\n                Float32 theVolumeValue = *reinterpret_cast<Float32*>(outData);\n                theVolumeValue = std::min(mMaxVolumeDb, std::max(mMinVolumeDb, theVolumeValue));\n\n                // do the conversion\n                *reinterpret_cast<Float32*>(outData) =\n                        mVolumeCurve.ConvertDBToScalar(theVolumeValue);\n\n                // report how much we wrote\n                outDataSize = sizeof(Float32);\n            }\n            break;\n\n        default:\n            BGM_Control::GetPropertyData(inObjectID,\n                                         inClientPID,\n                                         inAddress,\n                                         inQualifierDataSize,\n                                         inQualifierData,\n                                         inDataSize,\n                                         outDataSize,\n                                         outData);\n            break;\n    };\n}\n\nvoid    BGM_VolumeControl::SetPropertyData(AudioObjectID inObjectID,\n                                           pid_t inClientPID,\n                                           const AudioObjectPropertyAddress& inAddress,\n                                           UInt32 inQualifierDataSize,\n                                           const void* inQualifierData,\n                                           UInt32 inDataSize,\n                                           const void* inData)\n{\n    CheckObjectID(inObjectID);\n\n    switch(inAddress.mSelector)\n    {\n        case kAudioLevelControlPropertyScalarValue:\n            {\n                ThrowIf(inDataSize != sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::SetPropertyData: wrong size for the data for \"\n                        \"kAudioLevelControlPropertyScalarValue\");\n\n                // Read the new scalar volume.\n                Float32 theNewVolumeScalar = *reinterpret_cast<const Float32*>(inData);\n                SetVolumeScalar(theNewVolumeScalar);\n            }\n            break;\n\n        case kAudioLevelControlPropertyDecibelValue:\n            {\n                ThrowIf(inDataSize != sizeof(Float32),\n                        CAException(kAudioHardwareBadPropertySizeError),\n                        \"BGM_VolumeControl::SetPropertyData: wrong size for the data for \"\n                        \"kAudioLevelControlPropertyDecibelValue\");\n\n                // Read the new volume in dB.\n                Float32 theNewVolumeDb = *reinterpret_cast<const Float32*>(inData);\n                SetVolumeDb(theNewVolumeDb);\n            }\n            break;\n\n        default:\n            BGM_Control::SetPropertyData(inObjectID,\n                                         inClientPID,\n                                         inAddress,\n                                         inQualifierDataSize,\n                                         inQualifierData,\n                                         inDataSize,\n                                         inData);\n            break;\n    };\n}\n\n#pragma mark Accessors\n\nvoid    BGM_VolumeControl::SetVolumeScalar(Float32 inNewVolumeScalar)\n{\n    // For the scalar volume, we clamp the new value to [0, 1]. Note that if this value changes, it\n    // implies that the dB value changes too.\n    inNewVolumeScalar = std::min(1.0f, std::max(0.0f, inNewVolumeScalar));\n\n    // Store the new volume.\n    SInt32 theNewVolumeRaw = mVolumeCurve.ConvertScalarToRaw(inNewVolumeScalar);\n    SetVolumeRaw(theNewVolumeRaw);\n}\n\nvoid    BGM_VolumeControl::SetVolumeDb(Float32 inNewVolumeDb)\n{\n    // For the dB value, we first convert it to a raw value since that is how the value is tracked.\n    // Note that if this value changes, it implies that the scalar value changes as well.\n\n    // Clamp the new volume.\n    inNewVolumeDb = std::min(mMaxVolumeDb, std::max(mMinVolumeDb, inNewVolumeDb));\n\n    // Store the new volume.\n    SInt32 theNewVolumeRaw = mVolumeCurve.ConvertDBToRaw(inNewVolumeDb);\n    SetVolumeRaw(theNewVolumeRaw);\n}\n\nvoid    BGM_VolumeControl::SetWillApplyVolumeToAudio(bool inWillApplyVolumeToAudio)\n{\n    mWillApplyVolumeToAudio = inWillApplyVolumeToAudio;\n}\n\n#pragma mark IO Operations\n\nbool    BGM_VolumeControl::WillApplyVolumeToAudioRT() const\n{\n    return mWillApplyVolumeToAudio;\n}\n\nvoid    BGM_VolumeControl::ApplyVolumeToAudioRT(Float32* ioBuffer, UInt32 inBufferFrameSize) const\n{\n    ThrowIf(!mWillApplyVolumeToAudio,\n            CAException(kAudioHardwareIllegalOperationError),\n            \"BGM_VolumeControl::ApplyVolumeToAudioRT: This control doesn't process audio data\");\n\n    // Don't bother if the change is very unlikely to be perceptible.\n    if((mAmplitudeGain < 0.99f) || (mAmplitudeGain > 1.01f))\n    {\n        // Apply the amount of gain/loss for the current volume to the audio signal by multiplying\n        // each sample. This call to vDSP_vsmul is equivalent to\n        //\n        // for(UInt32 i = 0; i < inBufferFrameSize * 2; i++)\n        // {\n        //     ioBuffer[i] *= mAmplitudeGain;\n        // }\n        //\n        // but a bit faster on processors with newer SIMD instructions. However, it shouldn't take\n        // more than a few microseconds either way. (Unless some of the samples were subnormal\n        // numbers for some reason.)\n        //\n        // It would be a tiny bit faster still to not do this in-place, i.e. use separate input and\n        // output buffers, but then we'd have to copy the data into the output buffer when the\n        // volume is at 1.0. With our current use of this class, most people will leave the volume\n        // at 1.0, so it wouldn't be worth it.\n        vDSP_vsmul(ioBuffer, 1, &mAmplitudeGain, ioBuffer, 1, inBufferFrameSize * 2);\n    }\n}\n\n#pragma mark Implementation\n\nvoid    BGM_VolumeControl::SetVolumeRaw(SInt32 inNewVolumeRaw)\n{\n    CAMutex::Locker theLocker(mMutex);\n\n    // Make sure the new raw value is in the proper range.\n    inNewVolumeRaw = std::min(std::max(mMinVolumeRaw, inNewVolumeRaw), mMaxVolumeRaw);\n\n    // Store the new volume.\n    if(mVolumeRaw != inNewVolumeRaw)\n    {\n        mVolumeRaw = inNewVolumeRaw;\n\n        // CAVolumeCurve deals with volumes in three different scales: scalar, dB and raw. Raw\n        // volumes are the number of steps along the dB curve, so dB and raw volumes are linearly\n        // related.\n        //\n        // macOS uses the scalar volume to set the position of its volume sliders for the\n        // device. We have to set the scalar volume to the position of our volume slider for a\n        // device (more specifically, a linear mapping of it onto [0,1]) or macOS's volume sliders\n        // or it will work differently to our own.\n        //\n        // When we set a new slider position as the device's scalar volume, we convert it to raw\n        // with CAVolumeCurve::ConvertScalarToRaw, which will \"undo the curve\". However, we haven't\n        // applied the curve at that point.\n        //\n        // So, to actually apply the curve, we use CAVolumeCurve::ConvertRawToScalar to get the\n        // linear slider position back, map it onto the range of raw volumes and use\n        // CAVolumeCurve::ConvertRawToScalar again to apply the curve.\n        //\n        // It might be that we should be using CAVolumeCurve with transfer functions x^n where\n        // 0 < n < 1, but a lot more of the transfer functions it supports have n >= 1, including\n        // the default one. So I'm a bit confused.\n        //\n        // TODO: I think this means the dB volume we report will be wrong. It also makes the code\n        //       pretty confusing.\n        Float32 theSliderPosition = mVolumeCurve.ConvertRawToScalar(mVolumeRaw);\n\n        // TODO: This assumes the control should never boost the signal. (So, technically, it never\n        //       actually applies gain, only loss.)\n        SInt32 theRawRange = mMaxVolumeRaw - mMinVolumeRaw;\n        SInt32 theSliderPositionInRawSteps = static_cast<SInt32>(theSliderPosition * theRawRange);\n        theSliderPositionInRawSteps += mMinVolumeRaw;\n\n        mAmplitudeGain = mVolumeCurve.ConvertRawToScalar(theSliderPositionInRawSteps);\n\n        BGMAssert((mAmplitudeGain >= 0.0f) && (mAmplitudeGain <= 1.0f), \"Gain not in [0,1]\");\n\n        // Send notifications.\n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n            AudioObjectPropertyAddress theChangedProperties[2];\n            theChangedProperties[0] = { kAudioLevelControlPropertyScalarValue, mScope, mElement };\n            theChangedProperties[1] = { kAudioLevelControlPropertyDecibelValue, mScope, mElement };\n\n            BGM_PlugIn::Host_PropertiesChanged(GetObjectID(), 2, theChangedProperties);\n        });\n    }\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_VolumeControl.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_VolumeControl.h\n//  BGMDriver\n//\n//  Copyright © 2017 Kyle Neideck\n//\n\n#ifndef BGMDriver__BGM_VolumeControl\n#define BGMDriver__BGM_VolumeControl\n\n// Superclass Includes\n#include \"BGM_Control.h\"\n\n// PublicUtility Includes\n#include \"CAVolumeCurve.h\"\n#include \"CAMutex.h\"\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_VolumeControl\n:\n    public BGM_Control\n{\n\n#pragma mark Construction/Destruction\n\npublic:\n                        BGM_VolumeControl(AudioObjectID inObjectID,\n                                          AudioObjectID inOwnerObjectID,\n                                          AudioObjectPropertyScope inScope =\n                                                  kAudioObjectPropertyScopeOutput,\n                                          AudioObjectPropertyElement inElement =\n                                                  kAudioObjectPropertyElementMaster);\n\n#pragma mark Property Operations\n\n    virtual bool        HasProperty(AudioObjectID inObjectID,\n                                    pid_t inClientPID,\n                                    const AudioObjectPropertyAddress& inAddress) const;\n    virtual bool        IsPropertySettable(AudioObjectID inObjectID,\n                                           pid_t inClientPID,\n                                           const AudioObjectPropertyAddress& inAddress) const;\n    virtual UInt32      GetPropertyDataSize(AudioObjectID inObjectID,\n                                            pid_t inClientPID,\n                                            const AudioObjectPropertyAddress& inAddress,\n                                            UInt32 inQualifierDataSize,\n                                            const void* inQualifierData) const;\n    virtual void        GetPropertyData(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress,\n                                        UInt32 inQualifierDataSize,\n                                        const void* inQualifierData,\n                                        UInt32 inDataSize,\n                                        UInt32& outDataSize,\n                                        void* outData) const;\n    virtual void        SetPropertyData(AudioObjectID inObjectID,\n                                        pid_t inClientPID,\n                                        const AudioObjectPropertyAddress& inAddress,\n                                        UInt32 inQualifierDataSize,\n                                        const void* inQualifierData,\n                                        UInt32 inDataSize,\n                                        const void* inData);\n\n#pragma mark Accessors\n\n    /*!\n     @return The curve used by this control to convert volume values from scalar into signal gain\n             and/or decibels. A continuous 2D function.\n     */\n    CAVolumeCurve&      GetVolumeCurve() { return mVolumeCurve; }\n\n    /*!\n     Set the volume of this control to a given position along its volume curve. (See\n     GetVolumeCurve.)\n\n     Passing 1.0 sets the volume to the maximum and 0.0 sets it to the minimum. The gain/loss the\n     control applies (and/or reports to apply) to the audio it controls is given by the y-position\n     of the curve at the x-position inNewVolumeScalar.\n\n     In general, since the control's volume curve will be applied to the given value, it should be\n     linearly related to a volume input by the user.\n\n     @param inNewVolumeScalar The volume to set. Will be clamped to [0.0, 1.0].\n     */\n    void                SetVolumeScalar(Float32 inNewVolumeScalar);\n    /*!\n     Set the volume of this control in decibels.\n\n     @param inNewVolumeDb The volume to set. Will be clamped to the minimum/maximum dB volumes of\n                          the control. See GetVolumeCurve.\n     */\n    void                SetVolumeDb(Float32 inNewVolumeDb);\n\n    /*!\n     Set this volume control to apply its volume to audio data, which allows clients to call\n     ApplyVolumeToAudioRT. When this is set true, WillApplyVolumeToAudioRT will return true. Set to\n     false initially.\n     */\n    void                SetWillApplyVolumeToAudio(bool inWillApplyVolumeToAudio);\n\n#pragma mark IO Operations\n\n    /*!\n     @return True if clients should use ApplyVolumeToAudioRT to apply this volume control's volume\n             to their audio data while doing IO.\n     */\n    bool                WillApplyVolumeToAudioRT() const;\n    /*!\n     Apply this volume control's volume to the samples in ioBuffer. That is, increase/decrease the\n     volumes of the samples by the current volume of this control.\n\n     @param ioBuffer The audio sample buffer to process.\n     @param inBufferFrameSize The number of sample frames in ioBuffer. The audio is assumed to be in\n                              stereo, i.e. two samples per frame. (Though, hopefully we'll support\n                              more at some point.)\n     @throws CAException If SetWillApplyVolumeToAudio hasn't been used to set this control to apply\n                         its volume to audio data.\n     */\n    void                ApplyVolumeToAudioRT(Float32* ioBuffer, UInt32 inBufferFrameSize) const;\n\n#pragma mark Implementation\n\nprotected:\n    void                SetVolumeRaw(SInt32 inNewVolumeRaw);\n\nprivate:\n    const SInt32        kDefaultMinRawVolume = 0;\n    const SInt32        kDefaultMaxRawVolume = 96;\n    const Float32       kDefaultMinDbVolume  = -96.0f;\n    const Float32       kDefaultMaxDbVolume  = 0.0f;\n\n    CAMutex             mMutex;\n\n    SInt32              mVolumeRaw;\n    SInt32              mMinVolumeRaw;\n    SInt32              mMaxVolumeRaw;\n    Float32             mMinVolumeDb;\n    Float32             mMaxVolumeDb;\n\n    CAVolumeCurve       mVolumeCurve;\n    // The gain (or loss) to apply to an audio signal to increase/decrease its volume by the current\n    // volume of this control.\n    Float32             mAmplitudeGain;\n\n    bool                mWillApplyVolumeToAudio;\n\n};\n\n#pragma clang assume_nonnull end\n\n#endif /* BGMDriver__BGM_VolumeControl */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_WrappedAudioEngine.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_WrappedAudioEngine.cpp\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_WrappedAudioEngine.h\"\n\n\n// TODO: Register to be notified when the IO Registry values for these change so we can cache them\n\nUInt64\tBGM_WrappedAudioEngine::GetSampleRate() const\n{\n    return 0;\n}\n\nkern_return_t BGM_WrappedAudioEngine::SetSampleRate(Float64 inNewSampleRate)\n{\n    #pragma unused (inNewSampleRate)\n    \n    return 0;\n}\n\nUInt32 BGM_WrappedAudioEngine::GetSampleBufferFrameSize() const\n{\n    return 0;\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_WrappedAudioEngine.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_WrappedAudioEngine.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  The plan for this is to allow devices with IOAudioEngine drivers to be used as the output device\n//  directly from BGMDriver, rather than going through BGMApp. That way we get roughly the same CPU\n//  usage and latency as normal, and don't need to worry about pausing BGMApp's IO when no clients\n//  are doing IO. It also lets BGMDriver mostly continue working without BGMApp running. I've written\n//  a very experimental version that mostly works but the code needs a lot of clean up so I haven't\n//  added it to this project yet.\n//\n\n#ifndef __BGMDriver__BGM_WrappedAudioEngine__\n#define __BGMDriver__BGM_WrappedAudioEngine__\n\n#include <CoreAudio/CoreAudioTypes.h>\n#include <mach/kern_return.h>\n\n\nclass BGM_WrappedAudioEngine\n{\n    \npublic:\n    UInt64          GetSampleRate() const;\n    kern_return_t   SetSampleRate(Float64 inNewSampleRate);\n    UInt32          GetSampleBufferFrameSize() const;\n    \n};\n\n#endif /* __BGMDriver__BGM_WrappedAudioEngine__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_XPCHelper.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_XPCHelper.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n#ifndef BGMDriver__BGM_XPCHelper\n#define BGMDriver__BGM_XPCHelper\n\n// System Includes\n#include <MacTypes.h>\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n// On failure, returns one of the kBGMXPC_* error codes, or the error code received from BGMXPCHelper. Returns kBGMXPC_Success otherwise.\nUInt64 StartBGMAppPlayThroughSync(bool inIsForUISoundsDevice);\n\n#if defined(__cplusplus)\n}\n#endif\n\n#endif /* BGMDriver__BGM_XPCHelper */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_XPCHelper.m",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_XPCHelper.m\n//  BGMDriver\n//\n//  Copyright © 2016, 2017, 2020, 2024 Kyle Neideck\n//  Copyright © 2020 Aleksey Yurkevich\n//\n\n// Self Include\n#import \"BGM_XPCHelper.h\"\n\n// Local Includes\n#import \"BGMXPCProtocols.h\"\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\nstatic const UInt64 REMOTE_CALL_DEFAULT_TIMEOUT_SECS = 30;\n\nstatic NSXPCConnection* CreateXPCHelperConnection(void)\n{\n    // Create a connection to BGMXPCHelper's Mach service. If it isn't already running, launchd will start BGMXPCHelper when we send\n    // a message to this connection.\n    //\n    // Uses the NSXPCConnectionPrivileged option because BGMXPCHelper has to run in the privileged/global bootstrap context for\n    // BGMDriver to be able to look it up. BGMDriver runs in the coreaudiod process, which runs in the global context, and services\n    // in the global context are only able to look up other services in that context.\n    NSXPCConnection* theConnection = [[NSXPCConnection alloc] initWithMachServiceName:kBGMXPCHelperMachServiceName\n                                                                              options:NSXPCConnectionPrivileged];\n    \n    if (theConnection) {\n        theConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(BGMXPCHelperXPCProtocol)];\n        [theConnection resume];\n    } else {\n        @throw(@\"BGM_XPCHelper::CreateXPCHelperConnection: initWithMachServiceName returned nil\");\n    }\n    \n    return theConnection;\n}\n\nUInt64 StartBGMAppPlayThroughSync(bool inIsForUISoundsDevice)\n{\n    __block UInt64 theAnswer = kBGMXPC_Success;\n    \n    // Connect to our XPC helper.\n    //\n    // We can't initiate an XPC connection with BGMApp directly for security reasons, so we use BGMXPCHelper as an intermediary. (We\n    // could use BGMXPCHelper to initiate the connection and then talk to BGMApp directly, but so far we haven't had any reason to.)\n    //\n    // It would be faster to keep the connection ready whenever BGMApp is a client of BGMDevice, but it's not important for this case.\n    NSXPCConnection* theConnection = CreateXPCHelperConnection();\n    \n    // This semaphore will be signalled when we get a reply from BGMXPCHelper, or the message fails.\n    dispatch_semaphore_t theReplySemaphore = dispatch_semaphore_create(0);\n   \n    // Set the failure callbacks to signal the reply semaphore so we can return immediately if BGMXPCHelper can't be reached. (It\n    // doesn't matter how many times we signal the reply semaphore because we create a new one each time.)\n    void (^failureHandler)(void) = ^{\n        DebugMsg(\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Connection to BGMXPCHelper failed\");\n        \n        theAnswer = kBGMXPC_MessageFailure;\n        dispatch_semaphore_signal(theReplySemaphore);\n    };\n    theConnection.interruptionHandler = failureHandler;\n    theConnection.invalidationHandler = failureHandler;\n    \n    // This remote call to BGMXPCHelper will send a reply when the output device is ready to receive IO. Note that, for security\n    // reasons, we shouldn't trust the reply object.\n    [[theConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {\n        (void)error;\n        DebugMsg(\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Remote call error: %s\",\n                 [[error debugDescription] UTF8String]);\n        \n        failureHandler();\n    }] startBGMAppPlayThroughSyncWithReply:^(NSError* reply) {\n        DebugMsg(\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Got reply from BGMXPCHelper: \\\"%s\\\"\",\n                 [[reply localizedDescription] UTF8String]);\n        \n        theAnswer = kBGMXPC_MessageFailure;\n\n        @try {\n            if (reply)\n            {\n                theAnswer = (UInt64)[reply code];\n            }\n        } @catch(...) {\n            NSLog(@\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Exception while reading reply code\");\n        }\n        \n        // We only need the connection for one call, which was successful, so the losing the connection is no longer a problem.\n        theConnection.interruptionHandler = nil;\n        theConnection.invalidationHandler = nil;\n        \n        // Tell the enclosing function it can return now.\n        dispatch_semaphore_signal(theReplySemaphore);\n    } forUISoundsDevice:inIsForUISoundsDevice];\n    \n    DebugMsg(\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Waiting for BGMApp to tell us the output device is ready for IO\");\n    \n    // Wait on the reply semaphore until we get the reply (or a connection failure).\n    if (0 != dispatch_semaphore_wait(theReplySemaphore,\n                                     dispatch_time(DISPATCH_TIME_NOW, REMOTE_CALL_DEFAULT_TIMEOUT_SECS * NSEC_PER_SEC))) {\n        // Log a warning if we timeout.\n        //\n        // TODO: It's possible that the output device is just taking a really long time to start. Is there some way we could check for\n        //       that, rather than timing out?\n        NSLog(@\"BGM_XPCHelper::StartBGMAppPlayThroughSync: Timed out waiting for the Background Music app to start the output device\");\n        \n        theAnswer = kBGMXPC_Timeout;\n    }\n    \n   [theConnection invalidate];\n    \n    return theAnswer;\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_Client.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Client.cpp\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_Client.h\"\n\n\nBGM_Client::BGM_Client(const AudioServerPlugInClientInfo* inClientInfo)\n:\n    mClientID(inClientInfo->mClientID),\n    mProcessID(inClientInfo->mProcessID),\n    mIsNativeEndian(inClientInfo->mIsNativeEndian),\n    mBundleID(inClientInfo->mBundleID)\n{\n    // The bundle ID ref we were passed is only valid until our plugin returns control to the HAL, so we need to retain\n    // it. (CACFString will handle the rest of its ownership/destruction.)\n    if(inClientInfo->mBundleID != NULL)\n    {\n        CFRetain(inClientInfo->mBundleID);\n    }\n}\n\nvoid    BGM_Client::Copy(const BGM_Client& inClient)\n{\n    mClientID = inClient.mClientID;\n    mProcessID = inClient.mProcessID;\n    mBundleID = inClient.mBundleID;\n    mIsNativeEndian = inClient.mIsNativeEndian;\n    mDoingIO = inClient.mDoingIO;\n    mIsMusicPlayer = inClient.mIsMusicPlayer;\n    mRelativeVolume = inClient.mRelativeVolume;\n    mPanPosition = inClient.mPanPosition;\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_Client.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Client.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n#ifndef __BGMDriver__BGM_Client__\n#define __BGMDriver__BGM_Client__\n\n// PublicUtility Includes\n#include \"CACFString.h\"\n\n// System Includes\n#include <CoreAudio/AudioServerPlugIn.h>\n\n\n#pragma clang assume_nonnull begin\n\n//==================================================================================================\n//\tBGM_Client\n//\n//  Client meaning a client (of the host) of the BGMDevice, i.e. an app registered with the HAL,\n//  generally so it can do IO at some point.\n//==================================================================================================\n\nclass BGM_Client\n{\n    \npublic:\n                                  BGM_Client() = default;\n                                  BGM_Client(const AudioServerPlugInClientInfo* inClientInfo);\n                                  ~BGM_Client() = default;\n                                  BGM_Client(const BGM_Client& inClient) { Copy(inClient); };\n                                  BGM_Client& operator=(const BGM_Client& inClient) { Copy(inClient); return *this; }\n    \nprivate:\n    void                          Copy(const BGM_Client& inClient);\n    \npublic:\n    // These fields are duplicated from AudioServerPlugInClientInfo (except the mBundleID CFStringRef is\n    // wrapped in a CACFString here).\n    UInt32                        mClientID;\n    pid_t                         mProcessID;\n    Boolean                       mIsNativeEndian = true;\n    CACFString                    mBundleID;\n    \n    // Becomes true when the client triggers the plugin host to call StartIO or to begin\n    // kAudioServerPlugInIOOperationThread, and false again on StopIO or when\n    // kAudioServerPlugInIOOperationThread ends\n    bool                          mDoingIO = false;\n    \n    // True if BGMApp has set this client as belonging to the music player app\n    bool                          mIsMusicPlayer = false;\n    \n    // The client's volume relative to other clients. In the range [0.0, 4.0], defaults to 1.0 (unchanged).\n    // mRelativeVolumeCurve is applied to this value when it's set.\n    Float32                       mRelativeVolume = 1.0;\n    \n    // The client's pan position, in the range [-100, 100] where -100 is left and 100 is right\n    SInt32                        mPanPosition = 0;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* __BGMDriver__BGM_Client__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_ClientMap.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017, 2019, 2025 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//\n\n// Self Include\n#include \"BGM_ClientMap.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CACFDictionary.h\"\n#include \"CAException.h\"\n\n\n#pragma clang assume_nonnull begin\n\nvoid    BGM_ClientMap::AddClient(BGM_Client inClient)\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    // If this client has been a client in the past (and has a bundle ID), copy its previous audio settings\n    auto pastClientItr = inClient.mBundleID.IsValid() ? mPastClientMap.find(inClient.mBundleID) : mPastClientMap.end();\n    if(pastClientItr != mPastClientMap.end())\n    {\n        DebugMsg(\"BGM_ClientMap::AddClient: Found previous volume %f and pan %d for client %u\",\n                 pastClientItr->second.mRelativeVolume,\n                 pastClientItr->second.mPanPosition,\n                 inClient.mClientID);\n        inClient.mRelativeVolume = pastClientItr->second.mRelativeVolume;\n        inClient.mPanPosition = pastClientItr->second.mPanPosition;\n    }\n    \n    // Add the new client to the shadow maps\n    AddClientToShadowMaps(inClient);\n    \n    // Swap the maps with their shadow maps\n    SwapInShadowMaps();\n    \n    // The shadow maps (which were the main maps until we swapped them) are now missing the new client. Add it again to\n    // keep the sets of maps identical.\n    AddClientToShadowMaps(inClient);\n\n    // Insert the client into the past clients map. We do this here rather than in RemoveClient\n    // because some apps add multiple clients with the same bundle ID and we want to give them all\n    // the same settings (volume, etc.).\n    if(inClient.mBundleID.IsValid())\n    {\n        mPastClientMap[inClient.mBundleID] = inClient;\n    }\n}\n\nvoid    BGM_ClientMap::AddClientToShadowMaps(BGM_Client inClient)\n{\n    ThrowIf(mClientMapShadow.count(inClient.mClientID) != 0,\n            BGM_InvalidClientException(),\n            \"BGM_ClientMap::AddClientToShadowMaps: Tried to add client whose client ID was already in use\");\n    \n    // Add to the client ID shadow map\n    mClientMapShadow[inClient.mClientID] = inClient;\n    \n    // Get a reference to the client in the map so we can add it to the pointer maps\n    BGM_Client& clientInMap = mClientMapShadow.at(inClient.mClientID);\n    \n    // Add to the PID shadow map\n    mClientMapByPIDShadow[inClient.mProcessID].push_back(&clientInMap);\n    \n    // Add to the bundle ID shadow map\n    if(inClient.mBundleID.IsValid())\n    {\n        mClientMapByBundleIDShadow[inClient.mBundleID].push_back(&clientInMap);\n    }\n}\n\nBGM_Client    BGM_ClientMap::RemoveClient(UInt32 inClientID)\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    auto theClientItr = mClientMapShadow.find(inClientID);\n    \n    // Removing a client that was never added is an error\n    ThrowIf(theClientItr == mClientMapShadow.end(),\n            BGM_InvalidClientException(),\n            \"BGM_ClientMap::RemoveClient: Could not find client to be removed\");\n    \n    BGM_Client theClient = theClientItr->second;\n    \n    // Remove the client from the shadow maps\n    mClientMapShadow.erase(theClientItr);\n    mClientMapByPIDShadow.erase(theClient.mProcessID);\n    if(theClient.mBundleID.IsValid())\n    {\n        mClientMapByBundleID.erase(theClient.mBundleID);\n    }\n    \n    // Swap the maps with their shadow maps\n    SwapInShadowMaps();\n    \n    // Erase the client again so the maps and their shadow maps are kept identical\n    mClientMapShadow.erase(inClientID);\n    mClientMapByPIDShadow.erase(theClient.mProcessID);\n    if(theClient.mBundleID.IsValid())\n    {\n        mClientMapByBundleID.erase(theClient.mBundleID);\n    }\n    \n    return theClient;\n}\n\nbool    BGM_ClientMap::GetClientRT(UInt32 inClientID, BGM_Client* outClient) const\n{\n    CAMutex::Locker theMapsLocker(mMapsMutex);\n    return GetClient(mClientMap, inClientID, outClient);\n}\n\nbool    BGM_ClientMap::GetClientNonRT(UInt32 inClientID, BGM_Client* outClient) const\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    return GetClient(mClientMapShadow, inClientID, outClient);\n}\n\n//static\nbool    BGM_ClientMap::GetClient(const std::map<UInt32, BGM_Client>& inClientMap, UInt32 inClientID, BGM_Client* outClient)\n{\n    auto theClientItr = inClientMap.find(inClientID);\n    \n    if(theClientItr != inClientMap.end())\n    {\n        *outClient = theClientItr->second;\n        return true;\n    }\n    \n    return false;\n}\n\nstd::vector<BGM_Client> BGM_ClientMap::GetClientsByPID(pid_t inPID) const\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    std::vector<BGM_Client> theClients;\n    \n    auto theMapItr = mClientMapByPIDShadow.find(inPID);\n    if(theMapItr != mClientMapByPIDShadow.end())\n    {\n        // Found clients with the PID, so copy them into the return vector\n        for(auto& theClientPtrsItr : theMapItr->second)\n        {\n            theClients.push_back(*theClientPtrsItr);\n        }\n    }\n    \n    return theClients;\n}\n\n#pragma mark Music Player\n\nvoid    BGM_ClientMap::UpdateMusicPlayerFlags(pid_t inMusicPlayerPID)\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    auto theIsMusicPlayerTest = [&] (BGM_Client theClient) {\n        return (theClient.mProcessID == inMusicPlayerPID);\n    };\n    \n    UpdateMusicPlayerFlagsInShadowMaps(theIsMusicPlayerTest);\n    SwapInShadowMaps();\n    UpdateMusicPlayerFlagsInShadowMaps(theIsMusicPlayerTest);\n}\n\nvoid    BGM_ClientMap::UpdateMusicPlayerFlags(CACFString inMusicPlayerBundleID)\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    auto theIsMusicPlayerTest = [&] (BGM_Client theClient) {\n        return (theClient.mBundleID.IsValid() && theClient.mBundleID == inMusicPlayerBundleID);\n    };\n    \n    UpdateMusicPlayerFlagsInShadowMaps(theIsMusicPlayerTest);\n    SwapInShadowMaps();\n    UpdateMusicPlayerFlagsInShadowMaps(theIsMusicPlayerTest);\n}\n\nvoid    BGM_ClientMap::UpdateMusicPlayerFlagsInShadowMaps(std::function<bool(BGM_Client)> inIsMusicPlayerTest)\n{\n    for(auto& theItr : mClientMapShadow)\n    {\n        BGM_Client& theClient = theItr.second;\n        theClient.mIsMusicPlayer = inIsMusicPlayerTest(theClient);\n    }\n}\n\n#pragma mark App Volumes\n\nCACFArray   BGM_ClientMap::CopyClientRelativeVolumesAsAppVolumes(CAVolumeCurve inVolumeCurve) const\n{\n    // Since this is a read-only, non-real-time operation, we can read from the shadow maps to avoid\n    // locking the main maps.\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    CACFArray theAppVolumes(false);\n    \n    for(auto& theClientEntry : mClientMapShadow)\n    {\n        CopyClientIntoAppVolumesArray(theClientEntry.second, inVolumeCurve, theAppVolumes);\n    }\n    \n    for(auto& thePastClientEntry : mPastClientMap)\n    {\n        CopyClientIntoAppVolumesArray(thePastClientEntry.second, inVolumeCurve, theAppVolumes);\n    }\n    \n    return theAppVolumes;\n}\n\nvoid    BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const\n{\n    // Only include clients set to a non-default volume or pan\n    if(inClient.mRelativeVolume != 1.0 || inClient.mPanPosition != 0)\n    {\n        CACFDictionary theAppVolume(false);\n        \n        theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), inClient.mProcessID);\n        theAppVolume.AddString(CFSTR(kBGMAppVolumesKey_BundleID), inClient.mBundleID.CopyCFString());\n        // Reverse the volume conversion from SetClientsRelativeVolumes\n        theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume),\n                               inVolumeCurve.ConvertScalarToRaw(inClient.mRelativeVolume / 4));\n        theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_PanPosition),\n                               inClient.mPanPosition);\n        \n        ioAppVolumes.AppendDictionary(theAppVolume.GetDict());\n    }\n}\n\ntemplate <typename T>\nstd::vector<BGM_Client*> * _Nullable GetClientsFromMap(std::map<T, std::vector<BGM_Client*>> & map, T key) {\n    auto theClientItr = map.find(key);\n    if(theClientItr != map.end()) {\n        return &theClientItr->second;\n    }\n    return nullptr;\n}\n\nstd::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(pid_t inAppPid) {\n    return GetClientsFromMap(mClientMapByPIDShadow, inAppPid);\n}\n\nstd::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(CACFString inAppBundleID) {\n    if(!inAppBundleID.IsValid()) {\n        return nullptr;\n    }\n    return GetClientsFromMap(mClientMapByBundleIDShadow, inAppBundleID);\n}\n\nvoid ShowSetRelativeVolumeMessage(pid_t inAppPID, BGM_Client* theClient);\nvoid ShowSetRelativeVolumeMessage(CACFString inAppBundleID, BGM_Client* theClient);\n\nvoid ShowSetRelativeVolumeMessage(pid_t inAppPID, BGM_Client* theClient) {\n    (void)inAppPID;\n    (void)theClient;\n    DebugMsg(\"BGM_ClientMap::ShowSetRelativeVolumeMessage: Set volume %f for client %u by pid (%d)\",\n             theClient->mRelativeVolume,\n             theClient->mClientID,\n             inAppPID);\n}\n\nvoid ShowSetRelativeVolumeMessage(CACFString inAppBundleID, BGM_Client* theClient) {\n    (void)inAppBundleID;\n    (void)theClient;\n    DebugMsg(\"BGM_ClientMap::ShowSetRelativeVolumeMessage: Set volume %f for client %u by bundle ID (%s)\",\n             theClient->mRelativeVolume,\n             theClient->mClientID,\n             CFStringGetCStringPtr(inAppBundleID.GetCFString(), kCFStringEncodingUTF8));\n}\n\n// Template method declarations are running into LLVM bug 23987\n// TODO: template these.\n\n//bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume) {\n//    return SetClientsRelativeVolumeT<pid_t>(inAppPID, inRelativeVolume);\n//}\n\n//bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume) {\n//    return SetClientsRelativeVolumeT<CACFString>(inAppBundleID, inRelativeVolume)\n//}\n\n//template <typename T>\n//bool BGM_ClientMap::SetClientsRelativeVolume(T searchKey, Float32 inRelativeVolume)\n\nbool BGM_ClientMap::SetClientsRelativeVolume(pid_t searchKey, Float32 inRelativeVolume)\n{\n    bool didChangeVolume = false;\n    \n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    auto theSetVolumesInShadowMapsFunc = [&] {\n        // Look up the clients for the key and update their volumes\n        \n        auto theClients = GetClients(searchKey);\n        if(theClients != nullptr)\n        {\n            for(BGM_Client* theClient : *theClients)\n            {\n                theClient->mRelativeVolume = inRelativeVolume;\n                \n                ShowSetRelativeVolumeMessage(searchKey, theClient);\n                \n                didChangeVolume = true;\n            }\n        }\n    };\n    \n    theSetVolumesInShadowMapsFunc();\n    SwapInShadowMaps();\n    theSetVolumesInShadowMapsFunc();\n    \n    return didChangeVolume;\n}\n\nbool BGM_ClientMap::SetClientsRelativeVolume(CACFString searchKey, Float32 inRelativeVolume)\n{\n    bool didChangeVolume = false;\n\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n\n    auto theSetVolumesInShadowMapsFunc = [&] {\n        // Look up the clients for the key and update their volumes\n\n        auto theClients = GetClients(searchKey);\n        if(theClients != nullptr)\n        {\n            for(BGM_Client* theClient : *theClients)\n            {\n                theClient->mRelativeVolume = inRelativeVolume;\n                \n                ShowSetRelativeVolumeMessage(searchKey, theClient);\n                \n                didChangeVolume = true;\n            }\n        }\n    };\n    \n    theSetVolumesInShadowMapsFunc();\n    SwapInShadowMaps();\n    theSetVolumesInShadowMapsFunc();\n    \n    return didChangeVolume;\n}\n\nbool BGM_ClientMap::SetClientsPanPosition(pid_t searchKey, SInt32 inPanPosition)\n{\n    bool didChangePanPosition = false;\n    \n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    auto theSetPansInShadowMapsFunc = [&] {\n        // Look up the clients for the key and update their pan positions\n        auto theClients = GetClients(searchKey);\n        if(theClients != nullptr) {\n            for(auto theClient: *theClients) {\n                theClient->mPanPosition = inPanPosition;\n                didChangePanPosition = true;\n            }\n        }\n    };\n    \n    theSetPansInShadowMapsFunc();\n    SwapInShadowMaps();\n    theSetPansInShadowMapsFunc();\n    \n    return didChangePanPosition;\n}\n\nbool BGM_ClientMap::SetClientsPanPosition(CACFString searchKey, SInt32 inPanPosition)\n{\n    bool didChangePanPosition = false;\n\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n\n    auto theSetPansInShadowMapsFunc = [&] {\n        // Look up the clients for the key and update their pan positions\n        auto theClients = GetClients(searchKey);\n        if(theClients != nullptr) {\n            for(auto theClient: *theClients) {\n                theClient->mPanPosition = inPanPosition;\n                didChangePanPosition = true;\n            }\n        }\n    };\n    \n    theSetPansInShadowMapsFunc();\n    SwapInShadowMaps();\n    theSetPansInShadowMapsFunc();\n    \n    return didChangePanPosition;\n}\n\nvoid    BGM_ClientMap::UpdateClientIOStateNonRT(UInt32 inClientID, bool inDoingIO)\n{\n    CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);\n    \n    mClientMapShadow[inClientID].mDoingIO = inDoingIO;\n    SwapInShadowMaps();\n    mClientMapShadow[inClientID].mDoingIO = inDoingIO;\n}\n\nvoid    BGM_ClientMap::SwapInShadowMaps()\n{\n    mTaskQueue->QueueSync_SwapClientShadowMaps(this);\n}\n\nvoid    BGM_ClientMap::SwapInShadowMapsRT()\n{\n#if DEBUG\n    // This method should only be called by the realtime worker thread in BGM_TaskQueue. The only safe way to call it is on a realtime\n    // thread while a non-realtime thread is holding the shadow maps mutex. (These assertions assume that the realtime worker thread is\n    // the only thread we'll call this on, but we could decide to change that at some point.)\n    mTaskQueue->AssertCurrentThreadIsRTWorkerThread(\"BGM_ClientMap::SwapInShadowMapsRT\");\n    \n    Assert(!mShadowMapsMutex.IsFree(), \"Can't swap in the shadow maps while the shadow maps mutex is free\");\n    Assert(!mShadowMapsMutex.IsOwnedByCurrentThread(), \"The shadow maps mutex should not be held by a realtime thread\");\n#endif\n    \n    CAMutex::Locker theMapsLocker(mMapsMutex);\n    \n    mClientMap.swap(mClientMapShadow);\n    mClientMapByPID.swap(mClientMapByPIDShadow);\n    mClientMapByBundleID.swap(mClientMapByBundleIDShadow);\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_ClientMap.h\n//  BGMDriver\n//\n//  Copyright © 2016, 2025 Kyle Neideck\n//\n\n#ifndef __BGMDriver__BGM_ClientMap__\n#define __BGMDriver__BGM_ClientMap__\n\n// Local Includes\n#include \"BGM_Client.h\"\n#include \"BGM_TaskQueue.h\"\n\n// PublicUtility Includes\n#include \"CAMutex.h\"\n#include \"CACFString.h\"\n#include \"CACFArray.h\"\n#include \"CAVolumeCurve.h\"\n\n// STL Includes\n#include <map>\n#include <vector>\n#include <functional>\n\n\n// Forward Declarations\nclass BGM_ClientTasks;\n\n\n#pragma clang assume_nonnull begin\n\n//==================================================================================================\n//\tBGM_ClientMap\n//\n//  This class stores the clients (BGM_Client) that have been registered with BGMDevice by the HAL.\n//  It also maintains maps from clients' PIDs and bundle IDs to the clients. When a client is\n//  removed by the HAL we add it to a map of past clients to keep track of settings specific to that\n//  client. (Currently only the client's volume.)\n//\n//  Since the maps are read from during IO, this class has to be real-time safe when accessing\n//  them. So each map has an identical \"shadow\" map, which we use to buffer updates.\n//\n//  To update the clients we lock the shadow maps, modify them, have BGM_TaskQueue's real-time\n//  thread swap them with the main maps, and then repeat the modification to keep both sets of maps\n//  identical. We have to swap the maps on a real-time thread so we can take the main maps' lock\n//  without risking priority inversion, but this way the actual work doesn't need to be real-time\n//  safe.\n//\n//  Methods that only read from the maps and are called on non-real-time threads will just read\n//  from the shadow maps because it's easier.\n//\n//  Methods whose names end with \"RT\" and \"NonRT\" can only safely be called from real-time and\n//  non-real-time threads respectively. (Methods with neither are most likely non-RT.)\n//==================================================================================================\n\nclass BGM_ClientMap\n{\n    \n    friend class BGM_ClientTasks;\n    \n    typedef std::vector<BGM_Client*> BGM_ClientPtrList;\n    \npublic:\n                                                        BGM_ClientMap(BGM_TaskQueue* inTaskQueue) : mTaskQueue(inTaskQueue), mMapsMutex(\"Maps mutex\"), mShadowMapsMutex(\"Shadow maps mutex\") { };\n\n    void                                                AddClient(BGM_Client inClient);\n    \nprivate:\n    void                                                AddClientToShadowMaps(BGM_Client inClient);\n    \npublic:\n    // Returns the removed client\n    BGM_Client                                          RemoveClient(UInt32 inClientID);\n    \n    // These methods are functionally identical except that GetClientRT must only be called from real-time threads and GetClientNonRT\n    // must only be called from non-real-time threads. Both return true if a client was found.\n    bool                                                GetClientRT(UInt32 inClientID, BGM_Client* outClient) const;\n    bool                                                GetClientNonRT(UInt32 inClientID, BGM_Client* outClient) const;\n    \nprivate:\n    static bool                                         GetClient(const std::map<UInt32, BGM_Client>& inClientMap,\n                                                                  UInt32 inClientID,\n                                                                  BGM_Client* outClient);\n    \npublic:\n    std::vector<BGM_Client>                             GetClientsByPID(pid_t inPID) const;\n    \n    // Set the isMusicPlayer flag for each client. (True if the client has the given bundle ID/PID, false otherwise.)\n    void                                                UpdateMusicPlayerFlags(pid_t inMusicPlayerPID);\n    void                                                UpdateMusicPlayerFlags(CACFString inMusicPlayerBundleID);\n    \nprivate:\n    void                                                UpdateMusicPlayerFlagsInShadowMaps(std::function<bool(BGM_Client)> inIsMusicPlayerTest);\n    \npublic:\n    // Copies the current and past clients into an array in the format expected for\n    // kAudioDeviceCustomPropertyAppVolumes. (Except that CACFArray and CACFDictionary are used instead\n    // of unwrapped CFArray and CFDictionary refs.)\n    CACFArray                                           CopyClientRelativeVolumesAsAppVolumes(CAVolumeCurve inVolumeCurve) const;\n    \nprivate:\n    void                                                CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const;\n    \npublic:\n    // Using the template function hits LLVM Bug 23987\n    // TODO Switch to template function\n    \n    // Returns true if a client for the key was found and its relative volume changed.\n    //template <typename T>\n    //bool                                                SetClientsRelativeVolume(T _Null_unspecified searchKey, Float32 inRelativeVolume);\n    //\n    //template <typename T>\n    //bool                                                SetClientsPanPosition(T _Null_unspecified searchKey, SInt32 inPanPosition);\n    \n    // Returns true if a client for PID inAppPID was found and its relative volume changed.\n    bool                                                SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume);\n    // Returns true if a client for bundle ID inAppBundleID was found and its relative volume changed.\n    // inAppBundleID may contain a null CFStringRef, in which case it returns false.\n    bool                                                SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume);\n    \n    // Returns true if a client for PID inAppPID was found and its pan position changed.\n    bool                                                SetClientsPanPosition(pid_t inAppPID, SInt32 inPanPosition);\n    // Returns true if a client for bundle ID inAppBundleID was found and its pan position changed.\n    // inAppBundleID may contain a null CFStringRef, in which case it returns false.\n    bool                                                SetClientsPanPosition(CACFString inAppBundleID, SInt32 inPanPosition);\n    \n    void                                                StartIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, true); }\n    void                                                StopIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, false); }\n    \nprivate:\n    void                                                UpdateClientIOStateNonRT(UInt32 inClientID, bool inDoingIO);\n    \n    // Has a real-time thread call SwapInShadowMapsRT. (Synchronously queues the call as a task on mTaskQueue.)\n    // The shadow maps mutex must be locked when calling this method.\n    void                                                SwapInShadowMaps();\n    // Note that this method is called by BGM_TaskQueue through the BGM_ClientTasks interface. The shadow maps\n    // mutex must be locked when calling this method.\n    void                                                SwapInShadowMapsRT();\n    \n    // Client lookup for PID inAppPID\n    std::vector<BGM_Client*> * _Nullable                GetClients(pid_t inAppPid);\n    // Client lookup for bundle ID inAppBundleID\n    std::vector<BGM_Client*> * _Nullable                GetClients(CACFString inAppBundleID);\n    \nprivate:\n    BGM_TaskQueue*                                      mTaskQueue;\n    \n    // Must be held to access mClientMap or mClientMapByPID. Code that runs while holding this mutex needs\n    // to be real-time safe. Should probably not be held for most operations on mClientMapByBundleID because,\n    // as far as I can tell, code that works with CFStrings is unlikely to be real-time safe.\n    CAMutex                                             mMapsMutex;\n    // Should only be locked by non-real-time threads. Should not be released until the maps have been\n    // made identical to their shadow maps.\n    CAMutex                                             mShadowMapsMutex;\n    \n    // The clients currently registered with BGMDevice. Indexed by client ID.\n    std::map<UInt32, BGM_Client>                        mClientMap;\n    // We keep this in sync with mClientMap so it can be modified outside of real-time safe sections and\n    // then swapped in on a real-time thread, which is safe.\n    std::map<UInt32, BGM_Client>                        mClientMapShadow;\n    \n    // These maps hold lists of pointers to clients in mClientMap/mClientMapShadow. Lists because a process\n    // can have multiple clients and clients can have the same bundle ID.\n    \n    std::map<pid_t, BGM_ClientPtrList>                  mClientMapByPID;\n    std::map<pid_t, BGM_ClientPtrList>                  mClientMapByPIDShadow;\n    \n    std::map<CACFString, BGM_ClientPtrList>             mClientMapByBundleID;\n    std::map<CACFString, BGM_ClientPtrList>             mClientMapByBundleIDShadow;\n    \n    // Clients are added to mPastClientMap so we can restore settings specific to them if they get\n    // added again.\n    std::map<CACFString, BGM_Client>                    mPastClientMap;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* __BGMDriver__BGM_ClientMap__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_ClientTasks.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_ClientTasks.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n//  The interface between the client classes (BGM_Client, BGM_Clients and BGM_ClientMap) and BGM_TaskQueue.\n//\n\n#ifndef __BGMDriver__BGM_ClientTasks__\n#define __BGMDriver__BGM_ClientTasks__\n\n// Local Includes\n#include \"BGM_Clients.h\"\n#include \"BGM_ClientMap.h\"\n\n\n// Forward Declarations\nclass BGM_TaskQueue;\n\n\n#pragma clang assume_nonnull begin\n\nclass BGM_ClientTasks\n{\n    \n    friend class BGM_TaskQueue;\n    \nprivate:\n    static bool                            StartIONonRT(BGM_Clients* inClients, UInt32 inClientID) { return inClients->StartIONonRT(inClientID); }\n    static bool                            StopIONonRT(BGM_Clients* inClients, UInt32 inClientID) { return inClients->StopIONonRT(inClientID); }\n    \n    static void                            SwapInShadowMapsRT(BGM_ClientMap* inClientMap) { inClientMap->SwapInShadowMapsRT(); }\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* __BGMDriver__BGM_ClientTasks__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Clients.cpp\n//  BGMDriver\n//\n//  Copyright © 2016, 2017, 2019, 2025 Kyle Neideck\n//  Copyright © 2017 Andrew Tonner\n//\n\n// Self Include\n#include \"BGM_Clients.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n#include \"BGM_Utils.h\"\n#include \"BGM_PlugIn.h\"\n\n// PublicUtility Includes\n#include \"CAException.h\"\n#include \"CACFDictionary.h\"\n#include \"CADispatchQueue.h\"\n\n\n#pragma mark Construction/Destruction\n\nBGM_Clients::BGM_Clients(AudioObjectID inOwnerDeviceID, BGM_TaskQueue* inTaskQueue)\n:\n    mOwnerDeviceID(inOwnerDeviceID),\n    mClientMap(inTaskQueue)\n{\n    mRelativeVolumeCurve.AddRange(kAppRelativeVolumeMinRawValue,\n                                  kAppRelativeVolumeMaxRawValue,\n                                  kAppRelativeVolumeMinDbValue,\n                                  kAppRelativeVolumeMaxDbValue);\n}\n\n#pragma mark Add/Remove Clients\n\nvoid    BGM_Clients::AddClient(BGM_Client inClient)\n{\n    CAMutex::Locker theLocker(mMutex);\n\n    // Check whether this is the music player's client\n    bool pidMatchesMusicPlayerProperty =\n        (mMusicPlayerProcessIDProperty != 0 && inClient.mProcessID == mMusicPlayerProcessIDProperty);\n    bool bundleIDMatchesMusicPlayerProperty =\n        (mMusicPlayerBundleIDProperty != \"\" &&\n         inClient.mBundleID.IsValid() &&\n         inClient.mBundleID == mMusicPlayerBundleIDProperty);\n    \n    inClient.mIsMusicPlayer = (pidMatchesMusicPlayerProperty || bundleIDMatchesMusicPlayerProperty);\n    \n    if(inClient.mIsMusicPlayer)\n    {\n        DebugMsg(\"BGM_Clients::AddClient: Adding music player client. mClientID = %u\", inClient.mClientID);\n    }\n    \n    mClientMap.AddClient(inClient);\n    \n    // If we're adding BGMApp, update our local copy of its client ID\n    if(inClient.mBundleID.IsValid() && inClient.mBundleID == kBGMAppBundleID)\n    {\n        mBGMAppClientID = inClient.mClientID;\n    }\n}\n\nvoid    BGM_Clients::RemoveClient(const UInt32 inClientID)\n{\n    CAMutex::Locker theLocker(mMutex);\n    \n    BGM_Client theRemovedClient = mClientMap.RemoveClient(inClientID);\n    \n    // If we're removing BGMApp, clear our local copy of its client ID\n    if(theRemovedClient.mClientID == mBGMAppClientID)\n    {\n        mBGMAppClientID = -1;\n    }\n}\n\n#pragma mark IO Status\n\nbool    BGM_Clients::StartIONonRT(UInt32 inClientID)\n{\n    CAMutex::Locker theLocker(mMutex);\n    \n    bool didStartIO = false;\n    \n    BGM_Client theClient;\n    bool didFindClient = mClientMap.GetClientNonRT(inClientID, &theClient);\n    \n    ThrowIf(!didFindClient, BGM_InvalidClientException(), \"BGM_Clients::StartIO: Cannot start IO for client that was never added\");\n    \n    bool sendIsRunningNotification = false;\n    bool sendIsRunningSomewhereOtherThanBGMAppNotification = false;\n\n    if(!theClient.mDoingIO)\n    {\n        // Make sure we can start\n        ThrowIf(mStartCount == UINT64_MAX, CAException(kAudioHardwareIllegalOperationError), \"BGM_Clients::StartIO: failed to start because the ref count was maxxed out already\");\n        \n        DebugMsg(\"BGM_Clients::StartIO: Client %u (%s, %d) starting IO\",\n                 inClientID,\n                 CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8),\n                 theClient.mProcessID);\n        \n        mClientMap.StartIONonRT(inClientID);\n        \n        mStartCount++;\n        \n        // Update mStartCountExcludingBGMApp\n        if(!IsBGMApp(inClientID))\n        {\n            ThrowIf(mStartCountExcludingBGMApp == UINT64_MAX, CAException(kAudioHardwareIllegalOperationError), \"BGM_Clients::StartIO: failed to start because mStartCountExcludingBGMApp was maxxed out already\");\n            \n            mStartCountExcludingBGMApp++;\n            \n            if(mStartCountExcludingBGMApp == 1)\n            {\n                sendIsRunningSomewhereOtherThanBGMAppNotification = true;\n            }\n        }\n        \n        // Return true if no other clients were running IO before this one started, which means the device should start IO\n        didStartIO = (mStartCount == 1);\n        sendIsRunningNotification = didStartIO;\n    }\n    \n    Assert(mStartCountExcludingBGMApp == mStartCount - 1 || mStartCountExcludingBGMApp == mStartCount,\n           \"mStartCount and mStartCountExcludingBGMApp are out of sync\");\n    \n    SendIORunningNotifications(sendIsRunningNotification, sendIsRunningSomewhereOtherThanBGMAppNotification);\n\n    return didStartIO;\n}\n\nbool    BGM_Clients::StopIONonRT(UInt32 inClientID)\n{\n    CAMutex::Locker theLocker(mMutex);\n    \n    bool didStopIO = false;\n    \n    BGM_Client theClient;\n    bool didFindClient = mClientMap.GetClientNonRT(inClientID, &theClient);\n    \n    ThrowIf(!didFindClient, BGM_InvalidClientException(), \"BGM_Clients::StopIO: Cannot stop IO for client that was never added\");\n    \n    bool sendIsRunningNotification = false;\n    bool sendIsRunningSomewhereOtherThanBGMAppNotification = false;\n    \n    if(theClient.mDoingIO)\n    {\n        DebugMsg(\"BGM_Clients::StopIO: Client %u (%s, %d) stopping IO\",\n                 inClientID,\n                 CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8),\n                 theClient.mProcessID);\n        \n        mClientMap.StopIONonRT(inClientID);\n        \n        ThrowIf(mStartCount <= 0, CAException(kAudioHardwareIllegalOperationError), \"BGM_Clients::StopIO: Underflowed mStartCount\");\n        \n        mStartCount--;\n        \n        // Update mStartCountExcludingBGMApp\n        if(!IsBGMApp(inClientID))\n        {\n            ThrowIf(mStartCountExcludingBGMApp <= 0, CAException(kAudioHardwareIllegalOperationError), \"BGM_Clients::StopIO: Underflowed mStartCountExcludingBGMApp\");\n            \n            mStartCountExcludingBGMApp--;\n            \n            if(mStartCountExcludingBGMApp == 0)\n            {\n                sendIsRunningSomewhereOtherThanBGMAppNotification = true;\n            }\n        }\n        \n        // Return true if we stopped IO entirely (i.e. there are no clients still running IO)\n        didStopIO = (mStartCount == 0);\n        sendIsRunningNotification = didStopIO;\n    }\n    \n    Assert(mStartCountExcludingBGMApp == mStartCount - 1 || mStartCountExcludingBGMApp == mStartCount,\n           \"mStartCount and mStartCountExcludingBGMApp are out of sync\");\n    \n    SendIORunningNotifications(sendIsRunningNotification, sendIsRunningSomewhereOtherThanBGMAppNotification);\n    \n    return didStopIO;\n}\n\nbool    BGM_Clients::ClientsRunningIO() const\n{\n    return mStartCount > 0;\n}\n\nbool    BGM_Clients::ClientsOtherThanBGMAppRunningIO() const\n{\n    return mStartCountExcludingBGMApp > 0;\n}\n\nvoid    BGM_Clients::SendIORunningNotifications(bool sendIsRunningNotification, bool sendIsRunningSomewhereOtherThanBGMAppNotification) const\n{\n    if(sendIsRunningNotification || sendIsRunningSomewhereOtherThanBGMAppNotification)\n    {\n        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{\n            AudioObjectPropertyAddress theChangedProperties[2];\n            UInt32 theNotificationCount = 0;\n\n            if(sendIsRunningNotification)\n            {\n                DebugMsg(\"BGM_Clients::SendIORunningNotifications: Sending kAudioDevicePropertyDeviceIsRunning\");\n                theChangedProperties[0] = { kAudioDevicePropertyDeviceIsRunning, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };\n                theNotificationCount++;\n            }\n\n            if(sendIsRunningSomewhereOtherThanBGMAppNotification)\n            {\n                DebugMsg(\"BGM_Clients::SendIORunningNotifications: Sending kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp\");\n                theChangedProperties[theNotificationCount] = kBGMRunningSomewhereOtherThanBGMAppAddress;\n                theNotificationCount++;\n            }\n\n            BGM_PlugIn::Host_PropertiesChanged(mOwnerDeviceID, theNotificationCount, theChangedProperties);\n        });\n    }\n}\n\n#pragma mark Music Player\n\nbool    BGM_Clients::SetMusicPlayer(const pid_t inPID)\n{\n    ThrowIf(inPID < 0, BGM_InvalidClientPIDException(), \"BGM_Clients::SetMusicPlayer: Invalid music player PID\");\n    \n    CAMutex::Locker theLocker(mMutex);\n    \n    if(mMusicPlayerProcessIDProperty == inPID)\n    {\n        // We're not changing the properties, so return false\n        return false;\n    }\n    \n    mMusicPlayerProcessIDProperty = inPID;\n    // Unset the bundle ID property\n    mMusicPlayerBundleIDProperty = \"\";\n    \n    DebugMsg(\"BGM_Clients::SetMusicPlayer: Setting music player by PID. inPID=%d\", inPID);\n    \n    // Update the clients' mIsMusicPlayer fields\n    mClientMap.UpdateMusicPlayerFlags(inPID);\n    \n    return true;\n}\n\nbool    BGM_Clients::SetMusicPlayer(const CACFString inBundleID)\n{\n    Assert(inBundleID.IsValid(), \"BGM_Clients::SetMusicPlayer: Invalid CACFString given as bundle ID\");\n    \n    CAMutex::Locker theLocker(mMutex);\n    \n    if(mMusicPlayerBundleIDProperty == inBundleID)\n    {\n        // We're not changing the properties, so return false\n        return false;\n    }\n    \n    mMusicPlayerBundleIDProperty = inBundleID;\n    // Unset the PID property\n    mMusicPlayerProcessIDProperty = 0;\n    \n    DebugMsg(\"BGM_Clients::SetMusicPlayer: Setting music player by bundle ID. inBundleID=%s\",\n             CFStringGetCStringPtr(inBundleID.GetCFString(), kCFStringEncodingUTF8));\n    \n    // Update the clients' mIsMusicPlayer fields\n    mClientMap.UpdateMusicPlayerFlags(inBundleID);\n    \n    return true;\n}\n\nbool    BGM_Clients::IsMusicPlayerRT(const UInt32 inClientID) const\n{\n    BGM_Client theClient;\n    bool didGetClient = mClientMap.GetClientRT(inClientID, &theClient);\n    return didGetClient && theClient.mIsMusicPlayer;\n}\n\n#pragma mark App Volumes\n\nFloat32 BGM_Clients::GetClientRelativeVolumeRT(UInt32 inClientID) const\n{\n    BGM_Client theClient;\n    bool didGetClient = mClientMap.GetClientRT(inClientID, &theClient);\n    return (didGetClient ? theClient.mRelativeVolume : 1.0f);\n}\n\nSInt32 BGM_Clients::GetClientPanPositionRT(UInt32 inClientID) const\n{\n    BGM_Client theClient;\n    bool didGetClient = mClientMap.GetClientRT(inClientID, &theClient);\n    return (didGetClient ? theClient.mPanPosition : kAppPanCenterRawValue);\n}\n\nbool    BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)\n{\n    bool didChangeAppVolumes = false;\n    \n    // Each element in appVolumes is a CFDictionary containing the process id and/or bundle id of an app, and its\n    // new relative volume\n    for(UInt32 i = 0; i < inAppVolumes.GetNumberItems(); i++)\n    {\n        CACFDictionary theAppVolume(false);\n        inAppVolumes.GetCACFDictionary(i, theAppVolume);\n        \n        // Get the app's PID from the dict\n        pid_t theAppPID;\n        bool didFindPID = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), theAppPID);\n\n        // Get the app's bundle ID from the dict\n        CACFString theAppBundleID;\n        theAppBundleID.DontAllowRelease();\n        theAppVolume.GetCACFString(CFSTR(kBGMAppVolumesKey_BundleID), theAppBundleID);\n\n        BGMAssert(didFindPID || theAppBundleID.IsValid(),\n                  \"BGM_Clients::SetClientsRelativeVolumes: No PID or bundle ID\");\n        (void)didFindPID;  // Suppress unused variable warning in release builds.\n\n        bool didGetVolume;\n        {\n            SInt32 theRawRelativeVolume;\n            didGetVolume = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theRawRelativeVolume);\n\n            if (didGetVolume) {\n                // Apply the volume curve to the raw volume\n                //\n                // mRelativeVolumeCurve uses the default kPow2Over1Curve transfer function, so we also multiply by 4 to\n                // keep the middle volume equal to 1 (meaning apps' volumes are unchanged by default).\n                Float32 theRelativeVolume = mRelativeVolumeCurve.ConvertRawToScalar(theRawRelativeVolume) * 4;\n\n                // Try to update the client's volume, first by PID and then by bundle ID. Always try\n                // both because apps can have multiple clients.\n                if(mClientMap.SetClientsRelativeVolume(theAppPID, theRelativeVolume))\n                {\n                    didChangeAppVolumes = true;\n                }\n\n                if(mClientMap.SetClientsRelativeVolume(theAppBundleID, theRelativeVolume))\n                {\n                    didChangeAppVolumes = true;\n                }\n\n                // TODO: If the app isn't currently a client, we should add it to the past clients\n                //       map, or update its past volume if it's already in there.\n            }\n        }\n        \n        bool didGetPanPosition;\n        {\n            SInt32 thePanPosition;\n            didGetPanPosition = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePanPosition);\n            if (didGetPanPosition) {\n                if(mClientMap.SetClientsPanPosition(theAppPID, thePanPosition))\n                {\n                    didChangeAppVolumes = true;\n                }\n\n                if(mClientMap.SetClientsPanPosition(theAppBundleID, thePanPosition))\n                {\n                    didChangeAppVolumes = true;\n                }\n\n                // TODO: If the app isn't currently a client, we should add it to the past clients\n                //       map, or update its past pan position if it's already in there.\n            }\n        }\n        \n        ThrowIf(!didGetVolume && !didGetPanPosition,\n                BGM_InvalidClientRelativeVolumeException(),\n                \"BGM_Clients::SetClientsRelativeVolumes: No volume or pan position in request\");\n    }\n    \n    return didChangeAppVolumes;\n}\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Clients.h\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n#ifndef __BGMDriver__BGM_Clients__\n#define __BGMDriver__BGM_Clients__\n\n// Local Includes\n#include \"BGM_Client.h\"\n#include \"BGM_ClientMap.h\"\n\n// PublicUtility Includes\n#include \"CAVolumeCurve.h\"\n#include \"CAMutex.h\"\n#include \"CACFArray.h\"\n\n// System Includes\n#include <CoreAudio/AudioServerPlugIn.h>\n\n\n// Forward Declations\nclass BGM_ClientTasks;\n\n\n#pragma clang assume_nonnull begin\n\n//==================================================================================================\n//\tBGM_Clients\n//\n//  Holds information about the clients (of the host) of the BGMDevice, i.e. the apps registered\n//  with the HAL, generally so they can do IO at some point. BGMApp and the music player are special\n//  case clients.\n//\n//  Methods whose names end with \"RT\" should only be called from real-time threads.\n//==================================================================================================\n\nclass BGM_Clients\n{\n    \n    friend class BGM_ClientTasks;\n    \npublic:\n                                        BGM_Clients(AudioObjectID inOwnerDeviceID, BGM_TaskQueue* inTaskQueue);\n                                        ~BGM_Clients() = default;\n    // Disallow copying. (It could make sense to implement these in future, but we don't need them currently.)\n                                        BGM_Clients(const BGM_Clients&) = delete;\n                                        BGM_Clients& operator=(const BGM_Clients&) = delete;\n    \n    void                                AddClient(BGM_Client inClient);\n    void                                RemoveClient(const UInt32 inClientID);\n    \nprivate:\n    // Only BGM_TaskQueue is allowed to call these (through the BGM_ClientTasks interface). We get notifications\n    // from the HAL when clients start/stop IO and they have to be processed in the order we receive them to\n    // avoid race conditions. If these methods could be called directly those calls would skip any queued calls.\n    bool                                StartIONonRT(UInt32 inClientID);\n    bool                                StopIONonRT(UInt32 inClientID);\n\npublic:\n    bool                                ClientsRunningIO() const;\n    bool                                ClientsOtherThanBGMAppRunningIO() const;\n    \nprivate:\n    void                                SendIORunningNotifications(bool sendIsRunningNotification, bool sendIsRunningSomewhereOtherThanBGMAppNotification) const;\npublic:\n    bool                                IsBGMApp(UInt32 inClientID) const { return inClientID == mBGMAppClientID; }\n    bool                                BGMAppHasClientRegistered() const { return mBGMAppClientID != -1; }\n    \n    inline pid_t                        GetMusicPlayerProcessIDProperty() const { return mMusicPlayerProcessIDProperty; }\n    inline CFStringRef                  CopyMusicPlayerBundleIDProperty() const { return mMusicPlayerBundleIDProperty.CopyCFString(); }\n    \n    // Returns true if the PID was changed\n    bool                                SetMusicPlayer(const pid_t inPID);\n    // Returns true if the bundle ID was changed\n    bool                                SetMusicPlayer(const CACFString inBundleID);\n    \n    bool                                IsMusicPlayerRT(const UInt32 inClientID) const;\n    \n    Float32                             GetClientRelativeVolumeRT(UInt32 inClientID) const;\n    SInt32                              GetClientPanPositionRT(UInt32 inClientID) const;\n    \n    // Copies the current and past clients into an array in the format expected for\n    // kAudioDeviceCustomPropertyAppVolumes. (Except that CACFArray and CACFDictionary are used instead\n    // of unwrapped CFArray and CFDictionary refs.)\n    CACFArray                           CopyClientRelativeVolumesAsAppVolumes() const { return mClientMap.CopyClientRelativeVolumesAsAppVolumes(mRelativeVolumeCurve); };\n    \n    // inAppVolumes is an array of dicts with the keys kBGMAppVolumesKey_ProcessID,\n    // kBGMAppVolumesKey_BundleID and optionally kBGMAppVolumesKey_RelativeVolume and\n    // kBGMAppVolumesKey_PanPosition. This method finds the client for\n    // each app by PID or bundle ID, sets the volume and applies mRelativeVolumeCurve to it.\n    //\n    // Returns true if any clients' relative volumes were changed.\n    bool                                SetClientsRelativeVolumes(const CACFArray inAppVolumes);\n    \nprivate:\n    AudioObjectID                       mOwnerDeviceID;\n    BGM_ClientMap                       mClientMap;\n    \n    // Counters for the number of clients that are doing IO. These are used to tell whether any clients\n    // are currently doing IO without having to check every client's mDoingIO.\n    //\n    // We need to reference count this rather than just using a bool because the HAL might (but usually\n    // doesn't) call our StartIO/StopIO functions for clients other than the first to start and last to\n    // stop.\n    UInt64                              mStartCount = 0;\n    UInt64                              mStartCountExcludingBGMApp = 0;\n    \n    CAMutex                             mMutex { \"Clients\" };\n    \n    SInt64                              mBGMAppClientID = -1;\n    \n    // The value of the kAudioDeviceCustomPropertyMusicPlayerProcessID property, or 0 if it's unset/null.\n    // We store this separately because the music player might not always be a client, but could be added\n    // as one at a later time.\n    pid_t                               mMusicPlayerProcessIDProperty = 0;\n    \n    // The value of the kAudioDeviceCustomPropertyMusicPlayerBundleID property, or the empty string if it's\n    // unset/null. UTF8 encoding.\n    //\n    // As with mMusicPlayerProcessID, we keep a copy of the bundle ID the user sets for the music player\n    // because there might be no client with that bundle ID. In that case we need to be able to give the\n    // property's value if the HAL asks for it, and to recognise the music player if it's added a client.\n    CACFString                          mMusicPlayerBundleIDProperty { \"\" };\n    \n    // The volume curve we apply to raw client volumes before they're used\n    CAVolumeCurve                       mRelativeVolumeCurve;\n    \n};\n\n#pragma clang assume_nonnull end\n\n#endif /* __BGMDriver__BGM_Clients__ */\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>AudioServerPlugIn_MachServices</key>\n\t<array>\n\t\t<string>com.bearisdriving.BGM.XPCHelper</string>\n\t</array>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.4.3</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1.0.0</string>\n\t<key>CFPlugInFactories</key>\n\t<dict>\n\t\t<key>C957AD43-DACA-4A40-8850-3BA8CE28FAF9</key>\n\t\t<string>BGM_Create</string>\n\t</dict>\n\t<key>CFPlugInTypes</key>\n\t<dict>\n\t\t<key>443ABAB8-E7B3-491A-B985-BEB9187030DB</key>\n\t\t<array>\n\t\t\t<string>C957AD43-DACA-4A40-8850-3BA8CE28FAF9</string>\n\t\t</array>\n\t</dict>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2016-2024 Background Music contributors</string>\n\t<key>NSPrincipalClass</key>\n\t<string></string>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMDriver/BGMDriver/quick_install.sh",
    "content": "#!/bin/bash\n# vim: tw=0:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# quick_install.sh\n# BGMDriver\n#\n# Copyright © 2016 Kyle Neideck\n#\n# Installs the HAL plugin to /Library/Audio/Plug-Ins/HAL and restarts coreaudiod to enable it.\n# For testing/debugging.\n#\n# If BGMDevice is set as your default device when you run this, you might have to change your\n# default device and change it back to BGMDevice to get programs that use audio working again.\n# Or you can just restart those programs.\n#\n\n# Safe mode\nset -euo pipefail\nIFS=$'\\n\\t'\n\n# The dir containing this script\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nSCRIPT_FILENAME=\"$( basename \"$0\" )\"\n# Use the same filename as this script but change the extension to .conf\nCONFIG_FILENAME=\"${SCRIPT_FILENAME%%.*}.conf\"\n# This config file just stores the paths to your BGMDriver builds (at least so far)\nCONFIG_FILE=\"${SCRIPT_DIR}/${CONFIG_FILENAME}\"\n\n# Sanity check\nif [[ ! -f \"${SCRIPT_DIR}/${SCRIPT_FILENAME}\" ]]; then\n    echo \"Assertion failed. Could not find this script ($0) in SCRIPT_DIR (${SCRIPT_DIR}).\" >&2\n    exit 1\nfi\n\nbold_face() {\n    echo $(tput bold)$*$(tput sgr0)\n}\n\nusage() {\n    echo \"Usage: $0 [options]\" >&2\n    echo -e \"\\t-u\\tUninstall only\" >&2\n    echo -e \"\\t-d\\tDon't restart coreaudiod\" >&2\n    echo -e \"\\t-r\\tInstall release build (debug is the default)\" >&2\n    echo -e \"\\t-h\\tPrint this usage statement\" >&2\n    exit 1\n}\n\n# Read in the config variables. (One var per line, formatted like \"VARIABLE_NAME=value\")\nread_quick_install_conf() {\n    # Set config vars to the empty string if they aren't set in the config file\n    DRIVER_PATH_RELEASE=\n    DRIVER_PATH_DEBUG=\n\n    if [[ -f \"${CONFIG_FILE}\" ]]; then\n        while IFS='' read -r LINE || [[ -n \"${LINE}\" ]]; do\n            # Take the chars before the first \"=\" to get the var name\n            # (\"%%\" deletes the longest substring match, starting at the end of the string)\n            if [[ \"DRIVER_PATH_DEBUG\" = \"${LINE%%=*}\" ]]; then\n                # Remove the \"VARIABLE_NAME=\" part to get the value\n                DRIVER_PATH_DEBUG=\"${LINE/DRIVER_PATH_DEBUG=}\"\n            fi\n\n            if [[ \"DRIVER_PATH_RELEASE\" = \"${LINE%%=*}\" ]]; then\n                DRIVER_PATH_RELEASE=\"${LINE/DRIVER_PATH_RELEASE=}\"\n            fi\n        done < \"${CONFIG_FILE}\"\n    else\n        touch \"${CONFIG_FILE}\"\n    fi\n}\n\n# Get the path to the build we're installing, from the user or their config, and set DRIVER_PATH to it\nget_build_path() {\n    if [[ ${USE_RELEASE_BUILD} == true ]] && [[ \"${DRIVER_PATH_RELEASE}\" != \"\" ]] && [[ -d \"${DRIVER_PATH_RELEASE}\" ]]; then\n        # The path to the release build was set, so just set it as the build we're installing\n        DRIVER_PATH=\"${DRIVER_PATH_RELEASE}\"\n    elif [[ ${USE_RELEASE_BUILD} == false ]] && [[ \"${DRIVER_PATH_DEBUG}\" != \"\" ]] && [[ -d \"${DRIVER_PATH_DEBUG}\" ]]; then\n        # The path to the debug build was set, so just set it as the build we're installing\n        DRIVER_PATH=\"${DRIVER_PATH_DEBUG}\"\n    else\n        # The build path isn't set yet, so ask the user to enter it\n        echo -n \"Enter the absolute path to your BGMDriver\"\n        ([[ ${USE_RELEASE_BUILD} == true ]] && echo -n \" release \") || echo -n \" debug \"\n        echo \"build. Probably something like\"\n        echo -n \"/Users/$(whoami)/Library/Developer/Xcode/DerivedData/BGM-somerandomchars/Build/Products/\"\n        ([[ ${USE_RELEASE_BUILD} == true ]] && echo -n \"Release\") || echo -n \"Debug\"\n        echo \"/Background Music Device.driver\"\n        cd /\n        read -e -p \": /\" DRIVER_PATH\n        cd -\n\n        # Add leading \"/\", strip trailing \"/\"\n        DRIVER_PATH=\"/${DRIVER_PATH%/}\"\n\n        # Offer to abort if they entered an invalid path\n        if [[ ! -d \"${DRIVER_PATH}/Contents/MacOS\" ]]; then\n            if [[ ! -d \"${DRIVER_PATH}\" ]]; then\n                echo \"$(bold_face Warning): this path doesn't seem to be a directory. Continue anyway?\" >&2\n            else\n                echo \"$(bold_face Warning): this directory doesn't seem to be a bundle. Continue anyway?\" >&2\n            fi\n\n            read -p \"[y/N]: \" CONTINUE_ANYWAY\n\n            if [[ \"${CONTINUE_ANYWAY}\" != \"y\" ]]; then\n                exit 1\n            fi\n        fi\n\n        # Write the path to the config file\n        if [[ ${USE_RELEASE_BUILD} == true ]]; then\n            echo \"DRIVER_PATH_RELEASE=${DRIVER_PATH}\" >> \"${CONFIG_FILE}\"\n        else\n            echo \"DRIVER_PATH_DEBUG=${DRIVER_PATH}\" >> \"${CONFIG_FILE}\"\n        fi\n    fi\n}\n\nUNINSTALL_ONLY=false\nDONT_RESTART_COREAUDIOD=false\nUSE_RELEASE_BUILD=false\n\n# Parse options\n\nwhile getopts \":udrh\" opt; do\n    case $opt in\n        u)\n            UNINSTALL_ONLY=true\n            ;;\n        d)\n            DONT_RESTART_COREAUDIOD=true\n            ;;\n        r)\n            USE_RELEASE_BUILD=true\n            ;;\n        h)\n            usage\n            ;;\n        \\?)\n            echo \"Invalid option: -$OPTARG\" >&2\n            usage\n            ;;\n    esac\ndone\n\n# Require sudo\n\nsudo -v\n\n# Get the path to the build we're installing\n\nif [[ ${UNINSTALL_ONLY} == false ]]; then\n    read_quick_install_conf\n    get_build_path\nfi\n\n# Remove installed build\n\nHAL_PLUGINS_DIR=\"/Library/Audio/Plug-Ins/HAL\"\nINSTALLED_DRIVER_PATH=\"${HAL_PLUGINS_DIR}/Background Music Device.driver\"\n\nif [[ -d \"${INSTALLED_DRIVER_PATH}\" ]]; then\n    echo \"$(bold_face Removing old version of the driver)\"\n\n    # Sanity check\n    INSTALLED_DRIVER_SIZE_KB=\"$(du -sk \"${INSTALLED_DRIVER_PATH}\" | cut -f1)\"\n    if [[ ! \"${INSTALLED_DRIVER_SIZE_KB}\" =~ ^-?[0-9]+$ ]] || [[ \"${INSTALLED_DRIVER_SIZE_KB}\" -gt 2048 ]]; then\n        echo \"$(bold_face Aborting). The driver currently installed in \\\"${INSTALLED_DRIVER_PATH}\\\" was much larger than expected (>2MB).\" >&2\n        exit 1\n    fi\n\n    # (\"set -x\" so the command is echoed)\n    (set -x; sudo rm -rf \"${INSTALLED_DRIVER_PATH}\")\nfi\n\n# Install new build\n\nif [[ ${UNINSTALL_ONLY} == false ]]; then\n    echo -n \"$(bold_face Installing driver) - \"\n    ([[ ${USE_RELEASE_BUILD} == true ]] && echo -n \"release \") || echo -n \"debug \"\n    echo \"build. (Edit or delete ${CONFIG_FILENAME} to change the path to your build.)\"\n\n    # (\"set -x\" so the command is echoed)\n    (set -x; sudo cp -r \"${DRIVER_PATH}\" \"${HAL_PLUGINS_DIR}\")\nfi\n\n# Restart coreaudiod to load the installed build\n\nif [[ ${DONT_RESTART_COREAUDIOD} == false ]]; then\n    echo \"$(bold_face Restarting coreaudiod). If a running app stops playing audio, change your default audio device (and change it back if you want) or open BGMApp.\"\n\n    # (\"set -x\" so the command is echoed)\n    (set -x; sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod || sudo killall coreaudiod)\nfi\n\n\n"
  },
  {
    "path": "BGMDriver/BGMDriver.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 47;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t19FE742AEBE30B21C4CF9285 /* BGM_Control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */; };\n\t\t19FE761291BF07AEA278F25C /* BGM_MuteControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */; };\n\t\t19FE766482B57D852CCF6F0A /* BGM_MuteControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_MuteControl.cpp\"; }; };\n\t\t19FE77D40F15EA060B462D83 /* BGM_Control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Control.cpp\"; }; };\n\t\t1C0CB6B91C642C600084C15A /* BGM_Client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B01C642C600084C15A /* BGM_Client.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Client.cpp\"; }; };\n\t\t1C0CB6BA1C642C600084C15A /* BGM_ClientMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B21C642C600084C15A /* BGM_ClientMap.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_ClientMap.cpp\"; }; };\n\t\t1C0CB6BB1C642C600084C15A /* BGM_Clients.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B41C642C600084C15A /* BGM_Clients.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Clients.cpp\"; }; };\n\t\t1C30A69F1C1E98F000C05AA5 /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3841BBCEFE8000E2DD1 /* CAMutex.cpp */; };\n\t\t1C38210E1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_TaskQueue.cpp\"; }; };\n\t\t1C3DB4871BE063C500EC8160 /* BGM_DeviceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4861BE063C500EC8160 /* BGM_DeviceTests.mm */; };\n\t\t1C6181A72388FC8A0068C4D3 /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C6181A52388FC8A0068C4D3 /* CARingBuffer.cpp */; };\n\t\t1C7010751F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_AudibleState.cpp\"; }; };\n\t\t1C7010761F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */; };\n\t\t1C7010791F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_VolumeControl.cpp\"; }; };\n\t\t1C70107A1F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; };\n\t\t1C780FEF1FEE78E800497FAD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C780FEE1FEE78E800497FAD /* Accelerate.framework */; };\n\t\t1C780FF41FF275F300497FAD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C780FEE1FEE78E800497FAD /* Accelerate.framework */; };\n\t\t1C8034DD1BDD073B00668E00 /* BGM_ClientsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */; };\n\t\t1CA2A9E21E8D1D08007A76A4 /* BGM_Stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Stream.cpp\"; }; };\n\t\t1CB8B36E1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_PlugInInterface.cpp\"; }; };\n\t\t1CB8B3761BBBD924000E2DD1 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB8B3741BBBD924000E2DD1 /* CoreAudio.framework */; };\n\t\t1CB8B3771BBBD924000E2DD1 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB8B3751BBBD924000E2DD1 /* CoreFoundation.framework */; };\n\t\t1CB8B37D1BBCCF62000E2DD1 /* BGM_PlugIn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B37B1BBCCF62000E2DD1 /* BGM_PlugIn.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_PlugIn.cpp\"; }; };\n\t\t1CB8B3801BBCCF87000E2DD1 /* BGM_Device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B37E1BBCCF87000E2DD1 /* BGM_Device.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Device.cpp\"; }; };\n\t\t1CB8B3831BBCE7B5000E2DD1 /* BGM_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3811BBCE7B5000E2DD1 /* BGM_Object.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Object.cpp\"; }; };\n\t\t1CB8B3921BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3901BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_WrappedAudioEngine.cpp\"; }; };\n\t\t1CBB322C1BDD3A3000C9BD55 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CB8B3741BBBD924000E2DD1 /* CoreAudio.framework */; };\n\t\t1CC1DF8D1BE5705700FB8FE4 /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CE3E68C1BE263CA00167F5D /* CACFDictionary.cpp */; };\n\t\t1CC1DF8E1BE5706C00FB8FE4 /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CE3E68F1BE2683900167F5D /* CACFArray.cpp */; };\n\t\t1CC1DF931BE7B79500FB8FE4 /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF871BE558B000FB8FE4 /* CADebugger.cpp */; };\n\t\t1CC1DF941BE7B79500FB8FE4 /* CAVolumeCurve.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B38C1BBCF4A9000E2DD1 /* CAVolumeCurve.cpp */; };\n\t\t1CC1DF9E1BE94AA200FB8FE4 /* DeviceIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 1CC1DF9D1BE94AA200FB8FE4 /* DeviceIcon.icns */; };\n\t\t1CD95B121E93AA5200EB8EF0 /* BGM_AbstractDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CDF3ABD1E8644C20001E9B7 /* BGM_AbstractDevice.cpp */; };\n\t\t1CD95B131E93AA5200EB8EF0 /* BGM_NullDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CDF3ABA1E863B980001E9B7 /* BGM_NullDevice.cpp */; };\n\t\t1CD95B141E93AA5200EB8EF0 /* BGM_Stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */; };\n\t\t1CDF3ABC1E863B980001E9B7 /* BGM_NullDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CDF3ABA1E863B980001E9B7 /* BGM_NullDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_NullDevice.cpp\"; }; };\n\t\t1CDF3ABF1E8644C20001E9B7 /* BGM_AbstractDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CDF3ABD1E8644C20001E9B7 /* BGM_AbstractDevice.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_AbstractDevice.cpp\"; }; };\n\t\t1CE03A4B238A5BF40036908D /* CABitOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CE03A4A238A5BF40036908D /* CABitOperations.h */; };\n\t\t1CE03A4C23928B370036908D /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C6181A52388FC8A0068C4D3 /* CARingBuffer.cpp */; };\n\t\t27379B821C76D62D0084A24C /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3701BBBD8A4000E2DD1 /* CADebugMacros.cpp */; };\n\t\t27379B831C76D62D0084A24C /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3781BBBDFA2000E2DD1 /* CADebugPrintf.cpp */; };\n\t\t27381A161C8EF50F00DF167C /* BGM_XPCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 27381A141C8EF50F00DF167C /* BGM_XPCHelper.m */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_XPCHelper.m\"; }; };\n\t\t2743C9CD1D7EF8760089613B /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CE3E68F1BE2683900167F5D /* CACFArray.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CACFArray.cpp\"; }; };\n\t\t2743C9CF1D7EF8760089613B /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CE3E68C1BE263CA00167F5D /* CACFDictionary.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CACFDictionary.cpp\"; }; };\n\t\t2743C9D11D7EF8760089613B /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C305D9B1BE294B5004EBB91 /* CACFNumber.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CACFNumber.cpp\"; }; };\n\t\t2743C9D31D7EF8760089613B /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B38A1BBCF4A9000E2DD1 /* CACFString.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CACFString.cpp\"; }; };\n\t\t2743C9D51D7EF8760089613B /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF871BE558B000FB8FE4 /* CADebugger.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CADebugger.cpp\"; }; };\n\t\t2743C9D71D7EF8760089613B /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3701BBBD8A4000E2DD1 /* CADebugMacros.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CADebugMacros.cpp\"; }; };\n\t\t2743C9D91D7EF8760089613B /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3781BBBDFA2000E2DD1 /* CADebugPrintf.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CADebugPrintf.cpp\"; }; };\n\t\t2743C9DB1D7EF8760089613B /* CADispatchQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3931BBD2418000E2DD1 /* CADispatchQueue.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CADispatchQueue.cpp\"; }; };\n\t\t2743C9DE1D7EF8760089613B /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3871BBCF08A000E2DD1 /* CAHostTimeBase.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CAHostTimeBase.cpp\"; }; };\n\t\t2743C9E01D7EF8760089613B /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3841BBCEFE8000E2DD1 /* CAMutex.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CAMutex.cpp\"; }; };\n\t\t2743C9E21D7EF8760089613B /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C38210F1C4A18DE00A0C8C6 /* CAPThread.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CAPThread.cpp\"; }; };\n\t\t2743C9E41D7EF8760089613B /* CAVolumeCurve.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B38C1BBCF4A9000E2DD1 /* CAVolumeCurve.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=libPublicUtility-CAVolumeCurve.cpp\"; }; };\n\t\t2743C9E61D7EF8E00089613B /* libPublicUtility.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2743C9C61D7EF84B0089613B /* libPublicUtility.a */; };\n\t\t275343BD1DE9B44900DF3858 /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 275343BC1DE9B44900DF3858 /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = \"-frandom-seed=BGMDriver-BGM_Utils.cpp\"; }; };\n\t\t277170101CA0CFC300AB34B4 /* BGM_PlugInInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */; };\n\t\t277170111CA0CFC300AB34B4 /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C305D9B1BE294B5004EBB91 /* CACFNumber.cpp */; };\n\t\t277EE6591C7269910037F1EE /* BGM_ClientMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 277EE6581C7269910037F1EE /* BGM_ClientMapTests.mm */; };\n\t\t277EE65A1C728C630037F1EE /* BGM_Client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B01C642C600084C15A /* BGM_Client.cpp */; };\n\t\t277EE65B1C728C630037F1EE /* BGM_ClientMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B21C642C600084C15A /* BGM_ClientMap.cpp */; };\n\t\t277EE65C1C728C630037F1EE /* BGM_Clients.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B41C642C600084C15A /* BGM_Clients.cpp */; };\n\t\t277EE65D1C728C630037F1EE /* BGM_TaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */; };\n\t\t277EE65E1C728C9D0037F1EE /* BGM_PlugIn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B37B1BBCCF62000E2DD1 /* BGM_PlugIn.cpp */; };\n\t\t277EE65F1C728C9D0037F1EE /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C38210F1C4A18DE00A0C8C6 /* CAPThread.cpp */; };\n\t\t277EE6601C728CA70037F1EE /* BGM_Device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B37E1BBCCF87000E2DD1 /* BGM_Device.cpp */; };\n\t\t277EE6611C728CAE0037F1EE /* BGM_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3811BBCE7B5000E2DD1 /* BGM_Object.cpp */; };\n\t\t277EE6621C728CC70037F1EE /* BGM_WrappedAudioEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3901BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp */; };\n\t\t277EE6631C728CC70037F1EE /* CADispatchQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3931BBD2418000E2DD1 /* CADispatchQueue.cpp */; };\n\t\t277EE6641C728CDB0037F1EE /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B38A1BBCF4A9000E2DD1 /* CACFString.cpp */; };\n\t\t277EE6651C728CDB0037F1EE /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3871BBCF08A000E2DD1 /* CAHostTimeBase.cpp */; };\n\t\t2795973E1C9847CF00A002FB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2795973D1C9847CF00A002FB /* Foundation.framework */; };\n\t\t27D643C31C9FBE1600737F6E /* BGM_XPCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 27381A141C8EF50F00DF167C /* BGM_XPCHelper.m */; };\n\t\t27E6B5F01E01966A00EC0AAB /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 275343BC1DE9B44900DF3858 /* BGM_Utils.cpp */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t19FE7431C588F36F4F1E70BB /* BGM_MuteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_MuteControl.h; sourceTree = \"<group>\"; };\n\t\t19FE7B8CE9148B3D8D7517C6 /* BGM_Control.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Control.h; sourceTree = \"<group>\"; };\n\t\t19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Control.cpp; sourceTree = \"<group>\"; };\n\t\t19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_MuteControl.cpp; sourceTree = \"<group>\"; };\n\t\t1C09150523F010E8001EB0E1 /* set-version.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = \"set-version.sh\"; sourceTree = \"<group>\"; };\n\t\t1C0CB6A61C4E06C00084C15A /* CAAtomicStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAtomicStack.h; path = PublicUtility/CAAtomicStack.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6A71C4E06F70084C15A /* CAAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAtomic.h; path = PublicUtility/CAAtomic.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6A91C50A3AF0084C15A /* CAAutoDisposer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAutoDisposer.h; path = PublicUtility/CAAutoDisposer.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6B01C642C600084C15A /* BGM_Client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Client.cpp; sourceTree = \"<group>\"; };\n\t\t1C0CB6B11C642C600084C15A /* BGM_Client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Client.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6B21C642C600084C15A /* BGM_ClientMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_ClientMap.cpp; sourceTree = \"<group>\"; };\n\t\t1C0CB6B31C642C600084C15A /* BGM_ClientMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_ClientMap.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6B41C642C600084C15A /* BGM_Clients.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Clients.cpp; sourceTree = \"<group>\"; };\n\t\t1C0CB6B51C642C600084C15A /* BGM_Clients.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Clients.h; sourceTree = \"<group>\"; };\n\t\t1C0CB6B81C642C600084C15A /* BGM_ClientTasks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_ClientTasks.h; sourceTree = \"<group>\"; };\n\t\t1C305D9B1BE294B5004EBB91 /* CACFNumber.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFNumber.cpp; path = PublicUtility/CACFNumber.cpp; sourceTree = \"<group>\"; };\n\t\t1C305D9C1BE294B5004EBB91 /* CACFNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFNumber.h; path = PublicUtility/CACFNumber.h; sourceTree = \"<group>\"; };\n\t\t1C37B3681E9B8D3C000DF98F /* CAPropertyAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPropertyAddress.h; path = PublicUtility/CAPropertyAddress.h; sourceTree = \"<group>\"; };\n\t\t1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_TaskQueue.cpp; sourceTree = \"<group>\"; };\n\t\t1C38210D1C4A163A00A0C8C6 /* BGM_TaskQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_TaskQueue.h; sourceTree = \"<group>\"; };\n\t\t1C38210F1C4A18DE00A0C8C6 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = \"<group>\"; };\n\t\t1C3821101C4A18DE00A0C8C6 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = \"<group>\"; };\n\t\t1C3DB4861BE063C500EC8160 /* BGM_DeviceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_DeviceTests.mm; sourceTree = \"<group>\"; };\n\t\t1C6181A42388FC8A0068C4D3 /* CARingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CARingBuffer.h; path = PublicUtility/CARingBuffer.h; sourceTree = \"<group>\"; };\n\t\t1C6181A52388FC8A0068C4D3 /* CARingBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CARingBuffer.cpp; path = PublicUtility/CARingBuffer.cpp; sourceTree = \"<group>\"; };\n\t\t1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_AudibleState.cpp; sourceTree = \"<group>\"; };\n\t\t1C7010741F05ED5100D8CCDC /* BGM_AudibleState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_AudibleState.h; sourceTree = \"<group>\"; };\n\t\t1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_VolumeControl.cpp; sourceTree = \"<group>\"; };\n\t\t1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_VolumeControl.h; sourceTree = \"<group>\"; };\n\t\t1C780FEE1FEE78E800497FAD /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };\n\t\t1C8034DA1BDD073B00668E00 /* BGMDriverTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMDriverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_ClientsTests.mm; sourceTree = \"<group>\"; };\n\t\t1C8034DE1BDD073B00668E00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Stream.cpp; sourceTree = \"<group>\"; };\n\t\t1CA2A9E11E8D1D08007A76A4 /* BGM_Stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Stream.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3641BBBB78D000E2DD1 /* Background Music Device.driver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = \"Background Music Device.driver\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t1CB8B3681BBBB78D000E2DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_PlugInInterface.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3701BBBD8A4000E2DD1 /* CADebugMacros.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugMacros.cpp; path = PublicUtility/CADebugMacros.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3711BBBD8A4000E2DD1 /* CADebugMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugMacros.h; path = PublicUtility/CADebugMacros.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3721BBBD8A4000E2DD1 /* CAException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAException.h; path = PublicUtility/CAException.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3741BBBD924000E2DD1 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };\n\t\t1CB8B3751BBBD924000E2DD1 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };\n\t\t1CB8B3781BBBDFA2000E2DD1 /* CADebugPrintf.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugPrintf.cpp; path = PublicUtility/CADebugPrintf.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3791BBBDFA2000E2DD1 /* CADebugPrintf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugPrintf.h; path = PublicUtility/CADebugPrintf.h; sourceTree = \"<group>\"; };\n\t\t1CB8B37B1BBCCF62000E2DD1 /* BGM_PlugIn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_PlugIn.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B37C1BBCCF62000E2DD1 /* BGM_PlugIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_PlugIn.h; sourceTree = \"<group>\"; };\n\t\t1CB8B37E1BBCCF87000E2DD1 /* BGM_Device.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Device.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B37F1BBCCF87000E2DD1 /* BGM_Device.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Device.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3811BBCE7B5000E2DD1 /* BGM_Object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Object.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3821BBCE7B5000E2DD1 /* BGM_Object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Object.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3841BBCEFE8000E2DD1 /* CAMutex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAMutex.cpp; path = PublicUtility/CAMutex.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3851BBCEFE8000E2DD1 /* CAMutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAMutex.h; path = PublicUtility/CAMutex.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3871BBCF08A000E2DD1 /* CAHostTimeBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAHostTimeBase.cpp; path = PublicUtility/CAHostTimeBase.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3881BBCF08A000E2DD1 /* CAHostTimeBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAHostTimeBase.h; path = PublicUtility/CAHostTimeBase.h; sourceTree = \"<group>\"; };\n\t\t1CB8B38A1BBCF4A9000E2DD1 /* CACFString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFString.cpp; path = PublicUtility/CACFString.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B38B1BBCF4A9000E2DD1 /* CACFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFString.h; path = PublicUtility/CACFString.h; sourceTree = \"<group>\"; };\n\t\t1CB8B38C1BBCF4A9000E2DD1 /* CAVolumeCurve.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAVolumeCurve.cpp; path = PublicUtility/CAVolumeCurve.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B38D1BBCF4A9000E2DD1 /* CAVolumeCurve.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAVolumeCurve.h; path = PublicUtility/CAVolumeCurve.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3901BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_WrappedAudioEngine.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3911BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_WrappedAudioEngine.h; sourceTree = \"<group>\"; };\n\t\t1CB8B3931BBD2418000E2DD1 /* CADispatchQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADispatchQueue.cpp; path = PublicUtility/CADispatchQueue.cpp; sourceTree = \"<group>\"; };\n\t\t1CB8B3941BBD2418000E2DD1 /* CADispatchQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADispatchQueue.h; path = PublicUtility/CADispatchQueue.h; sourceTree = \"<group>\"; };\n\t\t1CC1DF871BE558B000FB8FE4 /* CADebugger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CADebugger.cpp; path = PublicUtility/CADebugger.cpp; sourceTree = \"<group>\"; };\n\t\t1CC1DF881BE558B000FB8FE4 /* CADebugger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CADebugger.h; path = PublicUtility/CADebugger.h; sourceTree = \"<group>\"; };\n\t\t1CC1DF991BE865C000FB8FE4 /* quick_install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = quick_install.sh; sourceTree = \"<group>\"; };\n\t\t1CC1DF9D1BE94AA200FB8FE4 /* DeviceIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DeviceIcon.icns; sourceTree = \"<group>\"; };\n\t\t1CDF3ABA1E863B980001E9B7 /* BGM_NullDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_NullDevice.cpp; sourceTree = \"<group>\"; };\n\t\t1CDF3ABB1E863B980001E9B7 /* BGM_NullDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_NullDevice.h; sourceTree = \"<group>\"; };\n\t\t1CDF3ABD1E8644C20001E9B7 /* BGM_AbstractDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_AbstractDevice.cpp; sourceTree = \"<group>\"; };\n\t\t1CDF3ABE1E8644C20001E9B7 /* BGM_AbstractDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_AbstractDevice.h; sourceTree = \"<group>\"; };\n\t\t1CE03A4A238A5BF40036908D /* CABitOperations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CABitOperations.h; path = PublicUtility/CABitOperations.h; sourceTree = \"<group>\"; };\n\t\t1CE3E68C1BE263CA00167F5D /* CACFDictionary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFDictionary.cpp; path = PublicUtility/CACFDictionary.cpp; sourceTree = \"<group>\"; };\n\t\t1CE3E68D1BE263CA00167F5D /* CACFDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFDictionary.h; path = PublicUtility/CACFDictionary.h; sourceTree = \"<group>\"; };\n\t\t1CE3E68F1BE2683900167F5D /* CACFArray.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CACFArray.cpp; path = PublicUtility/CACFArray.cpp; sourceTree = \"<group>\"; };\n\t\t1CE3E6901BE2683900167F5D /* CACFArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CACFArray.h; path = PublicUtility/CACFArray.h; sourceTree = \"<group>\"; };\n\t\t27381A141C8EF50F00DF167C /* BGM_XPCHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGM_XPCHelper.m; sourceTree = \"<group>\"; };\n\t\t27381A151C8EF50F00DF167C /* BGM_XPCHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_XPCHelper.h; sourceTree = \"<group>\"; };\n\t\t2743C9C61D7EF84B0089613B /* libPublicUtility.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPublicUtility.a; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t275343BC1DE9B44900DF3858 /* BGM_Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BGM_Utils.cpp; path = ../SharedSource/BGM_Utils.cpp; sourceTree = \"<group>\"; };\n\t\t2771700E1CA0C16200AB34B4 /* BGM_Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Utils.h; path = ../SharedSource/BGM_Utils.h; sourceTree = \"<group>\"; };\n\t\t277EE6581C7269910037F1EE /* BGM_ClientMapTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_ClientMapTests.mm; sourceTree = \"<group>\"; };\n\t\t2795973D1C9847CF00A002FB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };\n\t\t27D643B71C9FABF600737F6E /* BGM_Types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Types.h; path = ../SharedSource/BGM_Types.h; sourceTree = \"<group>\"; };\n\t\t27D643B81C9FABF600737F6E /* BGMXPCProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCProtocols.h; path = ../SharedSource/BGMXPCProtocols.h; sourceTree = \"<group>\"; };\n\t\t27D643C21C9FBC5800737F6E /* BGM_TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_TestUtils.h; path = ../SharedSource/BGM_TestUtils.h; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t1C8034D71BDD073B00668E00 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C780FF41FF275F300497FAD /* Accelerate.framework in Frameworks */,\n\t\t\t\t1CBB322C1BDD3A3000C9BD55 /* CoreAudio.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CB8B3611BBBB78D000E2DD1 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1C780FEF1FEE78E800497FAD /* Accelerate.framework in Frameworks */,\n\t\t\t\t2743C9E61D7EF8E00089613B /* libPublicUtility.a in Frameworks */,\n\t\t\t\t2795973E1C9847CF00A002FB /* Foundation.framework in Frameworks */,\n\t\t\t\t1CB8B3761BBBD924000E2DD1 /* CoreAudio.framework in Frameworks */,\n\t\t\t\t1CB8B3771BBBD924000E2DD1 /* CoreFoundation.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2743C9C31D7EF84B0089613B /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t1C09150423F010E8001EB0E1 /* Scripts */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C09150523F010E8001EB0E1 /* set-version.sh */,\n\t\t\t);\n\t\t\tname = Scripts;\n\t\t\tpath = ../SharedSource/Scripts;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C0CB6AF1C642C600084C15A /* DeviceClients */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C0CB6B11C642C600084C15A /* BGM_Client.h */,\n\t\t\t\t1C0CB6B01C642C600084C15A /* BGM_Client.cpp */,\n\t\t\t\t1C0CB6B31C642C600084C15A /* BGM_ClientMap.h */,\n\t\t\t\t1C0CB6B21C642C600084C15A /* BGM_ClientMap.cpp */,\n\t\t\t\t1C0CB6B51C642C600084C15A /* BGM_Clients.h */,\n\t\t\t\t1C0CB6B41C642C600084C15A /* BGM_Clients.cpp */,\n\t\t\t\t1C0CB6B81C642C600084C15A /* BGM_ClientTasks.h */,\n\t\t\t);\n\t\t\tpath = DeviceClients;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1C8034DB1BDD073B00668E00 /* BGMDriverTests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */,\n\t\t\t\t277EE6581C7269910037F1EE /* BGM_ClientMapTests.mm */,\n\t\t\t\t1C3DB4861BE063C500EC8160 /* BGM_DeviceTests.mm */,\n\t\t\t\t1C8034DE1BDD073B00668E00 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = BGMDriverTests;\n\t\t\tsourceTree = SOURCE_ROOT;\n\t\t};\n\t\t1CB8B3591BBBB69C000E2DD1 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B3661BBBB78D000E2DD1 /* BGMDriver */,\n\t\t\t\t1C8034DB1BDD073B00668E00 /* BGMDriverTests */,\n\t\t\t\t27D643B61C9FABDE00737F6E /* SharedSource */,\n\t\t\t\t1CB8B36F1BBBD7AE000E2DD1 /* PublicUtility */,\n\t\t\t\t1CB8B3651BBBB78D000E2DD1 /* Products */,\n\t\t\t\t2743CA241D86E2E80089613B /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3651BBBB78D000E2DD1 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B3641BBBB78D000E2DD1 /* Background Music Device.driver */,\n\t\t\t\t1C8034DA1BDD073B00668E00 /* BGMDriverTests.xctest */,\n\t\t\t\t2743C9C61D7EF84B0089613B /* libPublicUtility.a */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3661BBBB78D000E2DD1 /* BGMDriver */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */,\n\t\t\t\t1CB8B37C1BBCCF62000E2DD1 /* BGM_PlugIn.h */,\n\t\t\t\t1CB8B37B1BBCCF62000E2DD1 /* BGM_PlugIn.cpp */,\n\t\t\t\t1CB8B3821BBCE7B5000E2DD1 /* BGM_Object.h */,\n\t\t\t\t1CB8B3811BBCE7B5000E2DD1 /* BGM_Object.cpp */,\n\t\t\t\t1CDF3ABE1E8644C20001E9B7 /* BGM_AbstractDevice.h */,\n\t\t\t\t1CDF3ABD1E8644C20001E9B7 /* BGM_AbstractDevice.cpp */,\n\t\t\t\t1CB8B37F1BBCCF87000E2DD1 /* BGM_Device.h */,\n\t\t\t\t1CB8B37E1BBCCF87000E2DD1 /* BGM_Device.cpp */,\n\t\t\t\t1C7010741F05ED5100D8CCDC /* BGM_AudibleState.h */,\n\t\t\t\t1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */,\n\t\t\t\t1CDF3ABB1E863B980001E9B7 /* BGM_NullDevice.h */,\n\t\t\t\t1CDF3ABA1E863B980001E9B7 /* BGM_NullDevice.cpp */,\n\t\t\t\t1CA2A9E11E8D1D08007A76A4 /* BGM_Stream.h */,\n\t\t\t\t1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */,\n\t\t\t\t19FE7B8CE9148B3D8D7517C6 /* BGM_Control.h */,\n\t\t\t\t19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */,\n\t\t\t\t1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */,\n\t\t\t\t1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */,\n\t\t\t\t19FE7431C588F36F4F1E70BB /* BGM_MuteControl.h */,\n\t\t\t\t19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */,\n\t\t\t\t1C0CB6AF1C642C600084C15A /* DeviceClients */,\n\t\t\t\t1C38210D1C4A163A00A0C8C6 /* BGM_TaskQueue.h */,\n\t\t\t\t1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */,\n\t\t\t\t27381A151C8EF50F00DF167C /* BGM_XPCHelper.h */,\n\t\t\t\t27381A141C8EF50F00DF167C /* BGM_XPCHelper.m */,\n\t\t\t\t1CB8B3911BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.h */,\n\t\t\t\t1CB8B3901BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp */,\n\t\t\t\t1CB8B3671BBBB78D000E2DD1 /* Supporting Files */,\n\t\t\t);\n\t\t\tpath = BGMDriver;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B3671BBBB78D000E2DD1 /* Supporting Files */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1CC1DF9D1BE94AA200FB8FE4 /* DeviceIcon.icns */,\n\t\t\t\t1CC1DF991BE865C000FB8FE4 /* quick_install.sh */,\n\t\t\t\t1CB8B3681BBBB78D000E2DD1 /* Info.plist */,\n\t\t\t);\n\t\t\tname = \"Supporting Files\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1CB8B36F1BBBD7AE000E2DD1 /* PublicUtility */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C0CB6A71C4E06F70084C15A /* CAAtomic.h */,\n\t\t\t\t1C37B3681E9B8D3C000DF98F /* CAPropertyAddress.h */,\n\t\t\t\t1C0CB6A61C4E06C00084C15A /* CAAtomicStack.h */,\n\t\t\t\t1C0CB6A91C50A3AF0084C15A /* CAAutoDisposer.h */,\n\t\t\t\t1CE03A4A238A5BF40036908D /* CABitOperations.h */,\n\t\t\t\t1CE3E68F1BE2683900167F5D /* CACFArray.cpp */,\n\t\t\t\t1CE3E6901BE2683900167F5D /* CACFArray.h */,\n\t\t\t\t1CE3E68C1BE263CA00167F5D /* CACFDictionary.cpp */,\n\t\t\t\t1CE3E68D1BE263CA00167F5D /* CACFDictionary.h */,\n\t\t\t\t1C305D9B1BE294B5004EBB91 /* CACFNumber.cpp */,\n\t\t\t\t1C305D9C1BE294B5004EBB91 /* CACFNumber.h */,\n\t\t\t\t1CB8B38A1BBCF4A9000E2DD1 /* CACFString.cpp */,\n\t\t\t\t1CB8B38B1BBCF4A9000E2DD1 /* CACFString.h */,\n\t\t\t\t1CC1DF871BE558B000FB8FE4 /* CADebugger.cpp */,\n\t\t\t\t1CC1DF881BE558B000FB8FE4 /* CADebugger.h */,\n\t\t\t\t1CB8B3701BBBD8A4000E2DD1 /* CADebugMacros.cpp */,\n\t\t\t\t1CB8B3711BBBD8A4000E2DD1 /* CADebugMacros.h */,\n\t\t\t\t1CB8B3781BBBDFA2000E2DD1 /* CADebugPrintf.cpp */,\n\t\t\t\t1CB8B3791BBBDFA2000E2DD1 /* CADebugPrintf.h */,\n\t\t\t\t1CB8B3931BBD2418000E2DD1 /* CADispatchQueue.cpp */,\n\t\t\t\t1CB8B3941BBD2418000E2DD1 /* CADispatchQueue.h */,\n\t\t\t\t1CB8B3721BBBD8A4000E2DD1 /* CAException.h */,\n\t\t\t\t1CB8B3871BBCF08A000E2DD1 /* CAHostTimeBase.cpp */,\n\t\t\t\t1CB8B3881BBCF08A000E2DD1 /* CAHostTimeBase.h */,\n\t\t\t\t1CB8B3841BBCEFE8000E2DD1 /* CAMutex.cpp */,\n\t\t\t\t1CB8B3851BBCEFE8000E2DD1 /* CAMutex.h */,\n\t\t\t\t1C38210F1C4A18DE00A0C8C6 /* CAPThread.cpp */,\n\t\t\t\t1C3821101C4A18DE00A0C8C6 /* CAPThread.h */,\n\t\t\t\t1C6181A42388FC8A0068C4D3 /* CARingBuffer.h */,\n\t\t\t\t1C6181A52388FC8A0068C4D3 /* CARingBuffer.cpp */,\n\t\t\t\t1CB8B38C1BBCF4A9000E2DD1 /* CAVolumeCurve.cpp */,\n\t\t\t\t1CB8B38D1BBCF4A9000E2DD1 /* CAVolumeCurve.h */,\n\t\t\t);\n\t\t\tname = PublicUtility;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2743CA241D86E2E80089613B /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1C780FEE1FEE78E800497FAD /* Accelerate.framework */,\n\t\t\t\t1CB8B3741BBBD924000E2DD1 /* CoreAudio.framework */,\n\t\t\t\t1CB8B3751BBBD924000E2DD1 /* CoreFoundation.framework */,\n\t\t\t\t2795973D1C9847CF00A002FB /* Foundation.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27D643B61C9FABDE00737F6E /* SharedSource */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27D643B71C9FABF600737F6E /* BGM_Types.h */,\n\t\t\t\t2771700E1CA0C16200AB34B4 /* BGM_Utils.h */,\n\t\t\t\t275343BC1DE9B44900DF3858 /* BGM_Utils.cpp */,\n\t\t\t\t1C09150423F010E8001EB0E1 /* Scripts */,\n\t\t\t\t27D643C21C9FBC5800737F6E /* BGM_TestUtils.h */,\n\t\t\t\t27D643B81C9FABF600737F6E /* BGMXPCProtocols.h */,\n\t\t\t);\n\t\t\tname = SharedSource;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\t2743C9C41D7EF84B0089613B /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1CE03A4B238A5BF40036908D /* CABitOperations.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\t1C8034D91BDD073B00668E00 /* BGMDriverTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 1C8034DF1BDD073B00668E00 /* Build configuration list for PBXNativeTarget \"BGMDriverTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t1C8034D61BDD073B00668E00 /* Sources */,\n\t\t\t\t1C8034D71BDD073B00668E00 /* Frameworks */,\n\t\t\t\t1C8034D81BDD073B00668E00 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = BGMDriverTests;\n\t\t\tproductName = BGMDriverTests;\n\t\t\tproductReference = 1C8034DA1BDD073B00668E00 /* BGMDriverTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t1CB8B3631BBBB78D000E2DD1 /* Background Music Device */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 1CB8B3691BBBB78D000E2DD1 /* Build configuration list for PBXNativeTarget \"Background Music Device\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t1CB8B3601BBBB78D000E2DD1 /* Sources */,\n\t\t\t\t1CB8B3611BBBB78D000E2DD1 /* Frameworks */,\n\t\t\t\t1CB8B3621BBBB78D000E2DD1 /* Resources */,\n\t\t\t\t1C09150823F01E6D001EB0E1 /* Run Script - set-version.sh */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = \"Background Music Device\";\n\t\t\tproductName = BGMDriver;\n\t\t\tproductReference = 1CB8B3641BBBB78D000E2DD1 /* Background Music Device.driver */;\n\t\t\tproductType = \"com.apple.product-type.bundle\";\n\t\t};\n\t\t2743C9C51D7EF84B0089613B /* PublicUtility */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 2743C9C71D7EF84B0089613B /* Build configuration list for PBXNativeTarget \"PublicUtility\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t2743C9C21D7EF84B0089613B /* Sources */,\n\t\t\t\t2743C9C31D7EF84B0089613B /* Frameworks */,\n\t\t\t\t2743C9C41D7EF84B0089613B /* Headers */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = PublicUtility;\n\t\t\tproductName = PublicUtility;\n\t\t\tproductReference = 2743C9C61D7EF84B0089613B /* libPublicUtility.a */;\n\t\t\tproductType = \"com.apple.product-type.library.static\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t1CB8B35A1BBBB69C000E2DD1 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tCLASSPREFIX = BGM_;\n\t\t\t\tLastUpgradeCheck = 0900;\n\t\t\t\tORGANIZATIONNAME = \"Background Music contributors\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t1C8034D91BDD073B00668E00 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.0.1;\n\t\t\t\t\t};\n\t\t\t\t\t1CB8B3631BBBB78D000E2DD1 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.4;\n\t\t\t\t\t};\n\t\t\t\t\t2743C9C51D7EF84B0089613B = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.0;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 1CB8B35D1BBBB69C000E2DD1 /* Build configuration list for PBXProject \"BGMDriver\" */;\n\t\t\tcompatibilityVersion = \"Xcode 6.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 1CB8B3591BBBB69C000E2DD1;\n\t\t\tproductRefGroup = 1CB8B3651BBBB78D000E2DD1 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t1CB8B3631BBBB78D000E2DD1 /* Background Music Device */,\n\t\t\t\t1C8034D91BDD073B00668E00 /* BGMDriverTests */,\n\t\t\t\t2743C9C51D7EF84B0089613B /* PublicUtility */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t1C8034D81BDD073B00668E00 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CB8B3621BBBB78D000E2DD1 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1CC1DF9E1BE94AA200FB8FE4 /* DeviceIcon.icns in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t1C09150823F01E6D001EB0E1 /* Run Script - set-version.sh */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script - set-version.sh\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds.\\n\\\"$SRCROOT/../SharedSource/Scripts/set-version.sh\\\"\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t1C8034D61BDD073B00668E00 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1CE03A4C23928B370036908D /* CARingBuffer.cpp in Sources */,\n\t\t\t\t1CD95B121E93AA5200EB8EF0 /* BGM_AbstractDevice.cpp in Sources */,\n\t\t\t\t1CD95B131E93AA5200EB8EF0 /* BGM_NullDevice.cpp in Sources */,\n\t\t\t\t1CD95B141E93AA5200EB8EF0 /* BGM_Stream.cpp in Sources */,\n\t\t\t\t27E6B5F01E01966A00EC0AAB /* BGM_Utils.cpp in Sources */,\n\t\t\t\t277170101CA0CFC300AB34B4 /* BGM_PlugInInterface.cpp in Sources */,\n\t\t\t\t277170111CA0CFC300AB34B4 /* CACFNumber.cpp in Sources */,\n\t\t\t\t1C7010761F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */,\n\t\t\t\t27D643C31C9FBE1600737F6E /* BGM_XPCHelper.m in Sources */,\n\t\t\t\t27379B821C76D62D0084A24C /* CADebugMacros.cpp in Sources */,\n\t\t\t\t27379B831C76D62D0084A24C /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t277EE6641C728CDB0037F1EE /* CACFString.cpp in Sources */,\n\t\t\t\t277EE6651C728CDB0037F1EE /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t277EE6621C728CC70037F1EE /* BGM_WrappedAudioEngine.cpp in Sources */,\n\t\t\t\t277EE6631C728CC70037F1EE /* CADispatchQueue.cpp in Sources */,\n\t\t\t\t277EE6611C728CAE0037F1EE /* BGM_Object.cpp in Sources */,\n\t\t\t\t277EE6601C728CA70037F1EE /* BGM_Device.cpp in Sources */,\n\t\t\t\t277EE65E1C728C9D0037F1EE /* BGM_PlugIn.cpp in Sources */,\n\t\t\t\t277EE65F1C728C9D0037F1EE /* CAPThread.cpp in Sources */,\n\t\t\t\t277EE65A1C728C630037F1EE /* BGM_Client.cpp in Sources */,\n\t\t\t\t1C70107A1F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */,\n\t\t\t\t277EE65B1C728C630037F1EE /* BGM_ClientMap.cpp in Sources */,\n\t\t\t\t277EE65C1C728C630037F1EE /* BGM_Clients.cpp in Sources */,\n\t\t\t\t277EE65D1C728C630037F1EE /* BGM_TaskQueue.cpp in Sources */,\n\t\t\t\t1C30A69F1C1E98F000C05AA5 /* CAMutex.cpp in Sources */,\n\t\t\t\t1CC1DF931BE7B79500FB8FE4 /* CADebugger.cpp in Sources */,\n\t\t\t\t1CC1DF941BE7B79500FB8FE4 /* CAVolumeCurve.cpp in Sources */,\n\t\t\t\t1CC1DF8E1BE5706C00FB8FE4 /* CACFArray.cpp in Sources */,\n\t\t\t\t277EE6591C7269910037F1EE /* BGM_ClientMapTests.mm in Sources */,\n\t\t\t\t1CC1DF8D1BE5705700FB8FE4 /* CACFDictionary.cpp in Sources */,\n\t\t\t\t1C3DB4871BE063C500EC8160 /* BGM_DeviceTests.mm in Sources */,\n\t\t\t\t1C8034DD1BDD073B00668E00 /* BGM_ClientsTests.mm in Sources */,\n\t\t\t\t19FE761291BF07AEA278F25C /* BGM_MuteControl.cpp in Sources */,\n\t\t\t\t19FE742AEBE30B21C4CF9285 /* BGM_Control.cpp in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t1CB8B3601BBBB78D000E2DD1 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1CA2A9E21E8D1D08007A76A4 /* BGM_Stream.cpp in Sources */,\n\t\t\t\t1C7010751F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */,\n\t\t\t\t1CB8B3801BBCCF87000E2DD1 /* BGM_Device.cpp in Sources */,\n\t\t\t\t1C0CB6B91C642C600084C15A /* BGM_Client.cpp in Sources */,\n\t\t\t\t1CB8B3921BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp in Sources */,\n\t\t\t\t1CB8B36E1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp in Sources */,\n\t\t\t\t1CB8B37D1BBCCF62000E2DD1 /* BGM_PlugIn.cpp in Sources */,\n\t\t\t\t27381A161C8EF50F00DF167C /* BGM_XPCHelper.m in Sources */,\n\t\t\t\t1CDF3ABF1E8644C20001E9B7 /* BGM_AbstractDevice.cpp in Sources */,\n\t\t\t\t1C7010791F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */,\n\t\t\t\t1C0CB6BA1C642C600084C15A /* BGM_ClientMap.cpp in Sources */,\n\t\t\t\t1CB8B3831BBCE7B5000E2DD1 /* BGM_Object.cpp in Sources */,\n\t\t\t\t275343BD1DE9B44900DF3858 /* BGM_Utils.cpp in Sources */,\n\t\t\t\t1C38210E1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp in Sources */,\n\t\t\t\t1C0CB6BB1C642C600084C15A /* BGM_Clients.cpp in Sources */,\n\t\t\t\t1CDF3ABC1E863B980001E9B7 /* BGM_NullDevice.cpp in Sources */,\n\t\t\t\t19FE766482B57D852CCF6F0A /* BGM_MuteControl.cpp in Sources */,\n\t\t\t\t19FE77D40F15EA060B462D83 /* BGM_Control.cpp in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t2743C9C21D7EF84B0089613B /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2743C9CD1D7EF8760089613B /* CACFArray.cpp in Sources */,\n\t\t\t\t2743C9CF1D7EF8760089613B /* CACFDictionary.cpp in Sources */,\n\t\t\t\t2743C9D11D7EF8760089613B /* CACFNumber.cpp in Sources */,\n\t\t\t\t1C6181A72388FC8A0068C4D3 /* CARingBuffer.cpp in Sources */,\n\t\t\t\t2743C9D31D7EF8760089613B /* CACFString.cpp in Sources */,\n\t\t\t\t2743C9D51D7EF8760089613B /* CADebugger.cpp in Sources */,\n\t\t\t\t2743C9D71D7EF8760089613B /* CADebugMacros.cpp in Sources */,\n\t\t\t\t2743C9D91D7EF8760089613B /* CADebugPrintf.cpp in Sources */,\n\t\t\t\t2743C9DB1D7EF8760089613B /* CADispatchQueue.cpp in Sources */,\n\t\t\t\t2743C9DE1D7EF8760089613B /* CAHostTimeBase.cpp in Sources */,\n\t\t\t\t2743C9E01D7EF8760089613B /* CAMutex.cpp in Sources */,\n\t\t\t\t2743C9E21D7EF8760089613B /* CAPThread.cpp in Sources */,\n\t\t\t\t2743C9E41D7EF8760089613B /* CAVolumeCurve.cpp in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t1C8034E01BDD073B00668E00 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tINFOPLIST_FILE = BGMDriverTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.DriverTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1C8034E11BDD073B00668E00 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tINFOPLIST_FILE = BGMDriverTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.DriverTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tWARNING_CFLAGS = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t1CB8B35E1BBBB69C000E2DD1 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_ASSIGN_ENUM = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = c11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"CoreAudio_Debug=1\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=1\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_NEWLINE = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES;\n\t\t\t\tGCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;\n\t\t\t\tGCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;\n\t\t\t\tGCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;\n\t\t\t\tGCC_WARN_SHADOW = YES;\n\t\t\t\tGCC_WARN_SIGN_COMPARE = YES;\n\t\t\t\tGCC_WARN_STRICT_SELECTOR_MATCH = YES;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_LABEL = YES;\n\t\t\t\tGCC_WARN_UNUSED_PARAMETER = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.13;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tRUN_CLANG_STATIC_ANALYZER = YES;\n\t\t\t\tWARNING_CFLAGS = \"-Wpartial-availability\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1CB8B35F1BBBB69C000E2DD1 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;\n\t\t\t\tCLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_ASSIGN_ENUM = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = c11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 3;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=0\",\n\t\t\t\t\t\"CoreAudio_Debug=0\",\n\t\t\t\t\t\"CoreAudio_UseSysLog=1\",\n\t\t\t\t\t\"CoreAudio_StopOnAssert=0\",\n\t\t\t\t\t\"CoreAudio_ThreadStampMessages=0\",\n\t\t\t\t);\n\t\t\t\tGCC_TREAT_WARNINGS_AS_ERRORS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_NEWLINE = YES;\n\t\t\t\tGCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES;\n\t\t\t\tGCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;\n\t\t\t\tGCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;\n\t\t\t\tGCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;\n\t\t\t\tGCC_WARN_SHADOW = YES;\n\t\t\t\tGCC_WARN_SIGN_COMPARE = YES;\n\t\t\t\tGCC_WARN_STRICT_SELECTOR_MATCH = YES;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_LABEL = YES;\n\t\t\t\tGCC_WARN_UNUSED_PARAMETER = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.13;\n\t\t\t\tRUN_CLANG_STATIC_ANALYZER = YES;\n\t\t\t\tWARNING_CFLAGS = \"-Wpartial-availability\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t1CB8B36A1BBBB78D000E2DD1 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = \"compiler-default\";\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_ENABLE_CPP_RTTI = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_FILE = BGMDriver/Info.plist;\n\t\t\t\tINSTALL_GROUP = wheel;\n\t\t\t\tINSTALL_OWNER = root;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.Driver;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tWRAPPER_EXTENSION = driver;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t1CB8B36B1BBBB78D000E2DD1 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tDEPLOYMENT_POSTPROCESSING = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = \"compiler-default\";\n\t\t\t\tGCC_ENABLE_CPP_RTTI = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_FILE = BGMDriver/Info.plist;\n\t\t\t\tINSTALL_GROUP = wheel;\n\t\t\t\tINSTALL_OWNER = root;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.Driver;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tWRAPPER_EXTENSION = driver;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t2743C9C81D7EF84B0089613B /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t2743C9C91D7EF84B0089613B /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++0x\";\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t1C8034DF1BDD073B00668E00 /* Build configuration list for PBXNativeTarget \"BGMDriverTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1C8034E01BDD073B00668E00 /* Debug */,\n\t\t\t\t1C8034E11BDD073B00668E00 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t1CB8B35D1BBBB69C000E2DD1 /* Build configuration list for PBXProject \"BGMDriver\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1CB8B35E1BBBB69C000E2DD1 /* Debug */,\n\t\t\t\t1CB8B35F1BBBB69C000E2DD1 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t1CB8B3691BBBB78D000E2DD1 /* Build configuration list for PBXNativeTarget \"Background Music Device\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t1CB8B36A1BBBB78D000E2DD1 /* Debug */,\n\t\t\t\t1CB8B36B1BBBB78D000E2DD1 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t2743C9C71D7EF84B0089613B /* Build configuration list for PBXNativeTarget \"PublicUtility\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t2743C9C81D7EF84B0089613B /* Debug */,\n\t\t\t\t2743C9C91D7EF84B0089613B /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 1CB8B35A1BBBB69C000E2DD1 /* Project object */;\n}\n"
  },
  {
    "path": "BGMDriver/BGMDriver.xcodeproj/xcshareddata/xcschemes/Background Music Device.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"1CB8B3631BBBB78D000E2DD1\"\n               BuildableName = \"Background Music Device.driver\"\n               BlueprintName = \"Background Music Device\"\n               ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      codeCoverageEnabled = \"YES\"\n      onlyGenerateCoverageForSpecifiedTargets = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3631BBBB78D000E2DD1\"\n            BuildableName = \"Background Music Device.driver\"\n            BlueprintName = \"Background Music Device\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <CodeCoverageTargets>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3631BBBB78D000E2DD1\"\n            BuildableName = \"Background Music Device.driver\"\n            BlueprintName = \"Background Music Device\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </CodeCoverageTargets>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\"\n            parallelizable = \"YES\"\n            testExecutionOrdering = \"random\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"1C8034D91BDD073B00668E00\"\n               BuildableName = \"BGMDriverTests.xctest\"\n               BlueprintName = \"BGMDriverTests\"\n               ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      debugAsWhichUser = \"root\"\n      enableASanStackUseAfterReturn = \"YES\"\n      disableMainThreadChecker = \"YES\"\n      launchStyle = \"1\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <PathRunnable\n         runnableDebuggingMode = \"0\"\n         FilePath = \"/usr/sbin/coreaudiod\">\n      </PathRunnable>\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3631BBBB78D000E2DD1\"\n            BuildableName = \"Background Music Device.driver\"\n            BlueprintName = \"Background Music Device\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <EnvironmentVariables>\n         <EnvironmentVariable\n            key = \"ASAN_OPTIONS\"\n            value = \"detect_odr_violation=1:use_odr_indicator=1:detect_stack_use_after_return=1:detect_invalid_pointer_pairs=2:check_initialization_order=1:log_to_syslog=1\"\n            isEnabled = \"YES\">\n         </EnvironmentVariable>\n      </EnvironmentVariables>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"1CB8B3631BBBB78D000E2DD1\"\n            BuildableName = \"Background Music Device.driver\"\n            BlueprintName = \"Background Music Device\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "BGMDriver/BGMDriver.xcodeproj/xcshareddata/xcschemes/PublicUtility.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"2743C9C51D7EF84B0089613B\"\n               BuildableName = \"libPublicUtility.a\"\n               BlueprintName = \"PublicUtility\"\n               ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      enableAddressSanitizer = \"YES\"\n      enableASanStackUseAfterReturn = \"YES\"\n      enableUBSanitizer = \"YES\"\n      disableMainThreadChecker = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      enableASanStackUseAfterReturn = \"YES\"\n      disableMainThreadChecker = \"YES\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"2743C9C51D7EF84B0089613B\"\n            BuildableName = \"libPublicUtility.a\"\n            BlueprintName = \"PublicUtility\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <EnvironmentVariables>\n         <EnvironmentVariable\n            key = \"ASAN_OPTIONS\"\n            value = \"detect_odr_violation=1:use_odr_indicator=1:detect_stack_use_after_return=1:detect_invalid_pointer_pairs=2:check_initialization_order=1:log_to_syslog=1\"\n            isEnabled = \"YES\">\n         </EnvironmentVariable>\n      </EnvironmentVariables>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"2743C9C51D7EF84B0089613B\"\n            BuildableName = \"libPublicUtility.a\"\n            BlueprintName = \"PublicUtility\"\n            ReferencedContainer = \"container:BGMDriver.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "BGMDriver/BGMDriverTests/BGM_ClientMapTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_ClientMapTests.mm\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Unit Include\n#include \"BGM_ClientMap.h\"\n\n// Local Includes\n#include \"BGM_TestUtils.h\"\n\n// BGMDriver Includes\n#include \"BGM_Client.h\"\n#include \"BGM_TaskQueue.h\"\n#include \"BGM_Types.h\"\n\n\nstatic BGM_TaskQueue taskQueue;\n\nstatic const AudioServerPlugInClientInfo client1Info = {\n    /* mClientID = */ 1,\n    /* mProcessID = */ 2291,\n    /* mIsNativeEndian = */ true,\n    /* mBundleID = */ CFSTR(\"com.example.background.music.client.one\")\n};\n\nstatic const AudioServerPlugInClientInfo client2Info = {\n    /* mClientID = */ 921,\n    /* mProcessID = */ 64372,\n    /* mIsNativeEndian = */ true,\n    /* mBundleID = */ CFSTR(\"com.example.background.music.client.two\")\n};\n\nstatic BGM_Client client1(&client1Info);\nstatic BGM_Client client2(&client2Info);\n\n@interface BGM_ClientMapTests : XCTestCase\n\n@end\n\n@implementation BGM_ClientMapTests\n\n- (void)setUp {\n    [super setUp];\n    \n    client1.mRelativeVolume = 0.625;\n    client2.mIsMusicPlayer = true;\n}\n\n- (void)tearDown {\n    [super tearDown];\n}\n\n// Asserts that in client the fields that come from AudioServerPlugInClientInfo are equal to the\n// corresponding fields in info.\n//\n// Requires that the mBundleID fields of both are either NULL or have not been released.\n+ (void)assertClient:(const BGM_Client*)client\n      hasInfoEqualTo:(const AudioServerPlugInClientInfo*)info {\n    XCTAssertEqual(client->mClientID, info->mClientID);\n    XCTAssertEqual(client->mProcessID, info->mProcessID);\n    XCTAssertEqual(client->mIsNativeEndian, info->mIsNativeEndian);\n    \n    if (!client->mBundleID.IsValid() || info->mBundleID == NULL) {\n        XCTAssert(!client->mBundleID.IsValid());\n        XCTAssert(info->mBundleID == NULL);\n    } else {\n        XCTAssert(client->mBundleID == info->mBundleID);\n    }\n}\n\n// Requires that the mBundleID fields of both clients are either NULL or have not been released.\n+ (void)assertClient:(const BGM_Client*)c1\n           isEqualTo:(const BGM_Client*)c2 {\n    const AudioServerPlugInClientInfo info =\n        { c2->mClientID, c2->mProcessID, c2->mIsNativeEndian, c2->mBundleID.GetCFString() };\n    \n    [BGM_ClientMapTests assertClient:c1 hasInfoEqualTo:&info];\n    \n    XCTAssertEqual(c1->mDoingIO, c2->mDoingIO);\n    XCTAssertEqual(c1->mIsMusicPlayer, c2->mIsMusicPlayer);\n    XCTAssertEqual(c1->mRelativeVolume, c2->mRelativeVolume);\n}\n\n- (void)testClientConstruction {\n    // Check that the BGM_Client instances we're testing with match the AudioServerPlugInClientInfos\n    // they were constructed from.\n    //\n    // TODO: This should be in a BGM_ClientTests class rather than here.\n    [BGM_ClientMapTests assertClient:&client1 hasInfoEqualTo:&client1Info];\n    [BGM_ClientMapTests assertClient:&client2 hasInfoEqualTo:&client2Info];\n}\n\n- (void)testAddRemoveClient {\n    BGM_ClientMap clientMap(&taskQueue);\n    \n    // Add a client\n    clientMap.AddClient(client1);\n    \n    // Get the client back out of the map\n    BGM_Client retrievedClient;\n    bool didGetClient = clientMap.GetClientNonRT(client1.mClientID, &retrievedClient);\n    XCTAssert(didGetClient);\n    \n    // Compare the client we added to the one we got back\n    [BGM_ClientMapTests assertClient:&retrievedClient isEqualTo:&client1];\n    [BGM_ClientMapTests assertClient:&retrievedClient hasInfoEqualTo:&client1Info];\n    \n    // A client to use as the out argument for GetClientNonRT when we don't expect to get a client back\n    BGM_Client notRetrievedClient(&client2Info);\n    notRetrievedClient.mRelativeVolume = 3.5;\n    notRetrievedClient.mDoingIO = true;\n    \n    // A known-good copy to check against\n    BGM_Client notRetrievedClientCopy(notRetrievedClient);\n    \n    // Try getting a client that we never added\n    didGetClient = clientMap.GetClientNonRT(/* inClientID = */ 12345, &retrievedClient);\n    XCTAssertFalse(didGetClient);\n    [BGM_ClientMapTests assertClient:&notRetrievedClient isEqualTo:&notRetrievedClientCopy];\n    \n    // Remove the client\n    BGM_Client removedClient = clientMap.RemoveClient(client1.mClientID);\n    \n    // Check that the client RemoveClient says we removed matches the one we added in the first place\n    [BGM_ClientMapTests assertClient:&removedClient isEqualTo:&client1];\n    \n    // We shouldn't be able to get the client after we've removed it\n    didGetClient = clientMap.GetClientNonRT(client1Info.mClientID, &notRetrievedClient);\n    XCTAssertFalse(didGetClient);\n    \n    // Calling GetClientNonRT should have left notRetrievedClient unchanged\n    [BGM_ClientMapTests assertClient:&notRetrievedClient isEqualTo:&notRetrievedClientCopy];\n    \n    // Check against hardcoded values as well just in case there's a problem with BGM_Client's copy constructor\n    XCTAssertEqual(notRetrievedClient.mRelativeVolume, 3.5);\n    XCTAssert(notRetrievedClient.mDoingIO);\n}\n\n- (void)testAddRemoveMultipleClients {\n    BGM_ClientMap clientMap(&taskQueue);\n    \n    // Add the clients\n    clientMap.AddClient(client1);\n    clientMap.AddClient(client2);\n    \n    // Get both clients from the map and check they match what we added\n    {\n        BGM_Client retrievedClient1, retrievedClient2;\n        bool didGetClient = clientMap.GetClientNonRT(client1Info.mClientID, &retrievedClient1);\n        XCTAssert(didGetClient);\n        [BGM_ClientMapTests assertClient:&retrievedClient1 isEqualTo:&client1];\n        \n        didGetClient = clientMap.GetClientNonRT(client2Info.mClientID, &retrievedClient2);\n        XCTAssert(didGetClient);\n        [BGM_ClientMapTests assertClient:&retrievedClient2 isEqualTo:&client2];\n    }\n    \n    // Remove one and check we can still get the other\n    clientMap.RemoveClient(client1Info.mClientID);\n    \n    {\n        BGM_Client retrievedClient2;\n        bool didGetClient = clientMap.GetClientNonRT(client2Info.mClientID, &retrievedClient2);\n        XCTAssert(didGetClient);\n        [BGM_ClientMapTests assertClient:&retrievedClient2 isEqualTo:&client2];\n    }\n    \n    // Remove the other\n    BGM_Client removedClient = clientMap.RemoveClient(client2Info.mClientID);\n    [BGM_ClientMapTests assertClient:&removedClient isEqualTo:&client2];\n    \n    // Check that we can't get either client from the map anymore\n    const AudioServerPlugInClientInfo notRetrievedClientInfo = { 5, 10, true, CFSTR(\"not.retrieved.client\") };\n    BGM_Client notRetrievedClient(&notRetrievedClientInfo);\n    \n    bool didGetClient = clientMap.GetClientNonRT(client1Info.mClientID, &notRetrievedClient);\n    XCTAssertFalse(didGetClient);\n    [BGM_ClientMapTests assertClient:&notRetrievedClient hasInfoEqualTo:&notRetrievedClientInfo];\n    \n    didGetClient = clientMap.GetClientNonRT(client2Info.mClientID, &notRetrievedClient);\n    XCTAssertFalse(didGetClient);\n    [BGM_ClientMapTests assertClient:&notRetrievedClient hasInfoEqualTo:&notRetrievedClientInfo];\n}\n\n- (void)testAddClientSeveralTimes {\n    BGM_ClientMap clientMap(&taskQueue);\n    \n    // Adding a client once should work\n    clientMap.AddClient(client2);\n    \n    // Adding a different client should work\n    clientMap.AddClient(client1);\n    \n    // Adding the same client twice should fail\n    BGMShouldThrow<BGM_InvalidClientException>(self, [&](){\n        clientMap.AddClient(client2);\n    });\n    \n    // Adding the other client again should fail too\n    BGMShouldThrow<BGM_InvalidClientException>(self, [&](){\n        clientMap.AddClient(client1);\n    });\n}\n\n@end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriverTests/BGM_ClientsTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_ClientsTests.mm\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Unit Include\n#include \"BGM_Clients.h\"\n\n// Local Includes\n#include \"BGM_TestUtils.h\"\n\n// BGMDriver Includes\n#include \"BGM_Types.h\"\n\n\nstatic BGM_TaskQueue taskQueue;\n\nstatic const AudioServerPlugInClientInfo client1Info = {\n    /* mClientID = */ 11,\n    /* mProcessID = */ 1181,\n    /* mIsNativeEndian = */ true,\n    /* mBundleID = */ CFSTR(\"com.bearisdriving.BGMDriver.ClientOne\")\n};\n\nstatic const AudioServerPlugInClientInfo client2Info = {\n    /* mClientID = */ 22,\n    /* mProcessID = */ 222,\n    /* mIsNativeEndian = */ true,\n    /* mBundleID = */ CFSTR(\"com.bearisdriving.BGMDriver.ClientTwo\")\n};\n\n@interface BGM_ClientsTests : XCTestCase\n\n@end\n\n@implementation BGM_ClientsTests {\n    BGM_Clients* clients;\n}\n\n- (void)setUp {\n    [super setUp];\n    \n    clients = new BGM_Clients(kAudioObjectUnknown, &taskQueue);\n}\n\n- (void)tearDown {\n    delete clients;\n    \n    [super tearDown];\n}\n\n- (void)testMusicPlayer {\n    // Should be able to set the music player PID before clients have been added\n    clients->SetMusicPlayer(441);\n    XCTAssertEqual(clients->GetMusicPlayerProcessIDProperty(), 441);\n    \n    // IsMusicPlayerRT takes a client ID, not a PID\n    XCTAssertFalse(clients->IsMusicPlayerRT(441));\n    \n    // Set the bundle ID\n    clients->SetMusicPlayer(\"com.example.music.player\");\n    NSString* bundleID = (NSString*)CFBridgingRelease(clients->CopyMusicPlayerBundleIDProperty());\n    XCTAssertEqualObjects(bundleID, @\"com.example.music.player\");\n    \n    // Setting the bundle ID unsets the PID, and vice versa\n    XCTAssertEqual(clients->GetMusicPlayerProcessIDProperty(), 0);\n    clients->SetMusicPlayer(2169);\n    bundleID = (NSString*)CFBridgingRelease(clients->CopyMusicPlayerBundleIDProperty());\n    XCTAssertEqualObjects(bundleID, @\"\");\n    \n    // Set client 2's bundle ID as the music player bundle ID\n    clients->SetMusicPlayer(client2Info.mBundleID);\n    \n    // No client can be the music player yet because we haven't added any clients yet\n    XCTAssertFalse(clients->IsMusicPlayerRT(client2Info.mClientID));\n    \n    // When we add client 2 it should become the music player because its bundle ID matches what we set above\n    clients->AddClient(&client2Info);\n    XCTAssert(clients->IsMusicPlayerRT(client2Info.mClientID));\n    XCTAssertFalse(clients->IsMusicPlayerRT(client1Info.mClientID));\n    \n    // Check the bundle ID property matches client 2's\n    CFStringRef bundleIDRef = clients->CopyMusicPlayerBundleIDProperty();\n    XCTAssertEqual(bundleIDRef, client2Info.mBundleID);\n    CFRelease(bundleIDRef);\n    \n    // Change the music player to client 1\n    clients->AddClient(&client1Info);\n    clients->SetMusicPlayer(client1Info.mProcessID);\n    \n    XCTAssert(clients->IsMusicPlayerRT(client1Info.mClientID));\n    XCTAssertFalse(clients->IsMusicPlayerRT(client2Info.mClientID));\n    \n    // The music player should be unset after removing the music player as a client\n    clients->RemoveClient(client1Info.mClientID);\n    XCTAssertFalse(clients->IsMusicPlayerRT(client1Info.mClientID));\n    \n    // ...but the music player PID shouldn't change\n    XCTAssertEqual(clients->GetMusicPlayerProcessIDProperty(), client1Info.mProcessID);\n}\n\n- (void)testSetMusicPlayerInvalidPID {\n    BGMShouldThrow<BGM_InvalidClientPIDException>(self, [=](){\n        clients->SetMusicPlayer(-1);\n    });\n    \n    BGMShouldThrow<BGM_InvalidClientPIDException>(self, [=](){\n        clients->SetMusicPlayer(INT_MIN);\n    });\n}\n\n@end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriverTests/BGM_DeviceTests.mm",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_DeviceTests.mm\n//  BGMDriver\n//\n//  Copyright © 2016 Kyle Neideck\n//\n\n// Unit Include\n#include \"BGM_Device.h\"\n\n// Local Includes\n#include \"BGM_TestUtils.h\"\n\n// BGMDriver Includes\n#include \"BGM_Types.h\"\n\n// PublicUtility Includes\n#include \"CAException.h\"\n\n// STL Includes\n#include <stdexcept>\n\n\n// Subclass BGM_Device to add some test-only functions.\nclass TestBGM_Device\n:\n    public BGM_Device\n{\n\npublic:\n    TestBGM_Device();\n    ~TestBGM_Device() = default;\n\n};\n\nTestBGM_Device::TestBGM_Device()\n:\n    BGM_Device(kObjectID_Device,\n               CFSTR(kDeviceName),\n               CFSTR(kBGMDeviceUID),\n               CFSTR(kBGMDeviceModelUID),\n               kObjectID_Stream_Input,\n               kObjectID_Stream_Output,\n               kObjectID_Volume_Output_Master,\n               kObjectID_Mute_Output_Master)\n{\n    Activate();\n}\n\n\n@interface BGM_DeviceTests : XCTestCase {\n    TestBGM_Device* testDevice;\n}\n\n@end\n\n\n@implementation BGM_DeviceTests\n\n- (void) setUp {\n    [super setUp];\n    testDevice = new TestBGM_Device();\n}\n\n- (void) tearDown {\n    delete testDevice;\n    [super tearDown];\n}\n\n- (void) testDoIOOperation_writeMix_readInput {\n    // The number of audio frames to send/receive in the IO operations.\n    const int kFrameSize = 512;\n\n    // Choose a sample time that will make the data wrap around to the start of the device's\n    // internal ring buffer.\n    AudioServerPlugInIOCycleInfo cycleInfo {};\n    cycleInfo.mOutputTime.mSampleTime = kLoopbackRingBufferFrameSize - 25.0;\n\n    // Generate the test input data.\n    Float32 inputBuffer[kFrameSize * 2];\n\n    for(int i = 0; i < kFrameSize * 2; i++)\n    {\n        inputBuffer[i] = static_cast<Float32>(i);\n    }\n\n    // Send a copy of the input buffer just in case DoIOOperation modifies the data for some reason.\n    Float32 inputBufferCopy[kFrameSize * 2];\n    memcpy(inputBufferCopy, inputBuffer, sizeof(inputBuffer));\n\n    // Send the input data to the device.\n    testDevice->DoIOOperation(/* inStreamObjectID = */ kObjectID_Stream_Output,\n                              /* inClientID = */ 0,\n                              /* inOperationID = */ kAudioServerPlugInIOOperationWriteMix,\n                              /* inIOBufferFrameSize = */ kFrameSize,\n                              /* inIOCycleInfo = */ cycleInfo,\n                              /* ioMainBuffer = */ inputBuffer,\n                              /* ioSecondaryBuffer = */ nullptr);\n\n    // Request data from the same point in time so we get the same data back.\n    cycleInfo.mInputTime.mSampleTime = kLoopbackRingBufferFrameSize - 25.0;\n\n    // Read the data back from the device.\n    Float32 outputBuffer[kFrameSize * 2];\n\n    testDevice->DoIOOperation(/* inStreamObjectID = */ kObjectID_Stream_Output,\n                              /* inClientID = */ 0,\n                              /* inOperationID = */ kAudioServerPlugInIOOperationReadInput,\n                              /* inIOBufferFrameSize = */ kFrameSize,\n                              /* inIOCycleInfo = */ cycleInfo,\n                              /* ioMainBuffer = */ outputBuffer,\n                              /* ioSecondaryBuffer = */ nullptr);\n\n    // Check the output matches the input.\n    for(int i = 0; i < kFrameSize * 2; i++)\n    {\n        XCTAssertEqual(inputBuffer[i], outputBuffer[i]);\n    }\n}\n\n- (void) testCustomPropertyMusicPlayerBundleID {\n    // Convenience wrappers\n    auto getBundleID = [&](UInt32 inDataSize = sizeof(CFStringRef)){\n        CFStringRef bundleID = nullptr;\n        UInt32 outDataSize;\n        \n        testDevice->GetPropertyData(/* inObjectID = */ kObjectID_Device,\n                                    /* inClientPID = */ 3,\n                                    /* inAddress = */ kBGMMusicPlayerBundleIDAddress,\n                                    /* inQualifierDataSize = */ 0,\n                                    /* inQualifierData = */ nullptr,\n                                    /* inDataSize = */ inDataSize,\n                                    /* outDataSize = */ outDataSize,\n                                    /* outData = */ reinterpret_cast<void* __nonnull>(&bundleID));\n        \n        // This isn't technically required, but we're unlikely to ever want to return any more/less data from GetPropertyData.\n        XCTAssertEqual(outDataSize, sizeof(CFStringRef));\n        \n        return (__bridge_transfer NSString*)bundleID;\n    };\n    \n    auto setBundleID = [&](const CFStringRef* __nullable bundleID, UInt32 dataSize = sizeof(CFStringRef)){\n        testDevice->SetPropertyData(/* inObjectID = */ kObjectID_Device,\n                                    /* inClientPID = */ 1234,\n                                    /* inAddress = */ kBGMMusicPlayerBundleIDAddress,\n                                    /* inQualifierDataSize = */ 0,\n                                    /* inQualifierData = */ nullptr,\n                                    /* inDataSize = */ dataSize,\n                                    /* inData = */ reinterpret_cast<const void* __nonnull>(bundleID));\n    };\n    \n    // Should be set to the empty string by default.\n    XCTAssertEqualObjects(getBundleID(), @\"\");\n    \n    // Should be able to set the property to an arbitrary string. (Purposefully not using CFSTR for this one just in case it\n    // makes a difference.)\n    CFStringRef newID = CFStringCreateWithCString(kCFAllocatorDefault, \"test.bundle.ID\", kCFStringEncodingUTF8);\n    setBundleID(&newID);\n    CFRelease(newID);\n    XCTAssertEqualObjects(getBundleID(), @\"test.bundle.ID\");\n    \n    // Should be able to set the property back to the empty string.\n    newID = CFSTR(\"\");\n    setBundleID(&newID);\n    XCTAssertEqualObjects(getBundleID(), @\"\");\n    \n    // Arguments should be null-checked.\n    BGMShouldThrow<std::runtime_error>(self, [&](){\n        UInt32 outDataSize;\n        testDevice->GetPropertyData(kObjectID_Device, 0, kBGMMusicPlayerBundleIDAddress, 0, nullptr,\n                                    sizeof(CFStringRef), outDataSize,\n                                    /* outData = */ reinterpret_cast<void* __nonnull>(NULL));\n    });\n    BGMShouldThrow<std::runtime_error>(self, [&](){\n        setBundleID(nullptr);\n    });\n    \n    // Invalid data should be rejected.\n    BGMShouldThrow<CAException>(self, [&](){\n        setBundleID((CFStringRef*)&kCFNull);\n    });\n    BGMShouldThrow<CAException>(self, [&](){\n        CFStringRef nullRef = nullptr;\n        setBundleID(&nullRef);\n    });\n    BGMShouldThrow<CAException>(self, [&](){\n        CFArrayRef array = (__bridge_retained CFArrayRef)@[ @1, @2 ];\n        setBundleID((CFStringRef*)&array);\n    });\n    \n    // Should throw if not given enough space for the return data.\n    BGMShouldThrow<CAException>(self, [&](){\n        getBundleID(/* inDataSize = */ 0);\n    });\n    \n    newID = CFSTR(\"bundle\");\n    \n    // Passing more data than needed should be fine as long as it starts with a CFStringRef.\n    setBundleID(&newID, sizeof(CFStringRef) * 2);\n    \n    // Should throw if not enough data is passed.\n    BGMShouldThrow<CAException>(self, [&](){\n        setBundleID(&newID, sizeof(CFStringRef) - 1);\n    });\n}\n\n// TODO: Performance tests?\n- (void) testPerformanceExample {\n    // This is an example of a performance test case.\n    [self measureBlock:^{\n        // Put the code you want to measure the time of here.\n    }];\n}\n\n@end\n\n"
  },
  {
    "path": "BGMDriver/BGMDriverTests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAAtomic.h",
    "content": "/*\n     File: CAAtomic.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n/*\n\tThis file implements all Atomic operations using Interlocked functions specified in\n\tWinbase.h\nNOTE: According to Microsoft documentation, all Interlocked functions generates a\nfull barrier. \n\tOn Windows:\n\tAs the Interlocked functions returns the Old value, Extra checks and operations \n\tare made after the atomic operation to return value consistent with OSX counterparts.\n*/\n#ifndef __CAAtomic_h__\n#define __CAAtomic_h__\n\n#if TARGET_OS_WIN32\n\t#include <windows.h>\n\t#include <intrin.h>\n\t#pragma intrinsic(_InterlockedOr)\n\t#pragma intrinsic(_InterlockedAnd)\n#else\n\t#include <CoreFoundation/CFBase.h>\n\t#include <libkern/OSAtomic.h>\n#endif\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\ninline void CAMemoryBarrier() \n{\n#if TARGET_OS_WIN32\n\tMemoryBarrier();\n#else\n\tOSMemoryBarrier();\n#endif\n}\n\ninline SInt32 CAAtomicAdd32Barrier(SInt32 theAmt, volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\tlong lRetVal = InterlockedExchangeAdd((volatile long*)theValue, theAmt);\n\t// InterlockedExchangeAdd returns the original value which differs from OSX version. \n\t// At this point the addition would have occured and hence returning the new value\n\t// to keep it sync with OSX.\n\treturn lRetVal + theAmt;\n#else\n\treturn OSAtomicAdd32Barrier(theAmt, (volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicOr32Barrier(UInt32 theMask, volatile UInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\t// InterlockedAnd macro is not defined in x86 platform, and hence using the intrinsic\n\t// function instead.\n\tlong j = _InterlockedOr((volatile long*)theValue, theMask);\n\t// _InterlockedOr returns the original value which differs from OSX version.\n\t// Returning the new value similar to OSX\n\treturn (SInt32)(j | theMask);\n#else\n\treturn OSAtomicOr32Barrier(theMask, (volatile uint32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicAnd32Barrier(UInt32 theMask, volatile UInt32* theValue)\n{\n#if TARGET_OS_WIN32\n// InterlockedAnd macro is not defined in x86 platform, and hence using the intrinsic\n// function instead.\n\tlong j = _InterlockedAnd((volatile long*)theValue, theMask);\n\t// _InterlockedAnd returns the original value which differs from OSX version.\n\t// Returning the new value similar to OSX\n\treturn (SInt32)(j & theMask);\n#else\n\treturn OSAtomicAnd32Barrier(theMask, (volatile uint32_t *)theValue);\n#endif\n}\n\ninline bool CAAtomicCompareAndSwap32Barrier(SInt32 oldValue, SInt32 newValue, volatile SInt32 *theValue)\n{\n#if TARGET_OS_WIN32\n\t// InterlockedCompareExchange returns the old value. But we need to return bool value.\n\tlong lRetVal = InterlockedCompareExchange((volatile long*)theValue, newValue, oldValue);\n// Hence we check if the new value is set and if it is we return true else false.\n// If theValue is equal to oldValue then the swap happens. Otherwise swap doesn't happen.\n\treturn (oldValue == lRetVal);\n#else\n\treturn OSAtomicCompareAndSwap32Barrier(oldValue, newValue, (volatile int32_t *)theValue);\n#endif\n}\n\n\ninline SInt32 CAAtomicIncrement32(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn (SInt32)InterlockedIncrement((volatile long*)theValue);\n#else\n\treturn OSAtomicIncrement32((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicDecrement32(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn (SInt32)InterlockedDecrement((volatile long*)theValue);\n#else\n\treturn OSAtomicDecrement32((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicIncrement32Barrier(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn CAAtomicIncrement32(theValue);\n#else\n\treturn OSAtomicIncrement32Barrier((volatile int32_t *)theValue);\n#endif\n}\n\ninline SInt32 CAAtomicDecrement32Barrier(volatile SInt32* theValue)\n{\n#if TARGET_OS_WIN32\n\treturn CAAtomicDecrement32(theValue);\n#else\n\treturn OSAtomicDecrement32Barrier((volatile int32_t *)theValue);\n#endif\n}\n\ninline bool CAAtomicTestAndClearBarrier(int bitToClear, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = InterlockedBitTestAndReset((long*)theAddress, bitToClear);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndClearBarrier(bitToClear, (volatile void *)theAddress);\n#endif\n}\n\ninline bool CAAtomicTestAndClear(int bitToClear, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = CAAtomicTestAndClearBarrier(bitToClear, (long*)theAddress);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndClear(bitToClear, (volatile void *)theAddress);\n#endif\n}\n\ninline bool CAAtomicTestAndSetBarrier(int bitToSet, void* theAddress)\n{\n#if TARGET_OS_WIN32\n\tBOOL bOldVal = InterlockedBitTestAndSet((long*)theAddress, bitToSet);\n\treturn (bOldVal ? true : false);\n#else\n\treturn OSAtomicTestAndSetBarrier(bitToSet, (volatile void *)theAddress);\n#endif\n}\n#pragma clang diagnostic pop\n\n// int32_t flavors -- for C++ only since we can't overload in C\n// CFBase.h defines SInt32 as signed int which is similar to int32_t. If CFBase.h is included, then\n// this will generate redefinition error. But on Mac, CFBase.h, still includes MacTypes.h where\n// SInt32 is defined as signed long so this would work there.\n// So in order to fix the redefinition errors, we define these functions only if MacTypes.h is included.\n#if defined(__cplusplus) && defined(__MACTYPES__) && !__LP64__\ninline int32_t CAAtomicAdd32Barrier(int32_t theAmt, volatile int32_t* theValue)\n{\n\treturn CAAtomicAdd32Barrier(theAmt, (volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicOr32Barrier(uint32_t theMask, volatile uint32_t* theValue)\n{\n\treturn CAAtomicOr32Barrier(theMask, (volatile UInt32 *)theValue);\n}\n\ninline int32_t CAAtomicAnd32Barrier(uint32_t theMask, volatile uint32_t* theValue)\n{\n\treturn CAAtomicAnd32Barrier(theMask, (volatile UInt32 *)theValue);\n}\n\ninline bool CAAtomicCompareAndSwap32Barrier(int32_t oldValue, int32_t newValue, volatile int32_t *theValue)\n{\n\treturn CAAtomicCompareAndSwap32Barrier(oldValue, newValue, (volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicIncrement32(volatile int32_t* theValue)\n{\n\treturn CAAtomicIncrement32((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicDecrement32(volatile int32_t* theValue)\n{\n\treturn CAAtomicDecrement32((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicIncrement32Barrier(volatile int32_t* theValue)\n{\n\treturn CAAtomicIncrement32Barrier((volatile SInt32 *)theValue);\n}\n\ninline int32_t CAAtomicDecrement32Barrier(volatile int32_t* theValue)\n{\n\treturn CAAtomicDecrement32Barrier((volatile SInt32 *)theValue);\n}\n#endif // __cplusplus && !__LP64__\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\n#if __LP64__\ninline bool CAAtomicCompareAndSwap64Barrier( int64_t __oldValue, int64_t __newValue, volatile int64_t *__theValue )\n{\n\treturn OSAtomicCompareAndSwap64Barrier(__oldValue, __newValue, __theValue);\n}\n#endif\n\ninline bool CAAtomicCompareAndSwapPtrBarrier(void *__oldValue, void *__newValue, volatile void ** __theValue)\n{\n#if __LP64__\n\treturn CAAtomicCompareAndSwap64Barrier((int64_t)__oldValue, (int64_t)__newValue, (int64_t *)__theValue);\n#else\n\treturn CAAtomicCompareAndSwap32Barrier((int32_t)__oldValue, (int32_t)__newValue, (int32_t *)__theValue);\n#endif\n}\n#pragma clang diagnostic pop\n\n/* Spinlocks.  These use memory barriers as required to synchronize access to shared\n * memory protected by the lock.  The lock operation spins, but employs various strategies\n * to back off if the lock is held, making it immune to most priority-inversion livelocks.\n * The try operation immediately returns false if the lock was held, true if it took the\n * lock.  The convention is that unlocked is zero, locked is nonzero.\n */\n#define\tCA_SPINLOCK_INIT    0\n\ntypedef int32_t CASpinLock;\n\nbool    CASpinLockTry( volatile CASpinLock *__lock );\nvoid    CASpinLockLock( volatile CASpinLock *__lock );\nvoid    CASpinLockUnlock( volatile CASpinLock *__lock );\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\ninline void    CASpinLockLock( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\tOSSpinLockLock(__lock);\n#else\n\twhile (CAAtomicTestAndSetBarrier(0, (void*)__lock))\n\t\tusleep(1000); // ???\n#endif\n}\n\ninline void    CASpinLockUnlock( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\tOSSpinLockUnlock(__lock);\n#else\n\tCAAtomicTestAndClearBarrier(0, (void*)__lock);\n#endif\n}\n\ninline bool    CASpinLockTry( volatile CASpinLock *__lock )\n{\n#if TARGET_OS_MAC\n\treturn OSSpinLockTry(__lock);\n#else\n\treturn (CAAtomicTestAndSetBarrier(0, (void*)__lock) == 0);\n#endif\n}\n\n#pragma clang diagnostic pop\n\n\n#endif // __CAAtomic_h__\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAAtomicStack.h",
    "content": "/*\n     File: CAAtomicStack.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#ifndef __CAAtomicStack_h__\n#define __CAAtomicStack_h__\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <libkern/OSAtomic.h>\n#else\n\t#include <CAAtomic.h>\n#endif\n\n#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_4\n\t#include <CoreServices/CoreServices.h>\n#endif\n\n//  linked list LIFO or FIFO (pop_all_reversed) stack, elements are pushed and popped atomically\n//  class T must implement T *& next().\ntemplate <class T>\nclass TAtomicStack {\npublic:\n\tTAtomicStack() : mHead(NULL) { }\n\t\n\t// non-atomic routines, for use when initializing/deinitializing, operate NON-atomically\n\tvoid\tpush_NA(T *item)\n\t{\n\t\titem->next() = mHead;\n\t\tmHead = item;\n\t}\n\t\n\tT *\t\tpop_NA()\n\t{\n\t\tT *result = mHead;\n\t\tif (result)\n\t\t\tmHead = result->next();\n\t\treturn result;\n\t}\n\t\n\tbool\tempty() const { return mHead == NULL; }\n\t\n\tT *\t\thead() { return mHead; }\n\t\n\t// atomic routines\n\tvoid\tpush_atomic(T *item)\n\t{\n\t\tT *head_;\n\t\tdo {\n\t\t\thead_ = mHead;\n\t\t\titem->next() = head_;\n\t\t} while (!compare_and_swap(head_, item, &mHead));\n\t}\n\t\n\tvoid\tpush_multiple_atomic(T *item)\n\t\t// pushes entire linked list headed by item\n\t{\n\t\tT *head_, *p = item, *tail;\n\t\t// find the last one -- when done, it will be linked to head\n\t\tdo {\n\t\t\ttail = p;\n\t\t\tp = p->next();\n\t\t} while (p);\n\t\tdo {\n\t\t\thead_ = mHead;\n\t\t\ttail->next() = head_;\n\t\t} while (!compare_and_swap(head_, item, &mHead));\n\t}\n\t\n\tT *\t\tpop_atomic_single_reader()\n\t\t// this may only be used when only one thread may potentially pop from the stack.\n\t\t// if multiple threads may pop, this suffers from the ABA problem.\n\t\t// <rdar://problem/4606346> TAtomicStack suffers from the ABA problem\n\t{\n\t\tT *result;\n\t\tdo {\n\t\t\tif ((result = mHead) == NULL)\n\t\t\t\tbreak;\n\t\t} while (!compare_and_swap(result, result->next(), &mHead));\n\t\treturn result;\n\t}\n\t\n\tT *\t\tpop_atomic()\n\t\t// This is inefficient for large linked lists.\n\t\t// prefer pop_all() to a series of calls to pop_atomic.\n\t\t// push_multiple_atomic has to traverse the entire list.\n\t{\n\t\tT *result = pop_all();\n\t\tif (result) {\n\t\t\tT *next = result->next();\n\t\t\tif (next)\n\t\t\t\t// push all the remaining items back onto the stack\n\t\t\t\tpush_multiple_atomic(next);\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tT *\t\tpop_all()\n\t{\n\t\tT *result;\n\t\tdo {\n\t\t\tif ((result = mHead) == NULL)\n\t\t\t\tbreak;\n\t\t} while (!compare_and_swap(result, NULL, &mHead));\n\t\treturn result;\n\t}\n\t\n\tT*\t\tpop_all_reversed()\n\t{\n\t\tTAtomicStack<T> reversed;\n\t\tT *p = pop_all(), *next;\n\t\twhile (p != NULL) {\n\t\t\tnext = p->next();\n\t\t\treversed.push_NA(p);\n\t\t\tp = next;\n\t\t}\n\t\treturn reversed.mHead;\n\t}\n\t\n\tstatic bool\tcompare_and_swap(T *oldvalue, T *newvalue, T **pvalue)\n\t{\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n#if TARGET_OS_MAC\n\t#if __LP64__\n\t\t\treturn ::OSAtomicCompareAndSwap64Barrier(int64_t(oldvalue), int64_t(newvalue), (int64_t *)pvalue);\n\t#elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4\n\t\t\treturn ::OSAtomicCompareAndSwap32Barrier(int32_t(oldvalue), int32_t(newvalue), (int32_t *)pvalue);\n\t#else\n\t\t\treturn ::CompareAndSwap(UInt32(oldvalue), UInt32(newvalue), (UInt32 *)pvalue);\n\t#endif\n#else\n\t\t\t//return ::CompareAndSwap(UInt32(oldvalue), UInt32(newvalue), (UInt32 *)pvalue);\n\t\t\treturn CAAtomicCompareAndSwap32Barrier(SInt32(oldvalue), SInt32(newvalue), (SInt32*)pvalue);\n#endif\n#pragma clang diagnostic pop\n\t}\n\t\nprotected:\n\tT *\t\tmHead;\n};\n\n#if ((MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) && !TARGET_OS_WIN32)\n#include <libkern/OSAtomic.h>\n\nclass CAAtomicStack {\npublic:\n\tCAAtomicStack(size_t nextPtrOffset) : mNextPtrOffset(nextPtrOffset) {\n\t\t/*OSQueueHead h = OS_ATOMIC_QUEUE_INIT; mHead = h;*/\n\t\tmHead.opaque1 = 0; mHead.opaque2 = 0;\n\t}\n\t// a subset of the above\n\tvoid\tpush_atomic(void *p) { OSAtomicEnqueue(&mHead, p, mNextPtrOffset); }\n\tvoid\tpush_NA(void *p) { push_atomic(p); }\n\n\tvoid *\tpop_atomic() { return OSAtomicDequeue(&mHead, mNextPtrOffset); }\n\tvoid *\tpop_atomic_single_reader() { return pop_atomic(); }\n\tvoid *\tpop_NA() { return pop_atomic(); }\n\t\nprivate:\n\tOSQueueHead\t\tmHead;\n\tsize_t\t\t\tmNextPtrOffset;\n};\n\n// a more efficient subset of TAtomicStack using OSQueue.\ntemplate <class T>\nclass TAtomicStack2 {\npublic:\n\tTAtomicStack2() {\n\t\t/*OSQueueHead h = OS_ATOMIC_QUEUE_INIT; mHead = h;*/\n\t\tmHead.opaque1 = 0; mHead.opaque2 = 0;\n\t\tmNextPtrOffset = -1;\n\t}\n\tvoid\tpush_atomic(T *item) {\n\t\tif (mNextPtrOffset < 0) {\n\t\t\tT **pnext = &item->next();\t// hack around offsetof not working with C++\n\t\t\tmNextPtrOffset = (Byte *)pnext - (Byte *)item;\n\t\t}\n\t\tOSAtomicEnqueue(&mHead, item, mNextPtrOffset);\n\t}\n\tvoid\tpush_NA(T *item) { push_atomic(item); }\n\n\tT *\t\tpop_atomic() { return (T *)OSAtomicDequeue(&mHead, mNextPtrOffset); }\n\tT *\t\tpop_atomic_single_reader() { return pop_atomic(); }\n\tT *\t\tpop_NA() { return pop_atomic(); }\n\t\n\t// caution: do not try to implement pop_all_reversed here. the writer could add new elements\n\t// while the reader is trying to pop old ones!\n\t\nprivate:\n\tOSQueueHead\t\tmHead;\n\tssize_t\t\t\tmNextPtrOffset;\n};\n\n#else\n\n#define TAtomicStack2 TAtomicStack\n\n#endif // MAC_OS_X_VERSION_MAX_ALLOWED && !TARGET_OS_WIN32\n\n#endif // __CAAtomicStack_h__\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAAutoDisposer.h",
    "content": "/*\n     File: CAAutoDisposer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPtr_h__)\n#define __CAPtr_h__\n\n#include <stdlib.h>\t\t// for malloc\n#include <new>\t\t\t// for bad_alloc\n#include <string.h>\t\t// for memset\n\ninline void* CA_malloc(size_t size)\n{\n\tvoid* p = malloc(size);\n\tif (!p && size) throw std::bad_alloc();\n\treturn p;\n}\n\ninline void* CA_realloc(void* old, size_t size)\n{\n#if TARGET_OS_WIN32\n\tvoid* p = realloc(old, size);\n#else\n\tvoid* p = reallocf(old, size); // reallocf ensures the old pointer is freed if memory is full (p is NULL).\n#endif\n\tif (!p && size) throw std::bad_alloc();\n\treturn p;\n}\n\n#ifndef UINTPTR_MAX\n#if __LP64__\n#define UINTPTR_MAX\t  18446744073709551615ULL\n#else\n#define UINTPTR_MAX\t  4294967295U\n#endif\n#endif\n\ninline void* CA_calloc(size_t n, size_t size)\n{\t\n\t// ensure that multiplication will not overflow\n\tif (n && UINTPTR_MAX / n < size) throw std::bad_alloc();\n\t\n\tsize_t nsize = n*size;\n\tvoid* p = malloc(nsize);\n\tif (!p && nsize) throw std::bad_alloc();\n\n\tmemset(p, 0, nsize);\n\treturn p;\n}\n\n\n// helper class for automatic conversions\ntemplate <typename T>\nstruct CAPtrRef\n{\n\tT* ptr_;\n\n\texplicit CAPtrRef(T* ptr) : ptr_(ptr) {}\n};\n\ntemplate <typename T>\nclass CAAutoFree\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\t\n\tCAAutoFree() : ptr_(0) {}\n\t\n\texplicit CAAutoFree(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoFree(CAAutoFree<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoFree(CAAutoFree<T>& that) : ptr_(that.release()) {} // take ownership\n\n\tCAAutoFree(size_t n, bool clear = false) \n\t\t// this becomes an ambiguous call if n == 0\n\t\t: ptr_(0) \n\t\t{\n\t\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\t\tif (n > maxItems) \n\t\t\t\tthrow std::bad_alloc();\n\n\t\t\tptr_ = static_cast<T*>(clear ? CA_calloc(n, sizeof(T)) : CA_malloc(n * sizeof(T)));\n\t\t}\n\t\n\t~CAAutoFree() { free(); }\n\t\n\tvoid alloc(size_t numItems, bool clear = false) \n\t{\n\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\tif (numItems > maxItems) throw std::bad_alloc();\n\t\t\n\t\tfree();\n\t\tptr_ = static_cast<T*>(clear ? CA_calloc(numItems, sizeof(T)) : CA_malloc(numItems * sizeof(T)));\n\t}\n\t\n\tvoid allocBytes(size_t numBytes, bool clear = false) \n\t{\n\t\tfree();\n\t\tptr_ = static_cast<T*>(clear ? CA_calloc(1, numBytes) : CA_malloc(numBytes));\n\t}\n\t\n\tvoid reallocBytes(size_t numBytes) \n\t{\n\t\tptr_ = static_cast<T*>(CA_realloc(ptr_, numBytes));\n\t}\n\n\tvoid reallocItems(size_t numItems) \n\t{\n\t\tsize_t maxItems = ~size_t(0) / sizeof(T);\n\t\tif (numItems > maxItems) throw std::bad_alloc();\n\t\t\n\t\tptr_ = static_cast<T*>(CA_realloc(ptr_, numItems * sizeof(T)));\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoFree& operator=(CAAutoFree<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoFree& operator=(CAAutoFree& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoFree& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoFree& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\n\tbool operator==(CAAutoFree const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoFree const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\t::free(ptr_);\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoFree(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoFree& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoFree<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\ntemplate <typename T>\nclass CAAutoDelete\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\tCAAutoDelete() : ptr_(0) {}\n\n\texplicit CAAutoDelete(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoDelete(CAAutoDelete<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoDelete(CAAutoDelete<T>& that) : ptr_(that.release()) {} // take ownership\n\t\n\t~CAAutoDelete() { free(); }\n\t\n\ttemplate <typename U>\n\tCAAutoDelete& operator=(CAAutoDelete<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoDelete& operator=(CAAutoDelete& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoDelete& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoDelete& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\t\n\tbool operator==(CAAutoDelete const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoDelete const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\tdelete ptr_;\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoDelete(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoDelete& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoFree<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\ntemplate <typename T>\nclass CAAutoArrayDelete\n{\nprivate:\n\tT* ptr_;\n\npublic:\n\tCAAutoArrayDelete() : ptr_(0) {}\n\n\texplicit CAAutoArrayDelete(T* ptr) : ptr_(ptr) {}\n\t\n\ttemplate<typename U>\n\tCAAutoArrayDelete(CAAutoArrayDelete<U>& that) : ptr_(that.release()) {} // take ownership\n\n\t// C++ std says: a template constructor is never a copy constructor\n\tCAAutoArrayDelete(CAAutoArrayDelete<T>& that) : ptr_(that.release()) {} // take ownership\n\t\n\t// this becomes an ambiguous call if n == 0\n\tCAAutoArrayDelete(size_t n) : ptr_(new T[n]) {}\n\t\n\t~CAAutoArrayDelete() { free(); }\n\t\n\tvoid alloc(size_t numItems) \n\t{\n\t\tfree();\n\t\tptr_ = new T [numItems];\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoArrayDelete& operator=(CAAutoArrayDelete<U>& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoArrayDelete& operator=(CAAutoArrayDelete& that) \n\t{ \n\t\tset(that.release());\t// take ownership\n\t\treturn *this;\n\t}\n\t\n\tCAAutoArrayDelete& operator=(T* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\n\ttemplate <typename U>\n\tCAAutoArrayDelete& operator=(U* ptr) \n\t{\n\t\tset(ptr); \n\t\treturn *this;\n\t}\n\t\t\n\tT& operator*() const { return *ptr_; }\n\tT* operator->() const { return ptr_; }\n\t\n\tT* operator()() const { return ptr_; }\n\tT* get() const { return ptr_; }\n\toperator T*() const { return ptr_; }\n\n\tbool operator==(CAAutoArrayDelete const& that) const { return ptr_ == that.ptr_; }\n\tbool operator!=(CAAutoArrayDelete const& that) const { return ptr_ != that.ptr_; }\n\tbool operator==(T* ptr) const { return ptr_ == ptr; }\n\tbool operator!=(T* ptr) const { return ptr_ != ptr; }\n\t\t\n\tT* release() \n\t{\n\t\t// release ownership\n\t\tT* result = ptr_;\n\t\tptr_ = 0;\n\t\treturn result;\n\t}\n\t\n\tvoid set(T* ptr)\n\t{\n\t\tif (ptr != ptr_)\n\t\t{\n\t\t\tdelete [] ptr_;\n\t\t\tptr_ = ptr;\n\t\t}\n\t}\n\t\n\tvoid free() \n\t{\n\t\tset(0);\n\t}\n\n\n\t// automatic conversions to allow assignment from results of functions.\n\t// hard to explain. see auto_ptr implementation and/or Josuttis' STL book.\n\tCAAutoArrayDelete(CAPtrRef<T> ref) : ptr_(ref.ptr_) { }\n\n\tCAAutoArrayDelete& operator=(CAPtrRef<T> ref)\n\t{\n\t\tset(ref.ptr_);\n\t\treturn *this;\n\t}\n\n\ttemplate<typename U>\n\toperator CAPtrRef<U>()\n\t\t{ return CAPtrRef<U>(release()); }\n\n\ttemplate<typename U>\n\toperator CAAutoArrayDelete<U>()\n\t\t{ return CAAutoFree<U>(release()); }\n\t\n};\n\n\n\n\n\n// convenience function\ntemplate <typename T>\nvoid free(CAAutoFree<T>& p)\n{\n\tp.free();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n#if 0\n// example program showing ownership transfer\n\nCAAutoFree<char> source()\n{\n\t// source allocates and returns ownership to the caller.\n\tconst char* str = \"this is a test\";\n\tsize_t size = strlen(str) + 1;\n\tCAAutoFree<char> captr(size, false);\n\tstrlcpy(captr(), str, size);\n\tprintf(\"source %08X %08X '%s'\\n\", &captr, captr(), captr());\n\treturn captr;\n}\n\nvoid user(CAAutoFree<char> const& captr)\n{\n\t// passed by const reference. user can access the pointer but does not take ownership.\n\tprintf(\"user: %08X %08X '%s'\\n\", &captr, captr(), captr());\n}\n\nvoid sink(CAAutoFree<char> captr)\n{\n\t// passed by value. sink takes ownership and frees the pointer on return.\n\tprintf(\"sink: %08X %08X '%s'\\n\", &captr, captr(), captr());\n}\n\n\nint main (int argc, char * const argv[]) \n{\n\n\tCAAutoFree<char> captr(source());\n\tprintf(\"main captr A %08X %08X\\n\", &captr, captr());\n\tuser(captr);\n\tsink(captr);\n\tprintf(\"main captr B %08X %08X\\n\", &captr, captr());\n    return 0;\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CABitOperations.h",
    "content": "/*\n     File: CABitOperations.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#ifndef _CABitOperations_h_\n#define _CABitOperations_h_\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n    //#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>\n\t#include <CoreFoundation/CFBase.h>\n#else\n//\t#include <MacTypes.h>\n\t#include \"CFBase.h\"\n#endif\n#include <TargetConditionals.h>\n\n// return whether a number is a power of two\ninline UInt32 IsPowerOfTwo(UInt32 x) \n{ \n\treturn (x & (x-1)) == 0;\n}\n\n// count the leading zeros in a word\n// Metrowerks Codewarrior. powerpc native count leading zeros instruction:\n// I think it's safe to remove this ...\n//#define CountLeadingZeroes(x)  ((int)__cntlzw((unsigned int)x))\n\ninline UInt32 CountLeadingZeroes(UInt32 arg)\n{\n// GNUC / LLVM have a builtin\n#if defined(__GNUC__) || defined(__llvm___)\n#if (TARGET_CPU_X86 || TARGET_CPU_X86_64)\n\tif (arg == 0) return 32;\n#endif\t// TARGET_CPU_X86 || TARGET_CPU_X86_64\n\treturn __builtin_clz(arg);\n#elif TARGET_OS_WIN32\n\tUInt32 tmp;\n\t__asm{\n\t\tbsr eax, arg\n\t\tmov ecx, 63\n\t\tcmovz eax, ecx\n\t\txor eax, 31\n\t\tmov tmp, eax\t// this moves the result in tmp to return.\n    }\n\treturn tmp;\n#else\n#error \"Unsupported architecture\"\n#endif\t// defined(__GNUC__)\n}\n// Alias (with different spelling)\n#define CountLeadingZeros CountLeadingZeroes\n\ninline UInt32 CountLeadingZeroesLong(UInt64 arg)\n{\n// GNUC / LLVM have a builtin\n#if defined(__GNUC__) || defined(__llvm___)\n#if (TARGET_CPU_X86 || TARGET_CPU_X86_64)\n\tif (arg == 0) return 64;\n#endif\t// TARGET_CPU_X86 || TARGET_CPU_X86_64\n\treturn __builtin_clzll(arg);\n#elif TARGET_OS_WIN32\n\tUInt32 x = CountLeadingZeroes((UInt32)(arg >> 32));\n\tif(x < 32)\n\t\treturn x;\n\telse\n\t\treturn 32+CountLeadingZeroes((UInt32)arg);\n#else\n#error \"Unsupported architecture\"\n#endif\t// defined(__GNUC__)\n}\n#define CountLeadingZerosLong CountLeadingZeroesLong\n\n// count trailing zeroes\ninline UInt32 CountTrailingZeroes(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(~x & (x-1));\n}\n\n// count leading ones\ninline UInt32 CountLeadingOnes(UInt32 x)\n{\n\treturn CountLeadingZeroes(~x);\n}\n\n// count trailing ones\ninline UInt32 CountTrailingOnes(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x & (~x-1));\n}\n\n// number of bits required to represent x.\ninline UInt32 NumBits(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x);\n}\n\n// base 2 log of next power of two greater or equal to x\ninline UInt32 Log2Ceil(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x - 1);\n}\n\n// base 2 log of next power of two less or equal to x\ninline UInt32 Log2Floor(UInt32 x)\n{\n\treturn 32 - CountLeadingZeroes(x) - 1;\n}\n\n// next power of two greater or equal to x\ninline UInt32 NextPowerOfTwo(UInt32 x)\n{\n\treturn 1 << Log2Ceil(x);\n}\n\n// counting the one bits in a word\ninline UInt32 CountOnes(UInt32 x)\n{\n\t// secret magic algorithm for counting bits in a word.\n    x = x - ((x >> 1) & 0x55555555);\n    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\n    return (((x + (x >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;\n}\n\n// counting the zero bits in a word\ninline UInt32 CountZeroes(UInt32 x)\n{\n\treturn CountOnes(~x);\n}\n\n// return the bit position (0..31) of the least significant bit\ninline UInt32 LSBitPos(UInt32 x)\n{\n\treturn CountTrailingZeroes(x & -(SInt32)x);\n}\n\n// isolate the least significant bit\ninline UInt32 LSBit(UInt32 x)\n{\n\treturn x & -(SInt32)x;\n}\n\n// return the bit position (0..31) of the most significant bit\ninline UInt32 MSBitPos(UInt32 x)\n{\n\treturn 31 - CountLeadingZeroes(x);\n}\n\n// isolate the most significant bit\ninline UInt32 MSBit(UInt32 x)\n{\n\treturn 1 << MSBitPos(x);\n}\n\n// Division optimized for power of 2 denominators\ninline UInt32 DivInt(UInt32 numerator, UInt32 denominator)\n{\n\tif(IsPowerOfTwo(denominator))\n\t\treturn numerator >> (31 - CountLeadingZeroes(denominator));\n\telse\n\t\treturn numerator/denominator;\n}\n\n#endif\n\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFArray.cpp",
    "content": "/*\n     File: CACFArray.cpp\n Abstract: CACFArray.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CACFArray.h\"\n\n//\tPublicUtility Includes\n#include \"CACFDictionary.h\"\n#include \"CACFNumber.h\"\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFArray\n//=============================================================================\n\nbool\tCACFArray::HasItem(const void* inItem) const\n{\n\tbool theAnswer = false;\n\tif(mCFArray != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFArrayGetCount(mCFArray)};\n\t\ttheAnswer = CFArrayContainsValue(mCFArray, theRange, inItem);\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetIndexOfItem(const void* inItem, UInt32& outIndex) const\n{\n\tbool theAnswer = false;\n\toutIndex = 0;\n\tif(mCFArray != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFArrayGetCount(mCFArray)};\n\t\tCFIndex theIndex = CFArrayGetFirstIndexOfValue(mCFArray, theRange, inItem);\n\t\tif(theIndex != -1)\n\t\t{\n\t\t\ttheAnswer = true;\n\t\t\toutIndex = ToUInt32(theIndex);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetBool(UInt32 inIndex, bool& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inIndex, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFBooleanGetTypeID()))\n\t\t{\n\t\t\toutValue = CFBooleanGetValue(static_cast<CFBooleanRef>(theValue));\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theNumericValue = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theNumericValue);\n\t\t\toutValue = theNumericValue != 0;\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetSInt32(UInt32 inIndex, SInt32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUInt32(UInt32 inIndex, UInt32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetSInt64(UInt32 inIndex, SInt64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUInt64(UInt32 inIndex, UInt64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberSInt64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetFloat32(UInt32 inIndex, Float32& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberFloat32Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetFloat64(UInt32 inIndex, Float64& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theItem), kCFNumberFloat64Type, &outItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::Get4CC(UInt32 inIndex, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inIndex, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\tCFStringRef theString = static_cast<CFStringRef>(theValue);\n\t\t\tif(CFStringGetLength(theString) == 4)\n\t\t\t{\n\t\t\t\tchar theCString[5];\n\t\t\t\tCFStringGetCString(theString, theCString, 5, kCFStringEncodingASCII);\n\t\t\t\toutValue = CFSwapInt32BigToHost(*reinterpret_cast<UInt32*>(theCString));\n\t\t\t}\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetString(UInt32 inIndex, CFStringRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFStringRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetArray(UInt32 inIndex, CFArrayRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFArrayRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetDictionary(UInt32 inIndex, CFDictionaryRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDictionaryRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetData(UInt32 inIndex, CFDataRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDataGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDataRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetUUID(UInt32 inIndex, CFUUIDRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFUUIDGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFUUIDRef>(theItem);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::GetCFType(UInt32 inIndex, CFTypeRef& outItem) const\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && (inIndex < GetNumberItems()))\n\t{\n\t\toutItem = CFArrayGetValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex));\n\t\ttheAnswer = outItem != NULL;\n\t}\n\t\n\treturn theAnswer;\n}\n\nvoid\tCACFArray::GetCACFString(UInt32 inIndex, CACFString& outItem) const\n{\n\toutItem = static_cast<CFStringRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFStringRef>(theItem);\n\t\t}\n\t}\n}\n\nvoid\tCACFArray::GetCACFArray(UInt32 inIndex, CACFArray& outItem) const\n{\n\toutItem = static_cast<CFArrayRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFArrayRef>(theItem);\n\t\t}\n\t}\n}\n\nvoid\tCACFArray::GetCACFDictionary(UInt32 inIndex, CACFDictionary& outItem) const\n{\n\toutItem = static_cast<CFDictionaryRef>(NULL);\n\tCFTypeRef theItem = NULL;\n\tif(GetCFType(inIndex, theItem))\n\t{\n\t\tif((theItem != NULL) && (CFGetTypeID(theItem) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutItem = static_cast<CFDictionaryRef>(theItem);\n\t\t}\n\t}\n}\n\nbool\tCACFArray::AppendBool(bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendSInt32(SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendUInt32(UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendSInt64(SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendUInt64(UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendFloat32(Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendFloat64(Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = AppendCFType(theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::AppendString(const CFStringRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendArray(const CFArrayRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendDictionary(const CFDictionaryRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendData(const CFDataRef inItem)\n{\n\treturn AppendCFType(inItem);\n}\n\nbool\tCACFArray::AppendCFType(const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCFArrayAppendValue(mCFArray, inItem);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertBool(UInt32 inIndex, bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertSInt32(UInt32 inIndex, SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertUInt32(UInt32 inIndex, UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertSInt64(UInt32 inIndex, SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertUInt64(UInt32 inIndex, UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertFloat32(UInt32 inIndex, Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertFloat64(UInt32 inIndex, Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = InsertCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::InsertString(UInt32 inIndex, const CFStringRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertArray(UInt32 inIndex, const CFArrayRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertDictionary(UInt32 inIndex, const CFDictionaryRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertData(UInt32 inIndex, const CFDataRef inItem)\n{\n\treturn InsertCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::InsertCFType(UInt32 inIndex, const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable)\n\t{\n\t\tif(inIndex < GetNumberItems())\n\t\t{\n\t\t\tCFArrayInsertValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex), inItem);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCFArrayAppendValue(mCFArray, inItem);\n\t\t}\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetBool(UInt32 inIndex, bool inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFBoolean theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFBoolean());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetSInt32(UInt32 inIndex, SInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetUInt32(UInt32 inIndex, UInt32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetSInt64(UInt32 inIndex, SInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetUInt64(UInt32 inIndex, UInt64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetFloat32(UInt32 inIndex, Float32 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetFloat64(UInt32 inIndex, Float64 inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCACFNumber theItem(inItem);\n\t\tif(theItem.IsValid())\n\t\t{\n\t\t\ttheAnswer = SetCFType(inIndex, theItem.GetCFNumber());\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFArray::SetString(UInt32 inIndex, const CFStringRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetArray(UInt32 inIndex, const CFArrayRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetDictionary(UInt32 inIndex, const CFDictionaryRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetData(UInt32 inIndex, const CFDataRef inItem)\n{\n\treturn SetCFType(inIndex, inItem);\n}\n\nbool\tCACFArray::SetCFType(UInt32 inIndex, const CFTypeRef inItem)\n{\n\tbool theAnswer = false;\n\t\n\tif((mCFArray != NULL) && mMutable && (inIndex <= GetNumberItems()))\n\t{\n\t\tCFArraySetValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex), inItem);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFArray.h",
    "content": "/*\n     File: CACFArray.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFArray_h__)\n#define __CACFArray_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CoreFoundation.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CoreFoundation.h>\n#endif\n\n#include \"CADebugMacros.h\"\n\n//=============================================================================\n//\tTypes\n//=============================================================================\n\nclass\tCACFDictionary;\nclass\tCACFString;\n\n//=============================================================================\n//\tCACFArray\n//=============================================================================\n\nclass CACFArray\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFArray()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)), mRelease(true), mMutable(true) {}\n\texplicit\t\t\tCACFArray(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(UInt32 inMaxNumberItems, bool inRelease)\t\t\t\t\t\t\t: mCFArray(CFArrayCreateMutable(NULL, static_cast<CFIndex>(inMaxNumberItems), &kCFTypeArrayCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(CFArrayRef inCFArray, bool inRelease)\t\t\t\t\t\t\t\t: mCFArray(const_cast<CFMutableArrayRef>(inCFArray)), mRelease(inRelease), mMutable(false) {}\n\t\t\t\t\t\tCACFArray(CFMutableArrayRef inCFArray, bool inRelease)\t\t\t\t\t\t: mCFArray(inCFArray), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\tCACFArray(const CACFArray& inArray)\t\t\t\t\t\t\t\t\t\t\t: mCFArray(inArray.mCFArray), mRelease(inArray.mRelease), mMutable(inArray.mMutable) { Retain(); }\n\tCACFArray&\t\t\toperator=(const CACFArray& inArray)\t\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = inArray.mCFArray; mRelease = inArray.mRelease; mMutable = inArray.mMutable; Retain(); return *this; }\n\tCACFArray&\t\t\toperator=(CFArrayRef inCFArray)\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = const_cast<CFMutableArrayRef>(inCFArray); mMutable = false; Retain(); return *this; }\n\tCACFArray&\t\t\toperator=(CFMutableArrayRef inCFArray)\t\t\t\t\t\t\t\t\t\t{ Release(); mCFArray = inCFArray; mMutable = true; Retain(); return *this; }\n\t\t\t\t\t\t~CACFArray()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); }\n\nprivate:\n\tvoid\t\t\t\tRetain()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFArray != NULL)) { CFRetain(mCFArray); } }\n\tvoid\t\t\t\tRelease()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFArray != NULL)) { CFRelease(mCFArray); } }\n\t\t\n//\tAttributes\npublic:\n\tbool\t\t\t\tIsValid() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray != NULL; }\n\tbool\t\t\t\tIsMutable() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable; }\n\tbool\t\t\t\tCanModify() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable && (mCFArray != NULL); }\n\t\n\tbool\t\t\t\tWillRelease() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mRelease; }\n\tvoid\t\t\t\tShouldRelease(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t\t{ mRelease = inRelease; }\n\t\n\tCFTypeID\t\t\tGetTypeID() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return CFGetTypeID(mCFArray); }\n\t\n\tCFArrayRef\t\t\tGetCFArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\tCFArrayRef\t\t\tCopyCFArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFArray != NULL) { CFRetain(mCFArray); } return mCFArray; }\n\t\n\tCFMutableArrayRef\tGetCFMutableArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\tCFMutableArrayRef\tCopyCFMutableArray() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFArray != NULL) { CFRetain(mCFArray); } return mCFArray; }\n\tCFPropertyListRef   AsPropertyList() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFArray; }\n\n\tvoid\t\t\t\tSetCFMutableArrayFromCopy(CFArrayRef inArray, bool inRelease = true)\t\t{ Release(); mCFArray = CFArrayCreateMutableCopy(NULL, 0, inArray); mMutable = true; mRelease = inRelease; }\n\n//\tItem Operations\npublic:\n\tUInt32\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ UInt32 theAnswer = 0; if(mCFArray != NULL) { theAnswer = ToUInt32(CFArrayGetCount(mCFArray)); } return theAnswer; }\n\tbool\t\t\t\tHasItem(const void* inItem) const;\n\tvoid\t\t\t\tRemoveItem(const void* inItem)\t\t\t\t\t\t\t\t\t\t\t\t{ UInt32 theIndex; if(CanModify() && GetIndexOfItem(inItem, theIndex)) { RemoveItemAtIndex(theIndex); } }\n\tbool\t\t\t\tGetIndexOfItem(const void* inItem, UInt32& outIndex) const;\n\tvoid\t\t\t\tRemoveItemAtIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFArrayRemoveValueAtIndex(mCFArray, static_cast<CFIndex>(inIndex)); } }\n\tvoid\t\t\t\tClear()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFArrayRemoveAllValues(mCFArray); } }\n\tvoid\t\t\t\tSort(CFComparatorFunction inCompareFunction)\t\t\t\t\t\t\t\t{ if(CanModify()) { CFRange theRange = { 0, CFArrayGetCount(mCFArray) }; CFArraySortValues(mCFArray, theRange, inCompareFunction, NULL); } }\n\tvoid\t\t\t\tSortNumbers()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Sort((CFComparatorFunction)CFNumberCompare); }\n\tvoid\t\t\t\tSortStrings()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Sort((CFComparatorFunction)CFStringCompare); }\n\t\n\tbool\t\t\t\tGetBool(UInt32 inIndex, bool& outValue) const;\n\tbool\t\t\t\tGetSInt32(UInt32 inIndex, SInt32& outItem) const;\n\tbool\t\t\t\tGetUInt32(UInt32 inIndex, UInt32& outItem) const;\n\tbool\t\t\t\tGetSInt64(UInt32 inIndex, SInt64& outItem) const;\n\tbool\t\t\t\tGetUInt64(UInt32 inIndex, UInt64& outItem) const;\n\tbool\t\t\t\tGetFloat32(UInt32 inIndex, Float32& outItem) const;\n\tbool\t\t\t\tGetFloat64(UInt32 inIndex, Float64& outItem) const;\n\tbool\t\t\t\tGet4CC(UInt32 inIndex, UInt32& outValue) const;\n\tbool\t\t\t\tGetString(UInt32 inIndex, CFStringRef& outItem) const;\n\tbool\t\t\t\tGetArray(UInt32 inIndex, CFArrayRef& outItem) const;\n\tbool\t\t\t\tGetDictionary(UInt32 inIndex, CFDictionaryRef& outItem) const;\n\tbool\t\t\t\tGetData(UInt32 inIndex, CFDataRef& outItem) const;\n\tbool\t\t\t\tGetUUID(UInt32 inIndex, CFUUIDRef& outItem) const;\n\tbool\t\t\t\tGetCFType(UInt32 inIndex, CFTypeRef& outItem) const;\n\t\n\tvoid\t\t\t\tGetCACFString(UInt32 inIndex, CACFString& outItem) const;\n\tvoid\t\t\t\tGetCACFArray(UInt32 inIndex, CACFArray& outItem) const;\n\tvoid\t\t\t\tGetCACFDictionary(UInt32 inIndex, CACFDictionary& outItem) const;\n\t\n\tbool\t\t\t\tAppendBool(bool inItem);\n\tbool\t\t\t\tAppendSInt32(SInt32 inItem);\n\tbool\t\t\t\tAppendUInt32(UInt32 inItem);\n\tbool\t\t\t\tAppendSInt64(SInt64 inItem);\n\tbool\t\t\t\tAppendUInt64(UInt64 inItem);\n\tbool\t\t\t\tAppendFloat32(Float32 inItem);\n\tbool\t\t\t\tAppendFloat64(Float64 inItem);\n\tbool\t\t\t\tAppendString(const CFStringRef inItem);\n\tbool\t\t\t\tAppendArray(const CFArrayRef inItem);\n\tbool\t\t\t\tAppendDictionary(const CFDictionaryRef inItem);\n\tbool\t\t\t\tAppendData(const CFDataRef inItem);\n\tbool\t\t\t\tAppendCFType(const CFTypeRef inItem);\n\t\n\tbool\t\t\t\tInsertBool(UInt32 inIndex, bool inItem);\n\tbool\t\t\t\tInsertSInt32(UInt32 inIndex, SInt32 inItem);\n\tbool\t\t\t\tInsertUInt32(UInt32 inIndex, UInt32 inItem);\n\tbool\t\t\t\tInsertSInt64(UInt32 inIndex, SInt64 inItem);\n\tbool\t\t\t\tInsertUInt64(UInt32 inIndex, UInt64 inItem);\n\tbool\t\t\t\tInsertFloat32(UInt32 inIndex, Float32 inItem);\n\tbool\t\t\t\tInsertFloat64(UInt32 inIndex, Float64 inItem);\n\tbool\t\t\t\tInsertString(UInt32 inIndex, const CFStringRef inItem);\n\tbool\t\t\t\tInsertArray(UInt32 inIndex, const CFArrayRef inItem);\n\tbool\t\t\t\tInsertDictionary(UInt32 inIndex, const CFDictionaryRef inItem);\n\tbool\t\t\t\tInsertData(UInt32 inIndex, const CFDataRef inItem);\n\tbool\t\t\t\tInsertCFType(UInt32 inIndex, const CFTypeRef inItem);\n\t\n\tbool\t\t\t\tSetBool(UInt32 inIndex, bool inItem);\n\tbool\t\t\t\tSetSInt32(UInt32 inIndex, SInt32 inItem);\n\tbool\t\t\t\tSetUInt32(UInt32 inIndex, UInt32 inItem);\n\tbool\t\t\t\tSetSInt64(UInt32 inIndex, SInt64 inItem);\n\tbool\t\t\t\tSetUInt64(UInt32 inIndex, UInt64 inItem);\n\tbool\t\t\t\tSetFloat32(UInt32 inIndex, Float32 inItem);\n\tbool\t\t\t\tSetFloat64(UInt32 inIndex, Float64 inItem);\n\tbool\t\t\t\tSetString(UInt32 inIndex, const CFStringRef inItem);\n\tbool\t\t\t\tSetArray(UInt32 inIndex, const CFArrayRef inItem);\n\tbool\t\t\t\tSetDictionary(UInt32 inIndex, const CFDictionaryRef inItem);\n\tbool\t\t\t\tSetData(UInt32 inIndex, const CFDataRef inItem);\n\tbool\t\t\t\tSetCFType(UInt32 inIndex, const CFTypeRef inItem);\n\n//\tImplementation\nprivate:\n\tCFMutableArrayRef\tmCFArray;\n\tbool\t\t\t\tmRelease;\n\tbool\t\t\t\tmMutable;\n\t\n\t\t\t\t\t\tCACFArray(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFDictionary.cpp",
    "content": "/*\n     File: CACFDictionary.cpp\n Abstract: CACFDictionary.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CACFDictionary.h\"\n\n//\tPublicUtility Includes\n#include \"CACFArray.h\"\n#include \"CACFNumber.h\"\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFDictionary\n//=============================================================================\n\nbool\tCACFDictionary::HasKey(const CFStringRef inKey) const\n{\n\treturn CFDictionaryContainsKey(mCFDictionary, inKey) != 0;\n}\n\nUInt32\tCACFDictionary::Size () const\n{\n\treturn mCFDictionary ? ToUInt32(CFDictionaryGetCount(mCFDictionary)) : 0;\n}\n\nvoid\tCACFDictionary::GetKeys (const void **keys) const\n{\n\tCFDictionaryGetKeysAndValues(mCFDictionary, keys, NULL);\n}\n\nvoid\tCACFDictionary::GetKeysAndValues (const void **keys, const void **values) const\n{\n\tCFDictionaryGetKeysAndValues(mCFDictionary, keys, values);\n}\n\nbool\tCACFDictionary::GetBool(const CFStringRef inKey, bool& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFBooleanGetTypeID()))\n\t\t{\n\t\t\toutValue = CFBooleanGetValue(static_cast<CFBooleanRef>(theValue));\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theNumericValue = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theNumericValue);\n\t\t\toutValue = theNumericValue != 0;\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetSInt32(const CFStringRef inKey, SInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt32(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetSInt64(const CFStringRef inKey, SInt64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt64(const CFStringRef inKey, UInt64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat32FromString(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<Float32>(CFStringGetDoubleValue(static_cast<CFStringRef>(theValue)));\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetUInt32FromString(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = CFStringGetIntValue(static_cast<CFStringRef>(theValue));\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat32(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFloat64(const CFStringRef inKey, Float64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat64Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFixed32(const CFStringRef inKey, Float32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt32 theFixed32 = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theFixed32);\n\t\t\t\n\t\t\t//\tthis is a 16.16 value so convert it to a float\n\t\t\tFloat32 theSign = theFixed32 < 0 ? -1.0f : 1.0f;\n\t\t\ttheFixed32 *= (SInt32)theSign;\n\t\t\tFloat32 theWholePart = (theFixed32 & 0x7FFF0000) >> 16;\n\t\t\tFloat32 theFractPart = theFixed32 & 0x0000FFFF;\n\t\t\ttheFractPart /= 65536.0f;\n\t\t\toutValue = theSign * (theWholePart + theFractPart);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetFixed64(const CFStringRef inKey, Float64& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tSInt64 theFixed64 = 0;\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &theFixed64);\n\t\t\toutValue = static_cast<Float64>(theFixed64 >> 32);\n\t\t\toutValue += static_cast<Float64>(theFixed64 & 0x00000000FFFFFFFFLL) / static_cast<Float64>(0x0000000100000000LL);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::Get4CC(const CFStringRef inKey, UInt32& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFNumberGetTypeID()))\n\t\t{\n\t\t\tCFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t\telse if((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\tCFStringRef theString = static_cast<CFStringRef>(theValue);\n\t\t\tif(CFStringGetLength(theString) == 4)\n\t\t\t{\n\t\t\t\tchar theCString[5];\n\t\t\t\tCFStringGetCString(theString, theCString, 5, kCFStringEncodingASCII);\n\t\t\t\toutValue = CFSwapInt32BigToHost(*reinterpret_cast<UInt32*>(theCString));\n\t\t\t}\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetString(const CFStringRef inKey, CFStringRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFStringRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\t\nbool\tCACFDictionary::GetArray(const CFStringRef inKey, CFArrayRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFArrayRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\t\nbool\tCACFDictionary::GetDictionary(const CFStringRef inKey, CFDictionaryRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDictionaryRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetData(const CFStringRef inKey, CFDataRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDataGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDataRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetCFType(const CFStringRef inKey, CFTypeRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tif(mCFDictionary != NULL)\n\t{\n\t\toutValue = CFDictionaryGetValue(mCFDictionary, inKey);\n\t\ttheAnswer = (outValue != NULL);\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetURL(const CFStringRef inKey, CFURLRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFURLGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFURLRef>(theValue);\n\t\t\ttheAnswer = true;\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::GetCFTypeWithCStringKey(const char* inKey, CFTypeRef& outValue) const\n{\n\tbool theAnswer = false;\n\t\n\tif(mCFDictionary != NULL)\n\t{\n\t\tCACFString theKey(inKey);\n\t\tif(theKey.IsValid())\n\t\t{\n\t\t\ttheAnswer = GetCFType(theKey.GetCFString(), outValue);\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nvoid\tCACFDictionary::GetCACFString(const CFStringRef inKey, CACFString& outValue) const\n{\n\toutValue = static_cast<CFStringRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFStringGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFStringRef>(theValue);\n\t\t}\n\t}\n}\n\t\nvoid\tCACFDictionary::GetCACFArray(const CFStringRef inKey, CACFArray& outValue) const\n{\n\toutValue = static_cast<CFArrayRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFArrayGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFArrayRef>(theValue);\n\t\t}\n\t}\n}\n\t\nvoid\tCACFDictionary::GetCACFDictionary(const CFStringRef inKey, CACFDictionary& outValue) const\n{\n\toutValue = static_cast<CFDictionaryRef>(NULL);\n\tCFTypeRef theValue = NULL;\n\tif(GetCFType(inKey, theValue))\n\t{\n\t\tif((theValue != NULL) && (CFGetTypeID(theValue) == CFDictionaryGetTypeID()))\n\t\t{\n\t\t\toutValue = static_cast<CFDictionaryRef>(theValue);\n\t\t}\n\t}\n}\n\nbool\tCACFDictionary::AddBool(const CFStringRef inKey, bool inValue)\n{\n\tCACFBoolean theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFBoolean());\n}\n\nbool\tCACFDictionary::AddSInt32(const CFStringRef inKey, SInt32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddUInt32(const CFStringRef inKey, UInt32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddSInt64(const CFStringRef inKey, SInt64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddUInt64(const CFStringRef inKey, UInt64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddFloat32(const CFStringRef inKey, Float32 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddFloat64(const CFStringRef inKey, Float64 inValue)\n{\n\tCACFNumber theValue(inValue);\n\treturn AddCFType(inKey, theValue.GetCFNumber());\n}\n\nbool\tCACFDictionary::AddNumber(const CFStringRef inKey, const CFNumberRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddString(const CFStringRef inKey, const CFStringRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddArray(const CFStringRef inKey, const CFArrayRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddDictionary(const CFStringRef inKey, const CFDictionaryRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddData(const CFStringRef inKey, const CFDataRef inValue)\n{\n\treturn AddCFType(inKey, inValue);\n}\n\nbool\tCACFDictionary::AddURL(const CFStringRef inKey, const CFURLRef inValue)\n{\n\treturn AddCFType (inKey, inValue);\n}\n\nbool\tCACFDictionary::AddCFTypeWithCStringKey(const char* inKey, const CFTypeRef inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif (inKey)\n\t{\n\t\tCACFString theKey(inKey);\n\t\tif(theKey.IsValid())\n\t\t{\n\t\t\ttheAnswer = AddCFType(theKey.GetCFString(), inValue);\n\t\t}\n\t}\n\t\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::AddCString(const CFStringRef inKey, const char* inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif (inValue)\n\t{\n\t\tCACFString theValue(inValue);\n\t\tif(theValue.IsValid())\n\t\t{\n\t\t\ttheAnswer = AddCFType(inKey, theValue.GetCFString());\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\nbool\tCACFDictionary::AddCFType(const CFStringRef inKey, const CFTypeRef inValue)\n{\n\tbool theAnswer = false;\n\t\n\tif(mMutable && (mCFDictionary != NULL) && inValue)\n\t{\n\t\tCFDictionarySetValue(mCFDictionary, inKey, inValue);\n\t\ttheAnswer = true;\n\t}\n\t\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFDictionary.h",
    "content": "/*\n     File: CACFDictionary.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFDictionary_h__)\n#define __CACFDictionary_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreFoundation/CoreFoundation.h>\n#else\n\t#include <CoreFoundation.h>\n#endif\n\n//=============================================================================\n//\tTypes\n//=============================================================================\n\nclass\tCACFArray;\nclass\tCACFString;\n\n//=============================================================================\n//\tCACFDictionary\n//=============================================================================\n\nclass CACFDictionary \n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\tCACFDictionary()\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mCFDictionary(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), mRelease(true), mMutable(true) {}\n\texplicit\t\t\t\tCACFDictionary(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t: mCFDictionary(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\t\tCACFDictionary(CFDictionaryRef inCFDictionary, bool inRelease)\t\t\t: mCFDictionary(const_cast<CFMutableDictionaryRef>(inCFDictionary)), mRelease(inRelease), mMutable(false) {}\n\t\t\t\t\t\t\tCACFDictionary(CFMutableDictionaryRef inCFDictionary, bool inRelease)\t: mCFDictionary(inCFDictionary), mRelease(inRelease), mMutable(true) {}\n\t\t\t\t\t\t\tCACFDictionary(const CACFDictionary& inDictionary)\t\t\t\t\t\t: mCFDictionary(inDictionary.mCFDictionary), mRelease(inDictionary.mRelease), mMutable(inDictionary.mMutable) { Retain(); }\n\tCACFDictionary&\t\t\toperator=(const CACFDictionary& inDictionary)\t\t\t\t\t\t\t{ Release(); mCFDictionary = inDictionary.mCFDictionary; mRelease = inDictionary.mRelease; mMutable = inDictionary.mMutable; Retain(); return *this; } \n\tCACFDictionary&\t\t\toperator=(CFDictionaryRef inDictionary)\t\t\t\t\t\t\t\t\t{ Release(); mCFDictionary = const_cast<CFMutableDictionaryRef>(inDictionary); mMutable = false; Retain(); return *this; } \n\tCACFDictionary&\t\t\toperator=(CFMutableDictionaryRef inDictionary)\t\t\t\t\t\t\t{ Release(); mCFDictionary = inDictionary; mMutable = true; Retain(); return *this; } \n\t\t\t\t\t\t\t~CACFDictionary()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ Release(); }\n\nprivate:\n\tvoid\t\t\t\t\tRetain()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFDictionary != NULL)) { CFRetain(mCFDictionary); } }\n\tvoid\t\t\t\t\tRelease()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(mRelease && (mCFDictionary != NULL)) { CFRelease(mCFDictionary); } }\n\t\t\n//\tAttributes\npublic:\n\tbool\t\t\t\t\tIsValid() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary != NULL; }\n\tbool\t\t\t\t\tIsMutable() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable;}\n\tbool\t\t\t\t\tCanModify() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mMutable && (mCFDictionary != NULL); }\n\t\n\tbool\t\t\t\t\tWillRelease() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mRelease; }\n\tvoid\t\t\t\t\tShouldRelease(bool inRelease)\t\t\t\t\t\t\t\t\t\t\t{ mRelease = inRelease; }\n\t\n\tCFDictionaryRef\t\t\tGetDict() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFDictionaryRef\t\t\tGetCFDictionary() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFDictionaryRef\t\t\tCopyCFDictionary() const\t\t\t\t\t\t\t\t\t\t\t\t{ if(mCFDictionary != NULL) { CFRetain(mCFDictionary); } return mCFDictionary; }\n\n\tCFMutableDictionaryRef\tGetMutableDict()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFMutableDictionaryRef\tGetCFMutableDictionary() const\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tCFMutableDictionaryRef\tCopyCFMutableDictionary() const\t\t\t\t\t\t\t\t\t\t\t{ if(mCFDictionary != NULL) { CFRetain(mCFDictionary); } return mCFDictionary; }\n\tvoid\t\t\t\t\tSetCFMutableDictionaryFromCopy(CFDictionaryRef inDictionary, bool inRelease = true)\t\t{ Release(); mCFDictionary = CFDictionaryCreateMutableCopy(NULL, 0, inDictionary); mMutable = true; mRelease = inRelease; }\n\tvoid\t\t\t\t\tSetCFMutableDictionaryToEmpty(bool inRelease = true)\t\t\t\t\t{ Release(); mCFDictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); mMutable = true; mRelease = inRelease; }\n\n\tCFPropertyListRef\t\tAsPropertyList() const\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mCFDictionary; }\n\tOSStatus\t\t\t\tGetDictIfMutable(CFMutableDictionaryRef& outDict) const\t\t\t\t\t{ OSStatus theAnswer = -1; if(mMutable) { outDict = mCFDictionary; theAnswer = 0; } return theAnswer; }\n\n//\tItem Operations\npublic:\n\tbool\t\t\t\t\tHasKey(const CFStringRef inKey) const;\n\tUInt32\t\t\t\t\tSize() const;\n\tvoid\t\t\t\t\tGetKeys(const void** keys) const;\n\tvoid\t\t\t\t\tGetKeysAndValues (const void **keys, const void **values) const;\n\t\n\tbool\t\t\t\t\tGetBool(const CFStringRef inKey, bool& outValue) const;\n\tbool\t\t\t\t\tGetSInt32(const CFStringRef inKey, SInt32& outValue) const;\n\tbool\t\t\t\t\tGetUInt32(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetUInt32FromString(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetSInt64(const CFStringRef inKey, SInt64& outValue) const;\n\tbool\t\t\t\t\tGetUInt64(const CFStringRef inKey, UInt64& outValue) const;\n\tbool\t\t\t\t\tGetFloat32(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFloat32FromString(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFloat64(const CFStringRef inKey, Float64& outValue) const;\n\tbool\t\t\t\t\tGetFixed32(const CFStringRef inKey, Float32& outValue) const;\n\tbool\t\t\t\t\tGetFixed64(const CFStringRef inKey, Float64& outValue) const;\n\tbool\t\t\t\t\tGet4CC(const CFStringRef inKey, UInt32& outValue) const;\n\tbool\t\t\t\t\tGetString(const CFStringRef inKey, CFStringRef& outValue) const;\t\n\tbool\t\t\t\t\tGetArray(const CFStringRef inKey, CFArrayRef& outValue) const;\t\n\tbool\t\t\t\t\tGetDictionary(const CFStringRef inKey, CFDictionaryRef& outValue) const;\t\n\tbool\t\t\t\t\tGetData(const CFStringRef inKey, CFDataRef& outValue) const;\n\tbool\t\t\t\t\tGetCFType(const CFStringRef inKey, CFTypeRef& outValue) const;\n\tbool\t\t\t\t\tGetURL(const CFStringRef inKey, CFURLRef& outValue) const;\n\tbool\t\t\t\t\tGetCFTypeWithCStringKey(const char* inKey, CFTypeRef& outValue) const;\n\n\tvoid\t\t\t\t\tGetCACFString(const CFStringRef inKey, CACFString& outItem) const;\n\tvoid\t\t\t\t\tGetCACFArray(const CFStringRef inKey, CACFArray& outItem) const;\n\tvoid\t\t\t\t\tGetCACFDictionary(const CFStringRef inKey, CACFDictionary& outItem) const;\n\t\n\tbool\t\t\t\t\tAddBool(const CFStringRef inKey, bool inValue);\n\tbool\t\t\t\t\tAddSInt32(const CFStringRef inKey, SInt32 inValue);\n\tbool\t\t\t\t\tAddUInt32(const CFStringRef inKey, UInt32 inValue);\n\tbool\t\t\t\t\tAddSInt64(const CFStringRef inKey, SInt64 inValue);\n\tbool\t\t\t\t\tAddUInt64(const CFStringRef inKey, UInt64 inValue);\n\tbool\t\t\t\t\tAddFloat32(const CFStringRef inKey, Float32 inValue);\n\tbool\t\t\t\t\tAddFloat64(const CFStringRef inKey, Float64 inValue);\n\tbool\t\t\t\t\tAddNumber(const CFStringRef inKey, const CFNumberRef inValue);\n\tbool\t\t\t\t\tAddString(const CFStringRef inKey, const CFStringRef inValue);\n\tbool\t\t\t\t\tAddArray(const CFStringRef inKey, const CFArrayRef inValue);\n\tbool\t\t\t\t\tAddDictionary(const CFStringRef inKey, const CFDictionaryRef inValue);\n\tbool\t\t\t\t\tAddData(const CFStringRef inKey, const CFDataRef inValue);\n\tbool\t\t\t\t\tAddCFType(const CFStringRef inKey, const CFTypeRef inValue);\n\tbool\t\t\t\t\tAddURL(const CFStringRef inKey, const CFURLRef inValue);\n\t\n\tbool\t\t\t\t\tAddCFTypeWithCStringKey(const char* inKey, const CFTypeRef inValue);\n\tbool\t\t\t\t\tAddCString(const CFStringRef inKey, const char* inValue);\n\n\tvoid\t\t\t\t\tRemoveKey(const CFStringRef inKey)\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFDictionaryRemoveValue(mCFDictionary, inKey); } }\n\tvoid\t\t\t\t\tClear()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(CanModify()) { CFDictionaryRemoveAllValues(mCFDictionary); } }\n\t\n\tvoid\t\t\t\t\tShow()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ CFShow(mCFDictionary); }\n\t\n//\tImplementation\nprivate:\n\tCFMutableDictionaryRef \tmCFDictionary;\n\tbool\t\t\t\t\tmRelease;\n\tbool\t\t\t\t\tmMutable;\n\t\n\t\t\t\t\t\t\tCACFDictionary(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif //__CACFDictionary_h__\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFNumber.cpp",
    "content": "/*\n     File: CACFNumber.cpp\n Abstract: CACFNumber.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CACFNumber.h\"\n\n//=============================================================================\n//\tCACFNumber\n//=============================================================================\n\nFloat32\tCACFNumber::GetFixed32() const\n{\n\tSInt32 theFixedValue = GetSInt32();\n\t\n\t//\tthis is a 16.16 value so convert it to a float\n\tFloat32 theSign = theFixedValue < 0 ? -1.0f : 1.0f;\n\ttheFixedValue *= (SInt32)theSign;\n\tFloat32 theWholePart = (theFixedValue & 0x7FFF0000) >> 16;\n\tFloat32 theFractPart = theFixedValue & 0x0000FFFF;\n\ttheFractPart /= 65536.0f;\n\t\n\treturn theSign * (theWholePart + theFractPart);\n}\n\nFloat64\tCACFNumber::GetFixed64() const\n{\n\tSInt64 theFixedValue = GetSInt64();\n\t\n\t//\tthis is a 32.32 value so convert it to a double\n\tFloat64 theSign = theFixedValue < 0 ? -1.0 : 1.0;\n\ttheFixedValue *= (SInt64)theSign;\n\tFloat64 theWholePart = (theFixedValue & 0x7FFFFFFF00000000LL) >> 32;\n\tFloat64 theFractPart = theFixedValue & 0x00000000FFFFFFFFLL;\n\ttheFractPart /= 4294967296.0;\n\t\n\treturn theSign * (theWholePart + theFractPart);\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFNumber.h",
    "content": "/*\n     File: CACFNumber.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CACFNumber_h__)\n#define __CACFNumber_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CFNumber.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CFNumber.h>\n#endif\n\n//=============================================================================\n//\tCACFBoolean\n//=============================================================================\n\nclass\tCACFBoolean\n{\n//\tConstruction/Destruction\npublic:\n\texplicit\t\tCACFBoolean(CFBooleanRef inCFBoolean) : mCFBoolean(inCFBoolean), mWillRelease(true) {}\n\t\t\t\t\tCACFBoolean(CFBooleanRef inCFBoolean, bool inWillRelease) : mCFBoolean(inCFBoolean), mWillRelease(inWillRelease) {}\n\texplicit\t\tCACFBoolean(bool inValue) : mCFBoolean(inValue ? kCFBooleanTrue : kCFBooleanFalse), mWillRelease(true) { Retain(); }\n\t\t\t\t\t~CACFBoolean() { Release(); }\n\t\t\t\t\tCACFBoolean(const CACFBoolean& inBoolean) : mCFBoolean(inBoolean.mCFBoolean), mWillRelease(inBoolean.mWillRelease) { Retain(); }\n\tCACFBoolean&\toperator=(const CACFBoolean& inBoolean) { Release(); mCFBoolean = inBoolean.mCFBoolean; mWillRelease = inBoolean.mWillRelease; Retain(); return *this; }\n\tCACFBoolean&\toperator=(CFBooleanRef inCFBoolean) { Release(); mCFBoolean = inCFBoolean; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\t\tRetain() { if(mWillRelease && (mCFBoolean != NULL)) { CFRetain(mCFBoolean); } }\n\tvoid\t\t\tRelease() { if(mWillRelease && (mCFBoolean != NULL)) { CFRelease(mCFBoolean); } }\n\t\n\tCFBooleanRef\tmCFBoolean;\n\tbool\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\tIsValid() { return mCFBoolean != NULL; }\n\n//\tValue Access\npublic:\n\tCFBooleanRef\tGetCFBoolean() const { return mCFBoolean; }\n\tCFBooleanRef\tCopyCFBoolean() const { if(mCFBoolean != NULL) { CFRetain(mCFBoolean); } return mCFBoolean; }\n\n\tbool\t\t\tGetBoolean() const { bool theAnswer = false; if(mCFBoolean != NULL) { theAnswer = CFEqual(mCFBoolean, kCFBooleanTrue); } return theAnswer; }\n\t\n\t\t\t\t\tCACFBoolean(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n//=============================================================================\n//\tCACFNumber\n//=============================================================================\n\nclass\tCACFNumber\n{\n//\tConstruction/Destruction\npublic:\n\texplicit\tCACFNumber(CFNumberRef inCFNumber) : mCFNumber(inCFNumber), mWillRelease(true) {}\n\t\t\t\tCACFNumber(CFNumberRef inCFNumber, bool inWillRelease) : mCFNumber(inCFNumber), mWillRelease(inWillRelease) {}\n\t\t\t\tCACFNumber(SInt32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(UInt32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(SInt64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(UInt64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberSInt64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(Float32 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberFloat32Type, &inValue)), mWillRelease(true) {}\n\t\t\t\tCACFNumber(Float64 inValue) : mCFNumber(CFNumberCreate(NULL, kCFNumberFloat64Type, &inValue)), mWillRelease(true) {}\n\t\t\t\t~CACFNumber() { Release(); }\n\t\t\t\tCACFNumber(const CACFNumber& inNumber) : mCFNumber(inNumber.mCFNumber), mWillRelease(inNumber.mWillRelease) { Retain(); }\n\tCACFNumber&\toperator=(const CACFNumber& inNumber) { Release(); mCFNumber = inNumber.mCFNumber; mWillRelease = inNumber.mWillRelease; Retain(); return *this; }\n\tCACFNumber&\toperator=(CFNumberRef inCFNumber) { Release(); mCFNumber = inCFNumber; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\tRetain() { if(mWillRelease && (mCFNumber != NULL)) { CFRetain(mCFNumber); } }\n\tvoid\t\tRelease() { if(mWillRelease && (mCFNumber != NULL)) { CFRelease(mCFNumber); } }\n\t\n\tCFNumberRef\tmCFNumber;\n\tbool\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\tIsValid() const { return mCFNumber != NULL; }\n\n//\tValue Access\npublic:\n\tCFNumberRef\tGetCFNumber() const { return mCFNumber; }\n\tCFNumberRef\tCopyCFNumber() const { if(mCFNumber != NULL) { CFRetain(mCFNumber); } return mCFNumber; }\n\n\tSInt8\t\tGetSInt8() const { SInt8 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt8Type, &theAnswer); } return theAnswer; }\n\tSInt32\t\tGetSInt32() const { SInt32 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt32Type, &theAnswer); } return theAnswer; }\n\tUInt32\t\tGetUInt32() const { UInt32 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt32Type, &theAnswer); } return theAnswer; }\n\tFloat32\t\tGetFloat32() const { Float32 theAnswer = 0.0f; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberFloat32Type, &theAnswer); } return theAnswer; }\n\tFloat32\t\tGetFixed32() const;\n\tFloat64\t\tGetFixed64() const;\n\tSInt64\t\tGetSInt64() const { SInt64 theAnswer = 0; if(mCFNumber != NULL) { CFNumberGetValue(mCFNumber, kCFNumberSInt64Type, &theAnswer); } return theAnswer; }\n\t\n\t\t\t\tCACFNumber(const void*);\t// prevent accidental instantiation with a pointer via bool constructor\n};\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFString.cpp",
    "content": "/*\n     File: CACFString.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CACFString.h\"\n\n//=============================================================================\n//\tCACFString\n//=============================================================================\n\nUInt32\tCACFString::GetStringByteLength(CFStringRef inCFString, CFStringEncoding inEncoding)\n{\n\tCFIndex theAnswer = 0;\n\t\n\tif(inCFString != NULL)\n\t{\n\t\tCFRange theRange = { 0, CFStringGetLength(inCFString) };\n\t\tCFStringGetBytes(inCFString, theRange, inEncoding, 0, false, NULL, 0x7FFFFFFF, &theAnswer);\n\t}\n\t\n\treturn UInt32(theAnswer);\n}\n\nvoid\tCACFString::GetCString(CFStringRef inCFString, char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding)\n{\n\tif(ioStringSize > 0)\n\t{\n\t\tif(inCFString != NULL)\n\t\t{\n\t\t\tCFIndex theLength = 0;\n\t\t\tCFRange theRange = { 0, CFStringGetLength(inCFString) };\n\t\t\tCFStringGetBytes(inCFString, theRange, inEncoding, 0, false, (UInt8*)outString, static_cast<CFIndex>(ioStringSize - 1), &theLength);\n\t\t\toutString[theLength] = 0;\n\t\t\tioStringSize = ToUInt32(theLength) + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutString[0] = 0;\n\t\t\tioStringSize = 1;\n\t\t}\n\t}\n}\n\nvoid\tCACFString::GetUnicodeString(CFStringRef inCFString, UInt16* outString, UInt32& ioStringSize)\n{\n\tif(ioStringSize > 0)\n\t{\n\t\tif(inCFString != NULL)\n\t\t{\n\t\t\tCFRange theStringRange = { 0, CFStringGetLength(inCFString) };\n\t\t\tif(static_cast<UInt32>(theStringRange.length) > ioStringSize)\n\t\t\t{\n\t\t\t\ttheStringRange.length = static_cast<CFIndex>(ioStringSize);\n\t\t\t}\n\t\t\tCFStringGetCharacters(inCFString, theStringRange, outString);\n\t\t\tioStringSize = ToUInt32(theStringRange.length);\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutString[0] = 0;\n\t\t\tioStringSize = 0;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CACFString.h",
    "content": "/*\n     File: CACFString.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CACFString_h__)\n#define __CACFString_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CADebugMacros.h\"\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n\t#include <CoreFoundation/CFString.h>\n#else\n\t#include <CoreAudioTypes.h>\n\t#include <CFString.h>\n#endif\n\n//=============================================================================\n//\tCACFString\n//\n//\tNotes\n//\t-\tUsing the AssignWithoutRetain() method will fool the static analyzer into thinking that the\n//\t\tCFString being assigned will be leaked. This is because the static analyzer is not smart\n//\t\tenough to understand that the destructor will release the object.\n//=============================================================================\n\nclass\tCACFString\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFString() : mCFString(NULL), mWillRelease(true) {}\n\t\t\t\t\t\tCACFString(CFStringRef inCFString, bool inWillRelease = true) : mCFString(inCFString), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFString(const char* inCString, bool inWillRelease = true) : mCFString(CFStringCreateWithCString(NULL, inCString, kCFStringEncodingASCII)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFString(const char* inCString, CFStringEncoding inCStringEncoding, bool inWillRelease = true) : mCFString(CFStringCreateWithCString(NULL, inCString, inCStringEncoding)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\t~CACFString() { Release(); }\n\t\t\t\t\t\tCACFString(const CACFString& inString) : mCFString(inString.mCFString), mWillRelease(inString.mWillRelease) { Retain(); }\n\tCACFString&\t\t\toperator=(const CACFString& inString) { if (inString.mCFString != mCFString) { Release(); mCFString = inString.mCFString; mWillRelease = inString.mWillRelease; Retain(); } return *this; }\n\tCACFString&\t\t\toperator=(CFStringRef inCFString) { if (inCFString != mCFString) { Release(); mCFString = inCFString; } mWillRelease = true; Retain(); return *this; }\n\tvoid\t\t\t\tAssignWithoutRetain(CFStringRef inCFString) { if (inCFString != mCFString) { Release(); mCFString = inCFString; } mWillRelease = true; }\n\nprivate:\n\tvoid\t\t\t\tRetain() { if(mWillRelease && (mCFString != NULL)) { CFRetain(mCFString); } }\n\tvoid\t\t\t\tRelease() { if(mWillRelease && (mCFString != NULL)) { CFRelease(mCFString); } }\n\t\n\tCFStringRef\t\t\tmCFString;\n\tbool\t\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\t\tIsValid() const { return mCFString != NULL; }\n\tbool\t\t\t\tIsEqualTo(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringCompare(inString, mCFString, 0) == kCFCompareEqualTo; } return theAnswer; }\n\tbool\t\t\t\tStartsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringHasPrefix(mCFString, inString); } return theAnswer; }\n\tbool\t\t\t\tEndsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFString != NULL) { theAnswer = CFStringHasSuffix(mCFString, inString); } return theAnswer; }\n\n//\tValue Access\npublic:\n\tCFStringRef\t\t\tGetCFString() const { return mCFString; }\n\tCFStringRef\t\t\tCopyCFString() const { if(mCFString != NULL) { CFRetain(mCFString); } return mCFString; }\n\tconst CFStringRef*\tGetPointerToStorage() const\t{ return &mCFString; }\n\tCFStringRef&\t\tGetStorage() { Release(); mWillRelease = true; return mCFString; }\n\tUInt32\t\t\t\tGetLength() const { UInt32 theAnswer = 0; if(mCFString != NULL) { theAnswer = ToUInt32(CFStringGetLength(mCFString)); } return theAnswer; }\n\tUInt32\t\t\t\tGetByteLength(CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { UInt32 theAnswer = 0; if(mCFString != NULL) { theAnswer = GetStringByteLength(mCFString, inEncoding); } return theAnswer; }\n\tvoid\t\t\t\tGetCString(char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { GetCString(mCFString, outString, ioStringSize, inEncoding); }\n\tvoid\t\t\t\tGetUnicodeString(UInt16* outString, UInt32& ioStringSize) const { GetUnicodeString(mCFString, outString, ioStringSize); }\n\tSInt32\t\t\t\tGetAsInteger() { return GetAsInteger(mCFString); }\n\tFloat64\t\t\t\tGetAsFloat64() { return GetAsFloat64(mCFString); }\n\n\tstatic UInt32\t\tGetStringLength(CFStringRef inCFString)  { UInt32 theAnswer = 0; if(inCFString != NULL) { theAnswer = ToUInt32(CFStringGetLength(inCFString)); } return theAnswer; }\n\tstatic UInt32\t\tGetStringByteLength(CFStringRef inCFString, CFStringEncoding inEncoding = kCFStringEncodingUTF8);\n\tstatic void\t\t\tGetCString(CFStringRef inCFString, char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8);\n\tstatic void\t\t\tGetUnicodeString(CFStringRef inCFString, UInt16* outString, UInt32& ioStringSize);\n\tstatic SInt32\t\tGetAsInteger(CFStringRef inCFString) { SInt32 theAnswer = 0; if(inCFString != NULL){ theAnswer = CFStringGetIntValue(inCFString); } return theAnswer; }\n\tstatic Float64\t\tGetAsFloat64(CFStringRef inCFString) { Float64 theAnswer = 0; if(inCFString != NULL){ theAnswer = CFStringGetDoubleValue(inCFString); } return theAnswer; }\n\t\n};\n\ninline bool\toperator<(const CACFString& x, const CACFString& y) { return CFStringCompare(x.GetCFString(), y.GetCFString(), 0) == kCFCompareLessThan; }\ninline bool\toperator==(const CACFString& x, const CACFString& y) { return CFStringCompare(x.GetCFString(), y.GetCFString(), 0) == kCFCompareEqualTo; }\ninline bool\toperator!=(const CACFString& x, const CACFString& y) { return !(x == y); }\ninline bool\toperator<=(const CACFString& x, const CACFString& y) { return (x < y) || (x == y); }\ninline bool\toperator>=(const CACFString& x, const CACFString& y) { return !(x < y); }\ninline bool\toperator>(const CACFString& x, const CACFString& y) { return !((x < y) || (x == y)); }\n\n//=============================================================================\n//\tCACFMutableString\n//=============================================================================\n\nclass\tCACFMutableString\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCACFMutableString() : mCFMutableString(NULL), mWillRelease(true) {}\n\t\t\t\t\t\tCACFMutableString(CFMutableStringRef inCFMutableString, bool inWillRelease = true) : mCFMutableString(inCFMutableString), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFMutableString(CFStringRef inStringToCopy, bool /*inMakeCopy*/, bool inWillRelease = true) : mCFMutableString(CFStringCreateMutableCopy(NULL, 0, inStringToCopy)), mWillRelease(inWillRelease) {}\n\t\t\t\t\t\tCACFMutableString(const char* inCString, bool inWillRelease = true) : mCFMutableString(NULL), mWillRelease(inWillRelease) { CACFString theString(inCString); mCFMutableString = CFStringCreateMutableCopy(NULL, 0, theString.GetCFString()); }\n\t\t\t\t\t\tCACFMutableString(const char* inCString, CFStringEncoding inCStringEncoding, bool inWillRelease = true) : mCFMutableString(NULL), mWillRelease(inWillRelease) { CACFString theString(inCString, inCStringEncoding); mCFMutableString = CFStringCreateMutableCopy(NULL, 0, theString.GetCFString()); }\n\t\t\t\t\t\t~CACFMutableString() { Release(); }\n\t\t\t\t\t\tCACFMutableString(const CACFMutableString& inString) : mCFMutableString(inString.mCFMutableString), mWillRelease(inString.mWillRelease) { Retain(); }\n\tCACFMutableString&\toperator=(const CACFMutableString& inString) { Release(); mCFMutableString = inString.mCFMutableString; mWillRelease = inString.mWillRelease; Retain(); return *this; }\n\tCACFMutableString&\toperator=(CFMutableStringRef inCFMutableString) { Release(); mCFMutableString = inCFMutableString; mWillRelease = true; return *this; }\n\nprivate:\n\tvoid\t\t\t\tRetain() { if(mWillRelease && (mCFMutableString != NULL)) { CFRetain(mCFMutableString); } }\n\tvoid\t\t\t\tRelease() { if(mWillRelease && (mCFMutableString != NULL)) { CFRelease(mCFMutableString); } }\n\t\n\tCFMutableStringRef\tmCFMutableString;\n\tbool\t\t\t\tmWillRelease;\n\n//\tOperations\npublic:\n\tvoid\t\t\t\tAllowRelease() { mWillRelease = true; }\n\tvoid\t\t\t\tDontAllowRelease() { mWillRelease = false; }\n\tbool\t\t\t\tIsValid() { return mCFMutableString != NULL; }\n\tbool\t\t\t\tIsEqualTo(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringCompare(inString, mCFMutableString, 0) == kCFCompareEqualTo; } return theAnswer; }\n\tbool\t\t\t\tStartsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringHasPrefix(mCFMutableString, inString); } return theAnswer; }\n\tbool\t\t\t\tEndsWith(CFStringRef inString) const { bool theAnswer = false; if(mCFMutableString != NULL) { theAnswer = CFStringHasSuffix(mCFMutableString, inString); } return theAnswer; }\n\tvoid\t\t\t\tAppend(CFStringRef inString) { if(mCFMutableString != NULL) { CFStringAppend(mCFMutableString, inString); } }\n\n//\tValue Access\npublic:\n\tCFMutableStringRef\tGetCFMutableString() const { return mCFMutableString; }\n\tCFMutableStringRef\tCopyCFMutableString() const { if(mCFMutableString != NULL) { CFRetain(mCFMutableString); } return mCFMutableString; }\n\tUInt32\t\t\t\tGetLength() const { UInt32 theAnswer = 0; if(mCFMutableString != NULL) { theAnswer = ToUInt32(CFStringGetLength(mCFMutableString)); } return theAnswer; }\n\tUInt32\t\t\t\tGetByteLength(CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { UInt32 theAnswer = 0; if(mCFMutableString != NULL) { theAnswer = CACFString::GetStringByteLength(mCFMutableString, inEncoding); } return theAnswer; }\n\tvoid\t\t\t\tGetCString(char* outString, UInt32& ioStringSize, CFStringEncoding inEncoding = kCFStringEncodingUTF8) const { CACFString::GetCString(mCFMutableString, outString, ioStringSize, inEncoding); }\n\tvoid\t\t\t\tGetUnicodeString(UInt16* outString, UInt32& ioStringSize) const { CACFString::GetUnicodeString(mCFMutableString, outString, ioStringSize); }\n\tSInt32\t\t\t\tGetAsInteger() { return CACFString::GetAsInteger(mCFMutableString); }\n\tFloat64\t\t\t\tGetAsFloat64() { return CACFString::GetAsFloat64(mCFMutableString); }\n\n};\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugMacros.cpp",
    "content": "/*\n     File: CADebugMacros.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#include \"CADebugMacros.h\"\n#include <stdio.h>\n#include <stdarg.h>\n#if TARGET_API_MAC_OSX\n\t#include <syslog.h>\n#endif\n\n#if DEBUG\n#include <stdio.h>\n\nvoid\tDebugPrint(const char *fmt, ...)\n{\n\tva_list args;\n\tva_start(args, fmt);\n\tvprintf(fmt, args);\n\tva_end(args);\n}\n#endif // DEBUG\n\nvoid\tLogError(const char *fmt, ...)\n{\n\tva_list args;\n    va_start(args, fmt);\n    // BGM edit: vprintf leaves args in an undefined state, which can cause a crash in\n    //           vsyslog. Also added __ASSERT_STOP. Original code commented out below.\n//#if DEBUG\n//\tvprintf(fmt, args);\n//#endif\n//#if TARGET_API_MAC_OSX\n//\tvsyslog(LOG_ERR, fmt, args);\n//#endif\n#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog\n    printf(\"[ERROR] \");\n    vprintf(fmt, args);\n    printf(\"\\n\");\n#else\n    vsyslog(LOG_ERR, fmt, args);\n#endif\n#if DEBUG\n    __ASSERT_STOP;\n#endif\n    // BGM edit end\n\tva_end(args);\n}\n\nvoid\tLogWarning(const char *fmt, ...)\n{\n\tva_list args;\n\tva_start(args, fmt);\n    // BGM edit: vprintf leaves args in an undefined state, which can cause a crash in\n    //           vsyslog. Also added __ASSERT_STOP. Original code commented out below.\n//#if DEBUG\n//\tvprintf(fmt, args);\n//#endif\n//#if TARGET_API_MAC_OSX\n//\tvsyslog(LOG_WARNING, fmt, args);\n//#endif\n#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog\n    printf(\"[WARNING] \");\n    vprintf(fmt, args);\n    printf(\"\\n\");\n#else\n    vsyslog(LOG_WARNING, fmt, args);\n#endif\n#if DEBUG\n    //__ASSERT_STOP; // TODO: Add a toggle for this to the project file (under \"Preprocessor Macros\"). Default to off.\n#endif\n    // BGM edit end\n\tva_end(args);\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugMacros.h",
    "content": "/*\n     File: CADebugMacros.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CADebugMacros_h__)\n#define __CADebugMacros_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n//=============================================================================\n//\tCADebugMacros\n//=============================================================================\n\n//#define\tCoreAudio_StopOnFailure\t\t\t1\n//#define\tCoreAudio_TimeStampMessages\t\t1\n//#define\tCoreAudio_ThreadStampMessages\t1\n//#define\tCoreAudio_FlushDebugMessages\t1\n\n#if TARGET_RT_BIG_ENDIAN\n\t#define\tCA4CCToCString(the4CC)\t\t\t\t\t{ ((char*)&the4CC)[0], ((char*)&the4CC)[1], ((char*)&the4CC)[2], ((char*)&the4CC)[3], 0 }\n\t#define CACopy4CCToCString(theCString, the4CC)\t{ theCString[0] = ((char*)&the4CC)[0]; theCString[1] = ((char*)&the4CC)[1]; theCString[2] = ((char*)&the4CC)[2]; theCString[3] = ((char*)&the4CC)[3]; theCString[4] = 0; }\n#else\n\t#define\tCA4CCToCString(the4CC)\t\t\t\t\t{ ((char*)&the4CC)[3], ((char*)&the4CC)[2], ((char*)&the4CC)[1], ((char*)&the4CC)[0], 0 }\n\t#define CACopy4CCToCString(theCString, the4CC)\t{ theCString[0] = ((char*)&the4CC)[3]; theCString[1] = ((char*)&the4CC)[2]; theCString[2] = ((char*)&the4CC)[1]; theCString[3] = ((char*)&the4CC)[0]; theCString[4] = 0; }\n#endif\n\n//\tThis is a macro that does a sizeof and casts the result to a UInt32. This is useful for all the\n//\tplaces where -wshorten64-32 catches assigning a sizeof expression to a UInt32.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tSizeOf32(X)\t((UInt32)sizeof(X))\n\n//\tThis is a macro that does a offsetof and casts the result to a UInt32. This is useful for all the\n//\tplaces where -wshorten64-32 catches assigning an offsetof expression to a UInt32.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tOffsetOf32(X, Y)\t((UInt32)offsetof(X, Y))\n\n//\tThis macro casts the expression to a UInt32. It is called out specially to allow us to track casts\n//\tthat have been added purely to avert -wshorten64-32 warnings on 64 bit platforms.\n//\tFor want of a better place to park this, we'll park it here.\n#define\tToUInt32(X)\t((UInt32)(X))\n#define\tToSInt32(X)\t((SInt32)(X))\n\n#pragma mark\tBasic Definitions\n\n#if\tDEBUG || CoreAudio_Debug\n\t// can be used to break into debugger immediately, also see CADebugger\n\t#define BusError()\t\t{ long* p=NULL; *p=0; }\n\t\n\t//\tbasic debugging print routines\n\t#if\tTARGET_OS_MAC && !TARGET_API_MAC_CARBON\n\t\textern void DebugStr(const unsigned char* debuggerMsg);\n\t\t#define\tDebugMessage(msg)\tDebugStr(\"\\p\"msg)\n\t\t#define DebugMessageN1(msg, N1)\n\t\t#define DebugMessageN2(msg, N1, N2)\n\t\t#define DebugMessageN3(msg, N1, N2, N3)\n\t#else\n\t\t#include \"CADebugPrintf.h\"\n\t\t\n\t\t#if\t(CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)\n\t\t\t#define\tFlushRtn\t,fflush(DebugPrintfFile)\n\t\t#else\n\t\t\t#define\tFlushRtn\n\t\t#endif\n\t\t\n\t\t#if\t\tCoreAudio_ThreadStampMessages\n\t\t\t#include <pthread.h>\n\t\t\t#include \"CAHostTimeBase.h\"\n\t\t\t#if TARGET_RT_64_BIT\n\t\t\t\t#define\tDebugPrintfThreadIDFormat\t\"%16p\"\n\t\t\t#else\n\t\t\t\t#define\tDebugPrintfThreadIDFormat\t\"%8p\"\n\t\t\t#endif\n\t\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(\"%17qd: \" DebugPrintfThreadIDFormat \" \" inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn\n\t\t#elif\tCoreAudio_TimeStampMessages\n\t\t\t#include \"CAHostTimeBase.h\"\n\t\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(\"%17qd: \" inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn\n\t\t#else\n\t\t\t#define\tDebugMsg(inFormat, ...)\tDebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn\n\t\t#endif\n\t#endif\n\tvoid\tDebugPrint(const char *fmt, ...);\t// can be used like printf\n\t#ifndef DEBUGPRINT\n\t\t#define DEBUGPRINT(msg) DebugPrint msg\t\t// have to double-parenthesize arglist (see Debugging.h)\n\t#endif\n\t#if VERBOSE\n\t\t#define vprint(msg) DEBUGPRINT(msg)\n\t#else\n\t\t#define vprint(msg)\n\t#endif\n\t\n\t// Original macro keeps its function of turning on and off use of CADebuggerStop() for both asserts and throws.\n\t// For backwards compat, it overrides any setting of the two sub-macros.\n\t#if\tCoreAudio_StopOnFailure\n\t\t#include \"CADebugger.h\"\n\t\t#undef CoreAudio_StopOnAssert\n\t\t#define CoreAudio_StopOnAssert 1\n\t\t#undef CoreAudio_StopOnThrow\n\t\t#define CoreAudio_StopOnThrow 1\n\t\t#define STOP\tCADebuggerStop()\n\t#else\n\t\t#define STOP\n\t#endif\n\n\t#if CoreAudio_StopOnAssert\n\t\t#if !CoreAudio_StopOnFailure\n\t\t\t#include \"CADebugger.h\"\n\t\t\t#define STOP\n\t\t#endif\n\t\t#define __ASSERT_STOP CADebuggerStop()\n\t#else\n\t\t#define __ASSERT_STOP\n\t#endif\n\n\t#if CoreAudio_StopOnThrow\n\t\t#if !CoreAudio_StopOnFailure\n\t\t\t#include \"CADebugger.h\"\n\t\t\t#define STOP\n\t\t#endif\n\t\t#define __THROW_STOP CADebuggerStop()\n\t#else\n\t\t#define __THROW_STOP\n\t#endif\n\n#else\n\t#define\tDebugMsg(inFormat, ...)\n\t#ifndef DEBUGPRINT\n\t\t#define DEBUGPRINT(msg)\n\t#endif\n\t#define vprint(msg)\n\t#define\tSTOP\n\t#define __ASSERT_STOP\n\t#define __THROW_STOP\n#endif\n\n//\tOld-style numbered DebugMessage calls are implemented in terms of DebugMsg() now\n#define\tDebugMessage(msg)\t\t\t\t\t\t\t\t\t\tDebugMsg(msg)\n#define DebugMessageN1(msg, N1)\t\t\t\t\t\t\t\t\tDebugMsg(msg, N1)\n#define DebugMessageN2(msg, N1, N2)\t\t\t\t\t\t\t\tDebugMsg(msg, N1, N2)\n#define DebugMessageN3(msg, N1, N2, N3)\t\t\t\t\t\t\tDebugMsg(msg, N1, N2, N3)\n#define DebugMessageN4(msg, N1, N2, N3, N4)\t\t\t\t\t\tDebugMsg(msg, N1, N2, N3, N4)\n#define DebugMessageN5(msg, N1, N2, N3, N4, N5)\t\t\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5)\n#define DebugMessageN6(msg, N1, N2, N3, N4, N5, N6)\t\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6)\n#define DebugMessageN7(msg, N1, N2, N3, N4, N5, N6, N7)\t\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7)\n#define DebugMessageN8(msg, N1, N2, N3, N4, N5, N6, N7, N8)\t\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8)\n#define DebugMessageN9(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9)\tDebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9)\n\n// BGM edit: Added __printflike.\nvoid\tLogError(const char *fmt, ...) __printflike(1, 2);\t\t\t// writes to syslog (and stderr if debugging)\nvoid\tLogWarning(const char *fmt, ...) __printflike(1, 2);\t\t// writes to syslog (and stderr if debugging)\n\n#define\tNO_ACTION\t(void)0\n\n#if\tDEBUG || CoreAudio_Debug\n\n#pragma mark\tDebug Macros\n\n#define\tAssert(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertFileLine(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessageN3(\"%s, line %d: %s\", __FILE__, __LINE__, inMessage);\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %d (%s)\", (int)__Err, __4CC);\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoKernelError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (unsigned int)(inError);\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNotNULL(inPtr, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif((inPtr) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIf(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithAction(inCondition, inAction, inHandler, inMessage)\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULL(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelError(inKernelError, inAction, inHandler, inMessage)\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfError(inError, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %ld (%s)\", (long int)__Err, __4CC);\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNoMessage(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithActionNoMessage(inCondition, inAction, inHandler, inMessage)\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULLNoMessage(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelErrorNoMessage(inKernelError, inAction, inHandler, inMessage)\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfErrorNoMessage(inError, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if defined(__cplusplus)\n\n#define Throw(inException)  __THROW_STOP; throw (inException)\n\n#define\tThrowIf(inCondition, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfNULL(inPointer, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMessage);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfKernelError(inKernelError, inException, inMessage)\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tint __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN1(inMessage \", Error: 0x%X\", __Err);\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tchar __4CC[5] = CA4CCToCString(__Err);\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Error: %d (%s)\", (int)__Err, __4CC);\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if TARGET_OS_WIN32\n#define\tThrowIfWinError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tHRESULT __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(FAILED(__Err))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tDebugMessageN2(inMessage \", Code: %d, Facility: 0x%X\", HRESULT_CODE(__Err), HRESULT_FACILITY(__Err));\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n#endif\n\n#define\tSubclassResponsibility(inMethodName, inException)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tDebugMessage(inMethodName\": Subclasses must implement this method\");\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#endif\t//\tdefined(__cplusplus)\n\n#else\n\n#pragma mark\tRelease Macros\n\n#define\tAssert(inCondition, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(!(inCondition))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define AssertFileLine(inCondition, inMessage) Assert(inCondition, inMessage)\n\n#define\tAssertNoError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNoKernelError(inError, inMessage)\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (unsigned int)(inError);\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tAssertNotNULL(inPtr, inMessage)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif((inPtr) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t__ASSERT_STOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIf(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithAction(inCondition, inAction, inHandler, inMessage)\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULL(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelError(inKernelError, inAction, inHandler, inMessage)\t\t\t\t\\\n\t\t\tif((inKernelError) != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfError(inError, inAction, inHandler, inMessage)\t\t\t\t\t\t\t\\\n\t\t\tif((inError) != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNoMessage(inCondition, inHandler, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailWithActionNoMessage(inCondition, inAction, inHandler, inMessage)\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfNULLNoMessage(inPointer, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfKernelErrorNoMessage(inKernelError, inAction, inHandler, inMessage)\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tunsigned int __Err = (inKernelError);\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tFailIfErrorNoMessage(inError, inAction, inHandler, inMessage)\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tSTOP;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t{ inAction; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tgoto inHandler;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if defined(__cplusplus)\n\n#define Throw(inException)  __THROW_STOP; throw (inException)\n\n#define\tThrowIf(inCondition, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif(inCondition)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfNULL(inPointer, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\tif((inPointer) == NULL)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n// BGM edit: Changed \"unsigned int\" to \"int\" to silence -Wsign-conversion.\n#define\tThrowIfKernelError(inKernelError, inException, inMessage)\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tint __Err = (inKernelError);                                            \\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#define\tThrowIfError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tSInt32 __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(__Err != 0)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#if TARGET_OS_WIN32\n#define\tThrowIfWinError(inError, inException, inMessage)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tHRESULT __Err = (inError);\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif(FAILED(__Err))\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n#endif\n\n#define\tSubclassResponsibility(inMethodName, inException)\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tThrow(inException);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\n\n#endif\t//\tdefined(__cplusplus)\n\n#endif  //  DEBUG || CoreAudio_Debug\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugPrintf.cpp",
    "content": "/*\n     File: CADebugPrintf.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CADebugPrintf.h\"\n\n#if\tDEBUG || CoreAudio_Debug\n\n\t#if\tTARGET_OS_WIN32\n\t\t#include <stdarg.h>\n\t\t#include <stdio.h>\n\t\t#include <Windows.h>\n\t\textern \"C\"\n\t\tint\tCAWin32DebugPrintf(char* inFormat, ...)\n\t\t{\n\t\t\tchar theMessage[1024];\n\t\t\tva_list theArguments;\n\t\t\tva_start(theArguments, inFormat);\n\t\t\t_vsnprintf(theMessage, 1024, inFormat, theArguments);\n\t\t\tva_end(theArguments);\n\t\t\tOutputDebugString(theMessage);\n\t\t\treturn 0;\n\t\t}\n\t#endif\n\t\n\t#if defined(CoreAudio_UseSideFile)\n\t\t#include <unistd.h>\n\t\tFILE* sDebugPrintfSideFile = NULL;\n\t\textern \"C\"\n\t\tvoid OpenDebugPrintfSideFile()\n\t\t{\n\t\t\tif(sDebugPrintfSideFile == NULL)\n\t\t\t{\n\t\t\t\tchar theFileName[1024];\n\t\t\t\tsnprintf(theFileName, sizeof(theFileName), CoreAudio_UseSideFile, getpid());\n\t\t\t\tsDebugPrintfSideFile = fopen(theFileName, \"a+\");\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"\\n------------------------------\\n\");\n\t\t\t}\n\t\t}\n\t#endif\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugPrintf.h",
    "content": "/*\n     File: CADebugPrintf.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CADebugPrintf_h__)\n#define __CADebugPrintf_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n//=============================================================================\n//\tMacros to redirect debugging output to various logging services\n//=============================================================================\n\n//#define\tCoreAudio_UseSysLog\t\t1\n//#define\tCoreAudio_UseSideFile\t\"/CoreAudio-%d.txt\"\n\n#if\tDEBUG || CoreAudio_Debug\n\t\n\t#if\tTARGET_OS_WIN32\n\t\t#if defined(__cplusplus)\n\t\textern \"C\"\n\t\t#endif\n\t\textern int CAWin32DebugPrintf(char* inFormat, ...);\n\t\t#define\tDebugPrintfRtn\t\t\tCAWin32DebugPrintf\n\t\t#define\tDebugPrintfFile\t\t\t\n\t\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t\t#define\tDebugPrintfFileComma\n\t#else\n\t\t#if\tCoreAudio_UseSysLog\n\t\t\t#include <sys/syslog.h>\n\t\t\t#define\tDebugPrintfRtn\tsyslog\n\t\t\t#define\tDebugPrintfFile\tLOG_NOTICE\n\t\t\t#define\tDebugPrintfLineEnding\t\"\"\n\t\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t\t#elif defined(CoreAudio_UseSideFile)\n\t\t\t#include <stdio.h>\n\t\t\t#if defined(__cplusplus)\n\t\t\textern \"C\"\n\t\t\t#endif\n\t\t\tvoid OpenDebugPrintfSideFile();\n\t\t\textern FILE* sDebugPrintfSideFile;\n\t\t\t#define\tDebugPrintfRtn\tfprintf\n\t\t\t#define\tDebugPrintfFile\t((sDebugPrintfSideFile != NULL) ? sDebugPrintfSideFile : stderr)\n\t\t\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t\t#else\n\t\t\t#include <stdio.h>\n\t\t\t#define\tDebugPrintfRtn\tfprintf\n\t\t\t#define\tDebugPrintfFile\tstderr\n\t\t\t#define\tDebugPrintfLineEnding\t\"\\n\"\n\t\t\t#define\tDebugPrintfFileComma\tDebugPrintfFile,\n\t\t#endif\n\t#endif\n\n\t#define\tDebugPrintf(inFormat, ...)\tDebugPrintfRtn(DebugPrintfFileComma inFormat DebugPrintfLineEnding, ## __VA_ARGS__)\n#else\n\t#define\tDebugPrintfRtn\t\n\t#define\tDebugPrintfFile\t\n\t#define\tDebugPrintfLineEnding\t\n\t#define\tDebugPrintfFileComma\n\t#define\tDebugPrintf(inFormat, ...)\n#endif\n\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugger.cpp",
    "content": "/*\n     File: CADebugger.cpp\n Abstract: CADebugger.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CADebugger.h\"\n\n//=============================================================================\n//\tCADebugger\n//=============================================================================\n\n#if TARGET_API_MAC_OSX\n\n#include <sys/sysctl.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nbool CAIsDebuggerAttached(void)\n{\n\tint\t\t\t\t\tmib[4];\n\tstruct kinfo_proc\tinfo;\n\tsize_t\t\t\t\tsize;\n\n\tmib[0] = CTL_KERN;\n\tmib[1] = KERN_PROC;\n\tmib[2] = KERN_PROC_PID;\n\tmib[3] = getpid();\n\tsize = sizeof(info);\n\tinfo.kp_proc.p_flag = 0;\n\n\tsysctl(mib, 4, &info, &size, NULL, 0);\n\n\treturn (info.kp_proc.p_flag & P_TRACED) == P_TRACED;\n}\n\n#endif\n\nvoid\tCADebuggerStop(void)\n{\n\t#if\tCoreAudio_Debug\n\t\t#if\tTARGET_API_MAC_OSX\n\t\t\tif(CAIsDebuggerAttached())\n\t\t\t{\n\t\t\t\t#if defined(__i386__) || defined(__x86_64__)\n\t\t\t\t\tasm(\"int3\");\n\t\t\t\t#else\n\t\t\t\t\t__builtin_trap();\n\t\t\t\t#endif\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tabort();\n\t\t\t}\n\t\t#else\n\t\t\t__debugbreak();\n\t\t#endif\n\t#endif\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADebugger.h",
    "content": "/*\n     File: CADebugger.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CADebugger_h__)\n#define __CADebugger_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n//=============================================================================\n//\tCADebugger\n//=============================================================================\n\n#if\tTARGET_API_MAC_OSX\n\textern bool CAIsDebuggerAttached(void);\n#endif\nextern void\tCADebuggerStop(void);\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADispatchQueue.cpp",
    "content": "/*\n     File: CADispatchQueue.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n/*==================================================================================================\n\tCADispatchQueue.cpp\n==================================================================================================*/\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CADispatchQueue.h\"\n\n//\tPublicUtility Includes\n#include \"CACFString.h\"\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAHostTimeBase.h\"\n\n//\tSystem Includes\n#include <mach/mach.h>\n\n//\tStandard Library Includes\n#include <algorithm>\n\n//==================================================================================================\n//\tCADispatchQueue\n//==================================================================================================\n\nCADispatchQueue::CADispatchQueue(const char* inName)\n:\n\tmDispatchQueue(NULL),\n\tmPortDeathList(),\n\tmMachPortReceiverList()\n{\n\tmDispatchQueue = dispatch_queue_create(inName, NULL);\n\tThrowIfNULL(mDispatchQueue, CAException('what'), \"CADispatchQueue::CADispatchQueue: failed to create the dispatch queue\");\n}\n\nCADispatchQueue::CADispatchQueue(CFStringRef inName)\n:\n\tmDispatchQueue(NULL),\n\tmPortDeathList(),\n\tmMachPortReceiverList()\n{\n\tCACFString theCFName(inName, false);\n\tchar theName[256];\n\tUInt32 theSize = 256;\n\ttheCFName.GetCString(theName, theSize);\n\tmDispatchQueue = dispatch_queue_create(theName, NULL);\n\tThrowIfNULL(mDispatchQueue, CAException('what'), \"CADispatchQueue::CADispatchQueue: failed to create the dispatch queue\");\n}\n\nCADispatchQueue::CADispatchQueue(CFStringRef inPattern, CFStringRef inName)\n:\n\tmDispatchQueue(NULL),\n\tmPortDeathList(),\n\tmMachPortReceiverList()\n{\n\tCACFString theCFName(CFStringCreateWithFormat(NULL, NULL, inPattern, inName), true);\n\tchar theName[256];\n\tUInt32 theSize = 256;\n\ttheCFName.GetCString(theName, theSize);\n\tmDispatchQueue = dispatch_queue_create(theName, NULL);\n\tThrowIfNULL(mDispatchQueue, CAException('what'), \"CADispatchQueue::CADispatchQueue: failed to create the dispatch queue\");\n}\n\nCADispatchQueue::~CADispatchQueue()\n{\n\t//\tClean up the port death watchers if any are still around. Note that we do this explicitly to\n\t//\tbe sure the source is cleaned up before the queue is released\n\tmPortDeathList.clear();\n\tAssert(mMachPortReceiverList.size() == 0, \"CADispatchQueue::~CADispatchQueue: Implicitly removing the mach port receviers. It is best to explicitly call RemoveMachPortRecevier().\");\n\tmMachPortReceiverList.clear();\n\t\n\t//\trelease the dispatch queue\n\tdispatch_release(mDispatchQueue);\n}\n\nvoid\tCADispatchQueue::Dispatch(bool inDoSync, dispatch_block_t inTask) const\n{\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync(mDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async(mDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch(UInt64 inNanoseconds, dispatch_block_t inTask) const\n{\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async(mDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), mDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch(bool inDoSync, void* inTaskContext, dispatch_function_t inTask) const\n{\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync_f(mDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async_f(mDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch(UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask) const\n{\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async_f(mDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after_f(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), mDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Global(dispatch_queue_priority_t inQueuePriority, bool inDoSync, dispatch_block_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_global_queue(inQueuePriority, 0);\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync(theDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async(theDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Global(dispatch_queue_priority_t inQueuePriority, UInt64 inNanoseconds, dispatch_block_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_global_queue(inQueuePriority, 0);\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async(theDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), theDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Global(dispatch_queue_priority_t inQueuePriority, bool inDoSync, void* inTaskContext, dispatch_function_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_global_queue(inQueuePriority, 0);\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Global(dispatch_queue_priority_t inQueuePriority, UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_global_queue(inQueuePriority, 0);\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after_f(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), theDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Main(bool inDoSync, dispatch_block_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_main_queue();\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync(theDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async(theDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Main(UInt64 inNanoseconds, dispatch_block_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_main_queue();\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async(theDispatchQueue, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), theDispatchQueue, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Main(bool inDoSync, void* inTaskContext, dispatch_function_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_main_queue();\n\tif(inDoSync)\n\t{\n\t\t//\tExecuting a task synchronously while already on the dispatch queue will result in a deadlock\n\t\tdispatch_sync_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_async_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::Dispatch_Main(UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask)\n{\n\tdispatch_queue_t theDispatchQueue = dispatch_get_main_queue();\n\tif(inNanoseconds == 0)\n\t{\n\t\tdispatch_async_f(theDispatchQueue, inTaskContext, inTask);\n\t}\n\telse\n\t{\n\t\tdispatch_after_f(dispatch_time(0, static_cast<int64_t>(CAHostTimeBase::ConvertFromNanos(inNanoseconds))), theDispatchQueue, inTaskContext, inTask);\n\t}\n}\n\nvoid\tCADispatchQueue::InstallMachPortDeathNotification(mach_port_t inMachPort, dispatch_block_t inNotificationTask)\n{\n\tThrowIf(inMachPort == MACH_PORT_NULL, CAException('nope'), \"CADispatchQueue::InstallMachPortDeathNotification: a mach port is required\");\n\t\n\t//\t look in the list to see if we've already created an event source for it\n\tbool wasFound = false;\n\tEventSourceList::iterator theIterator = mPortDeathList.begin();\n\twhile(!wasFound && (theIterator != mPortDeathList.end()))\n\t{\n\t\twasFound = theIterator->mMachPort == inMachPort;\n\t\tif(!wasFound)\n\t\t{\n\t\t\t++theIterator;\n\t\t}\n\t}\n\t\n\t//\tcreate and install the event source for the port\n\tif(!wasFound)\n\t{\n\t\t//\tcreate an event source for the mach port\n\t\tdispatch_source_t theDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_SEND, inMachPort, DISPATCH_MACH_SEND_DEAD, mDispatchQueue);\n\t\tThrowIfNULL(theDispatchSource, CAException('what'), \"CADispatchQueue::InstallMachPortDeathNotification: failed to create the mach port event source\");\n\t\t\n\t\t//\tinstall the event handler\n\t\tdispatch_source_set_event_handler(theDispatchSource, inNotificationTask);\n\t\t\n\t\t//\tput the info in the list\n\t\tmPortDeathList.push_back(EventSource(theDispatchSource, inMachPort));\n\t\t\n\t\t//\tresume the event source so that it can start handling messages and also so that the source can be released\n\t\tdispatch_resume(theDispatchSource);\n\t}\n}\n\nvoid\tCADispatchQueue::RemoveMachPortDeathNotification(mach_port_t inMachPort)\n{\n\tbool wasFound = false;\n\tEventSourceList::iterator theIterator = mPortDeathList.begin();\n\twhile(!wasFound && (theIterator != mPortDeathList.end()))\n\t{\n\t\twasFound = theIterator->mMachPort == inMachPort;\n\t\tif(!wasFound)\n\t\t{\n\t\t\t++theIterator;\n\t\t}\n\t}\n\tif(wasFound)\n\t{\n\t\tif(theIterator->mDispatchSource != NULL)\n\t\t{\n\t\t\tdispatch_source_cancel(theIterator->mDispatchSource);\n\t\t}\n\t\tmPortDeathList.erase(theIterator);\n\t}\n}\n\nvoid\tCADispatchQueue::InstallMachPortReceiver(mach_port_t inMachPort, dispatch_block_t inMessageTask)\n{\n\tThrowIf(inMachPort == MACH_PORT_NULL, CAException('nope'), \"CADispatchQueue::InstallMachPortReceiver: a mach port is required\");\n\t\n\t//\t look in the list to see if we've already created an event source for it\n\tbool wasFound = false;\n\tEventSourceList::iterator theIterator = mMachPortReceiverList.begin();\n\twhile(!wasFound && (theIterator != mMachPortReceiverList.end()))\n\t{\n\t\twasFound = theIterator->mMachPort == inMachPort;\n\t\tif(!wasFound)\n\t\t{\n\t\t\t++theIterator;\n\t\t}\n\t}\n\t\n\t//\tcreate and install the event source for the port\n\tif(!wasFound)\n\t{\n\t\t//\tcreate an event source for the mach port\n\t\tdispatch_source_t theDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, inMachPort, 0, mDispatchQueue);\n\t\tThrowIfNULL(theDispatchSource, CAException('what'), \"CADispatchQueue::InstallMachPortReceiver: failed to create the mach port event source\");\n\t\t\n\t\t//\tinstall an event handler that maps the mach messages to the MIG server function\n\t\tdispatch_source_set_event_handler(theDispatchSource, inMessageTask);\n\t\t\n\t\t//\tput the info in the list\n\t\tmMachPortReceiverList.push_back(EventSource(theDispatchSource, inMachPort));\n\t\t\n\t\t//\tresume the event source so that it can start handling messages and also so that the source can be released\n\t\tdispatch_resume(theDispatchSource);\n\t}\n}\n\nvoid\tCADispatchQueue::RemoveMachPortReceiver(mach_port_t inMachPort, dispatch_block_t inCompletionTask)\n{\n\tbool wasFound = false;\n\tEventSourceList::iterator theIterator = mMachPortReceiverList.begin();\n\twhile(!wasFound && (theIterator != mMachPortReceiverList.end()))\n\t{\n\t\twasFound = theIterator->mMachPort == inMachPort;\n\t\tif(!wasFound)\n\t\t{\n\t\t\t++theIterator;\n\t\t}\n\t}\n\tif(wasFound)\n\t{\n\t\tif(theIterator->mDispatchSource != NULL)\n\t\t{\n\t\t\t//\tSet the cancel handler to the completion block. Note that the mach port cannot be freed\n\t\t\t//\tbefore the completion block runs due to a race condition. See the note in the comments\n\t\t\t//\tdispatch_source_set_cancel_handler in <dispatch/source.h>.\n\t\t\tif(inCompletionTask != 0)\n\t\t\t{\n\t\t\t\tdispatch_source_set_cancel_handler(theIterator->mDispatchSource, inCompletionTask);\n\t\t\t}\n\t\t\n\t\t\tdispatch_source_cancel(theIterator->mDispatchSource);\n\t\t}\n\t\tmMachPortReceiverList.erase(theIterator);\n\t}\n}\n\nvoid\tCADispatchQueue::RemoveMachPortReceiver(mach_port_t inMachPort, bool inDestroySendRight, bool inDestroyReceiveRight)\n{\n\tRemoveMachPortReceiver(inMachPort,\t^{\n\t\t\t\t\t\t\t\t\t\t\tif(inDestroySendRight)\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tkern_return_t theError = mach_port_deallocate(mach_task_self(), inMachPort);\n\t\t\t\t\t\t\t\t\t\t\t\tAssertNoKernelError(theError, \"CADispatchQueue::RemoveMachPortReceiver: deallocating the send right failed\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif(inDestroyReceiveRight)\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tkern_return_t theError = mach_port_mod_refs(mach_task_self(), inMachPort, MACH_PORT_RIGHT_RECEIVE, -1);\n\t\t\t\t\t\t\t\t\t\t\t\tAssertNoKernelError(theError, \"CADispatchQueue::RemoveMachPortReceiver: deallocating the receive right failed\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n}\n\nCADispatchQueue&\tCADispatchQueue::GetGlobalSerialQueue()\n{\n\tdispatch_once_f(&sGlobalSerialQueueInitialized, NULL, InitializeGlobalSerialQueue);\n\tThrowIfNULL(sGlobalSerialQueue, CAException('nope'), \"CADispatchQueue::GetGlobalSerialQueue: there is no global serial queue\");\n\treturn *sGlobalSerialQueue;\n}\n\nvoid\tCADispatchQueue::InitializeGlobalSerialQueue(void*)\n{\n\ttry\n\t{\n\t\tsGlobalSerialQueue = new CADispatchQueue(\"com.apple.audio.CADispatchQueue.SerialQueue\");\n\t}\n\tcatch(...)\n\t{\n\t\tsGlobalSerialQueue = NULL;\n\t}\n}\n\nCADispatchQueue*\tCADispatchQueue::sGlobalSerialQueue = NULL;\ndispatch_once_t\t\tCADispatchQueue::sGlobalSerialQueueInitialized = 0;\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CADispatchQueue.h",
    "content": "/*\n     File: CADispatchQueue.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n/*==================================================================================================\n\tCADispatchQueue.h\n==================================================================================================*/\n#if !defined(__CADispatchQueue_h__)\n#define __CADispatchQueue_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSystem Includes\n#include <CoreFoundation/CFString.h>\n#include <dispatch/dispatch.h>\n\n//\tStandard Library Includes\n#include <functional>\n#include <vector>\n\n/*==================================================================================================\n\tCADispatchQueue\n\t\n\tThis class provides a wrapper for a libdispatch dispatch queue and several kinds of event\n\tsources such as MIG servers, port death notifications and other mach port related support. Being\n\ta libdispatch client, the unit of work is represented as either a function pointer and context\n\tpointer pair or as a Block.\n\t\n\tOne thing to keep in mind when using a Block-based construct is that you get a copy of the\n\tpointer but you do not get a copy of the memory that the pointer is pointing at. The net effect\n\tis that if you have a task that frees the memory that is referenced by a subsequent task,\n\tthis second task would crash when dereferencing the pointer. This means that every task\n\tneeds to include some code to validate any pointers it is using before dereferencing them.\n\t\n\tA common example of this problem comes up with C++ objects. Suppose you have an instance method\n\tthat creates a Block that calls other instance methods or accesses the object's fields. Suppose\n\tfurther that the Block is submitted to a dispatch queue for execution. If the object gets\n\tdeallocated before the Block can run, the Block will crash. Thus, the Block needs to validate\n\tthat the \"this\" pointer is still valid when it executes.\n\t\n\tAnother place where this comes up often is when attempting to implment a function by dispatching\n\tthe work asynchronously. Any arguments to the function that point to the stack will be invalid.\n\tThis is particularly troublesome inside of a MIG function.\n\t\n\tStill another common issue with using Blocks and dispatch functions with C++ is that it is vital\n\tthat no exceptions ever leave a Block or a dispatch function. If an exception was thrown out of\n\ta block, the result would be undefined and probably would result in the program calling\n\tthe terminate() function in the standard C++ library (whose default implementation is to call\n\tabort(3)). Given that, all Blocks and dispatch functions that might end up throwing an exception\n\tneed to catch those exceptions.\n==================================================================================================*/\n\nclass CADispatchQueue\n{\n\n#pragma mark Construction/Destruction\npublic:\n\t\t\t\t\t\t\t\t\t\tCADispatchQueue(const char* inName);\n\t\t\t\t\t\t\t\t\t\tCADispatchQueue(CFStringRef inName);\n\t\t\t\t\t\t\t\t\t\tCADispatchQueue(CFStringRef inPattern, CFStringRef inName);\n\tvirtual\t\t\t\t\t\t\t\t~CADispatchQueue();\n\nprivate:\n\t\t\t\t\t\t\t\t\t\tCADispatchQueue(const CADispatchQueue&);\n\tCADispatchQueue&\t\t\t\t\toperator=(const CADispatchQueue&);\n\t\n#pragma mark Execution Operations\npublic:\n\tvoid\t\t\t\t\t\t\t\tDispatch(bool inDoSync, dispatch_block_t inTask) const;\n\tvoid\t\t\t\t\t\t\t\tDispatch(UInt64 inNanoseconds, dispatch_block_t inTask) const;\n\t\n\tvoid\t\t\t\t\t\t\t\tDispatch(bool inDoSync, void* inTaskContext, dispatch_function_t inTask) const;\n\tvoid\t\t\t\t\t\t\t\tDispatch(UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask) const;\n\n\tstatic void\t\t\t\t\t\t\tDispatch_Global(dispatch_queue_priority_t inQueuePriority, bool inDoSync, dispatch_block_t inTask);\n\tstatic void\t\t\t\t\t\t\tDispatch_Global(dispatch_queue_priority_t inQueuePriority, UInt64 inNanoseconds, dispatch_block_t inTask);\n\t\n\tstatic void\t\t\t\t\t\t\tDispatch_Global(dispatch_queue_priority_t inQueuePriority, bool inDoSync, void* inTaskContext, dispatch_function_t inTask);\n\tstatic void\t\t\t\t\t\t\tDispatch_Global(dispatch_queue_priority_t inQueuePriority, UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask);\n\n\tstatic void\t\t\t\t\t\t\tDispatch_Main(bool inDoSync, dispatch_block_t inTask);\n\tstatic void\t\t\t\t\t\t\tDispatch_Main(UInt64 inNanoseconds, dispatch_block_t inTask);\n\t\n\tstatic void\t\t\t\t\t\t\tDispatch_Main(bool inDoSync, void* inTaskContext, dispatch_function_t inTask);\n\tstatic void\t\t\t\t\t\t\tDispatch_Main(UInt64 inNanoseconds, void* inTaskContext, dispatch_function_t inTask);\n\n#pragma mark Event Sources\npublic:\n\tvoid\t\t\t\t\t\t\t\tInstallMachPortDeathNotification(mach_port_t inMachPort, dispatch_block_t inNotificationTask);\n\tvoid\t\t\t\t\t\t\t\tRemoveMachPortDeathNotification(mach_port_t inMachPort);\n\t\n\tvoid\t\t\t\t\t\t\t\tInstallMachPortReceiver(mach_port_t inMachPort, dispatch_block_t inMessageTask);\n\tvoid\t\t\t\t\t\t\t\tRemoveMachPortReceiver(mach_port_t inMachPort, dispatch_block_t inCompletionTask);\n\tvoid\t\t\t\t\t\t\t\tRemoveMachPortReceiver(mach_port_t inMachPort, bool inDestroySendRight, bool inDestroyReceiveRight);\n\n#pragma mark Implementation\npublic:\n\tdispatch_queue_t\t\t\t\t\tGetDispatchQueue() const;\n\t\n\tstatic CADispatchQueue&\t\t\t\tGetGlobalSerialQueue();\n\t\nprotected:\t\n\tstatic void\t\t\t\t\t\t\tInitializeGlobalSerialQueue(void*);\n\n\tstruct\t\t\t\t\t\t\t\tEventSource\n\t{\n\t\tdispatch_source_t\t\t\t\tmDispatchSource;\n\t\tmach_port_t\t\t\t\t\t\tmMachPort;\n\t\t\n\t\t\t\t\t\t\t\t\t\tEventSource();\n\t\t\t\t\t\t\t\t\t\tEventSource(dispatch_source_t inDispatchSource, mach_port_t inMachPort);\n\t\t\t\t\t\t\t\t\t\tEventSource(const EventSource& inEventSource);\n\t\tEventSource&\t\t\t\t\toperator=(const EventSource& inEventSource);\n\t\t\t\t\t\t\t\t\t\t~EventSource();\n\t\tvoid\t\t\t\t\t\t\tRetain();\n\t\tvoid\t\t\t\t\t\t\tRelease();\n\t};\n\ttypedef std::vector<EventSource>\tEventSourceList;\n\t\n\tdispatch_queue_t\t\t\t\t\tmDispatchQueue;\n\tEventSourceList\t\t\t\t\t\tmPortDeathList;\n\tEventSourceList\t\t\t\t\t\tmMachPortReceiverList;\n\n\tstatic CADispatchQueue*\t\t\t\tsGlobalSerialQueue;\n\tstatic dispatch_once_t\t\t\t\tsGlobalSerialQueueInitialized;\n\n};\n\n//==================================================================================================\n#pragma mark CADispatchQueue Inline Method Implementations\n//==================================================================================================\n\ninline dispatch_queue_t\tCADispatchQueue::GetDispatchQueue() const\n{\n\treturn mDispatchQueue;\n}\n\ninline CADispatchQueue::EventSource::EventSource()\n:\n\tmDispatchSource(NULL),\n\tmMachPort(MACH_PORT_NULL)\n{\n}\n\ninline CADispatchQueue::EventSource::EventSource(dispatch_source_t inDispatchSource, mach_port_t inMachPort)\n:\n\tmDispatchSource(inDispatchSource),\n\tmMachPort(inMachPort)\n{\n}\n\ninline CADispatchQueue::EventSource::EventSource(const EventSource& inEventSource)\n:\n\tmDispatchSource(inEventSource.mDispatchSource),\n\tmMachPort(inEventSource.mMachPort)\n{\n\tRetain();\n}\n\ninline CADispatchQueue::EventSource&\tCADispatchQueue::EventSource::operator=(const EventSource& inEventSource)\n{\n\tRelease();\n\tmDispatchSource = inEventSource.mDispatchSource;\n\tmMachPort = inEventSource.mMachPort;\n\tRetain();\n\treturn *this;\n}\n\ninline CADispatchQueue::EventSource::~EventSource()\n{\n\tRelease();\n}\n\ninline void\tCADispatchQueue::EventSource::Retain()\n{\n\tif(mDispatchSource != NULL)\n\t{\n\t\tdispatch_retain(mDispatchSource);\n\t}\n}\n\ninline void\tCADispatchQueue::EventSource::Release()\n{\n\tif(mDispatchSource != NULL)\n\t{\n\t\tdispatch_release(mDispatchSource);\n\t\tmDispatchSource = NULL;\n\t}\n}\n\n#endif\t//\t__CADispatchQueue_h__\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAException.h",
    "content": "/*\n     File: CAException.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CAException_h__)\n#define __CAException_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include \"CoreAudioTypes.h\"\n#endif\n\n//=============================================================================\n//\tCAException\n//=============================================================================\n\nclass CAException\n{\n\npublic:\n\t\t\t\t\tCAException(OSStatus inError) : mError(inError) {}\n\t\t\t\t\tCAException(const CAException& inException) : mError(inException.mError) {}\n\tCAException&\toperator=(const CAException& inException) { mError = inException.mError; return *this; }\n\t\t\t\t\t~CAException() {}\n\n\tOSStatus\t\tGetError() const { return mError; }\n\t\nprotected:\n\tOSStatus\t\tmError;\n};\n\n#define\tCATry\t\t\t\t\t\t\t\ttry{\n#define CACatch\t\t\t\t\t\t\t\t} catch(...) {}\n#define\tCASwallowException(inExpression)\ttry { inExpression; } catch(...) {}\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAHostTimeBase.cpp",
    "content": "/*\n     File: CAHostTimeBase.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CAHostTimeBase.h\"\n\nFloat64\t\t\tCAHostTimeBase::sFrequency = 0;\nFloat64\t\t\tCAHostTimeBase::sInverseFrequency = 0;\nUInt32\t\t\tCAHostTimeBase::sMinDelta = 0;\nUInt32\t\t\tCAHostTimeBase::sToNanosNumerator = 0;\nUInt32\t\t\tCAHostTimeBase::sToNanosDenominator = 0;\npthread_once_t\tCAHostTimeBase::sIsInited = PTHREAD_ONCE_INIT;\n#if Track_Host_TimeBase\nUInt64\t\t\tCAHostTimeBase::sLastTime = 0;\n#endif\n\n//=============================================================================\n//\tCAHostTimeBase\n//\n//\tThis class provides platform independent access to the host's time base.\n//=============================================================================\n\nvoid\tCAHostTimeBase::Initialize()\n{\n\t//\tget the info about Absolute time\n\t#if TARGET_OS_MAC\n\t\tstruct mach_timebase_info\ttheTimeBaseInfo;\n\t\tmach_timebase_info(&theTimeBaseInfo);\n\t\tsMinDelta = 1;\n\t\tsToNanosNumerator = theTimeBaseInfo.numer;\n\t\tsToNanosDenominator = theTimeBaseInfo.denom;\n\n\t\t//\tthe frequency of that clock is: (sToNanosDenominator / sToNanosNumerator) * 10^9\n\t\tsFrequency = static_cast<Float64>(sToNanosDenominator) / static_cast<Float64>(sToNanosNumerator);\n\t\tsFrequency *= 1000000000.0;\n\t#elif TARGET_OS_WIN32\n\t\tLARGE_INTEGER theFrequency;\n\t\tQueryPerformanceFrequency(&theFrequency);\n\t\tsMinDelta = 1;\n\t\tsToNanosNumerator = 1000000000ULL;\n\t\tsToNanosDenominator = *((UInt64*)&theFrequency);\n\t\tsFrequency = static_cast<Float64>(*((UInt64*)&theFrequency));\n\t#endif\n\tsInverseFrequency = 1.0 / sFrequency;\n\t\n\t#if\tLog_Host_Time_Base_Parameters\n\t\tDebugPrintf(\"Host Time Base Parameters\");\n\t\tDebugPrintf(\" Minimum Delta:          %lu\", (unsigned long)sMinDelta);\n\t\tDebugPrintf(\" Frequency:              %f\", sFrequency);\n\t\tDebugPrintf(\" To Nanos Numerator:     %lu\", (unsigned long)sToNanosNumerator);\n\t\tDebugPrintf(\" To Nanos Denominator:   %lu\", (unsigned long)sToNanosDenominator);\n\t#endif\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAHostTimeBase.h",
    "content": "/*\n     File: CAHostTimeBase.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CAHostTimeBase_h__)\n#define __CAHostTimeBase_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <mach/mach_time.h>\n\t#include <pthread.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n\t#include \"WinPThreadDefs.h\"\n#else\n\t#error\tUnsupported operating system\n#endif\n\n#include \"CADebugPrintf.h\"\n\n//=============================================================================\n//\tCAHostTimeBase\n//\n//\tThis class provides platform independent access to the host's time base.\n//=============================================================================\n\n#if CoreAudio_Debug\n//\t#define Log_Host_Time_Base_Parameters\t1\n//\t#define Track_Host_TimeBase\t\t\t\t1\n#endif\n\nclass\tCAHostTimeBase\n{\n\npublic:\n\tstatic UInt64\t\t\tConvertToNanos(UInt64 inHostTime);\n\tstatic UInt64\t\t\tConvertFromNanos(UInt64 inNanos);\n\n\tstatic UInt64\t\t\tGetTheCurrentTime();\n#if TARGET_OS_MAC\n\tstatic UInt64\t\t\tGetCurrentTime() { return GetTheCurrentTime(); }\n#endif\n\tstatic UInt64\t\t\tGetCurrentTimeInNanos();\n\n\tstatic Float64\t\t\tGetFrequency() { pthread_once(&sIsInited, Initialize); return sFrequency; }\n\tstatic Float64\t\t\tGetInverseFrequency() { pthread_once(&sIsInited, Initialize); return sInverseFrequency; }\n\tstatic UInt32\t\t\tGetMinimumDelta() { pthread_once(&sIsInited, Initialize); return sMinDelta; }\n\n\tstatic UInt64\t\t\tAbsoluteHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime);\n\tstatic SInt64\t\t\tHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime);\n\n\tstatic UInt64\t\t\tMultiplyByRatio(UInt64 inMuliplicand, UInt32 inNumerator, UInt32 inDenominator);\n\t\nprivate:\n\tstatic void\t\t\t\tInitialize();\n\t\n\tstatic pthread_once_t\tsIsInited;\n\t\n\tstatic Float64\t\t\tsFrequency;\n\tstatic Float64\t\t\tsInverseFrequency;\n\tstatic UInt32\t\t\tsMinDelta;\n\tstatic UInt32\t\t\tsToNanosNumerator;\n\tstatic UInt32\t\t\tsToNanosDenominator;\n#if Track_Host_TimeBase\n\tstatic UInt64\t\t\tsLastTime;\n#endif\n};\n\ninline UInt64\tCAHostTimeBase::GetTheCurrentTime()\n{\n\tUInt64 theTime = 0;\n\n\t#if TARGET_OS_MAC\n\t\ttheTime = mach_absolute_time();\n\t#elif TARGET_OS_WIN32\n\t\tLARGE_INTEGER theValue;\n\t\tQueryPerformanceCounter(&theValue);\n\t\ttheTime = *((UInt64*)&theValue);\n\t#endif\n\t\n\t#if\tTrack_Host_TimeBase\n\t\tif(sLastTime != 0)\n\t\t{\n\t\t\tif(theTime <= sLastTime)\n\t\t\t{\n\t\t\t\tDebugPrintf(\"CAHostTimeBase::GetTheCurrentTime: the current time is earlier than the last time, now: %qd, then: %qd\", theTime, sLastTime);\n\t\t\t}\n\t\t\tsLastTime = theTime;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsLastTime = theTime;\n\t\t}\n\t#endif\n\n\treturn theTime;\n}\n\ninline UInt64\tCAHostTimeBase::ConvertToNanos(UInt64 inHostTime)\n{\n\tpthread_once(&sIsInited, Initialize);\n\t\n\tUInt64 theAnswer = MultiplyByRatio(inHostTime, sToNanosNumerator, sToNanosDenominator);\n\t#if CoreAudio_Debug\n\t\tif(((sToNanosNumerator > sToNanosDenominator) && (theAnswer < inHostTime)) || ((sToNanosDenominator > sToNanosNumerator) && (theAnswer > inHostTime)))\n\t\t{\n\t\t\tDebugPrintf(\"CAHostTimeBase::ConvertToNanos: The conversion wrapped\");\n\t\t}\n\t#endif\n\t\n\treturn theAnswer;\n}\n\ninline UInt64\tCAHostTimeBase::ConvertFromNanos(UInt64 inNanos)\n{\n\tpthread_once(&sIsInited, Initialize);\n\n\tUInt64 theAnswer = MultiplyByRatio(inNanos, sToNanosDenominator, sToNanosNumerator);\n\t#if CoreAudio_Debug\n\t\tif(((sToNanosDenominator > sToNanosNumerator) && (theAnswer < inNanos)) || ((sToNanosNumerator > sToNanosDenominator) && (theAnswer > inNanos)))\n\t\t{\n\t\t\tDebugPrintf(\"CAHostTimeBase::ConvertFromNanos: The conversion wrapped\");\n\t\t}\n\t#endif\n\n\treturn theAnswer;\n}\n\ninline UInt64\tCAHostTimeBase::GetCurrentTimeInNanos()\n{\n\treturn ConvertToNanos(GetTheCurrentTime());\n}\n\ninline UInt64\tCAHostTimeBase::AbsoluteHostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime)\n{\n\tUInt64 theAnswer;\n\t\n\tif(inStartTime <= inEndTime)\n\t{\n\t\ttheAnswer = inEndTime - inStartTime;\n\t}\n\telse\n\t{\n\t\ttheAnswer = inStartTime - inEndTime;\n\t}\n\t\n\treturn ConvertToNanos(theAnswer);\n}\n\ninline SInt64\tCAHostTimeBase::HostDeltaToNanos(UInt64 inStartTime, UInt64 inEndTime)\n{\n\tSInt64 theAnswer;\n\tSInt64 theSign = 1;\n\t\n\tif(inStartTime <= inEndTime)\n\t{\n\t\ttheAnswer = static_cast<SInt64>(inEndTime - inStartTime);\n\t}\n\telse\n\t{\n\t\ttheAnswer = static_cast<SInt64>(inStartTime - inEndTime);\n\t\ttheSign = -1;\n\t}\n\t\n\treturn theSign * static_cast<SInt64>(ConvertToNanos(static_cast<UInt64>(theAnswer)));\n}\n\ninline UInt64\tCAHostTimeBase::MultiplyByRatio(UInt64 inMuliplicand, UInt32 inNumerator, UInt32 inDenominator)\n{\n#if TARGET_OS_MAC && TARGET_RT_64_BIT\n\t__uint128_t theAnswer = inMuliplicand;\n#else\n\tlong double theAnswer = inMuliplicand;\n#endif\n\tif(inNumerator != inDenominator)\n\t{\n\t\ttheAnswer *= inNumerator;\n\t\ttheAnswer /= inDenominator;\n\t}\n\treturn static_cast<UInt64>(theAnswer);\n}\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAMutex.cpp",
    "content": "/*\n     File: CAMutex.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSelf Include\n#include \"CAMutex.h\"\n\n#if TARGET_OS_MAC\n\t#include <errno.h>\n#endif\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n#include \"CAHostTimeBase.h\"\n\n//==================================================================================================\n//\tLogging\n//==================================================================================================\n\n#if CoreAudio_Debug\n//\t#define\tLog_Ownership\t\t1\n//\t#define\tLog_Errors\t\t\t1\n//\t#define Log_LongLatencies\t1\n//\t#define LongLatencyThreshholdNS\t1000000ULL\t// nanoseconds\n#endif\n\n//==================================================================================================\n//\tCAMutex\n//==================================================================================================\n\nCAMutex::CAMutex(const char* inName)\n:\n\tmName(inName),\n\tmOwner(0)\n{\n#if TARGET_OS_MAC\n\tOSStatus theError = pthread_mutex_init(&mMutex, NULL);\n\tThrowIf(theError != 0, CAException(theError), \"CAMutex::CAMutex: Could not init the mutex\");\n\t\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::CAMutex: creating %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n#elif TARGET_OS_WIN32\n\tmMutex = CreateMutex(NULL, false, NULL);\n\tThrowIfNULL(mMutex, CAException(GetLastError()), \"CAMutex::CAMutex: could not create the mutex.\");\n\t\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::CAMutex: creating %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n#endif\n}\n\nCAMutex::~CAMutex()\n{\n#if TARGET_OS_MAC\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::~CAMutex: destroying %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n\tpthread_mutex_destroy(&mMutex);\n#elif TARGET_OS_WIN32\n\t#if\tLog_Ownership\n\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::~CAMutex: destroying %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);\n\t#endif\n\tif(mMutex != NULL)\n\t{\n\t\tCloseHandle(mMutex);\n\t}\n#endif\n}\n\nbool\tCAMutex::Lock()\n{\n\tbool theAnswer = false;\n\t\n#if TARGET_OS_MAC\n\tpthread_t theCurrentThread = pthread_self();\n\tif(!pthread_equal(theCurrentThread, mOwner))\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Lock: thread %p is locking %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t#endif\n\t\t\n\t\t#if Log_LongLatencies\n\t\t\tUInt64 lockTryTime = CAHostTimeBase::GetCurrentTimeInNanos();\n\t\t#endif\n\t\t\n\t\tOSStatus theError = pthread_mutex_lock(&mMutex);\n\t\tThrowIf(theError != 0, CAException(theError), \"CAMutex::Lock: Could not lock the mutex\");\n\t\tmOwner = theCurrentThread;\n\t\ttheAnswer = true;\n\t\n\t\t#if Log_LongLatencies\n\t\t\tUInt64 lockAcquireTime = CAHostTimeBase::GetCurrentTimeInNanos();\n\t\t\tif (lockAcquireTime - lockTryTime >= LongLatencyThresholdNS)\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"Thread %p took %.6fs to acquire the lock %s\\n\", theCurrentThread, (lockAcquireTime - lockTryTime) * 1.0e-9 /* nanos to seconds */, mName);\n\t\t#endif\n\t\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Lock: thread %p has locked %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner != GetCurrentThreadId())\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Lock: thread %lu is locking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\n\t\tOSStatus theError = WaitForSingleObject(mMutex, INFINITE);\n\t\tThrowIfError(theError, CAException(theError), \"CAMutex::Lock: could not lock the mutex\");\n\t\tmOwner = GetCurrentThreadId();\n\t\ttheAnswer = true;\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Lock: thread %lu has locked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t}\n#endif\n\n\treturn theAnswer;\n}\n\nvoid\tCAMutex::Unlock()\n{\n#if TARGET_OS_MAC\n\tif(pthread_equal(pthread_self(), mOwner))\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Unlock: thread %p is unlocking %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\n\t\tmOwner = 0;\n\t\tOSStatus theError = pthread_mutex_unlock(&mMutex);\n\t\tThrowIf(theError != 0, CAException(theError), \"CAMutex::Unlock: Could not unlock the mutex\");\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Unlock: thread %p has unlocked %s, owner: %p\\n\", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);\n\t\t#endif\n\t}\n\telse\n\t{\n\t\tDebugMessage(\"CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own\");\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner == GetCurrentThreadId())\n\t{\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Unlock: thread %lu is unlocking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\n\t\tmOwner = 0;\n\t\tbool wasReleased = ReleaseMutex(mMutex);\n\t\tThrowIf(!wasReleased, CAException(GetLastError()), \"CAMutex::Unlock: Could not unlock the mutex\");\n\t\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Unlock: thread %lu has unlocked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t}\n\telse\n\t{\n\t\tDebugMessage(\"CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own\");\n\t}\n#endif\n}\n\nbool\tCAMutex::Try(bool& outWasLocked)\n{\n\tbool theAnswer = false;\n\toutWasLocked = false;\n\n#if TARGET_OS_MAC\n\tpthread_t theCurrentThread = pthread_self();\n\tif(!pthread_equal(theCurrentThread, mOwner))\n\t{\n\t\t//\tthis means the current thread doesn't already own the lock\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p is try-locking %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t#endif\n\n\t\t//\tgo ahead and call trylock to see if we can lock it.\n\t\tint theError = pthread_mutex_trylock(&mMutex);\n\t\tif(theError == 0)\n\t\t{\n\t\t\t//\treturn value of 0 means we successfully locked the lock\n\t\t\tmOwner = theCurrentThread;\n\t\t\ttheAnswer = true;\n\t\t\toutWasLocked = true;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p has locked %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse if(theError == EBUSY)\n\t\t{\n\t\t\t//\treturn value of EBUSY means that the lock was already locked by another thread\n\t\t\ttheAnswer = false;\n\t\t\toutWasLocked = false;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%p %.4f: CAMutex::Try: thread %p failed to lock %s, owner: %p\\n\", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\tany other return value means something really bad happenned\n\t\t\tThrowIfError(theError, CAException(theError), \"CAMutex::Try: call to pthread_mutex_trylock failed\");\n\t\t}\n\t}\n\telse\n\t{\n\t\t//\tthis means the current thread already owns the lock\n\t\ttheAnswer = true;\n\t\toutWasLocked = false;\n\t}\n#elif TARGET_OS_WIN32\n\tif(mOwner != GetCurrentThreadId())\n\t{\n\t\t//\tthis means the current thread doesn't own the lock\n\t\t#if\tLog_Ownership\n\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu is try-locking %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t#endif\n\t\t\n\t\t//\ttry to acquire the mutex\n\t\tOSStatus theError = WaitForSingleObject(mMutex, 0);\n\t\tif(theError == WAIT_OBJECT_0)\n\t\t{\n\t\t\t//\tthis means we successfully locked the lock\n\t\t\tmOwner = GetCurrentThreadId();\n\t\t\ttheAnswer = true;\n\t\t\toutWasLocked = true;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu has locked %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse if(theError == WAIT_TIMEOUT)\n\t\t{\n\t\t\t//\tthis means that the lock was already locked by another thread\n\t\t\ttheAnswer = false;\n\t\t\toutWasLocked = false;\n\t\n\t\t\t#if\tLog_Ownership\n\t\t\t\tDebugPrintfRtn(DebugPrintfFileComma \"%lu %.4f: CAMutex::Try: thread %lu failed to lock %s, owner: %lu\\n\", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);\n\t\t\t#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\tany other return value means something really bad happenned\n\t\t\tThrowIfError(theError, CAException(GetLastError()), \"CAMutex::Try: call to lock the mutex failed\");\n\t\t}\n\t}\n\telse\n\t{\n\t\t//\tthis means the current thread already owns the lock\n\t\ttheAnswer = true;\n\t\toutWasLocked = false;\n\t}\n#endif\n\t\n\treturn theAnswer;\n}\n\nbool\tCAMutex::IsFree() const\n{\n\treturn mOwner == 0;\n}\n\nbool\tCAMutex::IsOwnedByCurrentThread() const\n{\n\tbool theAnswer = true;\n\t\n#if TARGET_OS_MAC\n\ttheAnswer = pthread_equal(pthread_self(), mOwner);\n#elif TARGET_OS_WIN32\n\ttheAnswer = (mOwner == GetCurrentThreadId());\n#endif\n\n\treturn theAnswer;\n}\n\n\nCAMutex::Unlocker::Unlocker(CAMutex& inMutex)\n:\tmMutex(inMutex),\n\tmNeedsLock(false)\n{\n\tAssert(mMutex.IsOwnedByCurrentThread(), \"Major problem: Unlocker attempted to unlock a mutex not owned by the current thread!\");\n\n\tmMutex.Unlock();\n\tmNeedsLock = true;\n}\n\nCAMutex::Unlocker::~Unlocker()\n{\n\tif(mNeedsLock)\n\t{\n\t\tmMutex.Lock();\n\t}\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAMutex.h",
    "content": "/*\n     File: CAMutex.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#ifndef __CAMutex_h__\n#define __CAMutex_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <pthread.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n#else\n\t#error\tUnsupported operating system\n#endif\n\n//==================================================================================================\n//\tA recursive mutex.\n//==================================================================================================\n\nclass\tCAMutex\n{\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\tCAMutex(const char* inName);\n\tvirtual\t\t\t~CAMutex();\n\n//\tActions\npublic:\n\tvirtual bool\tLock();\n\tvirtual void\tUnlock();\n\tvirtual bool\tTry(bool& outWasLocked);\t// returns true if lock is free, false if not\n\t\n\tvirtual bool\tIsFree() const;\n\tvirtual bool\tIsOwnedByCurrentThread() const;\n\t\t\n//\tImplementation\nprotected:\n\tconst char*\t\tmName;\n#if TARGET_OS_MAC\n\tpthread_t\t\tmOwner;\n\tpthread_mutex_t\tmMutex;\n#elif TARGET_OS_WIN32\n\tUInt32\t\t\tmOwner;\n\tHANDLE\t\t\tmMutex;\n#endif\n\n//\tHelper class to manage taking and releasing recursively\npublic:\n\tclass\t\t\tLocker\n\t{\n\t\n\t//\tConstruction/Destruction\n\tpublic:\n\t\t\t\t\tLocker(CAMutex& inMutex) : mMutex(&inMutex), mNeedsRelease(false) { mNeedsRelease = mMutex->Lock(); }\n\t\t\t\t\tLocker(const CAMutex& inMutex) : mMutex(const_cast<CAMutex*>(&inMutex)), mNeedsRelease(false) { mNeedsRelease = mMutex->Lock(); }\n\t\t\t\t\tLocker(CAMutex* inMutex) : mMutex(inMutex), mNeedsRelease(false) { mNeedsRelease = (mMutex != NULL && mMutex->Lock()); }\n\t\t\t\t\t\t// in this case the mutex can be null\n\t\t\t\t\t~Locker() { if(mNeedsRelease) { mMutex->Unlock(); } }\n\t\n\t\n\tprivate:\n\t\t\t\t\tLocker(const Locker&);\n\t\tLocker&\t\toperator=(const Locker&);\n\t\n\t//\tImplementation\n\tprivate:\n\t\tCAMutex*\tmMutex;\n\t\tbool\t\tmNeedsRelease;\n\t\n\t};\n\n// Unlocker\n\tclass Unlocker\n\t{\n\tpublic:\n\t\t\t\t\t\tUnlocker(CAMutex& inMutex);\n\t\t\t\t\t\t~Unlocker();\n\t\t\n\tprivate:\n\t\tCAMutex&\tmMutex;\n\t\tbool\t\tmNeedsLock;\n\t\t\n\t\t// Hidden definitions of copy ctor, assignment operator\n\t\tUnlocker(const Unlocker& copy);\t\t\t\t// Not implemented\n\t\tUnlocker& operator=(const Unlocker& copy);\t// Not implemented\n\t};\n\t\n// you can use this with Try - if you take the lock in try, pass in the outWasLocked var\n\tclass Tryer {\n\t\n\t//\tConstruction/Destruction\n\tpublic:\n\t\tTryer (CAMutex &mutex) : mMutex(mutex), mNeedsRelease(false), mHasLock(false) { mHasLock = mMutex.Try (mNeedsRelease); }\n\t\t~Tryer () { if (mNeedsRelease) mMutex.Unlock(); }\n\t\t\n\t\tbool HasLock () const { return mHasLock; }\n\n\tprivate:\n\t\t\t\t\tTryer(const Tryer&);\n\t\tTryer&\t\toperator=(const Tryer&);\n\n\t//\tImplementation\n\tprivate:\n\t\tCAMutex &\t\tmMutex;\n\t\tbool\t\t\tmNeedsRelease;\n\t\tbool\t\t\tmHasLock;\n\t};\n};\n\n\n#endif // __CAMutex_h__\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAPThread.cpp",
    "content": "/*\n     File: CAPThread.cpp\n Abstract: CAPThread.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n//\tSelf Include\n#include \"CAPThread.h\"\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n#include \"CAException.h\"\n\n//\tSystem Includes\n#if\tTARGET_OS_MAC\n\t#include <mach/mach.h>\n#endif\n\n//\tStandard Library Includes\n#include <stdio.h>\n\n//==================================================================================================\n//\tCAPThread\n//==================================================================================================\n\n// returns the thread's priority as it was last set by the API\n#define CAPTHREAD_SET_PRIORITY\t\t\t\t0\n// returns the thread's priority as it was last scheduled by the Kernel\n#define CAPTHREAD_SCHEDULED_PRIORITY\t\t1\n\n//#define\tLog_SetPriority\t\t\t\t\t\t1\n\nCAPThread::CAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPriority, bool inFixedPriority, bool inAutoDelete, const char* inThreadName)\n:\n#if TARGET_OS_MAC\n\tmPThread(0),\n    mSpawningThreadPriority(getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY)),\n#elif TARGET_OS_WIN32\n\tmThreadHandle(NULL),\n\tmThreadID(0),\n#endif\n\tmThreadRoutine(inThreadRoutine),\n\tmThreadParameter(inParameter),\n\tmPriority(inPriority),\n\tmPeriod(0),\n\tmComputation(0),\n\tmConstraint(0),\n\tmIsPreemptible(true),\n\tmTimeConstraintSet(false),\n\tmFixedPriority(inFixedPriority),\n\tmAutoDelete(inAutoDelete)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\nCAPThread::CAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible, bool inAutoDelete, const char* inThreadName)\n:\n#if TARGET_OS_MAC\n\tmPThread(0),\n    mSpawningThreadPriority(getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY)),\n#elif TARGET_OS_WIN32\n\tmThreadHandle(NULL),\n\tmThreadID(0),\n#endif\n\tmThreadRoutine(inThreadRoutine),\n\tmThreadParameter(inParameter),\n\tmPriority(kDefaultThreadPriority),\n\tmPeriod(inPeriod),\n\tmComputation(inComputation),\n\tmConstraint(inConstraint),\n\tmIsPreemptible(inIsPreemptible),\n\tmTimeConstraintSet(true),\n\tmFixedPriority(false),\n\tmAutoDelete(inAutoDelete)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\nCAPThread::~CAPThread()\n{\n}\n\nUInt32\tCAPThread::GetScheduledPriority()\n{\n#if TARGET_OS_MAC\n    return CAPThread::getScheduledPriority( mPThread, CAPTHREAD_SCHEDULED_PRIORITY );\n#elif TARGET_OS_WIN32\n\tUInt32 theAnswer = 0;\n\tif(mThreadHandle != NULL)\n\t{\n\t\ttheAnswer = GetThreadPriority(mThreadHandle);\n\t}\n\treturn theAnswer;\n#endif\n}\n\nUInt32\tCAPThread::GetScheduledPriority(NativeThread thread)\n{\n#if TARGET_OS_MAC\n    return getScheduledPriority( thread, CAPTHREAD_SCHEDULED_PRIORITY );\n#elif TARGET_OS_WIN32\n\treturn 0;\t// ???\n#endif\n}\n\nvoid\tCAPThread::SetPriority(UInt32 inPriority, bool inFixedPriority)\n{\n\tmPriority = inPriority;\n\tmTimeConstraintSet = false;\n\tmFixedPriority = inFixedPriority;\n#if TARGET_OS_MAC\n\tif(mPThread != 0)\n\t{\n\t\tSetPriority(mPThread, mPriority, mFixedPriority);\n    } \n#elif TARGET_OS_WIN32\n\tif(mThreadID != NULL)\n\t{\n\t\tSetPriority(mThreadID, mPriority, mFixedPriority);\n\t}\n#endif\n}\n\nvoid\tCAPThread::SetPriority(NativeThread inThread, UInt32 inPriority, bool inFixedPriority)\n{\n#if TARGET_OS_MAC\n\tif(inThread != 0)\n\t{\n\t\tkern_return_t theError = 0;\n\t\t\n\t\t//\tset whether or not this is a fixed priority thread\n\t\tif (inFixedPriority)\n\t\t{\n\t\t\tthread_extended_policy_data_t theFixedPolicy = { false };\n\t\t\ttheError = thread_policy_set(pthread_mach_thread_np(inThread), THREAD_EXTENDED_POLICY, (thread_policy_t)&theFixedPolicy, THREAD_EXTENDED_POLICY_COUNT);\n\t\t\tAssertNoKernelError(theError, \"CAPThread::SetPriority: failed to set the fixed-priority policy\");\n\t\t}\n\t\t\n\t\t//\tset the thread's absolute priority which is relative to the priority on which thread_policy_set() is called\n\t\tUInt32 theCurrentThreadPriority = getScheduledPriority(pthread_self(), CAPTHREAD_SET_PRIORITY);\n        thread_precedence_policy_data_t thePrecedencePolicy = { static_cast<integer_t>(inPriority - theCurrentThreadPriority) };\n\t\ttheError = thread_policy_set(pthread_mach_thread_np(inThread), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&thePrecedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT);\n        AssertNoKernelError(theError, \"CAPThread::SetPriority: failed to set the precedence policy\");\n\t\t\n\t\t#if\tLog_SetPriority\n\t\t\tDebugMessageN4(\"CAPThread::SetPriority: requsted: %lu spawning: %lu current: %lu assigned: %d\", mPriority, mSpawningThreadPriority, theCurrentThreadPriority, thePrecedencePolicy.importance);\n\t\t#endif\n    } \n#elif TARGET_OS_WIN32\n\tif(inThread != NULL)\n\t{\n\t\tHANDLE hThread = OpenThread(NULL, FALSE, inThread);\n\t\tif(hThread != NULL) {\n\t\t\tSetThreadPriority(hThread, inPriority);\n\t\t\tCloseHandle(hThread);\n\t\t}\n\t}\n#endif\n}\n\nvoid\tCAPThread::SetTimeConstraints(UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible)\n{\n\tmPeriod = inPeriod;\n\tmComputation = inComputation;\n\tmConstraint = inConstraint;\n\tmIsPreemptible = inIsPreemptible;\n\tmTimeConstraintSet = true;\n#if TARGET_OS_MAC\n\tif(mPThread != 0)\n\t{\n\t\tthread_time_constraint_policy_data_t thePolicy;\n\t\tthePolicy.period = mPeriod;\n\t\tthePolicy.computation = mComputation;\n\t\tthePolicy.constraint = mConstraint;\n\t\tthePolicy.preemptible = mIsPreemptible;\n\t\tAssertNoError(thread_policy_set(pthread_mach_thread_np(mPThread), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&thePolicy, THREAD_TIME_CONSTRAINT_POLICY_COUNT), \"CAPThread::SetTimeConstraints: thread_policy_set failed\");\n\t}\n#elif TARGET_OS_WIN32\n\tif(mThreadHandle != NULL)\n\t{\n\t\tSetThreadPriority(mThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);\n\t}\n#endif\n}\n\nvoid\tCAPThread::Start()\n{\n#if TARGET_OS_MAC\n\tAssert(mPThread == 0, \"CAPThread::Start: can't start because the thread is already running\");\n\tif(mPThread == 0)\n\t{\n\t\tOSStatus\t\t\ttheResult;\n\t\tpthread_attr_t\t\ttheThreadAttributes;\n\t\t\n\t\ttheResult = pthread_attr_init(&theThreadAttributes);\n\t\tThrowIf(theResult != 0, CAException(theResult), \"CAPThread::Start: Thread attributes could not be created.\");\n\t\t\n\t\ttheResult = pthread_attr_setdetachstate(&theThreadAttributes, PTHREAD_CREATE_DETACHED);\n\t\tThrowIf(theResult != 0, CAException(theResult), \"CAPThread::Start: A thread could not be created in the detached state.\");\n\t\t\n\t\ttheResult = pthread_create(&mPThread, &theThreadAttributes, (ThreadRoutine)CAPThread::Entry, this);\n\t\tThrowIf(theResult != 0 || !mPThread, CAException(theResult), \"CAPThread::Start: Could not create a thread.\");\n\t\t\n\t\tpthread_attr_destroy(&theThreadAttributes);\n\t\t\n\t}\n#elif TARGET_OS_WIN32\n\tAssert(mThreadID == 0, \"CAPThread::Start: can't start because the thread is already running\");\n\tif(mThreadID == 0)\n\t{\n\t\t//\tclean up the existing thread handle\n\t\tif(mThreadHandle != NULL)\n\t\t{\n\t\t\tCloseHandle(mThreadHandle);\n\t\t\tmThreadHandle = NULL;\n\t\t}\n\t\t\n\t\t//\tcreate a new thread\n\t\tmThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Entry, this, 0, &mThreadID);\n\t\tThrowIf(mThreadHandle == NULL, CAException(GetLastError()), \"CAPThread::Start: Could not create a thread.\");\n\t}\n#endif\n}\n\n#if TARGET_OS_MAC\n\nvoid*\tCAPThread::Entry(CAPThread* inCAPThread)\n{\n\tvoid* theAnswer = NULL;\n\n#if TARGET_OS_MAC\n\tinCAPThread->mPThread = pthread_self();\n#elif TARGET_OS_WIN32\n\t// do we need to do something here?\n#endif\n\t\n#if\t!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)\n\tif(inCAPThread->mThreadName[0] != 0)\n\t{\n\t\tpthread_setname_np(inCAPThread->mThreadName);\n\t}\n#endif\n\n\ttry \n\t{\n\t\tif(inCAPThread->mTimeConstraintSet)\n\t\t{\n\t\t\tinCAPThread->SetTimeConstraints(inCAPThread->mPeriod, inCAPThread->mComputation, inCAPThread->mConstraint, inCAPThread->mIsPreemptible);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tinCAPThread->SetPriority(inCAPThread->mPriority, inCAPThread->mFixedPriority);\n\t\t}\n\n\t\tif(inCAPThread->mThreadRoutine != NULL)\n\t\t{\n\t\t\ttheAnswer = inCAPThread->mThreadRoutine(inCAPThread->mThreadParameter);\n\t\t}\n\t}\n\tcatch (...)\n\t{\n\t\t// what should be done here?\n\t}\n\tinCAPThread->mPThread = 0;\n\tif (inCAPThread->mAutoDelete)\n\t\tdelete inCAPThread;\n\treturn theAnswer;\n}\n\nUInt32 CAPThread::getScheduledPriority(pthread_t inThread, int inPriorityKind)\n{\n    thread_basic_info_data_t\t\t\tthreadInfo;\n\tpolicy_info_data_t\t\t\t\t\tthePolicyInfo;\n\tunsigned int\t\t\t\t\t\tcount;\n\n\tif (inThread == NULL)\n\t\treturn 0;\n    \n    // get basic info\n    count = THREAD_BASIC_INFO_COUNT;\n    thread_info (pthread_mach_thread_np (inThread), THREAD_BASIC_INFO, (thread_info_t)&threadInfo, &count);\n    \n\tswitch (threadInfo.policy) {\n\t\tcase POLICY_TIMESHARE:\n\t\t\tcount = POLICY_TIMESHARE_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_TIMESHARE_INFO, (thread_info_t)&(thePolicyInfo.ts), &count);\n            if (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) {\n                return static_cast<UInt32>(thePolicyInfo.ts.cur_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.ts.base_priority);\n            break;\n            \n        case POLICY_FIFO:\n\t\t\tcount = POLICY_FIFO_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_FIFO_INFO, (thread_info_t)&(thePolicyInfo.fifo), &count);\n            if ( (thePolicyInfo.fifo.depressed) && (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) ) {\n                return static_cast<UInt32>(thePolicyInfo.fifo.depress_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.fifo.base_priority);\n            break;\n            \n\t\tcase POLICY_RR:\n\t\t\tcount = POLICY_RR_INFO_COUNT;\n\t\t\tthread_info(pthread_mach_thread_np (inThread), THREAD_SCHED_RR_INFO, (thread_info_t)&(thePolicyInfo.rr), &count);\n\t\t\tif ( (thePolicyInfo.rr.depressed) && (inPriorityKind == CAPTHREAD_SCHEDULED_PRIORITY) ) {\n                return static_cast<UInt32>(thePolicyInfo.rr.depress_priority);\n            }\n            return static_cast<UInt32>(thePolicyInfo.rr.base_priority);\n            break;\n\t}\n    \n    return 0;\n}\n\n#elif TARGET_OS_WIN32\n\nUInt32 WINAPI\tCAPThread::Entry(CAPThread* inCAPThread)\n{\n\tUInt32 theAnswer = 0;\n\n\ttry \n\t{\n\t\tif(inCAPThread->mTimeConstraintSet)\n\t\t{\n\t\t\tinCAPThread->SetTimeConstraints(inCAPThread->mPeriod, inCAPThread->mComputation, inCAPThread->mConstraint, inCAPThread->mIsPreemptible);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tinCAPThread->SetPriority(inCAPThread->mPriority, inCAPThread->mFixedPriority);\n\t\t}\n\n\t\tif(inCAPThread->mThreadRoutine != NULL)\n\t\t{\n\t\t\ttheAnswer = reinterpret_cast<UInt32>(inCAPThread->mThreadRoutine(inCAPThread->mThreadParameter));\n\t\t}\n\t\tinCAPThread->mThreadID = 0;\n\t}\n\tcatch (...)\n\t{\n\t\t// what should be done here?\n\t}\n\tCloseHandle(inCAPThread->mThreadHandle);\n\tinCAPThread->mThreadHandle = NULL;\n\tif (inCAPThread->mAutoDelete)\n\t\tdelete inCAPThread;\n\treturn theAnswer;\n}\n\nextern \"C\"\nBoolean CompareAndSwap(UInt32 inOldValue, UInt32 inNewValue, UInt32* inOldValuePtr)\n{\n\treturn InterlockedCompareExchange((volatile LONG*)inOldValuePtr, inNewValue, inOldValue) == inOldValue;\n}\n\n#endif\n\nvoid\tCAPThread::SetName(const char* inThreadName)\n{\n\tif(inThreadName != NULL)\n\t{\n\t\tstrlcpy(mThreadName, inThreadName, kMaxThreadNameLength);\n\t}\n\telse\n\t{\n\t\tmemset(mThreadName, 0, kMaxThreadNameLength);\n\t}\n}\n\n#if CoreAudio_Debug\nvoid\tCAPThread::DebugPriority(const char *label)\n{\n#if !TARGET_OS_WIN32\n\tif (mTimeConstraintSet)\n\t\tprintf(\"CAPThread::%s %p: pri=<time constraint>, spawning pri=%d, scheduled pri=%d\\n\", label, this, \n\t\t(int)mSpawningThreadPriority, (mPThread != NULL) ? (int)GetScheduledPriority() : -1);\n\telse\n\t\tprintf(\"CAPThread::%s %p: pri=%d%s, spawning pri=%d, scheduled pri=%d\\n\", label, this, (int)mPriority, mFixedPriority ? \" fixed\" : \"\", \n\t\t(int)mSpawningThreadPriority, (mPThread != NULL) ? (int)GetScheduledPriority() : -1);\n#else\n\tif (mTimeConstraintSet)\n\t{\n\t\tprintf(\"CAPThread::%s %p: pri=<time constraint>, spawning pri=%d, scheduled pri=%d\\n\", label, this, \n\t\t(int)mPriority, (mThreadHandle != NULL) ? (int)GetScheduledPriority() : -1);\n\t}\n\telse\n\t{\n\t\tprintf(\"CAPThread::%s %p: pri=%d%s, spawning pri=%d, scheduled pri=%d\\n\", label, this, (int)mPriority, mFixedPriority ? \" fixed\" : \"\", \n\t\t(int)mPriority, (mThreadHandle != NULL) ? (int)GetScheduledPriority() : -1);\n\t}\n#endif\n}\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAPThread.h",
    "content": "/*\n     File: CAPThread.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPThread_h__)\n#define __CAPThread_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tSystem Includes\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreFoundation/CFBase.h>\n#else\n\t#include <CFBase.h>\n#endif\n\n#if TARGET_OS_MAC\n\t#include <pthread.h>\n\t#include <unistd.h>\n#elif TARGET_OS_WIN32\n\t#include <windows.h>\n#else\n\t#error\tUnsupported operating system\n#endif\n\n//==================================================================================================\n//\tCAPThread\n//\n//\tThis class wraps a pthread and a Win32 thread.\n//\tcaution: long-running fixed priority threads can make the system unresponsive\n//==================================================================================================\n\nclass\tCAPThread\n{\n\n//\tTypes\npublic:\n\ttypedef void*\t\t\t(*ThreadRoutine)(void* inParameter);\n\n//\tConstants\npublic:\n\tenum\n\t{\n#if\tTARGET_OS_MAC\n\t\t\t\t\t\t\tkMinThreadPriority = 1,\n\t\t\t\t\t\t\tkMaxThreadPriority = 63,\n\t\t\t\t\t\t\tkDefaultThreadPriority = 31,\n\t\t\t\t\t\t\tkMaxThreadNameLength = 64\n#elif TARGET_OS_WIN32\n\t\t\t\t\t\t\tkMinThreadPriority = 1,\n\t\t\t\t\t\t\tkMaxThreadPriority = 31,\n\t\t\t\t\t\t\tkDefaultThreadPriority = THREAD_PRIORITY_NORMAL,\n\t\t\t\t\t\t\tkMaxThreadNameLength = 256\n#endif\n\t};\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\tCAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPriority = kDefaultThreadPriority, bool inFixedPriority=false, bool inAutoDelete=false, const char* inThreadName = NULL);\n\t\t\t\t\t\t\tCAPThread(ThreadRoutine inThreadRoutine, void* inParameter, UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible, bool inAutoDelete=false, const char* inThreadName = NULL);\n\tvirtual\t\t\t\t\t~CAPThread();\n\n//\tProperties\npublic:\n#if TARGET_OS_MAC\n\ttypedef pthread_t\t\tNativeThread;\n\n\tNativeThread\t\t\tGetNativeThread() { return mPThread; }\n\tstatic NativeThread\t\tGetCurrentThread() { return pthread_self(); }\n\tstatic bool\t\t\t\tIsNativeThreadsEqual(NativeThread a, NativeThread b) { return (a==b); }\n\n\tbool\t\t\t\t\toperator==(NativeThread b) { return pthread_equal(mPThread,b); }\n\n\tpthread_t\t\t\t\tGetPThread() const { return mPThread; }\n\tbool\t\t\t\t\tIsCurrentThread() const { return (0 != mPThread) && (pthread_self() == mPThread); }\n\tbool\t\t\t\t\tIsRunning() const { return 0 != mPThread; }\n    static UInt32\t\t\tgetScheduledPriority(pthread_t inThread, int inPriorityKind);\n#elif TARGET_OS_WIN32\n\ttypedef unsigned long\tNativeThread;\n\t\n\tNativeThread\t\t\tGetNativeThread() { return mThreadID; }\n\tstatic NativeThread\t\tGetCurrentThread() { return GetCurrentThreadId(); }\n\tstatic bool\t\t\t\tIsNativeThreadsEqual(NativeThread a, NativeThread b) { return (a==b); }\n\n\tbool\t\t\t\t\toperator ==(NativeThread b) { return (mThreadID==b); }\n\n\tHANDLE\t\t\t\t\tGetThreadHandle() const { return mThreadHandle; }\n\tUInt32\t\t\t\t\tGetThreadID() const { return mThreadID; }\n\tbool\t\t\t\t\tIsCurrentThread() const { return (0 != mThreadID) && (GetCurrentThreadId() == mThreadID); }\n\tbool\t\t\t\t\tIsRunning() const { return 0 != mThreadID; }\n#endif\n\n\tbool\t\t\t\t\tIsTimeShareThread() const { return !mTimeConstraintSet; }\n\tbool\t\t\t\t\tIsTimeConstraintThread() const { return mTimeConstraintSet; }\n\n\tUInt32\t\t\t\t\tGetPriority() const { return mPriority; }\n    UInt32\t\t\t\t\tGetScheduledPriority();\n\tstatic UInt32\t\t\tGetScheduledPriority(NativeThread thread);\n    void\t\t\t\t\tSetPriority(UInt32 inPriority, bool inFixedPriority=false);\n\tstatic void\t\t\t\tSetPriority(NativeThread inThread, UInt32 inPriority, bool inFixedPriority = false);\n\n\tvoid\t\t\t\t\tGetTimeConstraints(UInt32& outPeriod, UInt32& outComputation, UInt32& outConstraint, bool& outIsPreemptible) const { outPeriod = mPeriod; outComputation = mComputation; outConstraint = mConstraint; outIsPreemptible = mIsPreemptible; }\n\tvoid\t\t\t\t\tSetTimeConstraints(UInt32 inPeriod, UInt32 inComputation, UInt32 inConstraint, bool inIsPreemptible);\n\tvoid\t\t\t\t\tClearTimeConstraints() { SetPriority(mPriority); }\n\t\n\tbool\t\t\t\t\tWillAutoDelete() const { return mAutoDelete; }\n\tvoid\t\t\t\t\tSetAutoDelete(bool b) { mAutoDelete = b; }\n\t\n\tvoid\t\t\t\t\tSetName(const char* inThreadName);\n\n#if CoreAudio_Debug\t\n\tvoid\t\t\t\t\tDebugPriority(const char *label);\n#endif\n\n//\tActions\npublic:\n\tvirtual void\t\t\tStart();\n\n//\tImplementation\nprotected:\n#if TARGET_OS_MAC\n\tstatic void*\t\t\tEntry(CAPThread* inCAPThread);\n#elif TARGET_OS_WIN32\n\tstatic UInt32 WINAPI\tEntry(CAPThread* inCAPThread);\n#endif\n\n#if\tTARGET_OS_MAC\n\tpthread_t\t\t\t\tmPThread;\n    UInt32\t\t\t\t\tmSpawningThreadPriority;\n#elif TARGET_OS_WIN32\n\tHANDLE\t\t\t\t\tmThreadHandle;\n\tunsigned long\t\t\tmThreadID;\n#endif\n\tThreadRoutine\t\t\tmThreadRoutine;\n\tvoid*\t\t\t\t\tmThreadParameter;\n\tchar\t\t\t\t\tmThreadName[kMaxThreadNameLength];\n\tUInt32\t\t\t\t\tmPriority;\n\tUInt32\t\t\t\t\tmPeriod;\n\tUInt32\t\t\t\t\tmComputation;\n\tUInt32\t\t\t\t\tmConstraint;\n\tbool\t\t\t\t\tmIsPreemptible;\n\tbool\t\t\t\t\tmTimeConstraintSet;\n    bool\t\t\t\t\tmFixedPriority;\n\tbool\t\t\t\t\tmAutoDelete;\t\t// delete self when thread terminates\n};\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAPropertyAddress.h",
    "content": "/*\n     File: CAPropertyAddress.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__CAPropertyAddress_h__)\n#define __CAPropertyAddress_h__\n\n//==================================================================================================\n//\tIncludes\n//==================================================================================================\n\n//\tPublicUtility Includes\n#include \"CADebugMacros.h\"\n\n//\tSystem Includes\n#include <CoreAudio/AudioHardware.h>\n\n//  Standard Library Includes\n#include <algorithm>\n#include <functional>\n#include <vector>\n\n//==================================================================================================\n//\tCAPropertyAddress\n//\n//  CAPropertyAddress extends the AudioObjectPropertyAddress structure to C++ including constructors\n//  and other utility operations. Note that there is no defined operator< or operator== because the\n//  presence of wildcards for the fields make comparisons ambiguous without specifying whether or\n//  not to take the wildcards into account. Consequently, if you want to use this struct in an STL\n//  data structure, you'll need to specify the approriate function object explicitly in the template\n//  declaration.\n//==================================================================================================\n\nstruct CAPropertyAddress\n:\n\tpublic AudioObjectPropertyAddress\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\tCAPropertyAddress()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = 0; mScope = kAudioObjectPropertyScopeGlobal; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = inSelector; mScope = kAudioObjectPropertyScopeGlobal; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector, AudioObjectPropertyScope inScope)\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress() { mSelector = inSelector; mScope = inScope; mElement = kAudioObjectPropertyElementMaster; }\n\t\t\t\t\t\tCAPropertyAddress(AudioObjectPropertySelector inSelector, AudioObjectPropertyScope inScope, AudioObjectPropertyElement inElement)   : AudioObjectPropertyAddress() { mSelector = inSelector; mScope = inScope; mElement = inElement; }\n\t\t\t\t\t\tCAPropertyAddress(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress(inAddress){}\n\t\t\t\t\t\tCAPropertyAddress(const CAPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: AudioObjectPropertyAddress(inAddress){}\n\tCAPropertyAddress&  operator=(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ AudioObjectPropertyAddress::operator=(inAddress); return *this; }\n\tCAPropertyAddress&  operator=(const CAPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ AudioObjectPropertyAddress::operator=(inAddress); return *this; }\n\t\n//  Operations\npublic:\n\tstatic bool\t\t\tIsSameAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t\t{ return (inAddress1.mScope == inAddress2.mScope) && (inAddress1.mSelector == inAddress2.mSelector) && (inAddress1.mElement == inAddress2.mElement); }\n\tstatic bool\t\t\tIsLessThanAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t{ bool theAnswer = false; if(inAddress1.mScope != inAddress2.mScope) { theAnswer = inAddress1.mScope < inAddress2.mScope; } else if(inAddress1.mSelector != inAddress2.mSelector) { theAnswer = inAddress1.mSelector < inAddress2.mSelector; } else { theAnswer = inAddress1.mElement < inAddress2.mElement; } return theAnswer; }\n\tstatic bool\t\t\tIsCongruentSelector(AudioObjectPropertySelector inSelector1, AudioObjectPropertySelector inSelector2)\t\t\t\t\t\t\t\t{ return (inSelector1 == inSelector2) || (inSelector1 == kAudioObjectPropertySelectorWildcard) || (inSelector2 == kAudioObjectPropertySelectorWildcard); }\n\tstatic bool\t\t\tIsCongruentScope(AudioObjectPropertyScope inScope1, AudioObjectPropertyScope inScope2)\t\t\t\t\t\t\t\t\t\t\t\t{ return (inScope1 == inScope2) || (inScope1 == kAudioObjectPropertyScopeWildcard) || (inScope2 == kAudioObjectPropertyScopeWildcard); }\n\tstatic bool\t\t\tIsCongruentElement(AudioObjectPropertyElement inElement1, AudioObjectPropertyElement inElement2)\t\t\t\t\t\t\t\t\t{ return (inElement1 == inElement2) || (inElement1 == kAudioObjectPropertyElementWildcard) || (inElement2 == kAudioObjectPropertyElementWildcard); }\n\tstatic bool\t\t\tIsCongruentAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t\t\t{ return IsCongruentScope(inAddress1.mScope, inAddress2.mScope) && IsCongruentSelector(inAddress1.mSelector, inAddress2.mSelector) && IsCongruentElement(inAddress1.mElement, inAddress2.mElement); }\n\tstatic bool\t\t\tIsCongruentLessThanAddress(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2)\t\t\t\t{ bool theAnswer = false; if(!IsCongruentScope(inAddress1.mScope, inAddress2.mScope)) { theAnswer = inAddress1.mScope < inAddress2.mScope; } else if(!IsCongruentSelector(inAddress1.mSelector, inAddress2.mSelector)) { theAnswer = inAddress1.mSelector < inAddress2.mSelector; } else if(!IsCongruentElement(inAddress1.mElement, inAddress2.mElement)) { theAnswer = inAddress1.mElement < inAddress2.mElement; } return theAnswer; }\n\n//  STL Helpers\npublic:\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\tstruct EqualTo : public std::binary_function<AudioObjectPropertyAddress, AudioObjectPropertyAddress, bool>\n\t{\n\t\tbool\toperator()(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2) const\t\t\t\t\t\t\t\t{ return IsSameAddress(inAddress1, inAddress2); }\n\t};\n\n\tstruct LessThan : public std::binary_function<AudioObjectPropertyAddress, AudioObjectPropertyAddress, bool>\n\t{\n\t\tbool\toperator()(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2) const\t\t\t\t\t\t\t\t{ return IsLessThanAddress(inAddress1, inAddress2); }\n\t};\n\n\tstruct CongruentEqualTo : public std::binary_function<AudioObjectPropertyAddress, AudioObjectPropertyAddress, bool>\n\t{\n\t\tbool\toperator()(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2) const\t\t\t\t\t\t\t\t{ return IsCongruentAddress(inAddress1, inAddress2); }\n\t};\n\n\tstruct CongruentLessThan : public std::binary_function<AudioObjectPropertyAddress, AudioObjectPropertyAddress, bool>\n\t{\n\t\tbool\toperator()(const AudioObjectPropertyAddress& inAddress1, const AudioObjectPropertyAddress& inAddress2) const\t\t\t\t\t\t\t\t{ return IsCongruentLessThanAddress(inAddress1, inAddress2); }\n\t};\n#pragma clang diagnostic pop\n\n};\n\n//==================================================================================================\n//  CAPropertyAddressList\n//\n//  An auto-resizing array of CAPropertyAddress structures.\n//==================================================================================================\n\nclass   CAPropertyAddressList\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressList()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(NULL) {}\n\texplicit\t\t\t\t\t\t\t\tCAPropertyAddressList(void* inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(inToken) {}\n\texplicit\t\t\t\t\t\t\t\tCAPropertyAddressList(uintptr_t inToken)\t\t\t\t\t\t\t\t\t\t\t\t: mAddressList(), mToken(reinterpret_cast<void*>(inToken)) {}\n\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressList(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t: mAddressList(inAddressList.mAddressList), mToken(inAddressList.mToken) {}\n\tCAPropertyAddressList&\t\t\t\t\toperator=(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t\t\t\t{ mAddressList = inAddressList.mAddressList; mToken = inAddressList.mToken; return *this; }\n\t\t\t\t\t\t\t\t\t\t\t~CAPropertyAddressList()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{}\n\n//\tOperations\npublic:\n\tvoid*\t\t\t\t\t\t\t\t\tGetToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mToken; }\n\tvoid\t\t\t\t\t\t\t\t\tSetToken(void* inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mToken = inToken; }\n\t\n\tuintptr_t\t\t\t\t\t\t\t\tGetIntToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return reinterpret_cast<uintptr_t>(mToken); }\n\tvoid\t\t\t\t\t\t\t\t\tSetIntToken(uintptr_t inToken)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mToken = reinterpret_cast<void*>(inToken); }\n\t\n\tAudioObjectID\t\t\t\t\t\t\tGetAudioObjectIDToken() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return static_cast<AudioObjectID>(reinterpret_cast<uintptr_t>(mToken)); }\n\t\n\tbool\t\t\t\t\t\t\t\t\tIsEmpty() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressList.empty(); }\n\tUInt32\t\t\t\t\t\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return ToUInt32(mAddressList.size()); }\n\tvoid\t\t\t\t\t\t\t\t\tGetItemByIndex(UInt32 inIndex, AudioObjectPropertyAddress& outAddress) const\t\t\t{ if(inIndex < mAddressList.size()) { outAddress = mAddressList.at(inIndex); } }\n\tconst AudioObjectPropertyAddress*\t\tGetItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return &(*mAddressList.begin()); }\n\tAudioObjectPropertyAddress*\t\t\t\tGetItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return &(*mAddressList.begin()); }\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\tbool\t\t\t\t\t\t\t\t\tHasItem(const AudioObjectPropertyAddress& inAddress) const\t\t\t\t\t\t\t\t{ AddressList::const_iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), std::bind1st(CAPropertyAddress::CongruentEqualTo(), inAddress)); return theIterator != mAddressList.end(); }\n\tbool\t\t\t\t\t\t\t\t\tHasExactItem(const AudioObjectPropertyAddress& inAddress) const\t\t\t\t\t\t\t{ AddressList::const_iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), std::bind1st(CAPropertyAddress::EqualTo(), inAddress)); return theIterator != mAddressList.end(); }\n#pragma clang diagnostic pop\n\n\tvoid\t\t\t\t\t\t\t\t\tAppendItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t\t{ mAddressList.push_back(inAddress); }\n\tvoid\t\t\t\t\t\t\t\t\tAppendUniqueItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t{ if(!HasItem(inAddress)) { mAddressList.push_back(inAddress); } }\n\tvoid\t\t\t\t\t\t\t\t\tAppendUniqueExactItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t{ if(!HasExactItem(inAddress)) { mAddressList.push_back(inAddress); } }\n\tvoid\t\t\t\t\t\t\t\t\tInsertItemAtIndex(UInt32 inIndex, const AudioObjectPropertyAddress& inAddress)\t\t\t{ if(inIndex < mAddressList.size()) { AddressList::iterator theIterator = mAddressList.begin(); std::advance(theIterator, static_cast<int>(inIndex)); mAddressList.insert(theIterator, inAddress); } else { mAddressList.push_back(inAddress); } }\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated\"\n\tvoid\t\t\t\t\t\t\t\t\tEraseExactItem(const AudioObjectPropertyAddress& inAddress)\t\t\t\t\t\t\t\t{ AddressList::iterator theIterator = std::find_if(mAddressList.begin(), mAddressList.end(), std::bind1st(CAPropertyAddress::EqualTo(), inAddress)); if(theIterator != mAddressList.end()) { mAddressList.erase(theIterator); } }\n#pragma clang diagnostic pop\n\tvoid\t\t\t\t\t\t\t\t\tEraseItemAtIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ if(inIndex < mAddressList.size()) { AddressList::iterator theIterator = mAddressList.begin(); std::advance(theIterator, static_cast<int>(inIndex)); mAddressList.erase(theIterator); } }\n\tvoid\t\t\t\t\t\t\t\t\tEraseAllItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mAddressList.clear(); }\n\n//  Implementation\nprivate:\n\ttypedef std::vector<CAPropertyAddress>  AddressList;\n\n\tAddressList\t\t\t\t\t\t\t\tmAddressList;\n\tvoid*\t\t\t\t\t\t\t\t\tmToken;\n\t\n};\n\n//==================================================================================================\n//  CAPropertyAddressListVector\n//\n//  An auto-resizing array of CAPropertyAddressList objects.\n//==================================================================================================\n\nclass\tCAPropertyAddressListVector\n{\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressListVector()\t\t\t\t\t\t\t\t\t\t\t\t\t\t: mAddressListVector() {}\n\t\t\t\t\t\t\t\t\t\t\t\tCAPropertyAddressListVector(const CAPropertyAddressListVector& inAddressListVector)\t: mAddressListVector(inAddressListVector.mAddressListVector) {}\n\tCAPropertyAddressListVector&\t\t\t\toperator=(const CAPropertyAddressListVector& inAddressListVector)\t\t\t\t\t{ mAddressListVector = inAddressListVector.mAddressListVector; return *this; }\n\t\t\t\t\t\t\t\t\t\t\t\t~CAPropertyAddressListVector()\t\t\t\t\t\t\t\t\t\t\t\t\t\t{}\n\n//\tOperations\npublic:\n\tbool\t\t\t\t\t\t\t\t\t\tIsEmpty() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.empty(); }\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyNonEmptyItems() const;\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyItemsWithAddress(const AudioObjectPropertyAddress& inAddress) const;\n\tbool\t\t\t\t\t\t\t\t\t\tHasAnyItemsWithExactAddress(const AudioObjectPropertyAddress& inAddress) const;\n\n\tUInt32\t\t\t\t\t\t\t\t\t\tGetNumberItems() const\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return ToUInt32(mAddressListVector.size()); }\n\tconst CAPropertyAddressList&\t\t\t\tGetItemByIndex(UInt32 inIndex) const\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.at(inIndex); }\n\tCAPropertyAddressList&\t\t\t\t\t\tGetItemByIndex(UInt32 inIndex)\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ return mAddressListVector.at(inIndex); }\n\tconst CAPropertyAddressList*\t\t\t\tGetItemByToken(void* inToken) const;\n\tCAPropertyAddressList*\t\t\t\t\t\tGetItemByToken(void* inToken);\n\tconst CAPropertyAddressList*\t\t\t\tGetItemByIntToken(uintptr_t inToken) const;\n\tCAPropertyAddressList*\t\t\t\t\t\tGetItemByIntToken(uintptr_t inToken);\n\t\n\tvoid\t\t\t\t\t\t\t\t\t\tAppendItem(const CAPropertyAddressList& inAddressList)\t\t\t\t\t\t\t\t{ mAddressListVector.push_back(inAddressList); }\n\tvoid\t\t\t\t\t\t\t\t\t\tEraseAllItems()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ mAddressListVector.clear(); }\n\t\n//  Implementation\nprivate:\n\ttypedef std::vector<CAPropertyAddressList>\tAddressListVector;\n\n\tAddressListVector\t\t\t\t\t\t\tmAddressListVector;\n\n};\n\ninline bool\tCAPropertyAddressListVector::HasAnyNonEmptyItems() const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = !theIterator->IsEmpty();\n\t}\n\treturn theAnswer;\n}\n\ninline bool\tCAPropertyAddressListVector::HasAnyItemsWithAddress(const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = theIterator->HasItem(inAddress);\n\t}\n\treturn theAnswer;\n}\n\ninline bool\tCAPropertyAddressListVector::HasAnyItemsWithExactAddress(const AudioObjectPropertyAddress& inAddress) const\n{\n\tbool theAnswer = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !theAnswer && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\ttheAnswer = theIterator->HasExactItem(inAddress);\n\t}\n\treturn theAnswer;\n}\n\ninline const CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByToken(void* inToken) const\n{\n\tconst CAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByToken(void* inToken)\n{\n\tCAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline const CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByIntToken(uintptr_t inToken) const\n{\n\tconst CAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::const_iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetIntToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\ninline CAPropertyAddressList*\tCAPropertyAddressListVector::GetItemByIntToken(uintptr_t inToken)\n{\n\tCAPropertyAddressList* theAnswer = NULL;\n\tbool wasFound = false;\n\tfor(AddressListVector::iterator theIterator = mAddressListVector.begin(); !wasFound && (theIterator != mAddressListVector.end()); ++theIterator)\n\t{\n\t\tif(theIterator->GetIntToken() == inToken)\n\t\t{\n\t\t\twasFound = true;\n\t\t\ttheAnswer = &(*theIterator);\n\t\t}\n\t}\n\treturn theAnswer;\n}\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CARingBuffer.cpp",
    "content": "/*\n     File: CARingBuffer.cpp\n Abstract: CARingBuffer.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#include \"CARingBuffer.h\"\n#include \"CABitOperations.h\"\n#include \"CAAutoDisposer.h\"\n#include \"CAAtomic.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <algorithm>\n#include <libkern/OSAtomic.h>\n\nCARingBuffer::CARingBuffer() :\n\tmBuffers(NULL), mNumberChannels(0), mCapacityFrames(0), mCapacityBytes(0)\n{\n\n}\n\nCARingBuffer::~CARingBuffer()\n{\n\tDeallocate();\n}\n\n\nvoid\tCARingBuffer::Allocate(int nChannels, UInt32 bytesPerFrame, UInt32 capacityFrames)\n{\n\tDeallocate();\n\t\n\tcapacityFrames = NextPowerOfTwo(capacityFrames);\n\t\n\tmNumberChannels = nChannels;\n\tmBytesPerFrame = bytesPerFrame;\n\tmCapacityFrames = capacityFrames;\n\tmCapacityFramesMask = capacityFrames - 1;\n\tmCapacityBytes = bytesPerFrame * capacityFrames;\n\n\t// put everything in one memory allocation, first the pointers, then the deinterleaved channels\n\tUInt32 allocSize = (mCapacityBytes + sizeof(Byte *)) * nChannels;\n\tByte *p = (Byte *)CA_malloc(allocSize);\n\tmemset(p, 0, allocSize);\n\tmBuffers = (Byte **)p;\n\tp += nChannels * sizeof(Byte *);\n\tfor (int i = 0; i < nChannels; ++i) {\n\t\tmBuffers[i] = p;\n\t\tp += mCapacityBytes;\n\t}\n\t\n\tfor (UInt32 i = 0; i<kGeneralRingTimeBoundsQueueSize; ++i)\n\t{\n\t\tmTimeBoundsQueue[i].mStartTime = 0;\n\t\tmTimeBoundsQueue[i].mEndTime = 0;\n\t\tmTimeBoundsQueue[i].mUpdateCounter = 0;\n\t}\n\tmTimeBoundsQueuePtr = 0;\n}\n\nvoid\tCARingBuffer::Deallocate()\n{\n\tif (mBuffers) {\n\t\tfree(mBuffers);\n\t\tmBuffers = NULL;\n\t}\n\tmNumberChannels = 0;\n\tmCapacityBytes = 0;\n\tmCapacityFrames = 0;\n}\n\ninline void ZeroRange(Byte **buffers, int nchannels, int offset, int nbytes)\n{\n\twhile (--nchannels >= 0) {\n\t\tmemset(*buffers + offset, 0, nbytes);\n\t\t++buffers;\n\t}\n}\n\ninline void StoreABL(Byte **buffers, int destOffset, const AudioBufferList *abl, int srcOffset, int nbytes)\n{\n\tint nchannels = abl->mNumberBuffers;\n\tconst AudioBuffer *src = abl->mBuffers;\n\twhile (--nchannels >= 0) {\n\t\tif (srcOffset > (int)src->mDataByteSize) continue;\n\t\tmemcpy(*buffers + destOffset, (Byte *)src->mData + srcOffset, std::min(nbytes, (int)src->mDataByteSize - srcOffset));\n\t\t++buffers;\n\t\t++src;\n\t}\n}\n\ninline void FetchABL(AudioBufferList *abl, int destOffset, Byte **buffers, int srcOffset, int nbytes)\n{\n\tint nchannels = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nchannels >= 0) {\n\t\tif (destOffset > (int)dest->mDataByteSize) continue;\n\t\tmemcpy((Byte *)dest->mData + destOffset, *buffers + srcOffset, std::min(nbytes, (int)dest->mDataByteSize - destOffset));\n\t\t++buffers;\n\t\t++dest;\n\t}\n}\n\ninline void ZeroABL(AudioBufferList *abl, int destOffset, int nbytes)\n{\n\tint nBuffers = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nBuffers >= 0) {\n\t\tif (destOffset > (int)dest->mDataByteSize) continue;\n\t\tmemset((Byte *)dest->mData + destOffset, 0, std::min(nbytes, (int)dest->mDataByteSize - destOffset));\n\t\t++dest;\n\t}\n}\n\n\nCARingBufferError\tCARingBuffer::Store(const AudioBufferList *abl, UInt32 framesToWrite, SampleTime startWrite)\n{\n\tif (framesToWrite == 0)\n\t\treturn kCARingBufferError_OK;\n\t\n\tif (framesToWrite > mCapacityFrames)\n\t\treturn kCARingBufferError_TooMuch;\t\t// too big!\n\n\tSampleTime endWrite = startWrite + framesToWrite;\n\t\n\tif (startWrite < EndTime()) {\n\t\t// going backwards, throw everything out\n\t\tSetTimeBounds(startWrite, startWrite);\n\t} else if (endWrite - StartTime() <= mCapacityFrames) {\n\t\t// the buffer has not yet wrapped and will not need to\n\t} else {\n\t\t// advance the start time past the region we are about to overwrite\n\t\tSampleTime newStart = endWrite - mCapacityFrames;\t// one buffer of time behind where we're writing\n\t\tSampleTime newEnd = std::max(newStart, EndTime());\n\t\tSetTimeBounds(newStart, newEnd);\n\t}\n\t\n\t// write the new frames\n\tByte **buffers = mBuffers;\n\tint nchannels = mNumberChannels;\n\tint offset0, offset1, nbytes;\n\tSampleTime curEnd = EndTime();\n\t\n\tif (startWrite > curEnd) {\n\t\t// we are skipping some samples, so zero the range we are skipping\n\t\toffset0 = FrameOffset(curEnd);\n\t\toffset1 = FrameOffset(startWrite);\n\t\tif (offset0 < offset1)\n\t\t\tZeroRange(buffers, nchannels, offset0, offset1 - offset0);\n\t\telse {\n\t\t\tZeroRange(buffers, nchannels, offset0, mCapacityBytes - offset0);\n\t\t\tZeroRange(buffers, nchannels, 0, offset1);\n\t\t}\n\t\toffset0 = offset1;\n\t} else {\n\t\toffset0 = FrameOffset(startWrite);\n\t}\n\n\toffset1 = FrameOffset(endWrite);\n\tif (offset0 < offset1)\n\t\tStoreABL(buffers, offset0, abl, 0, offset1 - offset0);\n\telse {\n\t\tnbytes = mCapacityBytes - offset0;\n\t\tStoreABL(buffers, offset0, abl, 0, nbytes);\n\t\tStoreABL(buffers, 0, abl, nbytes, offset1);\n\t}\n\t\n\t// now update the end time\n\tSetTimeBounds(StartTime(), endWrite);\n\t\n\treturn kCARingBufferError_OK;\t// success\n}\n\nvoid\tCARingBuffer::SetTimeBounds(SampleTime startTime, SampleTime endTime)\n{\n\tUInt32 nextPtr = mTimeBoundsQueuePtr + 1;\n\tUInt32 index = nextPtr & kGeneralRingTimeBoundsQueueMask;\n\t\n\tmTimeBoundsQueue[index].mStartTime = startTime;\n\tmTimeBoundsQueue[index].mEndTime = endTime;\n\tmTimeBoundsQueue[index].mUpdateCounter = nextPtr;\n\tCAAtomicCompareAndSwap32Barrier(mTimeBoundsQueuePtr, mTimeBoundsQueuePtr + 1, (SInt32*)&mTimeBoundsQueuePtr);\n}\n\nCARingBufferError\tCARingBuffer::GetTimeBounds(SampleTime &startTime, SampleTime &endTime)\n{\n\tfor (int i=0; i<8; ++i) // fail after a few tries.\n\t{\n\t\tUInt32 curPtr = mTimeBoundsQueuePtr;\n\t\tUInt32 index = curPtr & kGeneralRingTimeBoundsQueueMask;\n\t\tCARingBuffer::TimeBounds* bounds = mTimeBoundsQueue + index;\n\t\t\n\t\tstartTime = bounds->mStartTime;\n\t\tendTime = bounds->mEndTime;\n\t\tUInt32 newPtr = bounds->mUpdateCounter;\n\t\t\n\t\tif (newPtr == curPtr) \n\t\t\treturn kCARingBufferError_OK;\n\t}\n\treturn kCARingBufferError_CPUOverload;\n}\n\nCARingBufferError\tCARingBuffer::ClipTimeBounds(SampleTime& startRead, SampleTime& endRead)\n{\n\tSampleTime startTime, endTime;\n\t\n\tCARingBufferError err = GetTimeBounds(startTime, endTime);\n\tif (err) return err;\n\t\n\tif (startRead > endTime || endRead < startTime) {\n\t\tendRead = startRead;\n\t\treturn kCARingBufferError_OK;\n\t}\n\t\n\tstartRead = std::max(startRead, startTime);\n\tendRead = std::min(endRead, endTime);\n\tendRead = std::max(endRead, startRead);\n\t\t\n\treturn kCARingBufferError_OK;\t// success\n}\n\nCARingBufferError\tCARingBuffer::Fetch(AudioBufferList *abl, UInt32 nFrames, SampleTime startRead)\n{\n\tif (nFrames == 0)\n\t\treturn kCARingBufferError_OK;\n\t\t\n\tstartRead = std::max(0LL, startRead);\n\t\n\tSampleTime endRead = startRead + nFrames;\n\n\tSampleTime startRead0 = startRead;\n\tSampleTime endRead0 = endRead;\n\n\tCARingBufferError err = ClipTimeBounds(startRead, endRead);\n\tif (err) return err;\n\n\tif (startRead == endRead) {\n\t\tZeroABL(abl, 0, nFrames * mBytesPerFrame);\n\t\treturn kCARingBufferError_OK;\n\t}\n\t\n\tSInt32 byteSize = (SInt32)((endRead - startRead) * mBytesPerFrame);\n\t\n\tSInt32 destStartByteOffset = std::max((SInt32)0, (SInt32)((startRead - startRead0) * mBytesPerFrame)); \n\t\t\n\tif (destStartByteOffset > 0) {\n\t\tZeroABL(abl, 0, std::min((SInt32)(nFrames * mBytesPerFrame), destStartByteOffset));\n\t}\n\n\tSInt32 destEndSize = std::max((SInt32)0, (SInt32)(endRead0 - endRead)); \n\tif (destEndSize > 0) {\n\t\tZeroABL(abl, destStartByteOffset + byteSize, destEndSize * mBytesPerFrame);\n\t}\n\t\n\tByte **buffers = mBuffers;\n\tint offset0 = FrameOffset(startRead);\n\tint offset1 = FrameOffset(endRead);\n\tint nbytes;\n\t\n\tif (offset0 < offset1) {\n\t\tnbytes = offset1 - offset0;\n\t\tFetchABL(abl, destStartByteOffset, buffers, offset0, nbytes);\n\t} else {\n\t\tnbytes = mCapacityBytes - offset0;\n\t\tFetchABL(abl, destStartByteOffset, buffers, offset0, nbytes);\n\t\tFetchABL(abl, destStartByteOffset + nbytes, buffers, 0, offset1);\n\t\tnbytes += offset1;\n\t}\n\n\tint nchannels = abl->mNumberBuffers;\n\tAudioBuffer *dest = abl->mBuffers;\n\twhile (--nchannels >= 0)\n\t{\n\t\tdest->mDataByteSize = nbytes;\n\t\tdest++;\n\t}\n\n\treturn noErr;\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CARingBuffer.h",
    "content": "/*\n     File: CARingBuffer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple\n Inc. (\"Apple\") in consideration of your agreement to the following\n terms, and your use, installation, modification or redistribution of\n this Apple software constitutes acceptance of these terms.  If you do\n not agree with these terms, please do not use, install, modify or\n redistribute this Apple software.\n \n In consideration of your agreement to abide by the following terms, and\n subject to these terms, Apple grants you a personal, non-exclusive\n license, under Apple's copyrights in this original Apple software (the\n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple\n Software, with or without modifications, in source and/or binary forms;\n provided that if you redistribute the Apple Software in its entirety and\n without modifications, you must retain this notice and the following\n text and disclaimers in all such redistributions of the Apple Software.\n Neither the name, trademarks, service marks or logos of Apple Inc. may\n be used to endorse or promote products derived from the Apple Software\n without specific prior written permission from Apple.  Except as\n expressly stated in this notice, no other rights or licenses, express or\n implied, are granted by Apple herein, including but not limited to any\n patent rights that may be infringed by your derivative works or by other\n works in which the Apple Software may be incorporated.\n \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE\n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION\n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS\n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND\n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\n \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL\n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,\n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED\n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),\n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE\n POSSIBILITY OF SUCH DAMAGE.\n \n Copyright (C) 2014 Apple Inc. All Rights Reserved.\n \n*/\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n\n\n#ifndef CARingBuffer_Header\n#define CARingBuffer_Header\n\nenum {\n\tkCARingBufferError_OK = 0,\n\tkCARingBufferError_TooMuch = 3, // fetch start time is earlier than buffer start time and fetch end time is later than buffer end time\n\tkCARingBufferError_CPUOverload = 4 // the reader is unable to get enough CPU cycles to capture a consistent snapshot of the time bounds\n};\n\ntypedef SInt32 CARingBufferError;\n\nconst UInt32 kGeneralRingTimeBoundsQueueSize = 32;\nconst UInt32 kGeneralRingTimeBoundsQueueMask = kGeneralRingTimeBoundsQueueSize - 1;\n\nclass CARingBuffer {\npublic:\n\ttypedef SInt64 SampleTime;\n\n\tCARingBuffer();\n\t~CARingBuffer();\n\t\n\tvoid\t\t\t\t\tAllocate(int nChannels, UInt32 bytesPerFrame, UInt32 capacityFrames);\n\t\t\t\t\t\t\t\t// capacityFrames will be rounded up to a power of 2\n\tvoid\t\t\t\t\tDeallocate();\n\t\n\tCARingBufferError\tStore(const AudioBufferList *abl, UInt32 nFrames, SampleTime frameNumber);\n\t\t\t\t\t\t\t// Copy nFrames of data into the ring buffer at the specified sample time.\n\t\t\t\t\t\t\t// The sample time should normally increase sequentially, though gaps\n\t\t\t\t\t\t\t// are filled with zeroes. A sufficiently large gap effectively empties\n\t\t\t\t\t\t\t// the buffer before storing the new data. \n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// If frameNumber is less than the previous frame number, the behavior is undefined.\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// Return false for failure (buffer not large enough).\n\t\t\t\t\n\tCARingBufferError\tFetch(AudioBufferList *abl, UInt32 nFrames, SampleTime frameNumber);\n\t\t\t\t\t\t\t\t// will alter mDataByteSize of the buffers\n\t\n\tCARingBufferError\tGetTimeBounds(SampleTime &startTime, SampleTime &endTime);\n\t\nprotected:\n\n\tUInt32\t\t\t\t\tFrameOffset(SampleTime frameNumber) { return (frameNumber & mCapacityFramesMask) * mBytesPerFrame; }\n\n\tCARingBufferError\t\tClipTimeBounds(SampleTime& startRead, SampleTime& endRead);\n\t\n\t// these should only be called from Store.\n\tSampleTime\t\t\t\tStartTime() const { return mTimeBoundsQueue[mTimeBoundsQueuePtr & kGeneralRingTimeBoundsQueueMask].mStartTime; }\n\tSampleTime\t\t\t\tEndTime()   const { return mTimeBoundsQueue[mTimeBoundsQueuePtr & kGeneralRingTimeBoundsQueueMask].mEndTime; }\n\tvoid\t\t\t\t\tSetTimeBounds(SampleTime startTime, SampleTime endTime);\n\t\nprotected:\n\tByte **\t\t\t\t\tmBuffers;\t\t\t\t// allocated in one chunk of memory\n\tint\t\t\t\t\t\tmNumberChannels;\n\tUInt32\t\t\t\t\tmBytesPerFrame;\t\t\t// within one deinterleaved channel\n\tUInt32\t\t\t\t\tmCapacityFrames;\t\t// per channel, must be a power of 2\n\tUInt32\t\t\t\t\tmCapacityFramesMask;\n\tUInt32\t\t\t\t\tmCapacityBytes;\t\t\t// per channel\n\t\n\t// range of valid sample time in the buffer\n\ttypedef struct {\n\t\tvolatile SampleTime\t\tmStartTime;\n\t\tvolatile SampleTime\t\tmEndTime;\n\t\tvolatile UInt32\t\t\tmUpdateCounter;\n\t} TimeBounds;\n\t\n\tCARingBuffer::TimeBounds mTimeBoundsQueue[kGeneralRingTimeBoundsQueueSize];\n\tUInt32 mTimeBoundsQueuePtr;\n};\n\n\n\n#endif\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAVolumeCurve.cpp",
    "content": "/*\n     File: CAVolumeCurve.cpp \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#include \"CAVolumeCurve.h\"\n#include \"CADebugMacros.h\"\n#include <math.h>\n\n//=============================================================================\n//\tCAVolumeCurve\n//=============================================================================\n\nCAVolumeCurve::CAVolumeCurve()\n:\n\tmTag(0),\n\tmCurveMap(),\n\tmIsApplyingTransferFunction(true),\n\tmTransferFunction(kPow2Over1Curve),\n\tmRawToScalarExponentNumerator(2.0f),\n\tmRawToScalarExponentDenominator(1.0f)\n{\n}\n\nCAVolumeCurve::~CAVolumeCurve()\n{\n}\n\nSInt32\tCAVolumeCurve::GetMinimumRaw() const\n{\n\tSInt32 theAnswer = 0;\n\t\n\tif(!mCurveMap.empty())\n\t{\n\t\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\ttheAnswer = theIterator->first.mMinimum;\n\t}\n\t\n\treturn theAnswer;\n}\n\nSInt32\tCAVolumeCurve::GetMaximumRaw() const\n{\n\tSInt32 theAnswer = 0;\n\t\n\tif(!mCurveMap.empty())\n\t{\n\t\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\tstd::advance(theIterator, static_cast<int>(mCurveMap.size() - 1));\n\t\ttheAnswer = theIterator->first.mMaximum;\n\t}\n\t\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::GetMinimumDB() const\n{\n\tFloat32 theAnswer = 0;\n\t\n\tif(!mCurveMap.empty())\n\t{\n\t\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\ttheAnswer = theIterator->second.mMinimum;\n\t}\n\t\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::GetMaximumDB() const\n{\n\tFloat32 theAnswer = 0;\n\t\n\tif(!mCurveMap.empty())\n\t{\n\t\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\tstd::advance(theIterator, static_cast<int>(mCurveMap.size() - 1));\n\t\ttheAnswer = theIterator->second.mMaximum;\n\t}\n\t\n\treturn theAnswer;\n}\n\nvoid\tCAVolumeCurve::SetTransferFunction(UInt32 inTransferFunction)\n{\n\tmTransferFunction = inTransferFunction;\n\t\n\t//\tfigure out the co-efficients\n\tswitch(inTransferFunction)\n\t{\n\t\tcase kLinearCurve:\n\t\t\tmIsApplyingTransferFunction = false;\n\t\t\tmRawToScalarExponentNumerator = 1.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow1Over3Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 1.0f;\n\t\t\tmRawToScalarExponentDenominator = 3.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow1Over2Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 1.0f;\n\t\t\tmRawToScalarExponentDenominator = 2.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow3Over4Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 3.0f;\n\t\t\tmRawToScalarExponentDenominator = 4.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow3Over2Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 3.0f;\n\t\t\tmRawToScalarExponentDenominator = 2.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow2Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 2.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\t\n\t\tcase kPow3Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 3.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow4Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 4.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow5Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 5.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow6Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 6.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow7Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 7.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow8Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 8.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow9Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 9.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow10Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 10.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow11Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 11.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tcase kPow12Over1Curve:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 12.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t\t\n\t\tdefault:\n\t\t\tmIsApplyingTransferFunction = true;\n\t\t\tmRawToScalarExponentNumerator = 2.0f;\n\t\t\tmRawToScalarExponentDenominator = 1.0f;\n\t\t\tbreak;\n\t};\n}\n\nvoid\tCAVolumeCurve::AddRange(SInt32 inMinRaw, SInt32 inMaxRaw, Float32 inMinDB, Float32 inMaxDB)\n{\n\tCARawPoint theRaw(inMinRaw, inMaxRaw);\n\tCADBPoint theDB(inMinDB, inMaxDB);\n\t\n\tbool isOverlapped = false;\n\tbool isDone = false;\n\tCurveMap::iterator theIterator = mCurveMap.begin();\n\twhile((theIterator != mCurveMap.end()) && !isOverlapped && !isDone)\n\t{\n\t\tisOverlapped = CARawPoint::Overlap(theRaw, theIterator->first);\n\t\tisDone = theRaw >= theIterator->first;\n\t\t\n\t\tif(!isOverlapped && !isDone)\n\t\t{\n\t\t\tstd::advance(theIterator, 1);\n\t\t}\n\t}\n\t\n\tif(!isOverlapped)\n\t{\n\t\tmCurveMap.insert(CurveMap::value_type(theRaw, theDB));\n\t}\n\telse\n\t{\n\t\tDebugMessage(\"CAVolumeCurve::AddRange: new point overlaps\");\n\t}\n}\n\nvoid\tCAVolumeCurve::ResetRange()\n{\n\tmCurveMap.clear();\n}\n\nbool\tCAVolumeCurve::CheckForContinuity() const\n{\n\tbool theAnswer = true;\n\t\n\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\tif(theIterator != mCurveMap.end())\n\t{\n\t\tSInt32 theRaw = theIterator->first.mMinimum;\n\t\tFloat32 theDB = theIterator->second.mMinimum;\n\t\tdo\n\t\t{\n\t\t\tSInt32 theRawMin = theIterator->first.mMinimum;\n\t\t\tSInt32 theRawMax = theIterator->first.mMaximum;\n\t\t\tSInt32 theRawRange = theRawMax - theRawMin;\n\t\t\t\n\t\t\tFloat32 theDBMin = theIterator->second.mMinimum;\n\t\t\tFloat32 theDBMax = theIterator->second.mMaximum;\n\t\t\tFloat32 theDBRange = theDBMax - theDBMin;\n\n\t\t\ttheAnswer = theRaw == theRawMin;\n\t\t\ttheAnswer = theAnswer && (theDB == theDBMin);\n\t\t\t\n\t\t\ttheRaw += theRawRange;\n\t\t\ttheDB += theDBRange;\n\t\t\t\n\t\t\tstd::advance(theIterator, 1);\n\t\t}\n\t\twhile((theIterator != mCurveMap.end()) && theAnswer);\n\t}\n\t\n\treturn theAnswer;\n}\n\nSInt32\tCAVolumeCurve::ConvertDBToRaw(Float32 inDB) const\n{\n\t//\tclamp the value to the dB range\n\tFloat32 theOverallDBMin = GetMinimumDB();\n\tFloat32 theOverallDBMax = GetMaximumDB();\n\t\n\tif(inDB < theOverallDBMin) inDB = theOverallDBMin;\n\tif(inDB > theOverallDBMax) inDB = theOverallDBMax;\n\t\n\t//\tget the first entry in the curve map;\n\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\n\t//\tinitialize the answer to the minimum raw of the first item in the curve map\n\tSInt32 theAnswer = theIterator->first.mMinimum;\n\t\n\t//\titerate through the curve map until we run out of dB\n\tbool isDone = false;\n\twhile(!isDone && (theIterator != mCurveMap.end()))\n\t{\n\t\tSInt32 theRawMin = theIterator->first.mMinimum;\n\t\tSInt32 theRawMax = theIterator->first.mMaximum;\n\t\tSInt32 theRawRange = theRawMax - theRawMin;\n\t\t\n\t\tFloat32 theDBMin = theIterator->second.mMinimum;\n\t\tFloat32 theDBMax = theIterator->second.mMaximum;\n\t\tFloat32 theDBRange = theDBMax - theDBMin;\n\t\t\n\t\tFloat32 theDBPerRaw = theDBRange / static_cast<Float32>(theRawRange);\n\t\t\n\t\t//\tfigure out how many steps we are into this entry in the curve map\n\t\tif(inDB > theDBMax)\n\t\t{\n\t\t\t//\twe're past the end of this one, so add in the whole range for this entry\n\t\t\ttheAnswer += theRawRange;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\tit's somewhere within the current entry\n\t\t\t//\tfigure out how many steps it is\n\t\t\tFloat32 theNumberRawSteps = inDB - theDBMin;\n\t\t\ttheNumberRawSteps /= theDBPerRaw;\n\t\t\t\n\t\t\t//\tonly move in whole steps\n\t\t\ttheNumberRawSteps = roundf(theNumberRawSteps);\n\t\t\t\n\t\t\t//\tadd this many steps to the answer\n\t\t\ttheAnswer += static_cast<SInt32>(theNumberRawSteps);\n\t\t\t\n\t\t\t//\tmark that we are done\n\t\t\tisDone = true;\n\t\t}\n\t\t\n\t\t//\tgo to the next entry in the curve map\n\t\tstd::advance(theIterator, 1);\n\t}\n\t\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::ConvertRawToDB(SInt32 inRaw) const\n{\n\tFloat32 theAnswer = 0;\n\t\n\t//\tclamp the raw value\n\tSInt32 theOverallRawMin = GetMinimumRaw();\n\tSInt32 theOverallRawMax = GetMaximumRaw();\n\t\n\tif(inRaw < theOverallRawMin) inRaw = theOverallRawMin;\n\tif(inRaw > theOverallRawMax) inRaw = theOverallRawMax;\n\t\n\t//\tfigure out how many raw steps need to be taken from the first one\n\tSInt32 theNumberRawSteps = inRaw - theOverallRawMin;\n\n\t//\tget the first item in the curve map\n\tCurveMap::const_iterator theIterator = mCurveMap.begin();\n\t\n\t//\tinitialize the answer to the minimum dB of the first item in the curve map\n\ttheAnswer = theIterator->second.mMinimum;\n\t\n\t//\titerate through the curve map until we run out of steps\n\twhile((theNumberRawSteps > 0) && (theIterator != mCurveMap.end()))\n\t{\n\t\t//\tcompute some values\n\t\tSInt32 theRawMin = theIterator->first.mMinimum;\n\t\tSInt32 theRawMax = theIterator->first.mMaximum;\n\t\tSInt32 theRawRange = theRawMax - theRawMin;\n\t\t\n\t\tFloat32 theDBMin = theIterator->second.mMinimum;\n\t\tFloat32 theDBMax = theIterator->second.mMaximum;\n\t\tFloat32 theDBRange = theDBMax - theDBMin;\n\t\t\n\t\tFloat32 theDBPerRaw = theDBRange / static_cast<Float32>(theRawRange);\n\t\t\n\t\t//\tthere might be more steps than the current map entry accounts for\n\t\tSInt32 theRawStepsToAdd = std::min(theRawRange, theNumberRawSteps);\n\t\t\n\t\t//\tadd this many steps worth of db to the answer;\n\t\ttheAnswer += theRawStepsToAdd * theDBPerRaw;\n\t\t\n\t\t//\tfigure out how many steps are left\n\t\ttheNumberRawSteps -= theRawStepsToAdd;\n\t\t\n\t\t//\tgo to the next map entry\n\t\tstd::advance(theIterator, 1);\n\t}\n\t\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::ConvertRawToScalar(SInt32 inRaw) const\n{\n\t//\tget some important values\n\tFloat32\ttheDBMin = GetMinimumDB();\n\tFloat32\ttheDBMax = GetMaximumDB();\n\tFloat32\ttheDBRange = theDBMax - theDBMin;\n\tSInt32\ttheRawMin = GetMinimumRaw();\n\tSInt32\ttheRawMax = GetMaximumRaw();\n\tSInt32\ttheRawRange = theRawMax - theRawMin;\n\t\n\t//\trange the raw value\n\tif(inRaw < theRawMin) inRaw = theRawMin;\n\tif(inRaw > theRawMax) inRaw = theRawMax;\n\n\t//\tcalculate the distance in the range inRaw is\n\tFloat32 theAnswer = static_cast<Float32>(inRaw - theRawMin) / static_cast<Float32>(theRawRange);\n\n\t//\tonly apply a curve to the scalar values if the dB range is greater than 30\n\tif(mIsApplyingTransferFunction && (theDBRange > 30.0f))\n\t{\n\t\ttheAnswer = powf(theAnswer, mRawToScalarExponentNumerator / mRawToScalarExponentDenominator);\n\t}\n\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::ConvertDBToScalar(Float32 inDB) const\n{\n\tSInt32 theRawValue = ConvertDBToRaw(inDB);\n\tFloat32 theAnswer = ConvertRawToScalar(theRawValue);\n\treturn theAnswer;\n}\n\nSInt32\tCAVolumeCurve::ConvertScalarToRaw(Float32 inScalar) const\n{\n\t//\trange the scalar value\n\tinScalar = std::min(1.0f, std::max(0.0f, inScalar));\n\t\n\t//\tget some important values\n\tFloat32\ttheDBMin = GetMinimumDB();\n\tFloat32\ttheDBMax = GetMaximumDB();\n\tFloat32\ttheDBRange = theDBMax - theDBMin;\n\tSInt32\ttheRawMin = GetMinimumRaw();\n\tSInt32\ttheRawMax = GetMaximumRaw();\n\tSInt32\ttheRawRange = theRawMax - theRawMin;\n\t\n\t//\thave to undo the curve if the dB range is greater than 30\n\tif(mIsApplyingTransferFunction && (theDBRange > 30.0f))\n\t{\n\t\tinScalar = powf(inScalar, mRawToScalarExponentDenominator / mRawToScalarExponentNumerator);\n\t}\n\t\n\t//\tnow we can figure out how many raw steps this is\n\tFloat32 theNumberRawSteps = inScalar * static_cast<Float32>(theRawRange);\n\ttheNumberRawSteps = roundf(theNumberRawSteps);\n\t\n\t//\tthe answer is the minimum raw value plus the number of raw steps\n\tSInt32 theAnswer = theRawMin + static_cast<SInt32>(theNumberRawSteps);\n\t\n\treturn theAnswer;\n}\n\nFloat32\tCAVolumeCurve::ConvertScalarToDB(Float32 inScalar) const\n{\n\tSInt32 theRawValue = ConvertScalarToRaw(inScalar);\n\tFloat32 theAnswer = ConvertRawToDB(theRawValue);\n\treturn theAnswer;\n}\n"
  },
  {
    "path": "BGMDriver/PublicUtility/CAVolumeCurve.h",
    "content": "/*\n     File: CAVolumeCurve.h \n Abstract:  Part of CoreAudio Utility Classes  \n  Version: 1.0.1 \n  \n Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \n Inc. (\"Apple\") in consideration of your agreement to the following \n terms, and your use, installation, modification or redistribution of \n this Apple software constitutes acceptance of these terms.  If you do \n not agree with these terms, please do not use, install, modify or \n redistribute this Apple software. \n  \n In consideration of your agreement to abide by the following terms, and \n subject to these terms, Apple grants you a personal, non-exclusive \n license, under Apple's copyrights in this original Apple software (the \n \"Apple Software\"), to use, reproduce, modify and redistribute the Apple \n Software, with or without modifications, in source and/or binary forms; \n provided that if you redistribute the Apple Software in its entirety and \n without modifications, you must retain this notice and the following \n text and disclaimers in all such redistributions of the Apple Software. \n Neither the name, trademarks, service marks or logos of Apple Inc. may \n be used to endorse or promote products derived from the Apple Software \n without specific prior written permission from Apple.  Except as \n expressly stated in this notice, no other rights or licenses, express or \n implied, are granted by Apple herein, including but not limited to any \n patent rights that may be infringed by your derivative works or by other \n works in which the Apple Software may be incorporated. \n  \n The Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \n MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \n THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \n FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \n OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n  \n IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \n OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \n SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \n INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \n MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \n AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \n STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \n POSSIBILITY OF SUCH DAMAGE. \n  \n Copyright (C) 2013 Apple Inc. All Rights Reserved. \n  \n*/\n#if !defined(__CAVolumeCurve_h__)\n#define __CAVolumeCurve_h__\n\n//=============================================================================\n//\tIncludes\n//=============================================================================\n\n#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)\n\t#include <CoreAudio/CoreAudioTypes.h>\n#else\n\t#include <CoreAudioTypes.h>\n#endif\n#include <map>\n\n//=============================================================================\n//\tTypes\n//=============================================================================\n\nstruct CARawPoint\n{\n\tSInt32\tmMinimum;\n\tSInt32\tmMaximum;\n\t\n\tCARawPoint() : mMinimum(0), mMaximum(0) {}\n\tCARawPoint(const CARawPoint& inPoint) : mMinimum(inPoint.mMinimum), mMaximum(inPoint.mMaximum) {}\n\tCARawPoint(SInt32 inMinimum, SInt32 inMaximum) : mMinimum(inMinimum), mMaximum(inMaximum) {}\n\tCARawPoint&\toperator=(const CARawPoint& inPoint) { mMinimum = inPoint.mMinimum; mMaximum = inPoint.mMaximum; return *this; }\n\t\n\tstatic bool\tOverlap(const CARawPoint& x, const CARawPoint& y) { return (x.mMinimum < y.mMaximum) && (x.mMaximum > y.mMinimum); }\n};\n\ninline bool\toperator<(const CARawPoint& x, const CARawPoint& y) { return x.mMinimum < y.mMinimum; }\ninline bool\toperator==(const CARawPoint& x, const CARawPoint& y) { return (x.mMinimum == y.mMinimum) && (x.mMaximum == y.mMaximum); }\ninline bool\toperator!=(const CARawPoint& x, const CARawPoint& y) { return !(x == y); }\ninline bool\toperator<=(const CARawPoint& x, const CARawPoint& y) { return (x < y) || (x == y); }\ninline bool\toperator>=(const CARawPoint& x, const CARawPoint& y) { return !(x < y); }\ninline bool\toperator>(const CARawPoint& x, const CARawPoint& y) { return !((x < y) || (x == y)); }\n\nstruct CADBPoint\n{\n\tFloat32\tmMinimum;\n\tFloat32\tmMaximum;\n\t\n\tCADBPoint() : mMinimum(0), mMaximum(0) {}\n\tCADBPoint(const CADBPoint& inPoint) : mMinimum(inPoint.mMinimum), mMaximum(inPoint.mMaximum) {}\n\tCADBPoint(Float32 inMinimum, Float32 inMaximum) : mMinimum(inMinimum), mMaximum(inMaximum) {}\n\tCADBPoint&\toperator=(const CADBPoint& inPoint) { mMinimum = inPoint.mMinimum; mMaximum = inPoint.mMaximum; return *this; }\n\t\n\tstatic bool\tOverlap(const CADBPoint& x, const CADBPoint& y) { return (x.mMinimum < y.mMaximum) && (x.mMaximum >= y.mMinimum); }\n};\n\ninline bool\toperator<(const CADBPoint& x, const CADBPoint& y) { return x.mMinimum < y.mMinimum; }\ninline bool\toperator==(const CADBPoint& x, const CADBPoint& y) { return (x.mMinimum == y.mMinimum) && (x.mMaximum == y.mMaximum); }\ninline bool\toperator!=(const CADBPoint& x, const CADBPoint& y) { return !(x == y); }\ninline bool\toperator<=(const CADBPoint& x, const CADBPoint& y) { return (x < y) || (x == y); }\ninline bool\toperator>=(const CADBPoint& x, const CADBPoint& y) { return !(x < y); }\ninline bool\toperator>(const CADBPoint& x, const CADBPoint& y) { return !((x < y) || (x == y)); }\n\n//=============================================================================\n//\tCAVolumeCurve\n//=============================================================================\n\nclass CAVolumeCurve\n{\n\n//\tConstants\npublic:\n\tenum\n\t{\n\t\t\t\t\tkLinearCurve\t\t= 0,\n\t\t\t\t\tkPow1Over3Curve\t\t= 1,\n\t\t\t\t\tkPow1Over2Curve\t\t= 2,\n\t\t\t\t\tkPow3Over4Curve\t\t= 3,\n\t\t\t\t\tkPow3Over2Curve\t\t= 4,\n\t\t\t\t\tkPow2Over1Curve\t\t= 5,\n\t\t\t\t\tkPow3Over1Curve\t\t= 6,\n\t\t\t\t\tkPow4Over1Curve\t\t= 7,\n\t\t\t\t\tkPow5Over1Curve\t\t= 8,\n\t\t\t\t\tkPow6Over1Curve\t\t= 9,\n\t\t\t\t\tkPow7Over1Curve\t\t= 10,\n\t\t\t\t\tkPow8Over1Curve\t\t= 11,\n\t\t\t\t\tkPow9Over1Curve\t\t= 12,\n\t\t\t\t\tkPow10Over1Curve\t= 13,\n\t\t\t\t\tkPow11Over1Curve\t= 14,\n\t\t\t\t\tkPow12Over1Curve\t= 15\n\t};\n\n//\tConstruction/Destruction\npublic:\n\t\t\t\t\tCAVolumeCurve();\n\tvirtual\t\t\t~CAVolumeCurve();\n\n//\tAttributes\npublic:\n\tUInt32\t\t\tGetTag() const\t\t\t{ return mTag; }\n\tvoid\t\t\tSetTag(UInt32 inTag)\t{ mTag = inTag; }\n\tSInt32\t\t\tGetMinimumRaw() const;\n\tSInt32\t\t\tGetMaximumRaw() const;\n\tFloat32\t\t\tGetMinimumDB() const;\n\tFloat32\t\t\tGetMaximumDB() const;\n\t\n\tvoid\t\t\tSetIsApplyingTransferFunction(bool inIsApplyingTransferFunction)  { mIsApplyingTransferFunction = inIsApplyingTransferFunction; }\n\tUInt32\t\t\tGetTransferFunction() const { return mTransferFunction; }\n\tvoid\t\t\tSetTransferFunction(UInt32 inTransferFunction);\n\n//\tOperations\npublic:\n\tvoid\t\t\tAddRange(SInt32 mMinRaw, SInt32 mMaxRaw, Float32 inMinDB, Float32 inMaxDB);\n\tvoid\t\t\tResetRange();\n\tbool\t\t\tCheckForContinuity() const;\n\t\n\tSInt32\t\t\tConvertDBToRaw(Float32 inDB) const;\n\tFloat32\t\t\tConvertRawToDB(SInt32 inRaw) const;\n\tFloat32\t\t\tConvertRawToScalar(SInt32 inRaw) const;\n\tFloat32\t\t\tConvertDBToScalar(Float32 inDB) const;\n\tSInt32\t\t\tConvertScalarToRaw(Float32 inScalar) const;\n\tFloat32\t\t\tConvertScalarToDB(Float32 inScalar) const;\n\n//\tImplementation\nprivate:\n\ttypedef\tstd::map<CARawPoint, CADBPoint>\tCurveMap;\n\t\n\tUInt32\t\t\tmTag;\n\tCurveMap\t\tmCurveMap;\n\tbool\t\t\tmIsApplyingTransferFunction;\n\tUInt32\t\t\tmTransferFunction;\n\tFloat32\t\t\tmRawToScalarExponentNumerator;\n\tFloat32\t\t\tmRawToScalarExponentDenominator;\n\n};\n\n#endif\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<!-- vim: set tw=120: -->\n\n# Contributing\n\nFirstly, thanks for reading this. Pull requests, bug reports, feature requests, questions, etc. are all very welcome --\nincluding ones from non-developers.\n\n## Issues\n\nYou'll probably want to update to the latest version of the code before creating an issue. The easiest way is to just\nrun the installation command from [README.md](/README.md#install) again. (But `git pull && ./build_and_install.sh` is\nfaster.)\n\nFor bug reports about `build_and_install.sh`, please include your `build_and_install.log`. It should be saved in the\ndirectory `build_and_install.sh` is in.\n\nIt might also be helpful to include logs in bug reports about Background Music itself. Those logs go to syslog by\ndefault, so you can use Console.app to read them. (It might help to search for \"BGM\" or \"Background Music\".)\n\nYou also might not get any log messages at all. Normally (i.e. in release builds) Background Music only logs errors and\nwarnings. We're still working on adding optional debug-level logging to release builds.\n\nIf you feel like being really helpful, you could reproduce your bug with a debug build and include the debug logs, which\nare much more detailed. But don't feel obligated to. To install a debug build, use `./build_and_install.sh -d`.\n\nIf you make an issue and you're interested in implementing/fixing it yourself, mention that in the issue so we can\nconfirm you're on the right track, assign the issue to you and so on.\n\n## Code\n\nThe code is mostly C++ and Objective-C. But don't worry if you don't know those languages--I don't either. Or Core\nAudio, for that matter. Also don't worry if you're not sure your code is right.\n\nNo dependencies so far. (Though you're welcome to add some.)\n\nThe best place to start is probably [DEVELOPING.md](/DEVELOPING.md), which has an overview of the project and\ninstructions for building, debugging, etc. It's kind of long, though, and not very interesting, so you might prefer to\ngo straight into the code. In that case, you'll probably want to start with\n[BGMAppDelegate.mm](/BGMApp/BGMApp/BGMAppDelegate.mm).\n\nIf you get stuck or have questions about the project, feel free to open an issue. You could also [email\nme](mailto:kyle@bearisdriving.com) or try [#backgroundmusic on\nFreenode](https://webchat.freenode.net/?channels=backgroundmusic).\n\nIf you have questions related to Core Audio, the [Core Audio mailing\nlist](https://lists.apple.com/archives/coreaudio-api) is very useful. There's also the [Core Audio\nOverview](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html)\nand the [Core Audio\nGlossary](https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioGlossary/Glossary/core_audio_glossary.html).\n\nIf you remember to, add a copyright notice with your name to any source files you change substantially. Let us know in\nthe PR if you've intentionally not added one so we know not to add one for you.\n\n\n"
  },
  {
    "path": "DEVELOPING.md",
    "content": "<!-- vim: set tw=120: -->\n\n# Development Overview\n\nThe codebase is split into two projects: BGMDriver, a [userspace](https://en.wikipedia.org/wiki/User_space) [Core\nAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html)\n[HAL](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingAudioDrivers/AudioOnMacOSX/AudioOnMacOSX.html#//apple_ref/doc/uid/TP30000730-TPXREF104)\n[plugin](https://developer.apple.com/library/prerelease/content/samplecode/AudioDriverExamples/Listings/ReadMe_txt.html)\nthat publishes the virtual audio device<sup id=\"a1\">[1](#f1)</sup>, and BGMApp, which handles the UI, passing audio from\nthe virtual device to the real output device and a few other things. The virtual device is usually referred to as\n\"BGMDevice\" in the code.  Any code shared between the two projects is kept in the `SharedSource` dir.\n\n## Summary\n\nFrom the user's perspective, BGMDevice appears as one input device and one output device, both named \"Background Music\".\nThey're shown in `System Settings > Sound` along with the real audio devices.\n\nWhen you start BGMApp, it sets BGMDevice as your system's default output device so the system (i.e. Core Audio) will\nstart sending all<sup id=\"a2\">[2](#f2)</sup> your audio data to BGMDriver. BGMDriver plays that audio on BGMDevice's\ninput stream, and the user can record it by selecting the Background Music device in QuickTime the same way they'd select\na microphone.\n\nSo that you can still hear the audio, BGMApp starts listening to BGMDevice's input stream and playing the audio out of\nyour real output device. (See [BGMPlayThrough](BGMApp/BGMApp/BGMPlayThrough.h)). \n\nThe auto-pausing and per-app volume features rely on the fact that BGMDriver can also see the audio from each program\n(technically each \"client\") before Core Audio mixes it all together. When you change an app's volume, BGMApp sends the\nnew volume to BGMDriver, which applies the app volumes by modifying the apps' audio data directly.\n\nTo know when to pause your music player, first BGMApp tells BGMDriver which app you've set as your music player. Then\nwhen audio is playing BGMDriver tells BGMApp whether it's coming from your music player or another app. If it's from\nanother app, BGMApp tells your music player to pause if it's playing as well. When the other program stops playing\naudio, BGMDriver tells BGMApp and BGMApp unpauses your music.\n\n## Real-time Constraints\n\nOne slightly tricky part of this project is the code that runs with real-time constraints. When [the\nHAL](https://developer.apple.com/library/prerelease/content/documentation/DeviceDrivers/Conceptual/WritingAudioDrivers/AudioOnMacOSX/AudioOnMacOSX.html#//apple_ref/doc/uid/TP30000730-TPXREF104)\ncalls certain functions of ours--the IO functions in BGMDriver and the IOProcs in BGMApp--they have to return within a\ncertain amount of time, every time. So they can't do things that aren't guaranteed to be fast, even if they almost\nalways are.\n\nThey can't dynamically allocate memory because OS X doesn't specify a maximum time for that to take. They also can't use\nalgorithms with a fast enough average-case run time if their worst-case is too slow.  In the `BGM_Device` class, they\ncan't lock the state mutex because a non-real-time function might be holding it and there's no guarantee it'll be\nreleased in time.\n\nIf you're interested, have a look at [Real-time Audio Programming 101: Time Waits for\nNothing](http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing).\n\n## BGMDriver\n\nThe BGMDriver project is an audio driver for a virtual audio device named \"Background Music\", which we use to intercept\nthe audio playing on the user's system. The driver processes the audio data to apply per-app volumes, see if the music\nplayer is playing, etc. and then writes the audio to BGMDevice's input stream. It's essentially a loopback device with a\nfew extra features.\n\nThere are quite a few other open-source projects with drivers that do the same\nthing--[Soundflower](https://github.com/mattingalls/Soundflower) is probably the most well known--but as far as I know\nall of those drivers were either written as kernel extensions or using AudioHardwarePlugIn, which is now deprecated\nbecause of [issues with the OS X sandbox](https://lists.apple.com/archives/coreaudio-api/2013/Aug/msg00030.html). The\nApple sample code we started from gives us most of the same functionality and uses the latest Core Audio APIs. The other\nprojects are still definitely worth reading, though. There's a list in [the README](README.md) with a few of them.\n\nBGMDriver is an AudioServerPlugin (see\n[CoreAudio/AudioServerPlugIn.h](https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.11.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioServerPlugIn.h))\nbased on [Apple's SimpleAudio\nexample](https://developer.apple.com/library/mac/samplecode/AudioDriverExamples/Introduction/Intro.html).  An\nAudioServerPlugIn is a Core Audio driver that runs in userspace and is \"hosted\" by the HAL, which is nice because the\nHAL handles a lot of things for us. Only running in userspace means our bugs shouldn't be able to cause a kernel panic\n(though we can definitely crash the audio system) and users don't have to restart after installation. It also makes\ndebugging much less painful and is less insecure. In addition to running in userspace, the plugin also runs in a fairly\nrestrictive sandbox.\n\n`BGM_PlugInInterface.cpp` is where you'll find the entry point functions that the HAL calls, so it's a good place to\nstart. Those functions are our implementation of the interface defined by `AudioServerPlugIn.h`. They're mostly\nboilerplate and error handling code, and largely unchanged from the sample code. They call `BGM_PlugIn` or `BGM_Device`\n(and probably other subclasses of `BGM_Object` in future) to do the actual work.\n\n`BGM_Device` is by far the largest class, and should really be split up, but most of its code is very straightforward.\nThe simple parts mostly handle audio object properties, which are often static. An audio object is a plugin, device,\nstream, control (e.g. volume), etc. and is part of the HAL's object model. Each object is identified by an\n`AudioObjectID`. When the HAL asks us for the value of a property (or the size of the value, whether the value can be\nset, etc.) `BGM_Device` handles properties relating to our virtual device, its controls and its streams.\n\nThe rest of `BGM_Device` mostly handles IO. During each IO cycle, the HAL calls us for each phase of the cycle we\nsupport--reading input, writing output, etc. When the user has our device set as default, we receive the system's audio\nduring the read-input phase and store it in our ring buffer. Then in the write-output phase (technically, the\n`ProcessOutput` and `WriteMix` phases) we process the data and write from our ring buffer to our output stream. By\n\"process\" I just mean that we apply the per-app volume, keep track of whether the audio is audible or not, and other\nthings like that.\n\nBGMDriver's IO functions have to be real-time safe, which means any functions they call do as well. Some other functions\nneed to be real-time safe as well because they access data shared with the IO functions and have to do so on a real-time\nthread to avoid [priority inversion](https://en.wikipedia.org/wiki/Priority_inversion). Those functions are usually\ncalled using [BGM_TaskQueue](BGMDriver/BGMDriver/BGM_TaskQueue.h), which can dispatch calls to a real-time worker\nthread. BGM_TaskQueue can also be used from a real-time thread to asynchronously dispatch calls to functions that aren't\nreal-time safe.\n\n### Building and Debugging\n\nTo test your changes, build `Background Music Device.driver`, either inside Xcode (set the active scheme to \"BGMDevice\",\ngo `Product > Build For > Running` and look in the `products` folder) or with something like\n```shell\nxcodebuild -project BGMDriver/BGMDriver.xcodeproj -target \"PublicUtility\" -configuration Debug\nxcodebuild -project BGMDriver/BGMDriver.xcodeproj -configuration Debug\n```\nAnd then run `BGMDriver/BGMDriver/quick_install.sh` to install. Or if you'd rather install manually, copy `Background\nMusic Device.driver` to `/Library/Audio/Plug-Ins/HAL` and restart coreaudiod.\n\nYou might have to delete `BGMDriver/build` first if you're using `xcodebuild` and run into permissions problems.\n\nBefore you build, Xcode might show incorrect warnings on the `#pragma clang assume_nonnull` lines for some reason. They\ngo away after you build and don't seem to cause any problems.\n\n**The following debug instructions stopped working in OS X 10.11 (El Capitan).** [System Integrity\nProtection](https://support.apple.com/en-us/HT204899) stops LLDB from attaching to coreaudiod. I don't know of a\nworkaround except to disable SIP.\n\nTo debug in Xcode,\n - edit the BGMDevice scheme and\n    - set the build configuration to Debug,\n    - set \"Executable\" to coreaudiod in `/usr/sbin` (you can use `shift+cmd+G` to get there),\n    - check \"Debug Executable\",\n    - set the \"debug as\" user to root,\n    - and select \"wait for executable to be launched\",\n - set BGMDevice as the active scheme,\n - build and install,\n - stop coreaudiod\n\n   ```shell\n   sudo launchctl unload /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist\n   ```\n - run in Xcode,\n - start coreaudiod\n\n   ```shell\n   sudo launchctl load /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist\n   ```\n\nXcode should attach to the coreaudiod process when it starts running.\n\nDebug logging is to syslog by default. Console.app is probably the most convenient way to read it.\n\n### HALLab\n\nApple's HALLab tool can be useful for inspecting the driver's properties, notifications, etc. It's in the Audio Tools\nfor Xcode package, which you can find in the [Apple developer downloads](https://developer.apple.com/downloads/).\n\n## BGMApp\n\nBGMApp is a fairly standard Cocoa status-bar app, for the most part. The UI is simple and mostly built in Interface\nBuilder.\n\n`awakeFromNib` in [BGMAppDelegate.mm](/BGMApp/BGMApp/BGMAppDelegate.mm) is (more or less) the entry point/main\nfunction. `applicationDidFinishLaunching` gets called next and finishes setting things up.\n\nAt launch, BGMApp sets BGMDevice as the system's default device and starts playing the audio from BGMDevice through the\nactual output device. Usually that's the device that BGMDevice replaced when we set it as the default device. When\nBGMApp closes, it sets that device back as the default device.\n\nBGMApp stores a small amount of state data using [User\nDefaults](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/UserDefaults/AboutPreferenceDomains/AboutPreferenceDomains.html)\n-- currently just whether auto-pause is enabled and which music player to pause. Other persistent state (e.g. app\nvolumes) is managed by BGMDriver.\n\nBGMApp mostly communicates with BGMDriver through HAL notifications, though in some special cases we use XPC instead.\nThe only other communication between them is BGMDriver sending the system's audio data through BGMDevice's output\nstream, which BGMApp receives.\n\nFor example, when an app other than the music player starts playing audio, BGMDriver sends out a notification saying\nthat BGMDevice's `kAudioDeviceCustomPropertyDeviceAudibleState` property has changed.  BGMApp receives the notification\nfrom the HAL and decides whether it should pause the music player.\n\nOur custom notifications are defined/documented in `BGM_Types.h`, which is shared between the two projects.\n\nBGMApp also keeps the output device in sync with BGMDevice. For example, since BGMDevice is set as the default device,\nwhen the user changes their system volume only BGMDevice's volume will change. So BGMApp listens for changes to\nBGMDevice's volume and sets the output device's volume to match. \n\nThe only code in BGMApp that has to be real-time safe is in `BGMPlayThrough`'s IOProcs, `InputDeviceIOProc` and\n`OutputDeviceIOProc`, which don't do very much. The most complicated part of BGMApp is probably pausing/reducing IO when\nno other processes are playing audio, which is also handled in `BGMPlayThrough`.\n\n### BGMXPCHelper\n\nFrom [main.m](BGMApp/BGMXPCHelper/main.m):\n\n>BGMXPCHelper passes XPC messages between BGMDriver and BGMApp. So far it's only used for synchronization while starting\n>IO.\n\n>BGMApp and BGMDriver usually communicate by changing device properties and listening for notifications about those\n>changes, which the HAL sends. We use XPC, or plan to use it, for the few cases that notifications don't suit.\n\n### Building\n\nBuild and install/run BGMXPCHelper and `Background Music.app` either inside Xcode or with something like\n```shell\nsudo xcodebuild -project BGMApp/BGMApp.xcodeproj \\\n                -target BGMXPCHelper \\\n                DSTROOT=\"/\" \\\n                INSTALL_PATH=\"$(BGMApp/BGMXPCHelper/safe_install_dir.sh)\" \\\n                -configuration Debug \\\n                install\nsudo chown -R $(whoami):staff BGMApp/build  # Fix build dir ownership\nxcodebuild -project BGMApp/BGMApp.xcodeproj \\\n           -configuration Debug\nopen \"BGMApp/build/Debug/Background Music.app\"\n```\n\nYou might have to delete `BGMApp/build` first if you're using `xcodebuild` and run into permissions problems.\n\nTo test with Address Sanitizer, you might have to set the environment var `ASAN_OPTIONS=detect_odr_violation=0` to work\naround [Issue #647](https://github.com/google/sanitizers/issues/647). (In Xcode, go `Product` > `Scheme` > `Edit\nScheme...`, select the Background Music scheme, and add the environment var in Run > Arguments.)\n\n----\n\n<b id=\"f1\">[1]</b> It actually publishes two devices -- the main one and one for UI-related sounds, but you probably\nonly need to know about the main one. [↩](#a1)\n\n<b id=\"f2\">[2]</b> All, unless you're playing audio through a program that's set to always use a specific device or,\nfor some reason, doesn't switch to the new default device right away. [↩](#a2)\n\n\n"
  },
  {
    "path": "Images/FermataIcon.tex",
    "content": "% This file is part of Background Music.\n%\n% Background Music is free software: you can redistribute it and/or\n% modify it under the terms of the GNU General Public License as\n% published by the Free Software Foundation, either version 2 of the\n% License, or (at your option) any later version.\n%\n% Background Music is distributed in the hope that it will be useful,\n% but WITHOUT ANY WARRANTY; without even the implied warranty of\n% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n% GNU General Public License for more details.\n%\n% You should have received a copy of the GNU General Public License\n% along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n%\n% FermataIcon.tex\n% Copyright © 2016 Kyle Neideck\n%\n% The app/device/status bar icon for BGMApp\n%\n% Build with XeTeX:\n%   xelatex FermataIcon.tex\n%\n% Then run generate_icon_pngs.sh\n%\n% Not sure why it doesn't build properly with regular LaTeX.\n%\n\n\\documentclass{article}\n\\thispagestyle{empty}\n\n\\usepackage{tikz}\n\\usepackage[paperwidth=100mm, paperheight=100mm, margin=0mm]{geometry}\n\n\\begin{document}\n\\begin{center}\n\\begin{tikzpicture}[remember picture,overlay]\n\n% A path that follows the edges of the current page\n\\tikzstyle{reverseclip}=[insert path={(current page.north east) --\n  (current page.south east) --\n  (current page.south west) --\n  (current page.north west) --\n  (current page.north east)}\n]\n\n\\begin{scope}\n\\begin{pgfinterruptboundingbox}\n\\path [clip] (0,-62.6mm) circle (33mm) [reverseclip];\n\\end{pgfinterruptboundingbox}\n\n\\fill[black] (0,-50mm) circle (44mm);\n\\end{scope}\n\n\\begin{scope}\n\\fill[black] (0,-68.5mm) circle (26mm);\n\\end{scope}\n\n\\end{tikzpicture}\n\\end{center}\n\\end{document}\n\n"
  },
  {
    "path": "Images/VolumeIcons.tex",
    "content": "% This file is part of Background Music.\n%\n% Background Music is free software: you can redistribute it and/or\n% modify it under the terms of the GNU General Public License as\n% published by the Free Software Foundation, either version 2 of the\n% License, or (at your option) any later version.\n%\n% Background Music is distributed in the hope that it will be useful,\n% but WITHOUT ANY WARRANTY; without even the implied warranty of\n% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n% GNU General Public License for more details.\n%\n% You should have received a copy of the GNU General Public License\n% along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n%\n% VolumeIcons.tex\n%\n% Build with XeTeX:\n%   xelatex -jobname=Volume0 '\\def\\UseOption{}\\input{VolumeIcons.tex}'\n%   xelatex -jobname=Volume1 '\\def\\UseOption{w1}\\input{VolumeIcons.tex}'\n%   xelatex -jobname=Volume2 '\\def\\UseOption{w1,w2}\\input{VolumeIcons.tex}'\n%   xelatex -jobname=Volume3 '\\def\\UseOption{w1,w2,w3}\\input{VolumeIcons.tex}'\n%   for n in 0 1 2 3; do mv Volume$n.pdf ../BGMApp/BGMApp/Images.xcassets/Volume$n.imageset/; done\n%\n% Might build correctly with regular LaTeX. I haven't tried it.\n%\n\n\\documentclass[tikz]{standalone}\n\\usepackage{tikz}\n% \"dummyOption\" prevents \"Package optional Warning: No options were selected,\n% so all optional text will be printed\" when building Volume0.pdf.\n\\usepackage[dummyOption]{optional}\n\n\\begin{document}\n\\begin{tikzpicture}\n\n% Speaker (Rounded box and triangle)\n\\fill[rounded corners=5mm]\n    (0mm, 62.5mm) rectangle (25mm, 37.5mm) {};\n\\draw[rounded corners=2.5mm,fill=black]\n    (3mm, 50mm)--(34mm, 76.5mm)--(34mm, 23.5mm)--cycle;\n\n% First sound wave (Curved line)\n\\opt{w1}{\n    \\draw[line width=4.3mm,line cap=round]\n        (44mm, 36.5mm) to[out=46,in=-46] (44mm, 63.5mm);\n}\n\n% Second sound wave (Curved line)\n\\opt{w2}{\n    \\draw[line width=4.3mm,line cap=round]\n        (57.5mm, 27.5mm) to[out=46,in=-46] (57.5mm, 72.5mm);\n}\n\n% Third sound wave (Curved line)\n\\opt{w3}{\n    \\draw[line width=4.3mm,line cap=round]\n        (72mm, 18.5mm) to[out=46,in=-46] (72mm, 81.5mm);\n}\n\n% Always draw a transparent copy of the third wave so the images will all have\n% the same width.\n\\draw[line width=4.3mm,line cap=round,opacity=0]\n    (72mm, 18.5mm) to[out=46,in=-46] (72mm, 81.5mm);\n\n\\end{tikzpicture}\n\\end{document}\n\n"
  },
  {
    "path": "Images/generate_icon_pngs.sh",
    "content": "#!/bin/bash\n# vim: tw=0:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# generate_icon_pngs.sh\n# Copyright © 2016 Kyle Neideck\n#\n# Creates the app and status bar icons for BGMApp in Images.xcassets, and DeviceIcon.icns\n# for BGMDriver, from FermataIcon.pdf\n#\n# Requires ImageMagick (for iconizer.sh)\n#\n\n# Safe mode\nset -euo pipefail\nIFS=$'\\n\\t'\n\necho Copying FermataIcon.pdf into FermataIcon.imageset for the status bar icon\necho ----\n\n(set -x; cp FermataIcon.pdf ../BGMApp/BGMApp/Images.xcassets/FermataIcon.imageset/)\n\necho\necho Generating app icon for BGMApp\necho ----\n\ncp ../BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset/Contents.json \\\n    ../BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset/Contents.json.brb\n\nsh iconizer.sh FermataIcon.pdf ../BGMApp/BGMApp\n\n# Delete unused sizes\ncd ../BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset/\n\nrm appicon_114.png appicon_144.png appicon_180.png appicon_80.png appicon_100.png appicon_120.png appicon_152.png appicon_40.png appicon_57.png appicon_72.png appicon_87.png appicon_29.png appicon_50.png appicon_58.png appicon_76.png\n\nmv Contents.json.brb Contents.json\n\ncd - > /dev/null\n\necho\necho Generating DeviceIcon.icns for BGMDriver\necho ----\n\nset -x\n\ncp -r ../BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset ../BGMDriver/BGMDriver/DeviceIcon.iconset\n\ncd ../BGMDriver/BGMDriver/DeviceIcon.iconset\n\nmv appicon_1024.png icon_512x512@2x.png\nmv appicon_512.png icon_512x512.png\ncp icon_512x512.png icon_256x256@2x.png\nmv appicon_256.png icon_256x256.png\ncp icon_256x256.png icon_128x128@2x.png\nmv appicon_128.png icon_128x128.png\nmv appicon_64.png icon_32x32@2x.png\nmv appicon_32.png icon_32x32.png\ncp icon_32x32.png icon_16x16@2x.png\nmv appicon_16.png icon_16x16.png\n\ncd -\n\niconutil -c icns -o ../BGMDriver/BGMDriver/DeviceIcon.icns ../BGMDriver/BGMDriver/DeviceIcon.iconset\n\n# Fail if the .icns wasn't created\nls ../BGMDriver/BGMDriver/DeviceIcon.icns\n\nrm -r ../BGMDriver/BGMDriver/DeviceIcon.iconset\n\n\n"
  },
  {
    "path": "Images/iconizer.sh",
    "content": "#!/bin/sh\n\n#\n# Iconizer shell script by Steve Richey (srichey@floatlearning.com)\n#\n# This is a simple tool to generate all necessary app icon sizes and the JSON file for an *EXISTING* Xcode project from one file.\n# To use: specify the path to your vector graphic (PDF format) and the path to your Xcode folder containing Images.xcassets\n# Example: sh iconizer.sh MyVectorGraphic.pdf MyXcodeProject\n#\n# Requires ImageMagick: http://www.imagemagick.org/\n\nif [ $# -ne 2 ]\n  then\n        echo \"\\nUsage: sh iconizer.sh file.pdf FolderName\\n\"\nelif [ ! -e \"$1\" ]\n    then\n        echo \"Did not find file $1, expected path to a vector image file.\\n\"\nelif [ ${1: -4} != \".pdf\" ]\n    then\n        echo \"File $1 is not a vector image file! Expected PDF file.\\n\"\nelif [ ! -d \"./$2/Images.xcassets/AppIcon.appiconset/\" ]\n    then\n        echo \"Did not find Xcode folder $2, expected folder which contains Images.xcassets/AppIcon.appiconset/ \\n\"\nelse\n    echo \"Creating icons from $1 into $2/Images.xcassets/AppIcon.appiconset/...\"\n\n    for i in 16 29 32 40 50 57 58 64 72 76 80 87 100 114 120 128 144 152 180 256 512 1024\n        do\n            echo \"Creating $i px icon\"\n            convert -density 400 $1 -scale $ix$i ./$2/Images.xcassets/AppIcon.appiconset/appicon_$i.png\n    done\n\n    echo \"Created app icon files, writing Contents.json file...\"\n\n    echo '{\"images\":[\\n{\\n\"size\":\"29x29\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_29.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"29x29\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_58.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"29x29\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_87.png\",\\n\"scale\":\"3x\"\\n},\\n{\\n\"size\":\"40x40\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_80.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"40x40\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_120.png\",\\n\"scale\":\"3x\"\\n},\\n{\\n\"size\":\"57x57\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_57.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"57x57\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_114.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"60x60\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_120.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"60x60\",\\n\"idiom\":\"iphone\",\\n\"filename\":\"appicon_180.png\",\\n\"scale\":\"3x\"\\n},\\n{\\n\"size\":\"29x29\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_29.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"29x29\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_58.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"40x40\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_40.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"40x40\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_80.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"50x50\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_50.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"50x50\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_100.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"72x72\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_72.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"72x72\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_144.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"76x76\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_76.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"76x76\",\\n\"idiom\":\"ipad\",\\n\"filename\":\"appicon_152.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"120x120\",\\n\"idiom\":\"car\",\\n\"filename\":\"appicon_120.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"16x16\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_16.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"16x16\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_32.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"32x32\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_32.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"32x32\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_64.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"128x128\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_128.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"128x128\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_256.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"256x256\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_256.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"256x256\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_512.png\",\\n\"scale\":\"2x\"\\n},\\n{\\n\"size\":\"512x512\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_512.png\",\\n\"scale\":\"1x\"\\n},\\n{\\n\"size\":\"512x512\",\\n\"idiom\":\"mac\",\\n\"filename\":\"appicon_1024.png\",\\n\"scale\":\"2x\"\\n}\\n],\\n\"info\":{\\n\"version\":1,\\n\"author\":\"xcode\"\\n}\\n}' > \"./$2/Images.xcassets/AppIcon.appiconset/Contents.json\"\n\n    echo \"Complete!\"\nfi"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "LICENSE-Apple-Sample-Code",
    "content": "Background Music includes code from Core Audio User-Space Driver\nExamples, see\n<https://developer.apple.com/library/mac/samplecode/AudioDriverExamples>,\nwhich was provided with the following copyright notice and the license\nbelow.\n \nCopyright (C) 2013 Apple Inc. All Rights Reserved.\n\nBackground Music includes code from Core Audio Utility Classes, see\n<https://developer.apple.com/library/content/samplecode/CoreAudioUtilityClasses>,\nwhich was provided with the following copyright notice and the license\nbelow.\n\nCopyright (C) 2014 Apple Inc. All Rights Reserved.\n\n------------------------------------------------------------------------\n\nDisclaimer: IMPORTANT:  This Apple software is supplied to you by Apple \nInc. (\"Apple\") in consideration of your agreement to the following \nterms, and your use, installation, modification or redistribution of \nthis Apple software constitutes acceptance of these terms.  If you do \nnot agree with these terms, please do not use, install, modify or \nredistribute this Apple software. \n \nIn consideration of your agreement to abide by the following terms, and \nsubject to these terms, Apple grants you a personal, non-exclusive \nlicense, under Apple's copyrights in this original Apple software (the \n\"Apple Software\"), to use, reproduce, modify and redistribute the Apple \nSoftware, with or without modifications, in source and/or binary forms; \nprovided that if you redistribute the Apple Software in its entirety and \nwithout modifications, you must retain this notice and the following \ntext and disclaimers in all such redistributions of the Apple Software. \nNeither the name, trademarks, service marks or logos of Apple Inc. may \nbe used to endorse or promote products derived from the Apple Software \nwithout specific prior written permission from Apple.  Except as \nexpressly stated in this notice, no other rights or licenses, express or \nimplied, are granted by Apple herein, including but not limited to any \npatent rights that may be infringed by your derivative works or by other \nworks in which the Apple Software may be incorporated. \n \nThe Apple Software is provided by Apple on an \"AS IS\" basis.  APPLE \nMAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION \nTHE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS \nFOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND \nOPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. \n \nIN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL \nOR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS \nINTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, \nMODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED \nAND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), \nSTRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE \nPOSSIBILITY OF SUCH DAMAGE. \n\n\n"
  },
  {
    "path": "MANUAL-INSTALL.md",
    "content": "<!-- vim: set tw=120: -->\n\n# Manual Build and Install\n\n- Install the virtual audio device `Background Music Device.driver` to `/Library/Audio/Plug-Ins/HAL`.\n\n  ```shell\n  sudo xcodebuild -project BGMDriver/BGMDriver.xcodeproj \\\n                  -target \"PublicUtility\" \\\n                  RUN_CLANG_STATIC_ANALYZER=0 \\\n                  clean build\n  sudo xcodebuild -project BGMDriver/BGMDriver.xcodeproj \\\n                  -target \"Background Music Device\" \\\n                  RUN_CLANG_STATIC_ANALYZER=0 \\\n                  DSTROOT=\"/\" \\\n                  clean install\n  ```\n- Install the XPC helper.\n\n  ```shell\n  sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \\\n                  -target BGMXPCHelper \\\n                  RUN_CLANG_STATIC_ANALYZER=0 \\\n                  DSTROOT=\"/\" \\\n                  INSTALL_PATH=\"$(BGMApp/BGMXPCHelper/safe_install_dir.sh)\" \\\n                  clean install\n  ```\n- Install `Background Music.app` to `/Applications` (or wherever).\n\n  ```shell\n  sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \\\n                  -target \"Background Music\" \\\n                  RUN_CLANG_STATIC_ANALYZER=0 \\\n                  DSTROOT=\"/\" \\\n                  clean install\n  ```\n- Restart `coreaudiod`: <br>\n  (Audio will stop working until the next step, so you might want to pause any running audio apps.)\n\n  ```shell\n  sudo killall coreaudiod\n  ```\n\n  or, if that fails\n\n  ```shell\n  sudo launchctl kickstart -kp system/com.apple.audio.coreaudiod\n  ```\n- Run `Background Music.app`.\n\n\n"
  },
  {
    "path": "MANUAL-UNINSTALL.md",
    "content": "<!-- vim: set tw=120: -->\n\n# Manual Uninstall\n\n- Delete `Background Music.app` from `/Applications`.\n- Delete `Background Music Device.driver` from `/Library/Audio/Plug-Ins/HAL`.\n- Pause apps that are playing audio, if you can.\n- Restart `coreaudiod`:<br>\n  <sup>(Open `/Applications/Utilities/Terminal.app` and paste the following at the prompt.)</sup>\n\n  ```shell\n  sudo killall coreaudiod\n  ```\n  or, if that fails\n\n  ```shell\n  sudo launchctl kickstart -kp system/com.apple.audio.coreaudiod\n  ```\n- Go to the Sound section in System Settings and change your default output device at least once. (If you only have\n  one device now, either use `Audio MIDI Setup.app` to create a temporary aggregate device, restart any audio apps that\n  have stopped working or just restart your system.)\n  \n## Troubleshooting\n\nIf you still have the Background Music audio device, try using `Terminal.app` to make sure you've deleted its files:\n\n```shell\nsudo ls /Library/Audio/Plug-Ins/HAL\n```\n\nIf you see `Background Music Device.driver` in the output of that command, use this command to actually delete it:\n\n```shell\nsudo rm -rf \"/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\n```\n\nThen restart `coreaudiod` again. If that still doesn't work, restart your computer. If that doesn't work, feel free to\nopen an issue. Include the output of `sudo ls /Library/Audio/Plug-Ins/HAL`.\n\n## Optional\n\n- Delete `BGMXPCHelper.xpc` from `/usr/local/libexec` or possibly `/Library/Application Support/Background Music`.\n- Unregister BGMXPCHelper.\n  - If you're using OS X 10.11 or later:\n\n    ```shell\n    sudo launchctl bootout system /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\n    ```\n  - If you're using an earlier version of OS X:\n\n    ```shell\n    sudo launchctl unload /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\n    ```\n- Delete BGMXPCHelper's launchd.plist.\n\n  ```shell\n  sudo rm /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\n  ```\n- Delete BGMXPCHelper's user and group.\n\n  ```shell\n  sudo dscl . -delete /Users/_BGMXPCHelper\n  sudo dscl . -delete /Groups/_BGMXPCHelper\n  ```\n\n\n"
  },
  {
    "path": "README.md",
    "content": "<!-- vim: set tw=120: -->\n\n![](Images/README/FermataIcon.png)\n\n# Background Music\n##### macOS audio utility\n\n<img src=\"Images/README/Screenshot.png\" width=\"340\" height=\"443\" />\n\n[Overview](#overview)<br/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Auto-pause music](#auto-pause-music)<br/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Application volume](#application-volume)<br/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Recording system audio](#recording-system-audio)<br/>\n[Download](#download)<br/>\n[Run / Configure](#run--configure)<br/>\n[Build and Install](#installing-from-source-code)</br>\n[Uninstall](#uninstall)<br/>\n[Troubleshooting](#troubleshooting)<br/>\n[Related Projects](#related-projects)<br/>\n[License](#license)<br/>\n\n# Overview\n\n+ Automatically pause/unpause your music player when other audio sources are playing/stopped\n+ Per-application volume control\n+ Record system audio\n+ No restart required to install\n\n##### *Note: Background Music is still in alpha.*\n\n## Auto-pause music\n\n**Background Music** automatically pauses your music player when a second audio source is playing and unpauses the player when the second source has stopped.\n\nThe auto-pause feature currently supports following music players:\n\n+ [iTunes](https://www.apple.com/itunes/)\n+ [Spotify](https://www.spotify.com)\n+ [VLC](https://www.videolan.org/vlc/)\n+ [VOX](https://vox.rocks/mac-music-player)\n+ [Decibel](https://sbooth.org/Decibel/)\n+ [Hermes](http://hermesapp.org/)\n+ [Swinsian](https://swinsian.com/)\n+ [GPMDP](https://www.googleplaymusicdesktopplayer.com/)\n\nAdding support for a new music player is usually straightforward.<sup id=\"a1\">[1](#f1)</sup> If you don't know how to program, or just don't feel\nlike it, feel free to [create an issue](https://github.com/kyleneideck/BackgroundMusic/issues/new). Otherwise, see\n[BGMMusicPlayer.h](BGMApp/BGMApp/Music%20Players/BGMMusicPlayer.h).\n\n## Application volume\n\n**Background Music** provides a volume slider for each application running your system. You can boost quiet applications above their maximum volume.\n\n## Recording system audio\n\nYou can record system audio with **Background Music**. With **Background Music** running, launch **QuickTime Player** and select **File > New Audio Recording** (or **New Screen Recording**, **New Movie Recording**). Then click the dropdown menu (`⌄`) next to the record button and select **Background Music** as the input device.\n\nYou can record system audio and a microphone together by creating an [aggregate\ndevice](https://support.apple.com/en-us/HT202000) that combines your input device (usually Built-in Input) with\nthe **Background Music** device. You can create the aggregate device using the **Audio MIDI Setup** utility under\n***/Applications/Utilities***.\n\n# Download\n\n**Requires macOS 10.13+**.\n\nYou can download the current version of **Background Music** using the following options. We also have [snapshot builds](https://github.com/kyleneideck/BackgroundMusic/releases).\n\n### Option 1\n\nDownload **version 0.4.3**:\n\n<a href=\"https://github.com/kyleneideck/BackgroundMusic/releases/download/v0.4.3/BackgroundMusic-0.4.3.pkg\"><img\nsrc=\"Images/README/pkg-icon.png\" width=\"32\" height=\"32\" align=\"absmiddle\" />\nBackgroundMusic-0.4.3.pkg</a> (771 KB)\n\n> <sub>MD5: 8c3bfe26c9cdf27365b9843f719ef188</sub><br/>\n> <sub>SHA256: c1c48a37c83af44ce50bee68879856c96b2f6c97360ce461b1c7d653515be7fd</sub><br/>\n> <sub>PGP:\n> [sig](https://github.com/kyleneideck/BackgroundMusic/releases/download/v0.4.3/BackgroundMusic-0.4.3.pkg.asc),\n> [key (0595DF814E41A6F69334C5E2CAA8D9B8E39EC18C)](https://bearisdriving.com/kyle-neideck.gpg)</sub>\n\n### Option 2\n\nInstall using [Homebrew](https://brew.sh/) by running the following command in **Terminal**:\n\n```bash\nbrew install --cask background-music\n```\n\n# Run / Configure\n\nJust run `Applications > Background Music.app`! **Background Music** sets itself as your default output device under\n`System Settings > Sound` when it starts up (and sets it back on Quit).\n\n### Launch at Startup (Optional)\n\nAdd **Background Music** to `System Settings > General > Login Items`.\n\n# Installing from Source Code\n\n**Background Music** usually takes less than a minute to build. You need [Xcode](https://developer.apple.com/xcode/download/) version\n10 or higher.\n\n### Option 1\n\n1. Open **Terminal**.\n2. Copy and paste the following command into **Terminal**:\n\n```shell\n(set -eo pipefail; URL='https://github.com/kyleneideck/BackgroundMusic/archive/master.tar.gz'; \\\n    cd $(mktemp -d); echo Downloading $URL to $(pwd); curl -qfL# $URL | gzcat - | tar x && \\\n    /bin/bash BackgroundMusic-master/build_and_install.sh -w && rm -rf BackgroundMusic-master)\n```\n\n<details><summary>More info...</summary>\n\nThis command uses `/bin/bash` instead of `bash` in case someone has a nonstandard Bash in their `$PATH`. However, it doesn't do this for `tar` or `curl`. In addition, `build_and_install.sh` doesn't call programs by absolute paths. This command also uses `gzcat - | tar x` instead of `tar xz` because `gzcat` will also check the file's integrity (gzip files\ninclude a checksum), and will ensure that a half-downloaded copy of `build_and_install.sh` doesn't run.\n\n</details>\n\n### Option 2\n\n1. Clone or [download](https://github.com/kyleneideck/BackgroundMusic/archive/master.zip) the project.\n2. If the project is in a zip, unzip it.\n3. Open **Terminal** and [change the directory](https://github.com/0nn0/terminal-mac-cheatsheet#core-commands) to the\n   directory containing the project.\n4. Run: `/bin/bash build_and_install.sh`.\n\nThe script restarts the system audio process (coreaudiod) at the end of the installation, so pause any applications\nplaying audio if you can.\n\nTo manually build and install, see [MANUAL_INSTALL.md](https://github.com/kyleneideck/BackgroundMusic/blob/master/MANUAL-INSTALL.md).\n\n# Uninstall\n\nTo uninstall **Background Music** from your system, follow these steps:\n\n1. Open **Terminal**.\n2. To locate `uninstall.sh`, run: `cd /Applications/Background\\ Music.app/Contents/Resources/`.\n3. Run: `bash uninstall.sh`.\n\nIf you cannot locate `uninstall.sh`, you can [download the project](https://github.com/kyleneideck/BackgroundMusic/archive/master.zip) again.\n\nTo manually uninstall, see [MANUAL_UNINSTALL.md](https://github.com/kyleneideck/BackgroundMusic/blob/master/MANUAL-UNINSTALL.md).\n\n# Troubleshooting\n\nIf Background Music crashes and your audio stops working, open `System Settings > Sound` and change your\nsystem's default output device to something other than the **Background Music device**. If it already is, then\nchange the default device and then change it back again.\n\nMake sure you allow \"microphone access\" when you first run Background Music. If you denied it, go to\n`System Settings > Security & Privacy > Privacy > Microphone`, find Background Music in the list\nand check the box next to it. Background Music doesn't actually listen to your microphone. It needs\nthe permission because it gets your system audio from its virtual input device, which macOS counts\nas a microphone. (We're working on it in [#177](/../../issues/177).)\n\nIf the volume slider for an app isn't working, try looking in `More Apps` for entries like `Some\nApp (Helper)`. For some meeting or video chat apps, you may need to do this to change the current\nmeeting volume.\n\n## Known issues and solutions\n\n- **Setting an application's volume above 50% can cause [clipping](https://en.wikipedia.org/wiki/Clipping_(audio)).**\n\n    - Set your volume to its maximum level and lower the volumes of other applications.\n\n- **Only 2-channel (stereo) audio devices are currently supported for output.**\n\n- **VLC pauses iTunes or Spotify when playing, and stops Background Music from unpausing your music afterward.**\n\n    - Under VLC's preferences, select **Show All**. Navigate to **Interface > Main interfaces > macosx** and change *Control external music players* to either *Do nothing* or *Pause and resume iTunes/Spotify*.\n\n- **Skype pauses iTunes during calls.**\n\n    - To disable this, uncheck *Pause iTunes during calls* on the **General** tab of **Skype**'s preferences.\n\n- **Plugging in or unplugging headphones when Background Music isn't running causes silence in the system audio.**\n    - Navigate to **System Settings > Sound**. Click the **Output** tab and change your default output device to something other than the **Background Music** device. Alternatively, press **Option + Click** on the sound icon within the menu bar to select a different output device. This happens when macOS remembers that the **Background Music** device was your default audio device the last time you used (or didn't use) headphones.\n\n- **[A Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=557620) stops Chrome from switching to the Background Music device after you open Background Music.**\n    - Chrome's audio will still play, but **Background Music** won't be aware of it.\n\n- **Some applications play notification sounds that are only just long enough to trigger an auto-pause.**\n    - Increase the `kPauseDelayNSec` constant in [BGMAutoPauseMusic.mm](/BGMApp/BGMApp/BGMAutoPauseMusic.mm). It will increase your music's overlap time over other audio, so don't increase it too much. See [#5](https://github.com/kyleneideck/BackgroundMusic/issues/5) for details.\n\nMany other issues are listed in [TODO.md](/TODO.md) and in [GitHub\nIssues](https://github.com/kyleneideck/BackgroundMusic/issues).\n\n# Related projects\n\n- [Core Audio User-Space Driver\n  Examples](https://developer.apple.com/library/mac/samplecode/AudioDriverExamples/Introduction/Intro.html)\n  The sample code from Apple that BGMDriver is based on.\n- [Soundflower](https://github.com/mattingalls/Soundflower) - \"MacOS system extension that allows applications to pass\n  audio to other applications.\"\n- [WavTap](https://github.com/pje/WavTap) - \"globally capture whatever your mac is playing—-as simply as a screenshot\"\n- [eqMac](http://www.bitgapp.com/eqmac/), [GitHub](https://github.com/nodeful/eqMac2) - \"System-wide Audio Equalizer for the Mac\"\n- [llaudio](https://github.com/mountainstorm/llaudio) - \"An old piece of work to reverse engineer the Mac OSX\n  user/kernel audio interface. Shows how to read audio straight out of the kernel as you would on Darwin (where most the\n  OSX goodness is missing)\"\n- [mute.fm](http://www.mutefm.com), [GitHub](https://github.com/jaredsohn/mutefm) (Windows) - Auto-pause music\n- [Jack OS X](http://www.jackosx.com) - \"A Jack audio connection kit implementation for Mac OS X\"\n- [PulseAudio OS X](https://github.com/zonque/PulseAudioOSX) - \"PulseAudio for Mac OS X\"\n- [Sound Pusher](https://github.com/q-p/SoundPusher) - \"Virtual audio device, real-time encoder and SPDIF forwarder for\n  Mac OS X\"\n- [Zirkonium](https://code.google.com/archive/p/zirkonium) - \"An infrastructure and application for multi-channel sound\n  spatialization on MacOS X.\"\n- [BlackHole](https://github.com/ExistentialAudio/BlackHole) - \"a modern macOS virtual audio driver that allows applications to pass audio to other applications with zero additional latency.\"\n\n### Non-free\n\n- [Audio Hijack](https://rogueamoeba.com/audiohijack/), [SoundSource](https://rogueamoeba.com/soundsource/) - \"Capture\n  Audio From Anywhere on Your Mac\", \"Get truly powerful control over all the audio on your Mac!\"\n- [Sound Siphon](https://staticz.com/soundsiphon/), [Sound Control](https://staticz.com/soundcontrol/) - System/app audio recording, per-app volumes, system audio equaliser\n- [SoundBunny](https://www.prosofteng.com/soundbunny-mac-volume-control/) - \"Control application volume independently.\"\n- [Boom 2](https://www.globaldelight.com/boom/) - \"The Best Volume Booster & Equalizer For Mac\"\n\n## License\n\nCopyright © 2016-2024 [Background Music contributors](https://github.com/kyleneideck/BackgroundMusic/graphs/contributors).\nLicensed under [GPLv2](https://www.gnu.org/licenses/gpl-2.0.html), or any later version.\n\n**Background Music** includes code from:\n\n- [Core Audio User-Space Driver\n  Examples](https://developer.apple.com/library/mac/samplecode/AudioDriverExamples/Introduction/Intro.html), [original\n  license](LICENSE-Apple-Sample-Code), Copyright (C) 2013 Apple Inc. All Rights Reserved.\n- [Core Audio Utility\n  Classes](https://developer.apple.com/library/content/samplecode/CoreAudioUtilityClasses/Introduction/Intro.html),\n  [original license](LICENSE-Apple-Sample-Code), Copyright (C) 2014 Apple Inc. All Rights Reserved.\n\n----\n\n<b id=\"f1\">[1]</b> However, if the music player doesn't support AppleScript, or doesn't support the events Background\nMusic needs (`isPlaying`, `isPaused`, `play` and `pause`), it can take significantly more effort to add. (And in some\ncases would require changes to the music player itself.) [↩](#a1)\n\n\n"
  },
  {
    "path": "SharedSource/BGMXPCProtocols.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGMXPCProtocols.h\n//  SharedSource\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// System Includes\n#import <Foundation/Foundation.h>\n\n\n#pragma clang assume_nonnull begin\n\nstatic NSString* kBGMXPCHelperMachServiceName = @kBGMXPCHelperBundleID;\n\n// The protocol that BGMXPCHelper will vend as its XPC API.\n@protocol BGMXPCHelperXPCProtocol\n\n// Tells BGMXPCHelper that the caller is BGMApp and passes a listener endpoint that BGMXPCHelper can use to create connections to BGMApp.\n// BGMXPCHelper may also pass the endpoint on to BGMDriver so it can do the same.\n- (void) registerAsBGMAppWithListenerEndpoint:(NSXPCListenerEndpoint*)endpoint reply:(void (^)(void))reply;\n- (void) unregisterAsBGMApp;\n\n// BGMDriver calls this remote method when it wants BGMApp to start IO. BGMXPCHelper passes the message along and then passes the response\n// back. This allows BGMDriver to wait for the audio hardware to start up, which means it can let the HAL know when it's safe to start\n// sending us audio data from the client.\n//\n// If BGMApp can be reached, the error it returns will be passed the reply block. Otherwise, the reply block will be passed an error with\n// one of the kBGMXPC_* error codes. It may have an underlying error using one of the NSXPCConnection* error codes from FoundationErrors.h.\n- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI;\n\n// BGMXPCHelper will set the system's default output device to deviceID if it loses its connection\n// to BGMApp and BGMApp has left BGMDevice as the default device. It waits for a short time first to\n// give BGMApp a chance to fix the connection.\n//\n// This is so BGMDevice isn't left as the default device if BGMApp crashes or otherwise terminates\n// abnormally. If audio is played to BGMDevice and BGMApp isn't running, the user won't hear it.\n- (void) setOutputDeviceToMakeDefaultOnAbnormalTermination:(AudioObjectID)deviceID;\n    \n@end\n\n\n// The protocol that BGMApp will vend as its XPC API.\n@protocol BGMAppXPCProtocol\n\n- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI;\n\n@end\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "SharedSource/BGM_TestUtils.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_TestUtils.h\n//  SharedSource\n//\n//  Copyright © 2016, 2021 Kyle Neideck\n//\n\n#ifndef __SharedSource__BGM_TestUtils__\n#define __SharedSource__BGM_TestUtils__\n\n// Test Framework\n#import <XCTest/XCTest.h>\n\n#if defined(__cplusplus)\n\n// STL Includes\n#include <functional>\n\n\n// Fails the test if f doesn't throw ExpectedException when run.\n// (The \"self\" argument is required by XCTFail, presumably so it can report the context.)\ntemplate<typename ExpectedException>\nvoid BGMShouldThrow(XCTestCase* self, const std::function<void()>& f)\n{\n#pragma unused (self)\n    try\n    {\n        f();\n        XCTFail();\n    }\n    catch (ExpectedException)\n    { }\n}\n\n#endif /* defined(__cplusplus) */\n\n#endif /* __SharedSource__BGM_TestUtils__ */\n\n"
  },
  {
    "path": "SharedSource/BGM_Types.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Types.h\n//  SharedSource\n//\n//  Copyright © 2016, 2017, 2019, 2024 Kyle Neideck\n//\n\n#ifndef SharedSource__BGM_Types\n#define SharedSource__BGM_Types\n\n// STL Includes\n#if defined(__cplusplus)\n#include <stdexcept>\n#endif\n\n// System Includes\n#include <CoreAudio/AudioServerPlugIn.h>\n\n\n#pragma mark Project URLs\n\nstatic const char* const kBGMProjectURL = \"https://github.com/kyleneideck/BackgroundMusic\";\nstatic const char* const kBGMIssueTrackerURL = \"https://github.com/kyleneideck/BackgroundMusic/issues\";\nstatic const char* const kBGMContributorsURL = \"https://github.com/kyleneideck/BackgroundMusic/graphs/contributors\";\n\n#pragma mark IDs\n\n// TODO: Change these and the other defines to const strings?\n#define kBGMDriverBundleID           \"com.bearisdriving.BGM.Driver\"\n#define kBGMAppBundleID              \"com.bearisdriving.BGM.App\"\n#define kBGMXPCHelperBundleID        \"com.bearisdriving.BGM.XPCHelper\"\n\n#define kBGMDeviceUID                \"BGMDevice\"\n#define kBGMDeviceModelUID           \"BGMDeviceModelUID\"\n#define kBGMDeviceUID_UISounds       \"BGMDevice_UISounds\"\n#define kBGMDeviceModelUID_UISounds  \"BGMDeviceModelUID_UISounds\"\n#define kBGMNullDeviceUID            \"BGMNullDevice\"\n#define kBGMNullDeviceModelUID       \"BGMNullDeviceModelUID\"\n\n// The object IDs for the audio objects this driver implements.\n//\n// BGMDevice always publishes this fixed set of objects (except when BGMDevice's volume or mute\n// controls are disabled). We might need to change that at some point, but so far it hasn't caused\n// any problems and it makes the driver much simpler.\nenum\n{\n\tkObjectID_PlugIn                            = kAudioObjectPlugInObject,\n    // BGMDevice\n\tkObjectID_Device                            = 2,   // Belongs to kObjectID_PlugIn\n\tkObjectID_Stream_Input                      = 3,   // Belongs to kObjectID_Device\n\tkObjectID_Stream_Output                     = 4,   // Belongs to kObjectID_Device\n\tkObjectID_Volume_Output_Master              = 5,   // Belongs to kObjectID_Device\n\tkObjectID_Mute_Output_Master                = 6,   // Belongs to kObjectID_Device\n    // Null Device\n    kObjectID_Device_Null                       = 7,   // Belongs to kObjectID_PlugIn\n    kObjectID_Stream_Null                       = 8,   // Belongs to kObjectID_Device_Null\n    // BGMDevice for UI sounds\n    kObjectID_Device_UI_Sounds                  = 9,   // Belongs to kObjectID_PlugIn\n    kObjectID_Stream_Input_UI_Sounds            = 10,  // Belongs to kObjectID_Device_UI_Sounds\n    kObjectID_Stream_Output_UI_Sounds           = 11,  // Belongs to kObjectID_Device_UI_Sounds\n    kObjectID_Volume_Output_Master_UI_Sounds    = 12,  // Belongs to kObjectID_Device_UI_Sounds\n};\n\n// AudioObjectPropertyElement docs: \"Elements are numbered sequentially where 0 represents the\n// master element.\"\nstatic const AudioObjectPropertyElement kMasterChannel = kAudioObjectPropertyElementMaster;\n\n#pragma BGM Plug-in Custom Properties\n\nenum\n{\n    // A CFBoolean. True if the null device is enabled. Settable, false by default.\n    kAudioPlugInCustomPropertyNullDeviceActive = 'nuld'\n};\n\n#pragma mark BGMDevice Custom Properties\n\nenum\n{\n    // TODO: Combine the two music player properties\n    \n    // The process ID of the music player as a CFNumber. Setting this property will also clear the value of\n    // kAudioDeviceCustomPropertyMusicPlayerBundleID. We use 0 to mean unset.\n    //\n    // There is currently no way for a client to tell whether the process it has set as the music player is a\n    // client of the BGMDevice.\n    kAudioDeviceCustomPropertyMusicPlayerProcessID                    = 'mppi',\n    // The music player's bundle ID as a CFString (UTF8), or the empty string if it's unset/null. Setting this\n    // property will also clear the value of kAudioDeviceCustomPropertyMusicPlayerProcessID.\n    kAudioDeviceCustomPropertyMusicPlayerBundleID                     = 'mpbi',\n    // A CFNumber that specifies whether the device is silent, playing only music (i.e. the client set as the\n    // music player is the only client playing audio) or audible. See enum values below. This property is only\n    // updated after the audible state has been different for kDeviceAudibleStateMinChangedFramesForUpdate\n    // consecutive frames. (To avoid excessive CPU use if for some reason the audible state starts changing\n    // very often.)\n    kAudioDeviceCustomPropertyDeviceAudibleState                      = 'daud',\n    // A CFBoolean similar to kAudioDevicePropertyDeviceIsRunning except it ignores whether IO is running for\n    // BGMApp. This is so BGMApp knows when it can stop doing IO to save CPU.\n    kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp = 'runo',\n    // A CFArray of CFDictionaries that each contain an app's pid, bundle ID and volume relative to other\n    // running apps. See the dictionary keys below for more info.\n    //\n    // Getting this property will only return apps with volumes other than the default. Setting this property\n    // will add new app volumes or replace existing ones, but there's currently no way to delete an app from\n    // the internal collection.\n    kAudioDeviceCustomPropertyAppVolumes                              = 'apvs',\n    // A CFArray of CFBooleans indicating which of BGMDevice's controls are enabled. All controls are enabled\n    // by default. This property is settable. See the array indices below for more info.\n    kAudioDeviceCustomPropertyEnabledOutputControls                   = 'bgct'\n};\n\n// The number of silent/audible frames before BGMDriver will change kAudioDeviceCustomPropertyDeviceAudibleState\n#define kDeviceAudibleStateMinChangedFramesForUpdate (2 << 11)\n\nenum BGMDeviceAudibleState : SInt32\n{\n    // kAudioDeviceCustomPropertyDeviceAudibleState values\n    //\n    // No audio is playing on the device's streams (regardless of whether IO is running or not)\n    kBGMDeviceIsSilent              = 'silt',\n    // The client whose bundle ID matches the current value of kCustomAudioDevicePropertyMusicPlayerBundleID is the\n    // only audible client\n    kBGMDeviceIsSilentExceptMusic   = 'olym',\n    kBGMDeviceIsAudible             = 'audi'\n};\n\n// kAudioDeviceCustomPropertyAppVolumes keys\n//\n// A CFNumber<SInt32> between kAppRelativeVolumeMinRawValue and kAppRelativeVolumeMaxRawValue. A value greater than\n// the midpoint increases the client's volume and a value less than the midpoint decreases it. A volume curve is\n// applied to kBGMAppVolumesKey_RelativeVolume when it's first set and then each of the app's samples are multiplied\n// by it.\n#define kBGMAppVolumesKey_RelativeVolume    \"rvol\"\n// A CFNumber<SInt32> between kAppPanLeftRawValue and kAppPanRightRawValue. A negative value has a higher proportion\n// of left channel, and a positive value has a higher proportion of right channel.\n#define kBGMAppVolumesKey_PanPosition       \"ppos\"\n// The app's pid as a CFNumber. May be omitted if kBGMAppVolumesKey_BundleID is present.\n#define kBGMAppVolumesKey_ProcessID         \"pid\"\n// The app's bundle ID as a CFString. May be omitted if kBGMAppVolumesKey_ProcessID is present.\n#define kBGMAppVolumesKey_BundleID          \"bid\"\n\n// Volume curve range for app volumes\n#define kAppRelativeVolumeMaxRawValue   100\n#define kAppRelativeVolumeMinRawValue   0\n#define kAppRelativeVolumeMinDbValue    -96.0f\n#define kAppRelativeVolumeMaxDbValue\t0.0f\n\n// Pan position values\n#define kAppPanLeftRawValue   -100\n#define kAppPanCenterRawValue 0\n#define kAppPanRightRawValue  100\n#define kAppPanNoValue INT_MIN\n\n// kAudioDeviceCustomPropertyEnabledOutputControls indices\nenum\n{\n    // True if BGMDevice's master output volume control is enabled.\n    kBGMEnabledOutputControlsIndex_Volume = 0,\n    // True if BGMDevice's master output mute control is enabled.\n    kBGMEnabledOutputControlsIndex_Mute   = 1\n};\n\n#pragma mark BGMDevice Custom Property Addresses\n\n// For convenience.\n\nstatic const AudioObjectPropertyAddress kBGMMusicPlayerProcessIDAddress = {\n    kAudioDeviceCustomPropertyMusicPlayerProcessID,\n    kAudioObjectPropertyScopeGlobal,\n    kAudioObjectPropertyElementMaster\n};\n\nstatic const AudioObjectPropertyAddress kBGMMusicPlayerBundleIDAddress = {\n    kAudioDeviceCustomPropertyMusicPlayerBundleID,\n    kAudioObjectPropertyScopeGlobal,\n    kAudioObjectPropertyElementMaster\n};\n\nstatic const AudioObjectPropertyAddress kBGMAudibleStateAddress = {\n    kAudioDeviceCustomPropertyDeviceAudibleState,\n    kAudioObjectPropertyScopeGlobal,\n    kAudioObjectPropertyElementMaster\n};\n\nstatic const AudioObjectPropertyAddress kBGMRunningSomewhereOtherThanBGMAppAddress = {\n    kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp,\n    kAudioObjectPropertyScopeGlobal,\n    kAudioObjectPropertyElementMaster\n};\n\nstatic const AudioObjectPropertyAddress kBGMAppVolumesAddress = {\n    kAudioDeviceCustomPropertyAppVolumes,\n    kAudioObjectPropertyScopeGlobal,\n    kAudioObjectPropertyElementMaster\n};\n\nstatic const AudioObjectPropertyAddress kBGMEnabledOutputControlsAddress = {\n    kAudioDeviceCustomPropertyEnabledOutputControls,\n    kAudioObjectPropertyScopeOutput,\n    kAudioObjectPropertyElementMaster\n};\n\n#pragma mark XPC Return Codes\n\nenum {\n    kBGMXPC_Success,\n    kBGMXPC_MessageFailure,\n    kBGMXPC_Timeout,\n    kBGMXPC_BGMAppStateError,\n    kBGMXPC_HardwareError,\n    kBGMXPC_ReturningEarlyError,\n    kBGMXPC_InternalError\n};\n\n#pragma mark Exceptions\n\n#if defined(__cplusplus)\n\nclass BGM_InvalidClientException : public std::runtime_error {\npublic:\n    BGM_InvalidClientException() : std::runtime_error(\"InvalidClient\") { }\n};\n\nclass BGM_InvalidClientPIDException : public std::runtime_error {\npublic:\n    BGM_InvalidClientPIDException() : std::runtime_error(\"InvalidClientPID\") { }\n};\n\nclass BGM_InvalidClientRelativeVolumeException : public std::runtime_error {\npublic:\n    BGM_InvalidClientRelativeVolumeException() : std::runtime_error(\"InvalidClientRelativeVolume\") { }\n};\n\nclass BGM_InvalidClientPanPositionException : public std::runtime_error {\npublic:\n    BGM_InvalidClientPanPositionException() : std::runtime_error(\"InvalidClientPanPosition\") { }\n};\n\nclass BGM_DeviceNotSetException : public std::runtime_error {\npublic:\n    BGM_DeviceNotSetException() : std::runtime_error(\"DeviceNotSet\") { }\n};\n\n#endif\n\n// Assume we've failed to start the output device if it isn't running IO after this timeout expires.\n//\n// Currently set to 30s because some devices, e.g. AirPlay, can legitimately take that long to start.\n//\n// TODO: Should we have a timeout at all? Is there a notification we can subscribe to that will tell us whether the\n//       device is still making progress? Should we regularly poll mOutputDevice.IsAlive() while we're waiting to\n//       check it's still responsive?\nstatic const UInt64 kStartIOTimeoutNsec = 30 * NSEC_PER_SEC;\n\n#endif /* SharedSource__BGM_Types */\n\n"
  },
  {
    "path": "SharedSource/BGM_Utils.cpp",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Utils.cpp\n//  SharedSource\n//\n//  Copyright © 2016, 2017 Kyle Neideck\n//\n\n// Self Include\n#include \"BGM_Utils.h\"\n\n// Local Includes\n#include \"BGM_Types.h\"\n\n// System Includes\n#include <MacTypes.h>\n#include <mach/mach_error.h>\n#include <CoreFoundation/CoreFoundation.h>  // For kCFCoreFoundationVersionNumber\n\n\n#pragma clang assume_nonnull begin\n\ndispatch_queue_t BGMGetDispatchQueue_PriorityUserInteractive()\n{\n    long queueClass;\n\n    // Compile-time check that QOS_CLASS_USER_INTERACTIVE can be used. It was added in 10.10.\n#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000  // MAC_OS_X_VERSION_10_10\n    // Runtime check for the same.\n    if(floor(kCFCoreFoundationVersionNumber) > kCFCoreFoundationVersionNumber10_9)\n    {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpartial-availability\"\n        queueClass = QOS_CLASS_USER_INTERACTIVE;\n#pragma clang diagnostic pop\n    }\n    else\n#endif\n    {\n        // Fallback for older versions.\n        queueClass = DISPATCH_QUEUE_PRIORITY_HIGH;\n    }\n\n    return dispatch_get_global_queue(queueClass, 0);\n}\n\nnamespace BGM_Utils\n{\n    // Forward declarations\n    static OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                            int lineNumber,\n                                            const char* callerName,\n                                            const char* __nullable message,\n                                            bool expected,\n                                            const std::function<void(void)>& function);\n\n#pragma mark Exception utils\n    \n    bool LogIfMachError(const char* callerName,\n                        const char* errorReturnedBy,\n                        mach_error_t error)\n    {\n        if(error != KERN_SUCCESS)\n        {\n            char* errorStr = mach_error_string(error);\n            LogError(\"%s: %s returned an error (%d): %s\\n\",\n                     callerName,\n                     errorReturnedBy,\n                     error,\n                     errorStr ? errorStr : \"Unknown error\");\n            return false;\n        }\n        \n        return true;\n    }\n\n    void ThrowIfMachError(const char* callerName,\n                          const char* errorReturnedBy,\n                          mach_error_t error)\n    {\n        if(!LogIfMachError(callerName, errorReturnedBy, error))\n        {\n            Throw(CAException(error));\n        }\n    }\n    \n    OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const std::function<void(void)>& function)\n    {\n        return LogAndSwallowExceptions(fileName, lineNumber, callerName, nullptr, true, function);\n    }\n\n    OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const char* __nullable message,\n                                     const std::function<void(void)>& function)\n    {\n        return LogAndSwallowExceptions(fileName, lineNumber, callerName, message, true, function);\n    }\n    \n    void LogException(const char* __nullable fileName,\n                      int lineNumber,\n                      const char* callerName,\n                      const CAException& e)\n    {\n        OSStatus err = e.GetError();\n        const char err4CC[5] = CA4CCToCString(err);\n\n        LogError(\"%s:%d:%s: CAException, code: '%s' (%d).\",\n                 (fileName ? fileName : \"\"),\n                 lineNumber,\n                 callerName,\n                 err4CC,\n                 err);\n    }\n    \n    void LogUnexpectedException(const char* __nullable fileName,\n                                int lineNumber,\n                                const char* callerName)\n    {\n        LogError(\"%s:%d:%s: Unknown unexpected exception.\",\n                 (fileName ? fileName : \"\"),\n                 lineNumber,\n                 callerName);\n    }\n    \n    OSStatus LogUnexpectedExceptions(const char* callerName,\n                                     const std::function<void(void)>& function)\n    {\n        return LogUnexpectedExceptions(nullptr, -1, callerName, nullptr, function);\n    }\n    \n    OSStatus LogUnexpectedExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const std::function<void(void)>& function)\n    {\n        return LogUnexpectedExceptions(fileName, lineNumber, callerName, nullptr, function);\n    }\n    \n    OSStatus LogUnexpectedExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const char* __nullable message,\n                                     const std::function<void(void)>& function)\n    {\n        return LogAndSwallowExceptions(fileName, lineNumber, callerName, message, false, function);\n    }\n\n#pragma mark Implementation\n\n    static OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                            int lineNumber,\n                                            const char* callerName,\n                                            const char* __nullable message,\n                                            bool expected,\n                                            const std::function<void(void)>& function)\n    {\n        try\n        {\n            function();\n        }\n        catch(const CAException& e)\n        {\n            // TODO: Can/should we log a stack trace somewhere? (If so, also in the following catch\n            //       block.)\n            // TODO: Log a warning instead of an error for expected exceptions?\n            OSStatus err = e.GetError();\n            const char err4CC[5] = CA4CCToCString(err);\n\n            LogError(\"%s:%d:%s: %sCAException, code: '%s' (%d). %s%s %s %s \",\n                     (fileName ? fileName : \"\"),\n                     lineNumber,\n                     callerName,\n                     (expected ? \"\" : \"Unexpected \"),\n                     err4CC,\n                     err,\n                     (message ? message : \"\"),\n                     (message ? \".\" : \"\"),\n                     (expected ? \"If you think this might be a bug:\"\n                               : \"Feel free to report this at\"),\n                     kBGMIssueTrackerURL);\n            \n#if BGM_StopDebuggerOnLoggedExceptions || BGM_StopDebuggerOnLoggedUnexpectedExceptions\n#if !BGM_StopDebuggerOnLoggedExceptions\n            if(!expected)\n#endif\n            {\n                BGMAssert(false, \"CAException\");\n            }\n#endif\n            return e.GetError();\n        }\n        catch(...)\n        {\n            LogError(\"%s:%d:%s: %s exception. %s%s %s %s\",\n                     (fileName ? fileName : \"\"),\n                     lineNumber,\n                     callerName,\n                     (expected ? \"Unknown\" : \"Unexpected unknown\"),\n                     (message ? message : \"\"),\n                     (message ? \".\" : \"\"),\n                     (expected ? \"If you think this might be a bug:\"\n                               : \"Feel free to report this at\"),\n                     kBGMIssueTrackerURL);\n\n#if BGM_StopDebuggerOnLoggedExceptions || BGM_StopDebuggerOnLoggedUnexpectedExceptions\n            BGMAssert(false, \"Unknown exception\");\n#endif\n            return -1;\n        }\n        \n        return noErr;\n    }\n}\n\n#pragma clang assume_nonnull end\n\n"
  },
  {
    "path": "SharedSource/BGM_Utils.h",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  BGM_Utils.h\n//  SharedSource\n//\n//  Copyright © 2016-2020 Kyle Neideck\n//\n\n#ifndef SharedSource__BGM_Utils\n#define SharedSource__BGM_Utils\n\n// PublicUtility Includes\n#include \"CADebugMacros.h\"\n\n#if defined(__cplusplus)\n\n#include \"CAException.h\"\n\n// STL Includes\n#include <functional>\n\n#endif /* defined(__cplusplus) */\n\n// System Includes\n#include <mach/error.h>\n#include <dispatch/dispatch.h>\n\n#pragma mark Macros\n\n// The Assert macro from CADebugMacros with support for format strings and line numbers added.\n#if DEBUG\n    #define BGMAssert(inCondition, inMessage, ...)                                  \\\n        if(!(inCondition))                                                          \\\n        {                                                                           \\\n            DebugMsg(\"%s:%d:%s: \" inMessage,                                        \\\n                     __FILE__,                                                      \\\n                     __LINE__,                                                      \\\n                     __FUNCTION__,                                                  \\\n                     ## __VA_ARGS__);                                               \\\n            __ASSERT_STOP;                                                          \\\n        }\n#else\n    #define BGMAssert(inCondition, inMessage, ...)\n#endif /* DEBUG */\n\n#define BGMAssertNonNull(expression) \\\n    BGMAssertNonNull2((expression), #expression)\n\n#define BGMAssertNonNull2(expression, expressionStr) \\\n    BGMAssert((expression), \\\n              \"%s:%d:%s: '%s' is null\", \\\n              __FILE__, \\\n              __LINE__, \\\n              __FUNCTION__, \\\n              expressionStr);\n\n// Used to give the first 3 arguments of BGM_Utils::LogAndSwallowExceptions and\n// BGM_Utils::LogUnexpectedExceptions (and probably others in future). Mainly so we can call those\n// functions directly instead of using the macro wrappers.\n#define BGMDbgArgs __FILE__, __LINE__, __FUNCTION__\n\n#pragma mark Objective-C Macros\n\n#if defined(__OBJC__)\n\n#if __has_feature(objc_generics)\n\n// This trick is from https://gist.github.com/robb/d55b72d62d32deaee5fa\n@interface BGMNonNullCastHelper<__covariant T>\n\n- (nonnull T) asNonNull;\n\n@end\n\n// Explicitly casts expressions from nullable to non-null. Only works with expressions that\n// evaluate to Objective-C objects. Use BGM_Utils::NN for other types.\n//\n// TODO: Replace existing non-null casts with this.\n#define BGMNN(expression) ({ \\\n        __typeof((expression)) value = (expression); \\\n        BGMAssertNonNull2(value, #expression); \\\n        BGMNonNullCastHelper<__typeof((expression))>* helper; \\\n        (__typeof(helper.asNonNull) __nonnull)value; \\\n    })\n\n#else /* __has_feature(objc_generics) */\n\n#define BGMNN(expression) ({ \\\n        id value = (expression); \\\n        BGMAssertNonNull2(value, #expression); \\\n        value; \\\n    })\n\n#endif /* __has_feature(objc_generics) */\n\n#endif /* defined(__OBJC__) */\n\n#pragma mark C++ Macros\n\n#if defined(__cplusplus)\n\n#define BGMLogException(exception) \\\n    BGM_Utils::LogException(__FILE__, __LINE__, __FUNCTION__, exception)\n\n#define BGMLogExceptionIn(callerName, exception) \\\n    BGM_Utils::LogException(__FILE__, __LINE__, callerName, exception)\n\n#define BGMLogAndSwallowExceptions(callerName, function) \\\n    BGM_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, function)\n\n#define BGMLogAndSwallowExceptionsMsg(callerName, message, function) \\\n    BGM_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, message, function)\n\n#define BGMLogUnexpectedException() \\\n    BGM_Utils::LogUnexpectedException(__FILE__, __LINE__, __FUNCTION__)\n\n#define BGMLogUnexpectedExceptionIn(callerName) \\\n    BGM_Utils::LogUnexpectedException(__FILE__, __LINE__, callerName)\n\n#define BGMLogUnexpectedExceptions(callerName, function) \\\n    BGM_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, function)\n\n#define BGMLogUnexpectedExceptionsMsg(callerName, message, function) \\\n    BGM_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, message, function)\n\n#endif /* defined(__cplusplus) */\n\n\n#pragma clang assume_nonnull begin\n\n#pragma mark C Utility Functions\n\ndispatch_queue_t BGMGetDispatchQueue_PriorityUserInteractive(void);\n\n#if defined(__cplusplus)\n\n#pragma mark C++ Utility Functions\n\nnamespace BGM_Utils\n{\n    // Used to explicitly cast from nullable to non-null. For Objective-C objects, use the BGMNN\n    // macro (above).\n    template <typename T>\n    inline T __nonnull NN(T __nullable v) {\n        BGMAssertNonNull(v);\n        return static_cast<T __nonnull>(v);\n    }\n    \n    // Log (and swallow) errors returned by Mach functions. Returns false if there was an error.\n    bool LogIfMachError(const char* callerName,\n                        const char* errorReturnedBy,\n                        mach_error_t error);\n    \n    // Similar to ThrowIfKernelError from CADebugMacros.h, but also logs (in debug builds) the\n    // Mach error string that corresponds to the error.\n    void ThrowIfMachError(const char* callerName,\n                          const char* errorReturnedBy,\n                          mach_error_t error);\n    \n    // If function throws an exception, log an error and continue.\n    //\n    // Fails/stops debug builds. It's likely that if we log an error for an exception in release\n    // builds, even if it's expected (i.e. not a bug in Background Music), we'd want to know if\n    // it gets thrown during testing/debugging.\n    OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const std::function<void(void)>& function);\n    \n    OSStatus LogAndSwallowExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const char* __nullable message,\n                                     const std::function<void(void)>& function);\n    \n    void     LogException(const char* __nullable fileName,\n                          int lineNumber,\n                          const char* callerName,\n                          const CAException& e);\n    \n    void     LogUnexpectedException(const char* __nullable fileName,\n                                    int lineNumber,\n                                    const char* callerName);\n    \n    OSStatus LogUnexpectedExceptions(const char* callerName,\n                                     const std::function<void(void)>& function);\n    \n    OSStatus LogUnexpectedExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const std::function<void(void)>& function);\n    \n    // Log unexpected exceptions and continue.\n    //\n    // Generally, you don't want to use this unless the alternative is to crash. And even then\n    // crashing is often the better option. (Especially if we've added crash reporting by the\n    // time you're reading this.)\n    //\n    // Fails/stops debug builds.\n    //\n    // TODO: Allow a format string and args for the message.\n    OSStatus LogUnexpectedExceptions(const char* __nullable fileName,\n                                     int lineNumber,\n                                     const char* callerName,\n                                     const char* __nullable message,\n                                     const std::function<void(void)>& function);\n}\n\n#endif /* defined(__cplusplus) */\n\n#pragma clang assume_nonnull end\n\n#endif /* SharedSource__BGM_Utils */\n\n"
  },
  {
    "path": "SharedSource/Scripts/set-version.sh",
    "content": "#!/bin/bash\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n#  set-version.sh\n#  SharedSource\n#\n#  Copyright © 2020 Kyle Neideck\n#\n#  Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds. For example,\n#  this might change the version string from \"0.4.0\" to \"0.4.0-SNAPSHOT-abc0123\".\n#\n#  Thanks to Václav Slavík for the initial version of this:\n#  <http://stackoverflow.com/a/26354117/1091063>.\n#\n#  TODO: Update CFBundleVersion as well?\n#\n\n# If HEAD isn't tagged, or has \"SNAPSHOT\" or \"DEBUG\" in the tag name, this is a snapshot build.\n# If HEAD is tagged more than once, use the most recent.\nTAG=$(/usr/bin/git tag --points-at HEAD --sort='-taggerdate' 2>/dev/null | head -n 1)\n\nif [[ $? -eq 0 ]] && ( [[ \"${TAG}\" == \"\" ]] || \\\n        [[ \"${TAG}\" =~ .*SNAPSHOT.* ]] || \\\n        [[ \"${TAG}\" =~ .*DEBUG.* ]] ); then\n    head_short_id=$(/usr/bin/git rev-list HEAD --max-count=1 --abbrev-commit)\n    info_plist=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n    if [[ \"${CONFIGURATION}\" != \"Release\" ]]; then\n        build_type=\"DEBUG\"\n    else\n        build_type=\"SNAPSHOT\"\n    fi\n\n    if [[ -f \"$info_plist\" ]]; then\n        current_version=$(/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"${info_plist}\")\n        base_version=$(/usr/libexec/PlistBuddy -c \"Print :BGMBundleVersionBase\" \"${info_plist}\" 2>/dev/null)\n\n        if [[ $? -ne 0 ]] || [[ \"${base_version}\" == \"\" ]]; then\n            base_version=\"${current_version}\"\n            /usr/libexec/PlistBuddy -c \"Add :BGMBundleVersionBase string ${base_version}\" \"${info_plist}\"\n        fi\n\n        new_version=\"${base_version}-${build_type}-${head_short_id}\"\n\n        if [[ \"${new_version}\" != \"${current_version}\" ]]; then  # Only touch the file if we need to.\n            /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString ${new_version}\" \"${info_plist}\"\n        fi\n    fi\nfi\n\n"
  },
  {
    "path": "TODO.md",
    "content": "<!-- vim: set tw=120: -->\n\n# TODOs for BGMApp and BGMDriver\n\nThere are also lots of other TODOs commented around the code.\n\n## Fairly quick\n\n- Add nullability qualifiers to files without them. Use `assume_nonnull` pragmas everywhere.\n\n- Keyboard shortcuts for the system volume boost and the frontmost app's volume\n\n- Should we hide music players in the preferences menu if Launch Services says they aren't installed? (And they aren't\n  running or otherwise obviously present on the system.) If so, should we have a \"show all\" menu item?\n\n- System-wide volume boost\n\n- Listen for notifications when the output device is removed/disabled and revert to the previous output device\n\n- User-friendly installation process and binaries. A .dmg with `Background Music.app` and `Background Music\n  Device.driver` should be fine. Include links to `/Applications` and `/Library/Audio/Plug-Ins/HAL` with instructions to\n  drag-and-drop. BGMApp will need to restart coreaudiod on the first run so it can use BGMDevice. (See `launchctl`\n  commands in README.) I'm not sure how much work it is to make builds reproducible with Xcode, but it would be worth\n  looking into.\n\n## Less quick\n\n- Recording system/application audio. You can already record system audio by selecting BGMDevice as the input device in\n  QuickTime Player but that isn't obvious.\n\n- Persist some of BGMDriver's state (app volumes, music player, etc.) using `WriteToStorage` and `CopyFromStorage` from\n  AudioServerPlugIn.h. Right now it's lost when you restart.  CACFPreferences.h looks like it might be appropriate for\n  this.\n\n- So we don't increase clipping, we should only increase an app's relative volume in the driver if the output device is\n  already at full volume. My first thought is to set the volume of the output device to the highest app volume and\n  reduce the other app's volumes in the driver so they sound the same. Matching the output device's volume curve might\n  be a little tricky.\n\n- More tests. Integration or performance tests would be nice.\n\n- Support for more music players\n\n- Support for custom music players. Probably easiest to ask the user for AppleScript snippets. It could also have an\n  option to simulate pressing the play/pause key on the keyboard, which would be less hassle for users.\n\n- Allow the user to select multiple music players.\n\n- Start at login option/setting\n\n- App volumes are only stored with the app's bundle ID and pid, so volumes for apps without bundle IDs are forgotten\n  when the apps are closed.\n\n- `-Wprofile-instr-out-of-date` is disabled in BGMApp because I think we're running into \n  <https://llvm.org/bugs/show\\_bug.cgi?id=24996>\n\n- Support for devices with more than two output channels\n\n- Split `BGM_Device.cpp` into smaller classes\n\n- MPlayer OSX Extended's HAL client isn't the same process as the one we get from `NSRunningApplications` and it gives\n  the HAL a different bundle ID. I think it would work (in most cases like that) to also send any child processes'\n  pids/bundle IDs with the app volumes.\n\n- Advanced preferences menu options:\n    - Uninstall\n    - Restart driver/coreaudiod\n    - Size of the IO buffers on the output device, to trade off latency for CPU. BGMDriver will have to be set to match\n      the output device. I don't think its IO buffer size is settable yet.\n\n- Should we hide the BGM device when BGMApp isn't running? This would fix the problem of our device being left as the\n  default device if BGMApp doesn't shutdown properly (because of a crash, hard reset, etc.), which stops the system from\n  playing audio. The problem with that is the Background Music device can still be used without BGMApp, to record\n  system/apps' audio, so ideally the BGM device would be able to just unset itself as the default device when BGMApp\n  isn't running.  For now, I think we should just have `kAudioDevicePropertyDeviceCanBeDefaultDevice` become false.\n\n- Fault-tolerance:\n    - BGMApp should catch any uncaught `CAException` from `BGMPlayThrough` and try to restart\n      playthrough. (In release builds only.)\n    - ...\n\n- Only allow one instance of BGMApp to be running. Show a warning if the user tries to start another one. It would be\n  nice if the warning offered to kill the other instances and start a new one.\n\n- When BGMApp changes the default device to BGMDevice, some apps seem to keep using the previous default device if they\n  were running IO at the time. Not sure if we can do anything about this.\n\n- Figure out how to test BGMDriver with Address Sanitizer enabled. It isn't working because it makes coreaudiod try to\n  read files outside of its sandbox and the system kills it.\n\n- Proper crash reporting.\n    - CrashReporter doesn't show a GUI if BGMApp crashes because it has `LSUIElement` set in its `Info.plist`.\n    - We can't always symbolicate users' crash logs (from CrashReporter) because we don't have their debug symbols.\n\n- Test Background Music on a system running OS X on a case-sensitive file system.\n    - In [#64](https://github.com/kyleneideck/BackgroundMusic/issues/64), BGMDriver failed to compile and the error\n      suggests it was a case-sensitivity problem. That should be fixed now, but I haven't looked for any runtime bugs.\n    - As a partial solution, Travis CI now builds Background Music in a case-sensitive disk image.\n\n- BGMApp and BGMXPCHelper should be sandboxed. (BGMDriver already is because it runs in the `coreaudiod` system\n  process.)\n\n\n"
  },
  {
    "path": "build_and_install.sh",
    "content": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# build_and_install.sh\n#\n# Copyright © 2016-2022, 2024 Kyle Neideck\n# Copyright © 2016 Nick Jacques\n#\n# Builds and installs BGMApp, BGMDriver and BGMXPCHelper. Requires xcodebuild and Xcode.\n#\n# Don't let the length of this script scare you away from building/installing without it (using\n# either Xcode or xcodebuild). 90% of this code is for error handling, logging, user friendliness,\n# etc. See MANUAL-INSTALL.md, DEVELOPING.md and BGMDriver/BGMDriver/quick_install.sh.\n#\n\n# Safe mode\nset -euo pipefail\nIFS=$'\\n\\t'\n\n# Subshells and functions inherit the ERR trap\nset -o errtrace\n\n# Go to the project directory.\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )\"\n\nerror_handler() {\n    LAST_COMMAND=\"$3\" LAST_COMMAND_EXIT_STATUS=\"$2\"\n\n    # Log the error.\n    echo \"Failure in $0 at line $1. The last command was (probably)\" >> ${LOG_FILE}\n    echo \"    ${LAST_COMMAND}\" >> ${LOG_FILE}\n    echo \"which exited with status ${LAST_COMMAND_EXIT_STATUS}.\" >> ${LOG_FILE}\n    echo \"Error message: ${ERROR_MSG}\" >> ${LOG_FILE}\n    echo >> ${LOG_FILE}\n\n    # Scrub username from log (and also real name just in case).\n    sed -i'tmp' \"s/$(whoami)/[username removed]/g\" ${LOG_FILE}\n    sed -i'tmp' \"s/$(id -F)/[name removed]/g\" ${LOG_FILE}\n    rm \"${LOG_FILE}tmp\"\n\n    # Print an error message.\n    echo \"$(tput setaf 9)ERROR$(tput sgr0): Install failed at line $1 with the message:\" >&2\n    echo -e \"${ERROR_MSG}\" >&2\n    echo >&2\n    echo \"Feel free to report this. If you do, you'll probably want to include the\" \\\n         \"build_and_install.log file from this directory ($(pwd)). But quickly skim through it\" \\\n         \"first to check that it doesn't include any personal information. It shouldn't, but this\" \\\n         \"is alpha software so you never know.\" >&2\n    echo >&2\n    echo \"To try building and installing without this build script, see MANUAL-INSTALL.md.\" >&2\n    echo >&2\n    echo \"You can also try ignoring compiler warnings with: $0 -w\" >&2\n\n    echo >&2\n    echo \"Error details:\" >&2\n    echo \"Line $1. The last command was (probably)\" >&2\n    echo \"    ${LAST_COMMAND}\" >&2\n    echo \"which exited with status ${LAST_COMMAND_EXIT_STATUS}.\" >&2\n    echo >&2\n\n    # Finish logging debug info if the script fails early.\n    if ! [[ -z ${LOG_DEBUG_INFO_TASK_PID:-} ]]; then\n        wait ${LOG_DEBUG_INFO_TASK_PID}\n    fi\n}\n\nenable_error_handling() {\n    if [[ -z ${CONTINUE_ON_ERROR} ]] || [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then\n        set -e\n        # TODO: The version of Bash that ships with OSX only gives you the line number of the\n        #       function the error occurred in -- not the line the error occurred on. There are a\n        #       few solutions suggested on various websites, but none of them work.\n        trap 'error_handler ${LINENO} $? \"${BASH_COMMAND}\"' ERR\n    fi\n}\n\ndisable_error_handling() {\n    set +e\n    trap - ERR\n}\n\n# Build for release by default. Use -d for a debug build.\nCONFIGURATION=Release\n#CONFIGURATION=Debug\n\nXCODEBUILD_ACTION=\"install\"\n\n# The default is to clean before installing because we want the log file to have roughly the same\n# information after every build.\nCLEAN=clean\n\nXCODEBUILD_OPTIONS=\"\"\n\nCONTINUE_ON_ERROR=0\n\n# Update .gitignore if you change this.\nLOG_FILE=build_and_install.log\n\n# Empty the log file\necho -n > ${LOG_FILE}\n\nCOREAUDIOD_PLIST=\"/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist\"\n\n# Output locations for installing. These are overwritten later if building or archiving.\n#\n# TODO: Should (can?) we use xcodebuild to get these from the Xcode project rather than duplicating\n#       them?\nAPP_PATH=\"/Applications\"\nAPP_DIR=\"Background Music.app\"\nDRIVER_PATH=\"/Library/Audio/Plug-Ins/HAL\"\nDRIVER_DIR=\"Background Music Device.driver\"\n# XPC_HELPER_OUTPUT_PATH is set below because it depends on the system (when installing).\nXPC_HELPER_DIR=\"BGMXPCHelper.xpc\"\n\n# The root output directory when archiving.\nARCHIVES_DIR=\"archives\"\n\nGENERAL_ERROR_MSG=\"Internal script error. Probably a bug in this script.\"\nBUILD_FAILED_ERROR_MSG=\"A build command failed. Probably a compilation error.\"\nBGMAPP_FAILED_TO_START_ERROR_MSG=\"Background Music (${APP_PATH}/${APP_DIR}) didn't seem to start \\\nup. It might just be taking a while.\n\nIf it didn't install correctly, you'll need to open the Sound control panel in System Settings \\\nand change your output device at least once. Your sound probably won't work until you do. (Or you \\\nrestart your computer.)\n\nIf you only have one device, you can create a temporary one by opening \\\n\\\"/Applications/Utilities/Audio MIDI Setup.app\\\", clicking the plus button and choosing \\\"Create \\\nMulti-Output Device\\\".\"\nERROR_MSG=\"${GENERAL_ERROR_MSG}\"\n\nXCODEBUILD=\"/usr/bin/xcodebuild\"\nif ! [[ -x \"${XCODEBUILD}\" ]]; then\n    XCODEBUILD=$(which xcodebuild || true)\nfi\n# This check is last because it takes 10 seconds or so if it fails.\nif ! [[ -x \"${XCODEBUILD}\" ]]; then\n    XCODEBUILD=\"$(/usr/bin/xcrun --find xcodebuild 2>>${LOG_FILE} || true)\"\nfi\n\nRECOMMENDED_MIN_XCODE_VERSION=8\n\nusage() {\n    echo \"Usage: $0 [options]\" >&2\n    echo -e \"\\t-n            Don't clean before building/installing.\" >&2\n    echo -e \"\\t-d            Debug build. (Release is the default.)\" >&2\n    echo -e \"\\t-a            Build and archive, don't install. See Xcode docs for info about archiving.\" >&2\n    echo -e \"\\t-b            Build only, don't install.\" >&2\n    echo -e \"\\t-w            Ignore compiler warnings. (They're treated as errors by default.)\" >&2\n    echo -e \"\\t-x [options]  Extra options to pass to xcodebuild.\" >&2\n    echo -e \"\\t-c            Continue on script errors. Might not be safe.\" >&2\n    echo -e \"\\t-h            Print this usage statement.\" >&2\n    exit 1\n}\n\nbold_face() {\n    echo $(tput bold)$*$(tput sgr0)\n}\n\n# Takes a PID and returns 0 if the process is running.\nis_alive() {\n    kill -0 $1 > /dev/null 2>&1 && return 0 || return 1\n}\n\n# Shows a \"...\" animation until the previous command finishes. Shows an error message and exits the\n# script if the command fails. The return value will be the exit status of the command.\n#\n# Params:\n#  - The error message to show if the previous command fails.\n#  - An optional timeout in seconds.\nshow_spinner() {\n    disable_error_handling\n\n    local PREV_COMMAND_PID=$!\n\n    # Get the previous command as a string, with variables resolved. Assumes that if the command has\n    # a child process we just want the text of the child process's command. (And that it only has\n    # one child.)\n    local CHILD_PID=$(pgrep -P ${PREV_COMMAND_PID} | head -n1 || echo ${PREV_COMMAND_PID})\n    local PREV_COMMAND_STRING=$(ps -o command= ${CHILD_PID})\n    local TIMEOUT=${2:-0}\n\n    exec 3>&1 # Creates an alias so the following subshell can print to stdout.\n    DID_TIMEOUT=$(\n        I=1\n        while (is_alive ${PREV_COMMAND_PID}) && \\\n            ([[ ${TIMEOUT} -lt 1 ]] || [[ $I -lt ${TIMEOUT} ]])\n        do\n            printf '.' >&3\n            sleep 1\n            # Erase after we've printed three dots. (\\b is backspace.)\n            [[ $((I % 3)) -eq 0 ]] && printf '\\b\\b\\b   \\b\\b\\b' >&3\n            ((I++))\n        done\n        if [[ $I -eq ${TIMEOUT} ]]; then\n            kill ${PREV_COMMAND_PID} >> ${LOG_FILE} 2>&1\n            echo 1\n        else\n            echo 0\n        fi)\n    exec 3<&- # Close the file descriptor.\n\n    wait ${PREV_COMMAND_PID}\n    local EXIT_STATUS=$?\n\n    # Clean up the dots.\n    printf '\\b\\b\\b   \\b\\b\\b'\n\n    # Print an error message if the command fails.\n    # (wait returns 127 if the process has already exited.)\n    if [[ ${EXIT_STATUS} -ne 0 ]] && [[ ${EXIT_STATUS} -ne 127 ]]; then\n        ERROR_MSG=\"$1\"\n        if [[ ${DID_TIMEOUT} -ne 0 ]]; then\n            ERROR_MSG+=\"\\n\\nCommand timed out after ${TIMEOUT} seconds.\"\n        fi\n\n        error_handler ${LINENO} ${EXIT_STATUS} \"${PREV_COMMAND_STRING}\"\n\n        if [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then\n            exit ${EXIT_STATUS}\n        fi\n    fi\n\n    enable_error_handling\n\n    return ${EXIT_STATUS}\n}\n\nparse_options() {\n    while getopts \":ndabwx:ch\" opt; do\n        case $opt in\n            n)\n                CLEAN=\"\"\n                ;;\n            d)\n                CONFIGURATION=\"Debug\"\n                ;;\n            a)\n                # The \"archive\" action makes a build for distribution. It's the same as the archive\n                # option in Xcode. It won't install.\n                XCODEBUILD_ACTION=\"archive\"\n                # The dirs xcodebuild will put the archives in.\n                APP_PATH=\"$ARCHIVES_DIR/BGMApp.xcarchive\"\n                XPC_HELPER_OUTPUT_PATH=\"$ARCHIVES_DIR/BGMXPCHelper.xcarchive\"\n                DRIVER_PATH=\"$ARCHIVES_DIR/BGMDriver.xcarchive\"\n                ;;\n            b)\n                # Just build; don't install.\n                XCODEBUILD_ACTION=\"build\"\n                # The dirs xcodebuild will build in.\n                # TODO: If these dirs were created by running this script without -b, they'll be\n                #       owned by root and xcodebuild will fail.\n                APP_PATH=\"./BGMApp/build\"\n                XPC_HELPER_OUTPUT_PATH=\"./BGMApp/build\"\n                DRIVER_PATH=\"./BGMDriver/build\"\n                ;;\n            w)\n                # TODO: What if they also pass their own OTHER_CFLAGS with -x?\n                XCODEBUILD_OPTIONS=\"${XCODEBUILD_OPTIONS} OTHER_CFLAGS=\\\"-Wno-error\\\"\"\n                ;;\n            x)\n                XCODEBUILD_OPTIONS=\"$OPTARG\"\n                ;;\n            c)\n                CONTINUE_ON_ERROR=1\n                echo \"$(tput setaf 11)WARNING$(tput sgr0): Ignoring errors.\"\n                disable_error_handling\n                ;;\n            h)\n                usage\n                ;;\n            \\?)\n                echo \"Invalid option: -$OPTARG\" >&2\n                usage\n                ;;\n        esac\n    done\n}\n\n# check_xcode return codes\nCHECK_XCODE_NO_ERR=0\nCHECK_XCODE_ERR_NO_CLTOOLS=1\nCHECK_XCODE_ERR_NO_XCODE=2\nCHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE=3\nCHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED=4\nCHECK_XCODE_ERR_OLD_VERSION=5\n\n# Checks if $XCODEBUILD is a usable xcodebuild. Exits with one of the status codes above.\ncheck_xcode() {\n    local EXIT_CODE=${CHECK_XCODE_NO_ERR}\n\n    # First, check xcodebuild exists on the system an is an executable.\n    if ! [[ -x \"${XCODEBUILD}\" ]] || ! /usr/bin/xcode-select --print-path &>/dev/null || \\\n        (! [[ -e /Library/Developer/CommandLineTools/usr/bin/git ]] && \\\n            ! pkgutil --pkg-info=com.apple.pkg.CLTools_Executables &>/dev/null); then\n        EXIT_CODE=${CHECK_XCODE_ERR_NO_CLTOOLS}\n    fi\n\n    # Check that Xcode is installed, not just the command line tools.\n    if [[ \"${XCODE_VERSION}\" == \"-1\" ]]; then\n        ((EXIT_CODE+=2))\n    fi\n\n    # Check they've already accepted the Xcode license. This code is mostly copied from\n    # Homebrew/Library/Homebrew/brew.sh.\n    disable_error_handling\n\n    local XCRUN_OUTPUT  # (Declared local before assigning so we can get $?.)\n    XCRUN_OUTPUT=\"$(/usr/bin/xcrun clang 2>&1)\"\n    local XCRUN_STATUS=\"$?\"\n    if [[ ${EXIT_CODE} -eq 0 ]] && \\\n        [[ \"${XCRUN_STATUS}\" -ne 0 ]] && \\\n        ( [[ \"${XCRUN_OUTPUT}\" = *license* ]] || [[ \"${XCRUN_OUTPUT}\" = *licence* ]] ); then\n        EXIT_CODE=${CHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED}\n    fi\n\n    enable_error_handling\n\n    # Version check.\n    local XCODE_MAJOR_VERSION=\"$(echo ${XCODE_VERSION} | sed 's/\\..*$//g')\" \n    if [[ ${EXIT_CODE} -eq 0 ]] && \\\n        [[ \"${XCODE_MAJOR_VERSION}\" -lt ${RECOMMENDED_MIN_XCODE_VERSION} ]]\n    then\n        EXIT_CODE=${CHECK_XCODE_ERR_OLD_VERSION}\n    fi\n\n    exit ${EXIT_CODE}\n}\n\n# Expects CHECK_XCODE_TASK_PID to be set and error handling to be disabled. Returns 1 if we need to\n# check for Xcode again.\nhandle_check_xcode_result() {\n    if [[ -z ${HANDLED_CHECK_XCODE_RESULT:-} ]]; then\n        HANDLED_CHECK_XCODE_RESULT=1\n        # Wait for the Xcode checks to finish.\n        wait ${CHECK_XCODE_TASK_PID}\n        CHECK_XCODE_TASK_STATUS=$?\n\n        # If there was a problem with Xcode/xcodebuild, print the error message and exit.\n        if [[ ${CHECK_XCODE_TASK_STATUS} -ne ${CHECK_XCODE_NO_ERR} ]]; then\n            handle_check_xcode_failure ${CHECK_XCODE_TASK_STATUS}\n            local STATUS=$?\n            return ${STATUS}\n        fi\n    fi\n\n    return 0\n}\n\n# Returns 1 if we need to check for Xcode again. Expects error handling to be disabled.\n#\n# Params:\n#  - The exit code of check_xcode.\nhandle_check_xcode_failure() {\n    local CONTINUE=${CONTINUE_ON_ERROR}\n\n    # No command line tools\n    if [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS} ]] || \\\n            [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]]; then\n        echo \"$(tput setaf 9)ERROR$(tput sgr0): The Xcode Command Line Tools don't seem to be\" \\\n             \"installed on your system.\" >&2\n        echo >&2\n        echo \"If you have Xcode installed, you should be able to install them with\" >&2\n        echo \"    sudo /usr/bin/xcode-select --install\" >&2\n        echo \"If not, you'll need to install Xcode (~9GB), because xcodebuild no longer works\" \\\n             \"without it.\" >&2\n        echo >&2\n    fi\n\n    # No Xcode (that the command line tools are aware of)\n    if [[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] || \\\n            [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]]; then\n        if ! handle_check_xcode_failure_no_xcode $1; then\n            return 1\n        fi\n    fi\n\n    # They need to agree to the Xcode license.\n    if [[ $1 -eq ${CHECK_XCODE_ERR_LICENSE_NOT_ACCEPTED} ]]; then\n        echo \"$(tput setaf 9)ERROR$(tput sgr0): You need to agree to the Xcode license before you\" \\\n             \"can build Background Music. Run this command and then try again:\" >&2\n        echo \"    sudo ${XCODEBUILD} -license\" >&2\n        echo >&2\n    fi\n\n    # Xcode version is probably too old.\n    if [[ $1 -eq ${CHECK_XCODE_ERR_OLD_VERSION} ]]; then\n        echo \"$(tput setaf 11)WARNING$(tput sgr0): Your version of Xcode (${XCODE_VERSION}) may\" \\\n             \"not be recent enough to build Background Music. If you have a newer version\" \\\n             \"installed, you can set the Xcode command line tools to use it with\" >&2\n        echo \"    sudo /usr/bin/xcode-select --switch /the/path/to/your/Xcode.app\" >&2\n        echo >&2\n        CONTINUE=1\n    fi\n\n    # Try to find Xcode and print a more useful error message.\n    if [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS} ]] || \\\n            [[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] || \\\n            [[ $1 -eq ${CHECK_XCODE_ERR_NO_CLTOOLS_OR_XCODE} ]] || \\\n            [[ $1 -eq ${CHECK_XCODE_ERR_OLD_VERSION} ]]; then\n        echo \"Searching for Xcode installations...\" >&2\n        echo >&2\n        XCODE_PATHS=$(mdfind \"kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode' || \\\n                              kMDItemCFBundleIdentifier == 'com.apple.Xcode'\")\n\n        if [[ \"${XCODE_PATHS}\" != \"\" ]]; then\n            echo \"It looks like you have Xcode installed to:\" >&2\n            echo \"${XCODE_PATHS}\" >&2\n        else\n            echo \"None found.\" >&2\n        fi\n    fi\n\n    # Exit with an error status, unless we only printed a warning or were told to continue anyway.\n    if [[ ${CONTINUE} -eq 0 ]]; then\n        exit $1\n    fi\n\n    return 0\n}\n\n# Returns 1 if we need to check for Xcode again. Expects error handling to be disabled.\n#\n# Params:\n#  - The exit code of check_xcode.\nhandle_check_xcode_failure_no_xcode() {\n    # The problem could be that they have Xcode installed, but the command line tools can't find\n    # it.\n\n    # Just check for Xcode in the default location at first, since searching with mdfind can\n    # take a while.\n    if [[ $1 -eq ${CHECK_XCODE_ERR_NO_XCODE} ]] && [[ -d /Applications/Xcode.app ]]; then\n        echo \"It looks like you have Xcode installed to /Applications/Xcode.app, but the\" \\\n             \"Xcode command line tools aren't set to use it. Try\" >&2\n        echo \"    sudo /usr/bin/xcode-select --switch /Applications/Xcode.app\" >&2\n        echo \"and then run this installer again.\" >&2\n    else\n        echo \"$(tput setaf 9)ERROR$(tput sgr0): Unfortunately, Xcode (~9GB) is required to\" \\\n             \"build Background Music, but ${XCODEBUILD} doesn't appear to be usable. If you\" \\\n             \"do have Xcode installed, try telling the Xcode command line tools where to find\" \\\n             \"it with\" >&2\n        echo \"    sudo /usr/bin/xcode-select --switch /the/path/to/your/Xcode.app\" >&2\n        echo \"and then running this installer again.\" >&2\n    fi\n    echo >&2\n\n    # Explain how to revert the command we suggested.\n    local DEV_DIR_PATH  # (Declared local before assigning so we can get $?.)\n    DEV_DIR_PATH=$(/usr/bin/xcode-select --print-path 2>/dev/null)\n    local XCSELECT_STATUS=\"$?\"\n    if [[ \"${XCSELECT_STATUS}\" -eq 0 ]] && [[ \"${DEV_DIR_PATH}\" != \"\" ]]; then\n        echo \"If you want to change it back afterwards for some reason, use\" >&2\n        echo \"    sudo /usr/bin/xcode-select --switch ${DEV_DIR_PATH}\" >&2\n    fi\n    echo >&2\n\n    # Print the error message from xcodebuild.\n    local OUTPUT_MSG=\"Output from ${XCODEBUILD}: ------------------------------------------\"\n    echo \"${OUTPUT_MSG}\" >&2\n    \"${XCODEBUILD}\" -version >&2 || true\n    echo \"${OUTPUT_MSG}\" | tr '[:print:]' - >&2\n    echo >&2\n\n    # Offer to switch if it will probably work.\n    if [[ -d /Applications/Xcode.app ]]; then\n        local SWITCH_CMD=\"sudo xcode-select --switch /Applications/Xcode.app\"\n        read -p \"Try \\\"$(bold_face ${SWITCH_CMD})\\\" now (y/N)? \" TRY_SWITCH\n        if [[ \"${TRY_SWITCH}\" == \"y\" ]] || [[ \"${TRY_SWITCH}\" == \"Y\" ]]; then\n            echo\n            echo \"+ ${SWITCH_CMD}\"\n            eval ${SWITCH_CMD}\n            local XCS_STATUS=$?\n            if [[ ${XCS_STATUS} -eq 0 ]]; then\n                # Return that we should call check_xcode again to see if it worked.\n                echo\n                echo Seemed to work. Trying to start installation again...\n                echo\n                return 1\n            else\n                echo >&2\n                echo \"\\\"$(bold_face ${SWITCH_CMD})\\\" failed with exit status ${XCS_STATUS}.\" >&2\n                exit ${XCS_STATUS}\n            fi\n        fi\n        echo\n    fi\n\n    return 0\n}\n\nlog_debug_info() {\n    # Log some environment details, version numbers, etc. This takes a while, so we do it in the\n    # background.\n\n    (disable_error_handling\n        echo \"Background Music Build Log\" >> ${LOG_FILE}\n        echo \"----\" >> ${LOG_FILE}\n        echo \"Build script args: $*\" >> ${LOG_FILE}\n        echo \"System details:\" >> ${LOG_FILE}\n\n        sw_vers >> ${LOG_FILE} 2>&1\n        # The same as uname -a, except without printing the nodename (for privacy).\n        uname -mrsv >> ${LOG_FILE} 2>&1\n\n        /bin/bash --version >> ${LOG_FILE} 2>&1\n\n        echo \"On git branch: $(git rev-parse --abbrev-ref HEAD 2>&1)\" >> ${LOG_FILE}\n        echo \"Most recent commit: $(git rev-parse HEAD 2>&1)\" \\\n             \"(\\\"$(git show -s --format=%s HEAD 2>&1)\\\")\" >> ${LOG_FILE}\n\n        echo \"Using xcodebuild: ${XCODEBUILD}\" >> ${LOG_FILE}\n        echo \"Using BGMXPCHelper output path: ${XPC_HELPER_OUTPUT_PATH}\" >> ${LOG_FILE}\n\n        xcode-select --version >> ${LOG_FILE} 2>&1\n        echo \"Xcode path: $(xcode-select --print-path 2>&1)\" >> ${LOG_FILE}\n        echo \"Xcode version:\" >> ${LOG_FILE}\n        xcodebuild -version >> ${LOG_FILE} 2>&1\n        echo \"Xcode SDKs:\" >> ${LOG_FILE}\n        xcodebuild -showsdks >> ${LOG_FILE} 2>&1\n        xcrun --version >> ${LOG_FILE} 2>&1\n        echo \"Clang version:\" >> ${LOG_FILE}\n        $(/usr/bin/xcrun --find clang 2>&1) --version >> ${LOG_FILE} 2>&1\n\n        echo \"launchctl version: $(launchctl version 2>&1)\" >> ${LOG_FILE}\n        echo \"----\" >> ${LOG_FILE}) &\n\n    LOG_DEBUG_INFO_TASK_PID=$!\n}\n\n# Cleans the build products and intermediate files for a build scheme.\n#\n# Params:\n#  - The Xcode build scheme to clean, e.g. \"Background Music Device\".\nclean() {\n    if [[ \"${CLEAN}\" != \"\" ]]; then\n        ${SUDO} \"${XCODEBUILD}\" -scheme \"$1\" \\\n                                -configuration ${CONFIGURATION} \\\n                                BUILD_DIR=./build \\\n                                ${CLEAN} >> ${LOG_FILE} 2>&1\n    fi\n}\n\n# Register our handler so we can print a message and clean up if there's an error.\nenable_error_handling\n\nparse_options \"$@\"\n\n# Warn if running as root.\nif [[ $(id -u) -eq 0 ]]; then\n    echo \"$(tput setaf 11)WARNING$(tput sgr0): This script is not intended to be run as root. Run\" \\\n         \"it normally and it'll sudo when it needs to.\" >&2\nfi\n\n# Print initial message.\nif [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n    XPC_HELPER_OUTPUT_PATH=\"$(BGMApp/BGMXPCHelper/safe_install_dir.sh)\"\n\n    echo \"$(bold_face About to install Background Music). Please pause all audio, if you can.\"\n    [[ \"${CONFIGURATION}\" == \"Debug\" ]] && echo \"Debug build.\"\n    echo\n    echo \"This script will install:\"\n    echo \" - ${APP_PATH}/${APP_DIR}\"\n    echo \" - ${DRIVER_PATH}/${DRIVER_DIR}\"\n    echo \" - ${XPC_HELPER_OUTPUT_PATH}/${XPC_HELPER_DIR}\"\n    echo \" - /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist\"\n    echo\nelif [[ \"${XCODEBUILD_ACTION}\" == \"archive\" ]]; then\n    echo \"$(bold_face Building and archiving Background Music...)\"\n    echo\nelse\n    echo \"$(bold_face Building Background Music...)\"\n    echo\nfi\n\n# Make sure Xcode and the command line tools are installed and recent enough.\n# This sets XCODE_VERSION to major.minor, e.g. 8.3, or -1 if Xcode isn't installed.\nget_xcode_version() {\n    local XCODE_VERSION_FULL\n    # Separated from the line below to workaround an intermittent bug in xcodebuild 16.1.\n    XCODE_VERSION_FULL=$(${XCODEBUILD} -version 2>/dev/null || echo 'V -1')\n    echo \"${XCODE_VERSION_FULL}\" | head -n 1 | awk '{ print $2 }'\n}\nXCODE_VERSION=$(get_xcode_version)\ncheck_xcode &\nCHECK_XCODE_TASK_PID=$!\n\nif [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n    read -p \"Continue (y/N)? \" CONTINUE_INSTALLATION\n\n    if [[ \"${CONTINUE_INSTALLATION}\" != \"y\" ]] && [[ \"${CONTINUE_INSTALLATION}\" != \"Y\" ]]; then\n        echo \"Installation cancelled.\"\n        exit 0\n    fi\nfi\n\n# If the check_xcode process has already finished, we can check the result early.\nNEED_TO_HANDLE_CHECK_XCODE_RESULT=1\nif ! is_alive ${CHECK_XCODE_TASK_PID}; then\n    disable_error_handling\n\n    handle_check_xcode_result\n    NEED_TO_HANDLE_CHECK_XCODE_RESULT=$?\n\n    enable_error_handling\nfi\n\n# Update the user's sudo timestamp if we're going to need to sudo at some point. This prompts the\n# user for their password.\nif [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n    if ! sudo -v; then\n        echo \"$(tput setaf 9)ERROR$(tput sgr0): This script must be run by a user with\" \\\n             \"administrator (sudo) privileges.\" >&2\n        exit 1\n    fi\n    echo\nfi\n\nwhile [[ ${NEED_TO_HANDLE_CHECK_XCODE_RESULT} -ne 0 ]]; do\n    disable_error_handling\n\n    handle_check_xcode_result\n    NEED_TO_HANDLE_CHECK_XCODE_RESULT=$?\n\n    enable_error_handling\ndone\n\nlog_debug_info \"$@\"\n\n# Set some variables that control the compilation commands below.\nif [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n    SUDO=\"sudo\"\n    ACTIONING=\"Installing\"\n    DSTROOT_ARG=\"DSTROOT=/\"\n    # Work around an Xcode (15.2) bug where xcodebuild incorrectly detects a dependency cycle if\n    # DSTROOT is set to /.\n    for v in /Volumes/*; do\n        if [[ \"$(realpath \"$v\")\" == \"/\" ]]; then\n            DSTROOT_ARG=\"DSTROOT=$v\"\n            echo \"Set DSTROOT_ARG to ${DSTROOT_ARG} to work around an Xcode bug.\" >> ${LOG_FILE}\n            break\n        fi\n    done\n    echo \"DSTROOT_ARG: ${DSTROOT_ARG}.\" >> ${LOG_FILE}\nelif [[ \"${XCODEBUILD_ACTION}\" == \"archive\" ]]; then\n    SUDO=\"\"\n    ACTIONING=\"Building and archiving\"\n    DSTROOT_ARG=\"\"\nelse\n    # No need to sudo if we're only building.\n    SUDO=\"\"\n    ACTIONING=\"Building\"\n    DSTROOT_ARG=\"\"\nfi\n\n# Enable AddressSanitizer in debug builds to catch memory bugs. Allow ENABLE_ASAN to be set as an\n# environment variable by only setting it here if it isn't already set. (Used by package.sh.)\nif [[ \"${CONFIGURATION}\" == \"Debug\" ]]; then\n    ENABLE_ASAN=\"${ENABLE_ASAN:-YES}\"\nelse\n    ENABLE_ASAN=\"${ENABLE_ASAN:-NO}\"\nfi\n\nenableUBSanArg() {\n    if [[ \"${ENABLE_UBSAN+}\" != \"\" ]]; then\n        echo \"-enableUndefinedBehaviorSanitizer\"\n        echo \"$ENABLE_UBSAN\"\n    fi\n}\n\n# Clean all projects. Done separately to workaround what I think is a bug in Xcode 10.0. If you just\n# add \"clean\" to the other xcodebuild commands, they seem to fail because of the DSTROOT=\"/\" arg.\nif [[ \"${CLEAN}\" != \"\" ]]; then\n    if [[ \"${XCODEBUILD_ACTION}\" == \"archive\" ]]; then\n        # Delete any previous archives to force Xcode to rebuild them.\n        /bin/rm -rf \"${ARCHIVES_DIR}\" >> ${LOG_FILE} 2>&1\n    fi\n\n    # Disable the -e shell option and error trap for build commands so we can handle errors\n    # differently.\n    (disable_error_handling\n        clean \"Background Music Device\"\n        clean \"PublicUtility\"\n        clean \"BGMXPCHelper\"\n        clean \"Background Music\"\n        # Also delete the build dirs as files/dirs left in them can make the install step fail and,\n        # if you're using Xcode 10, the commands above will have cleaned the DerivedData dir but not\n        # the build dirs. I think this is a separate Xcode bug. See\n        # <http://www.openradar.me/40906897>.\n        ${SUDO} /bin/rm -rf BGMDriver/build BGMApp/build >> ${LOG_FILE} 2>&1) &\n\n    echo \"Cleaning\"\n    show_spinner \"Clean command failed. Try deleting the directories BGMDriver/build and \\\nBGMApp/build manually and running '$0 -n' to skip the cleaning step.\"\nfi\n\n# Prints the -archivePath option if we're archiving (i.e. making a .xcarchive). Does nothing if not.\n# Params:\n#  - The name for the archive. The .xcarchive extension will be added.\narchivePath() {\n    if [[ \"${XCODEBUILD_ACTION}\" == \"archive\" ]]; then\n        echo \"-archivePath\"\n        echo \"$ARCHIVES_DIR/$1\"\n    fi\n}\n\n# Prints the INSTALL_OWNER and INSTALL_GROUP arguments to use for the xcodebuild commands.\nownershipArgs() {\n    if [[ \"${XCODEBUILD_ACTION}\" != \"install\" ]]; then\n        # Stop xcodebuild from trying to chown the files in the archive to root when making an\n        # archive, so we don't need to use sudo.\n        echo \"INSTALL_OWNER=\"\n        echo \"INSTALL_GROUP=\"\n    fi\n}\n\n# BGMDriver\n\necho \"[1/3] ${ACTIONING} the virtual audio device $(bold_face ${DRIVER_DIR}) to\" \\\n         \"$(bold_face ${DRIVER_PATH})\" \\\n     | tee -a ${LOG_FILE}\n\n(disable_error_handling\n    # Build and, if requested, archive or install BGMDriver.\n    ${SUDO} \"${XCODEBUILD}\" -scheme \"Background Music Device\" \\\n                            -configuration ${CONFIGURATION} \\\n                            -enableAddressSanitizer ${ENABLE_ASAN} \\\n                            $(enableUBSanArg) \\\n                            $(archivePath BGMDriver) \\\n                            BUILD_DIR=./build \\\n                            RUN_CLANG_STATIC_ANALYZER=0 \\\n                            $(ownershipArgs) \\\n                            ${DSTROOT_ARG} \\\n                            ${XCODEBUILD_OPTIONS} \\\n                            \"${XCODEBUILD_ACTION}\" >> ${LOG_FILE} 2>&1) &\n\nshow_spinner \"${BUILD_FAILED_ERROR_MSG}\"\n\n# BGMXPCHelper\n\necho \"[2/3] ${ACTIONING} $(bold_face ${XPC_HELPER_DIR}) to $(bold_face ${XPC_HELPER_OUTPUT_PATH})\" \\\n    | tee -a ${LOG_FILE}\n\nxpcHelperInstallPathArg() {\n    if [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n        echo \"INSTALL_PATH=${XPC_HELPER_OUTPUT_PATH}\"\n    fi\n}\n\n# The Xcode project file is configured so that xcodebuild will call post_install.sh after it\n# finishes building. It calls post_install.sh with the ACTION env var set to \"install\" when\n# XCODEBUILD_ACTION is set to \"archive\" here. The REAL_ACTION arg in this command is a workaround\n# that lets post_install.sh know when we're archiving.\n(disable_error_handling\n    ${SUDO} \"${XCODEBUILD}\" -scheme BGMXPCHelper \\\n                            -configuration ${CONFIGURATION} \\\n                            -enableAddressSanitizer ${ENABLE_ASAN} \\\n                            $(enableUBSanArg) \\\n                            $(archivePath BGMXPCHelper) \\\n                            BUILD_DIR=./build \\\n                            RUN_CLANG_STATIC_ANALYZER=0 \\\n                            $(xpcHelperInstallPathArg) \\\n                            $(ownershipArgs) \\\n                            REAL_ACTION=\"${XCODEBUILD_ACTION}\" \\\n                            ${DSTROOT_ARG} \\\n                            ${XCODEBUILD_OPTIONS} \\\n                            \"${XCODEBUILD_ACTION}\" >> ${LOG_FILE} 2>&1) &\n\nshow_spinner \"${BUILD_FAILED_ERROR_MSG}\"\n\n# BGMApp\n\necho \"[3/3] ${ACTIONING} $(bold_face ${APP_DIR}) to $(bold_face ${APP_PATH})\" \\\n     | tee -a ${LOG_FILE}\n\n(disable_error_handling\n    ${SUDO} \"${XCODEBUILD}\" -scheme \"Background Music\" \\\n                            -configuration ${CONFIGURATION} \\\n                            -enableAddressSanitizer ${ENABLE_ASAN} \\\n                            $(enableUBSanArg) \\\n                            $(archivePath BGMApp) \\\n                            BUILD_DIR=./build \\\n                            RUN_CLANG_STATIC_ANALYZER=0 \\\n                            $(ownershipArgs) \\\n                            ${DSTROOT_ARG} \\\n                            ${XCODEBUILD_OPTIONS} \\\n                            \"${XCODEBUILD_ACTION}\" >> ${LOG_FILE} 2>&1) &\n\nshow_spinner \"${BUILD_FAILED_ERROR_MSG}\"\n\nif [[ \"${XCODEBUILD_ACTION}\" == \"install\" ]]; then\n    # Fix Background Music.app owner/group.\n    #\n    # We have to run xcodebuild as root to install BGMXPCHelper because it installs to directories\n    # owned by root. But that means the build directory gets created by root, and since BGMApp uses\n    # the same build directory we have to run xcodebuild as root to install BGMApp as well.\n    #\n    # TODO: Can't we just chown -R the build dir before we install BGMApp? Then we wouldn't have to\n    #       install BGMApp as root. (But maybe still handle the unlikely case of APP_PATH not being\n    #       user-writable.)\n    sudo chown -R \"$(whoami):admin\" \"${APP_PATH}/${APP_DIR}\"\n\n    # Fix the build directories' owner/group. This is mainly so the whole source directory can be\n    # deleted easily after installing.\n    sudo chown -R \"$(whoami):admin\" \"BGMApp/build\" \"BGMDriver/build\"\n\n    # Restart coreaudiod.\n\n    echo \"Restarting coreaudiod to load the virtual audio device.\" \\\n         | tee -a ${LOG_FILE}\n\n    # The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently\n    # some of these commands don't work with older versions of launchctl, so I figure there's no\n    # harm in trying a bunch of different ways (which should all work).\n    (sudo launchctl kickstart -k system/com.apple.audio.coreaudiod &>/dev/null || \\\n        sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n        sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n        sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n        sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n        (sudo launchctl unload \"${COREAUDIOD_PLIST}\" &>/dev/null && \\\n            sudo launchctl load \"${COREAUDIOD_PLIST}\" &>/dev/null) || \\\n        sudo killall coreaudiod &>/dev/null) && \\\n        sleep 5\n\n    # Invalidate sudo ticket\n    sudo -k\n\n    # Open BGMApp. We have to change the default audio device after restarting coreaudiod and this\n    # is the easiest way.\n    echo \"Launching Background Music.\"\n\n    ERROR_MSG=\"${BGMAPP_FAILED_TO_START_ERROR_MSG}\"\n    open \"${APP_PATH}/${APP_DIR}\"\n\n    # Ignore script errors from this point.\n    disable_error_handling\n\n    # Wait up to 5 seconds for Background Music to start.\n    (trap 'exit 1' TERM\n        while ! (ps -Ao ucomm= | grep 'Background Music' > /dev/null); do\n            sleep 1\n        done) &\n    show_spinner \"${BGMAPP_FAILED_TO_START_ERROR_MSG}\" 5\n\n    echo \"Done.\"\nelif [[ \"${XCODEBUILD_ACTION}\" == \"archive\" ]]; then\n    # Copy the dSYMs (debug symbols) into the correct directories in the archives. I haven't been\n    # able to figure out why Xcode isn't doing this automatically.\n    cp -r \"BGMDriver/build/Release/Background Music Device.driver.dSYM\" \"$DRIVER_PATH/dSYMs\"\n    cp -r \"BGMApp/build/Release/BGMXPCHelper.xpc.dSYM\" \"$XPC_HELPER_OUTPUT_PATH/dSYMs\"\n    mv \"$APP_PATH/Products/Applications/Background Music.app/Contents/MacOS/Background Music.dSYM\" \\\n       \"$APP_PATH/dSYMs\"\nfi\n\n"
  },
  {
    "path": "package.sh",
    "content": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# package.sh\n#\n# Copyright © 2017-2022 Kyle Neideck\n# Copyright © 2016, 2017 Takayama Fumihiko\n# Copyright © 2023 modue sp. z o.o.\n#\n# Builds Background Music and packages it into a .pkg file. Call this script with -d to use the\n# debug build configuration.\n#\n# Call it with -r expanded_package_dir to repackage a package expanded using\n#     pkgutil --expand-full original_package.pkg expanded_package_dir\n# This is useful after code signing the bundles in the expanded package.\n#\n# Based on https://github.com/tekezo/Karabiner-Elements/blob/master/make-package.sh\n#\n\n# TODO: Code signing. See `man productbuild`.\n\nset -o nounset\nset -o errexit\nset -o pipefail\n\nPATH=\"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin\"; export PATH\n\n# Sets all dirs in $1 to 755 (rwxr-xr-x) and all files in $1 to 644 (rw-r--r--).\nset_permissions() {\n    find \"$1\" -print0 | while read -d $'\\0' filepath; do\n        if [ -d \"$filepath\" ]; then\n            chmod -h 755 \"$filepath\"\n        else\n            chmod -h 644 \"$filepath\"\n        fi\n    done\n}\n\n# --------------------------------------------------\n\n# Use the release configuration and archive by default.\npackaging_operation=\"make_release_package\"\n\n# Handle the options passed to this script.\nwhile getopts \":dr:h\" opt; do\n    case $opt in\n        d)\n            packaging_operation=\"make_debug_package\"\n            ;;\n        r)\n            packaging_operation=\"repackage\"\n            repackage_dir=\"$(realpath \"$OPTARG\")\"\n            ;;\n        h)\n            # Just print out the header from this file.\n            awk '/^# package.sh/,/^$/' \"$0\"\n            exit 0\n            ;;\n        \\?)\n            echo \"Invalid option: -$OPTARG\" >&2\n            exit 1\n            ;;\n    esac\ndone\n\nif [[ $packaging_operation == \"repackage\" ]]; then\n    # No need to build anything if we're repackaging.\n    build_status=0\n\n    # Set the paths to the bundles in the expanded package that we're repackaging.\n    bgmapp_path=\"${repackage_dir}/Installer.pkg/Payload/Applications/Background Music.app\"\n    bgmdriver_path=\"${repackage_dir}/Installer.pkg/Payload/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\n    bgmxpchelper_path=\"${repackage_dir}/Installer.pkg/Scripts/BGMXPCHelper.xpc\"\nelif [[ $packaging_operation == \"make_debug_package\" ]]; then\n    # Build using the debug configuration.\n    #\n    # Disable AddressSanitizer and UndefinedBehaviorSanitizer so we can distribute debug packages to\n    # users without worrying about loading the AddressSanitizer and UndefinedBehaviorSanitizer\n    # dylibs in coreaudiod. We've also had issues loading those dylibs in the other binaries when\n    # the binaries were built on other systems.\n    #\n    # TODO: Would debug packages be more useful if they were built with optimization (i.e. using the\n    #       DebugOpt configuration instead of Debug)?\n    ENABLE_ASAN=NO ENABLE_UBSAN=NO bash build_and_install.sh -b -d\n    build_status=$?\n\n    # Set the paths to the build products (i.e. the bundles).\n    bgmapp_path=\"BGMApp/build/Debug/Background Music.app\"\n    bgmdriver_path=\"BGMDriver/build/Debug/Background Music Device.driver\"\n    bgmxpchelper_path=\"BGMApp/build/Debug/BGMXPCHelper.xpc\"\nelse\n    # Build and archive. (It uses the release config when archiving.)\n    bash build_and_install.sh -a\n    build_status=$?\n\n    # Set the paths to the build products (i.e. the bundles). Note that these are in the archives.\n    bgmapp_path=\"archives/BGMApp.xcarchive/Products/Applications/Background Music.app\"\n    bgmdriver_path=\"archives/BGMDriver.xcarchive/Products/Library/Audio/Plug-Ins/HAL/Background Music Device.driver\"\n    bgmxpchelper_path=\"archives/BGMXPCHelper.xcarchive/Products/usr/local/libexec/BGMXPCHelper.xpc\"\nfi\n\n# Exit if the build failed.\nif [[ $build_status -ne 0 ]]; then\n    exit $build_status\nfi\n\n# Read the version string from the build.\nversion=\"$(/usr/libexec/PlistBuddy \\\n    -c \"Print CFBundleShortVersionString\" \\\n    \"${bgmapp_path}/Contents/Info.plist\")\"\n\n# Everything in out_dir at the end of this script will be released in the CI builds.\nout_dir=\"Background-Music-$version\"\nrm -rf \"$out_dir\"\nmkdir \"$out_dir\"\n\nif [[ $packaging_operation == \"make_release_package\" ]]; then\n    # Put the archives in a zip file. This file is mainly useful because the debug symbols (dSYMs)\n    # are in it.\n    echo \"Making archives zip\"\n    zip -r --quiet \"$out_dir/background-music-xcarchives-$version.zip\" \"archives\"\nfi\n\n# --------------------------------------------------\n\necho \"Compiling ListInputDevices\"\n\nif ! [[ $packaging_operation == \"repackage\" ]]; then\n    echo \"Compiling ListInputDevices\"\n    swiftc pkg/ListInputDevices.swift -o pkg/ListInputDevices-x86_64 -target x86_64-apple-macos13.0\n    swiftc pkg/ListInputDevices.swift -o pkg/ListInputDevices-arm64 -target arm64-apple-macos13.0\n    # Combine the x86_64 and arm64 binaries into a universal binary.\n    lipo -create pkg/ListInputDevices-x86_64 pkg/ListInputDevices-arm64 -output pkg/ListInputDevices\n    rm pkg/ListInputDevices-x86_64 pkg/ListInputDevices-arm64\nfi\n\n# --------------------------------------------------\n\necho \"Copying Files\"\n\nrm -rf \"pkgroot\"\nmkdir -p \"pkgroot\"\n\nmkdir -p \"pkgroot/Library/Audio/Plug-Ins/HAL\"\nmkdir -p \"pkgroot/Applications\"\nscripts_dir=\"$(mktemp -d)\"\n\nif [[ $packaging_operation == \"repackage\" ]]; then\n    # When repackaging, only set the permissions of the dirs this script creates. The copied\n    # dirs/files should already have the right permissions and we don't want to break any code\n    # signatures.\n    set_permissions \"pkgroot\"\n    set_permissions \"$scripts_dir\"\n\n    repackage_scripts_dir=\"${repackage_dir}/Installer.pkg/Scripts\"\n    cp \"${repackage_scripts_dir}/preinstall\" \"$scripts_dir\"\n    cp \"${repackage_scripts_dir}/postinstall\" \"$scripts_dir\"\n    cp \"${repackage_scripts_dir}/ListInputDevices\" \"$scripts_dir\"\n    cp \"${repackage_scripts_dir}/com.bearisdriving.BGM.XPCHelper.plist.template\" \"$scripts_dir\"\n    cp \"${repackage_scripts_dir}/safe_install_dir.sh\" \"$scripts_dir\"\n    cp \"${repackage_scripts_dir}/post_install.sh\" \"$scripts_dir\"\nelse\n    cp \"pkg/preinstall\" \"$scripts_dir\"\n    cp \"pkg/postinstall\" \"$scripts_dir\"\n    mv \"pkg/ListInputDevices\" \"$scripts_dir\"\n    cp \"BGMApp/BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template\" \"$scripts_dir\"\n    cp \"BGMApp/BGMXPCHelper/safe_install_dir.sh\" \"$scripts_dir\"\n    cp \"BGMApp/BGMXPCHelper/post_install.sh\" \"$scripts_dir\"\nfi\n\n# Copy the bundles.\ncp -R \"$bgmdriver_path\" \"pkgroot/Library/Audio/Plug-Ins/HAL/\"\ncp -R \"$bgmapp_path\" \"pkgroot/Applications\"\ncp -R \"$bgmxpchelper_path\" \"$scripts_dir\"\n\n# Set the file/dir permissions.\nif [[ $packaging_operation != \"repackage\" ]]; then\n    set_permissions \"pkgroot\"\n    chmod 755 \"pkgroot/Applications/Background Music.app/Contents/MacOS/Background Music\"\n    chmod 755 \"pkgroot/Applications/Background Music.app/Contents/Resources/uninstall.sh\"\n    chmod 755 \"pkgroot/Applications/Background Music.app/Contents/Resources/_uninstall-non-interactive.sh\"\n    chmod 755 \"pkgroot/Library/Audio/Plug-Ins/HAL/Background Music Device.driver/Contents/MacOS/Background Music Device\"\n\n    set_permissions \"$scripts_dir\"\n    chmod 755 \"$scripts_dir/preinstall\"\n    chmod 755 \"$scripts_dir/postinstall\"\n    chmod 755 \"$scripts_dir/ListInputDevices\"\n    chmod 755 \"$scripts_dir/BGMXPCHelper.xpc/Contents/MacOS/BGMXPCHelper\"\nfi\n\n# Copy the package resources.\nrm -rf \"pkgres\"\nmkdir -p \"pkgres\"\n\nif [[ $packaging_operation == \"repackage\" ]]; then\n    cp \"${repackage_dir}/Resources/FermataIcon.pdf\" \"pkgres\"\n    cp \"${repackage_dir}/Distribution\" \"pkg/Distribution.xml\"\nelse\n    cp \"Images/FermataIcon.pdf\" \"pkgres\"\n    # Populate the Distribution.xml template and copy it. It only has one template variable so far.\n    sed \"s/{{VERSION}}/$version/g\" \"pkg/Distribution.xml.template\" > \"pkg/Distribution.xml\"\nfi\n\n# --------------------------------------------------\n\nif [[ $packaging_operation == \"repackage\" ]]; then\n    # Include \"repackaged\" in the filename just to make it clear.\n    pkg=\"$out_dir/BackgroundMusic-$version.repackaged.pkg\"\nelse\n    # As a security check for releases, we manually build the same package locally, compare it to\n    # the release built in CI and then code sign it. (And then remove the code signature on a\n    # different computer and check that it still matches the one from CI.) So we include \"unsigned\"\n    # in the name to differentiate the two versions.\n    pkg=\"$out_dir/BackgroundMusic-$version.unsigned.pkg\"\nfi\n\npkg_identifier=\"com.bearisdriving.BGM\"\n\necho \"Creating .pkg installer: $pkg\"\n\n# Build the \"component package\". (See `man pkgbuild`.)\npkgbuild \\\n    --root \"pkgroot\" \\\n    --component-plist \"pkg/pkgbuild.plist\" \\\n    --identifier \"$pkg_identifier\" \\\n    --scripts \"$scripts_dir\" \\\n    --version \"$version\" \\\n    --install-location \"/\" \\\n    \"$out_dir/Installer.pkg\"\n\n# Build the .pkg installer. This is basically a wrapper around the Installer.pkg we just built that\n# adds the background image and the settings in Distribution.xml.\nproductbuild \\\n    --distribution \"pkg/Distribution.xml\" \\\n    --identifier \"$pkg_identifier\" \\\n    --resources \"pkgres\" \\\n    --package-path \"$out_dir\" \\\n    \"$pkg\"\n\n# Clean up.\nrm -f \"$out_dir/Installer.pkg\"\nrm -rf \"pkgroot\"\nrm -rf \"pkgres\"\nrm -f \"pkg/Distribution.xml\"\n\n# Print checksums.\necho \"Checksums:\"\nmd5 \"$pkg\"\necho -n \"SHA256 \"\nshasum -a 256 \"$pkg\"\n\nif [[ $packaging_operation == \"make_release_package\" ]]; then\n    # Print the checksums of the archives zip.\n    md5 \"$out_dir/background-music-xcarchives-$version.zip\"\n    echo -n \"SHA256 \"\n    shasum -a 256 \"$out_dir/background-music-xcarchives-$version.zip\"\nfi\n\n"
  },
  {
    "path": "pkg/Distribution.xml.template",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<installer-gui-script minSpecVersion=\"1\">\n    <title>Background Music {{VERSION}}</title>\n\n    <volume-check>\n        <allowed-os-versions>\n            <!-- TODO: Get this from the Xcode project files instead of hardcoding it. -->\n            <os-version min=\"10.13\" />\n        </allowed-os-versions>\n    </volume-check>\n\n    <!--\n      Do not specify <domains>.\n      Installer does not show \"OS X version X.Y.Z or later is required\" message when <domains> exists.\n      <domains enable_anywhere=\"false\" enable_currentUserHome=\"false\" enable_localSystem=\"true\" />\n      -->\n\n    <pkg-ref id=\"com.bearisdriving.BGM\"/>\n\n    <options customize=\"never\" require-scripts=\"false\" hostArchitectures=\"arm64,x86_64\" />\n\n    <choices-outline>\n        <line choice=\"default\">\n            <line choice=\"com.bearisdriving.BGM\" />\n        </line>\n    </choices-outline>\n\n    <choice id=\"default\"/>\n\n    <choice id=\"com.bearisdriving.BGM\" visible=\"false\">\n        <pkg-ref id=\"com.bearisdriving.BGM\" />\n    </choice>\n\n    <pkg-ref id=\"com.bearisdriving.BGM\" version=\"{{VERSION}}\">Installer.pkg</pkg-ref>\n\n    <pkg-ref id=\"com.bearisdriving.BGM\">\n        <must-close>\n            <app id=\"com.bearisdriving.BGM.App\" />\n        </must-close>\n    </pkg-ref>\n\n    <background file=\"FermataIcon.pdf\" alignment=\"bottomleft\" mime-type=\"application/pdf\" />\n</installer-gui-script>\n\n\n"
  },
  {
    "path": "pkg/ListInputDevices.swift",
    "content": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// modify it under the terms of the GNU General Public License as\n// published by the Free Software Foundation, either version 2 of the\n// License, or (at your option) any later version.\n//\n// Background Music is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n//\n//  ListInputDevices.swift\n//\n//  Copyright © 2022 Kyle Neideck\n//\n//  The postinstall script uses this to check that BGMDevice was installed successfully.\n//\n\nimport AVFoundation\n\nvar devices: Array<AVCaptureDevice>\n\nif #available(macOS 10.15, *) {\n    devices = AVCaptureDevice.DiscoverySession(\n        deviceTypes: [ .builtInMicrophone, .externalUnknown ],\n        mediaType: .audio,\n        position: .unspecified\n    ).devices\n} else {\n    devices = AVCaptureDevice.devices(for: .audio)\n}\n\nprint(devices.map {\n    (device: AVCaptureDevice) -> Array<String> in\n    [device.uniqueID, device.modelID, device.localizedName]\n})\n"
  },
  {
    "path": "pkg/README.md",
    "content": "Files used by [package.sh](../package.sh), the script that creates the .pkg installer.\n"
  },
  {
    "path": "pkg/pkgbuild.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <array>\n    <dict>\n      <key>RootRelativeBundlePath</key>      <string>Applications/Background Music.app</string>\n      <key>BundleHasStrictIdentifier</key>   <false/>\n      <key>BundleIsRelocatable</key>         <true/>\n      <key>BundleIsVersionChecked</key>      <false/>\n      <key>BundleOverwriteAction</key>       <string>upgrade</string>\n    </dict>\n    <dict>\n      <key>RootRelativeBundlePath</key>      <string>Library/Audio/Plug-Ins/HAL/Background Music Device.driver</string>\n      <key>BundleHasStrictIdentifier</key>   <false/>\n      <key>BundleIsRelocatable</key>         <false/>\n      <key>BundleIsVersionChecked</key>      <false/>\n      <key>BundleOverwriteAction</key>       <string>upgrade</string>\n    </dict>\n  </array>\n</plist>\n\n"
  },
  {
    "path": "pkg/postinstall",
    "content": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# postinstall\n#\n# Copyright © 2017-2022, 2024 Kyle Neideck\n# Copyright © 2023 modue sp. z o.o.\n#\n\n# Make sure we use the built-in versions of programs, for consistency. Probably not necessary\n# because the installer will set $PATH in a similar way, but it doesn't hurt.\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\nfunction log {\n    logger \"$@\"\n    echo \"$@\"\n}\n\nfunction log_output {\n    while read line\n    do\n        log \"$line\"\n    done < /dev/stdin\n}\n\ncoreaudiod_plist=\"/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist\"\ndest_volume=\"$3\"\nxpc_helper_install_path=\"$(bash safe_install_dir.sh -y)\"\n\nlog \"Installing BGMXPCHelper to $xpc_helper_install_path\"\n\n# Delete the old version first, if any, to work around a macOS bug where a codesigned Mach-O binary\n# can fail verification if you overwrite the old version instead of replacing it. Apparently the\n# kernel caches some codesigning info that doesn't get updated. See\n# <https://forums.developer.apple.com/thread/126187>.\nrm -rf \"${xpc_helper_install_path}/BGMXPCHelper.xpc\"\ncp -Rf \"BGMXPCHelper.xpc\" \"$xpc_helper_install_path\"\n\n# Remove the quarantine attribute from BGMXPCHelper. Since macOS 14.0, this is needed to keep\n# Gatekeeper from trying to show a permission prompt when BGMXPCHelper is launched. That would\n# fail because BGMXPCHelper is a daemon, and Gatekeeper would prevent it from launching.\n#\n# TODO: I think, ideally, we should use the new Service Management API to install BGMXPCHelper,\n#       which would probably avoid this issue. See\n#       <https://developer.apple.com/documentation/servicemanagement/updating_your_app_package_installer_to_use_the_new_service_management_api>.\nlog \"Removing com.apple.quarantine attribute from BGMXPCHelper.\"\nsudo xattr -dr com.apple.quarantine \"${xpc_helper_install_path}/BGMXPCHelper.xpc\" 2>&1 | log_output\nls -lah@ \"${xpc_helper_install_path}/BGMXPCHelper.xpc\" 2>&1 | log_output\n\n# TODO: Fail the install and show an error message if this fails.\nbash \"post_install.sh\" \"$xpc_helper_install_path\" \"BGMXPCHelper.xpc/Contents/MacOS/BGMXPCHelper\" \".\"\n\n# TODO: Verify the installed files, their permissions, the _BGMXPCHelper user/group, etc.\n\n# The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently\n# some of these commands don't work with older versions of launchctl, so I figure there's no\n# harm in trying a bunch of different ways (which should all work).\n(sudo launchctl kickstart -k system/com.apple.audio.coreaudiod &>/dev/null || \\\n    sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n    sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \\\n    sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n    sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \\\n    sudo killall coreaudiod &>/dev/null || \\\n    (sudo launchctl unload \"$coreaudiod_plist\" &>/dev/null && \\\n        sudo launchctl load \"$coreaudiod_plist\" &>/dev/null)\n    ) && \\\n    sleep 2\n\n# Wait until coreaudiod has restarted and BGMDevice is ready to use.\nretries=5\nwhile [[ $retries -gt 0 ]]; do\n    device_list=\"$(system_profiler SPAudioDataType)\"\n\n    # The system_profiler command above doesn't work on GitHub Actions, so also try this\n    # alternative, which does. I haven't looked into the root cause, so this may be necessary for\n    # other systems as well.\n    input_device_list=\"$(./ListInputDevices)\"\n\n    # \"BGMDevice\" is the UID, which is a stable identifier. device_list doesn't include the UIDs, so\n    # we have to check for BGMDevice's name.\n    if [[ \"$device_list\" =~ \"Background Music\" ]] || \\\n        [[ \"$input_device_list\" =~ \"BGMDevice\" ]]; then\n        log \"Background Music device is installed and available.\"\n        # Break out of the loop.\n        retries=0\n    else\n        retries=$((retries - 1))\n        if [[ $retries -gt 0 ]]; then\n            log \"Background Music device not found. Trying again in 3 seconds...\"\n            sleep 3\n        else\n            # TODO: We might be able to use <installation-check> to show error messages in the\n            #       installer GUI instead of just logging them. See\n            #       <https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html#//apple_ref/doc/uid/TP40005370-CH100-SW12>.\n            log \"Background Music device not found. Installation failed.\"\n            log \"Audio devices:\"\n            log \"$device_list\"\n            log \"Audio input devices:\"\n            log \"$input_device_list\"\n            exit 1\n        fi\n    fi\ndone\n\n# Launch BGMApp.\n#\n# Allow a few retries because it seems to sometimes fail intermittently in CI builds. For example,\n# <https://travis-ci.org/github/kyleneideck/BackgroundMusic/jobs/764126689>. From the error\n# messages, it looks like BGMApp isn't always registered with Launch Services by this point.\nlogged_in_user_id=\"$(id -u \"${USER}\")\"\ndid_open_bgmapp=false\nretries=5\n\nwhile [[ $did_open_bgmapp != \"true\" ]] && [[ $retries -gt 0 ]]; do\n    retries=$((retries - 1))\n\n    # Try opening BGMApp using its bundle ID first so the installer can declare it as \"relocatable\".\n    # That way, if the user moves BGMApp and then installs a newer version of Background Music, the\n    # installer will try to find the old version of BGMApp and put the new one in the same place.\n    #\n    # Use launchctl to make sure we don't run BGMApp as the installer user.\n    #\n    # TODO: If they have multiple copies of BGMApp, this might open one of the old ones.\n    log \"Opening Background Music.app by bundle ID\"\n    if launchctl asuser \"${logged_in_user_id}\" \\\n            open -b com.bearisdriving.BGM.App; then\n        did_open_bgmapp=true\n    fi\n\n    if [[ $did_open_bgmapp != \"true\" ]]; then\n        dest_volume_no_trailing_slash=\"${dest_volume/%\\//}\"\n        log \"Opening ${dest_volume_no_trailing_slash}/Applications/Background Music.app\"\n        if launchctl asuser \"${logged_in_user_id}\" \\\n                open \"${dest_volume_no_trailing_slash}/Applications/Background Music.app\"; then\n            did_open_bgmapp=true\n        fi\n    fi\n\n    if [[ $did_open_bgmapp != \"true\" ]]; then\n        log \"Opening Background Music.app using AppleScript\"\n        if osascript -e 'tell application \"Background Music\" to activate'; then\n            did_open_bgmapp=true\n        fi\n    fi\n\n    if [[ $did_open_bgmapp != \"true\" ]]; then\n        log \"Failed to open Background Music.app. Will retry shortly.\"\n        sleep 1\n    fi\ndone\n\n# If we couldn't open BGMApp, it probably didn't install properly, but it's not certain, so don't\n# fail the install.\nif [[ $did_open_bgmapp != \"true\" ]]; then\n    log \"Failed to open Background Music.app. Giving up.\"\nfi\n\n# The installer plays a sound when it finishes, so give BGMApp a second to launch.\nsleep 1\n\nexit 0\n\n\n\n"
  },
  {
    "path": "pkg/preinstall",
    "content": "#!/bin/sh\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# preinstall\n#\n# Copyright © 2017 Kyle Neideck\n#\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\n# Show a warning if we can't find a safe dir to install BGMXPCHelper in. Should be very unlikely.\nxpc_helper_path=\"$(bash safe_install_dir.sh -y)\"\n\nif [ \"$(bash safe_install_dir.sh \"$xpc_helper_path\")\" != \"1\" ]; then\n    # TODO: This message could be more user friendly.\n    msg=\"We need to install a helper app called BGMXPCHelper to ${xpc_helper_path}, but it may not \"\n    msg+=\"be secure to do so.\\n\\n\"\n    msg+=\"${xpc_helper_path} and all of its parent directories should be owned by 'root', with the \"\n    msg+=\"group 'wheel', and have permissions 755 (rwxr-xr-x).\\n\\n\"\n    msg+=\"Background Music will still work if you decide to continue.\"\n\n    if [ \"$COMMAND_LINE_INSTALL\" == \"1\" ]; then\n        # As far as I can tell, we can't print anything the user is likely to see.\n        logger \"WARNING: $msg\"\n        exit 1\n    else\n        if ! osascript <<EOT\n            tell application id \"com.apple.systemuiserver\"\n                display alert \"Warning\" \\\n                    message \"$msg\" \\\n                    buttons {\"Cancel\", \"Install Anyway\"} \\\n                    default button \"Cancel\" \\\n                    cancel button \"Cancel\" \\\n                    as warning\n            end tell\nEOT\n        then\n            exit 1\n        fi\n    fi\nfi\n\nexit 0\n\n\n"
  },
  {
    "path": "uninstall.sh",
    "content": "#!/bin/bash\n# vim: tw=120:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation, either version 2 of the\n# License, or (at your option) any later version.\n#\n# Background Music is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Background Music. If not, see <http://www.gnu.org/licenses/>.\n\n#\n# uninstall.sh\n#\n# Copyright © 2016 Nick Jacques\n# Copyright © 2016, 2017 Kyle Neideck\n#\n# Removes BGMApp, BGMDriver and BGMXPCHelper from the system.\n#\n\n# Halt on errors.\nset -e\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH\n\nbold=$(tput bold)\nnormal=$(tput sgr0)\n\n# Warn if running as root.\nif [[ $(id -u) -eq 0 ]]; then\n  echo \"$(tput setaf 11)WARNING$(tput sgr0): This script is not intended to be run as root. Run\" \\\n       \"it normally and it'll sudo when it needs to.\" >&2\n  echo \"\"\nfi\n\necho \"${bold}You are about to uninstall Background Music.${normal}\"\necho \"Please pause all audio before continuing.\"\necho \"\"\nread -p \"Continue (y/N)? \" user_prompt\n\nif [ \"$user_prompt\" == \"y\" ] || [ \"$user_prompt\" == \"Y\" ]; then\n  # Run from the dir containing this script.\n  cd \"$( dirname \"${BASH_SOURCE[0]}\" )\"\n\n  if [ -f \"BGMApp/BGMApp/_uninstall-non-interactive.sh\" ]; then\n    # Running from the source directory.\n    bash \"BGMApp/BGMApp/_uninstall-non-interactive.sh\"\n  elif [ -f \"_uninstall-non-interactive.sh\" ]; then\n    # Probably running from Background Music.app/Contents/Resources.\n    bash \"_uninstall-non-interactive.sh\"\n  else\n    echo \"${bold}ERROR: Could not find _uninstall-non-interactive.sh${normal}\" >&2\n    exit 1\n  fi\n\n  # Invalidate sudo ticket\n  sudo -k\n\n  # Open System Settings and go to Sound > Output.\n  osascript -e 'tell application id \"com.apple.systempreferences\"\n                  activate\n                  reveal anchor \"output\" of pane id \"com.apple.preference.sound\"\n                end tell' >/dev/null || true\n  echo \"\"\n\nelse\n  echo \"Uninstall cancelled.\"\nfi\n\n\n"
  }
]