Full Code of kyleneideck/BackgroundMusic for AI

master 7ea90878fa80 cached
289 files
2.2 MB
596.5k tokens
726 symbols
1 requests
Download .txt
Showing preview only (2,380K chars total). Download the full file or copy to clipboard to get everything.
Repository: kyleneideck/BackgroundMusic
Branch: master
Commit: 7ea90878fa80
Files: 289
Total size: 2.2 MB

Directory structure:
gitextract_t8bnn9v3/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── other.md
│   └── workflows/
│       └── build-test-release.yml
├── .gitignore
├── BGM.xcworkspace/
│   └── contents.xcworkspacedata
├── BGMApp/
│   ├── BGMApp/
│   │   ├── BGMApp-Debug.entitlements
│   │   ├── BGMApp.entitlements
│   │   ├── BGMAppDelegate.h
│   │   ├── BGMAppDelegate.mm
│   │   ├── BGMAppVolumes.h
│   │   ├── BGMAppVolumes.m
│   │   ├── BGMAppVolumesController.h
│   │   ├── BGMAppVolumesController.mm
│   │   ├── BGMAppWatcher.h
│   │   ├── BGMAppWatcher.m
│   │   ├── BGMAudioDevice.cpp
│   │   ├── BGMAudioDevice.h
│   │   ├── BGMAudioDeviceManager.h
│   │   ├── BGMAudioDeviceManager.mm
│   │   ├── BGMAutoPauseMenuItem.h
│   │   ├── BGMAutoPauseMenuItem.m
│   │   ├── BGMAutoPauseMusic.h
│   │   ├── BGMAutoPauseMusic.mm
│   │   ├── BGMBackgroundMusicDevice.cpp
│   │   ├── BGMBackgroundMusicDevice.h
│   │   ├── BGMDebugLoggingMenuItem.h
│   │   ├── BGMDebugLoggingMenuItem.m
│   │   ├── BGMDeviceControlSync.cpp
│   │   ├── BGMDeviceControlSync.h
│   │   ├── BGMDeviceControlsList.cpp
│   │   ├── BGMDeviceControlsList.h
│   │   ├── BGMOutputDeviceMenuSection.h
│   │   ├── BGMOutputDeviceMenuSection.mm
│   │   ├── BGMOutputVolumeMenuItem.h
│   │   ├── BGMOutputVolumeMenuItem.mm
│   │   ├── BGMPlayThrough.cpp
│   │   ├── BGMPlayThrough.h
│   │   ├── BGMPlayThroughRTLogger.cpp
│   │   ├── BGMPlayThroughRTLogger.h
│   │   ├── BGMPreferredOutputDevices.h
│   │   ├── BGMPreferredOutputDevices.mm
│   │   ├── BGMStatusBarItem.h
│   │   ├── BGMStatusBarItem.mm
│   │   ├── BGMSystemSoundsVolume.h
│   │   ├── BGMSystemSoundsVolume.mm
│   │   ├── BGMTermination.h
│   │   ├── BGMTermination.mm
│   │   ├── BGMUserDefaults.h
│   │   ├── BGMUserDefaults.m
│   │   ├── BGMVolumeChangeListener.cpp
│   │   ├── BGMVolumeChangeListener.h
│   │   ├── BGMXPCListener.h
│   │   ├── BGMXPCListener.mm
│   │   ├── Base.lproj/
│   │   │   └── MainMenu.xib
│   │   ├── Images.xcassets/
│   │   │   ├── AirPlayIcon.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── FermataIcon.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume0.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume1.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume2.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── Volume3.imageset/
│   │   │       └── Contents.json
│   │   ├── Info.plist
│   │   ├── LICENSE
│   │   ├── Music Players/
│   │   │   ├── BGMDecibel.h
│   │   │   ├── BGMDecibel.m
│   │   │   ├── BGMGooglePlayMusicDesktopPlayer.h
│   │   │   ├── BGMGooglePlayMusicDesktopPlayer.m
│   │   │   ├── BGMGooglePlayMusicDesktopPlayerConnection.h
│   │   │   ├── BGMGooglePlayMusicDesktopPlayerConnection.m
│   │   │   ├── BGMHermes.h
│   │   │   ├── BGMHermes.m
│   │   │   ├── BGMMusic.h
│   │   │   ├── BGMMusic.m
│   │   │   ├── BGMMusicPlayer.h
│   │   │   ├── BGMMusicPlayer.m
│   │   │   ├── BGMMusicPlayers.h
│   │   │   ├── BGMMusicPlayers.mm
│   │   │   ├── BGMScriptingBridge.h
│   │   │   ├── BGMScriptingBridge.m
│   │   │   ├── BGMSpotify.h
│   │   │   ├── BGMSpotify.m
│   │   │   ├── BGMSwinsian.h
│   │   │   ├── BGMSwinsian.m
│   │   │   ├── BGMVLC.h
│   │   │   ├── BGMVLC.m
│   │   │   ├── BGMVOX.h
│   │   │   ├── BGMVOX.m
│   │   │   ├── BGMiTunes.h
│   │   │   ├── BGMiTunes.m
│   │   │   ├── Decibel.h
│   │   │   ├── GooglePlayMusicDesktopPlayer.js
│   │   │   ├── Hermes.h
│   │   │   ├── Music.h
│   │   │   ├── Spotify.h
│   │   │   ├── Swinsian.h
│   │   │   ├── VLC.h
│   │   │   ├── VOX.h
│   │   │   └── iTunes.h
│   │   ├── Preferences/
│   │   │   ├── BGMAboutPanel.h
│   │   │   ├── BGMAboutPanel.m
│   │   │   ├── BGMAutoPauseMusicPrefs.h
│   │   │   ├── BGMAutoPauseMusicPrefs.mm
│   │   │   ├── BGMPreferencesMenu.h
│   │   │   └── BGMPreferencesMenu.mm
│   │   ├── Scripting/
│   │   │   ├── BGMASApplication.h
│   │   │   ├── BGMASApplication.m
│   │   │   ├── BGMASOutputDevice.h
│   │   │   ├── BGMASOutputDevice.mm
│   │   │   ├── BGMApp.sdef
│   │   │   ├── BGMAppDelegate+AppleScript.h
│   │   │   └── BGMAppDelegate+AppleScript.mm
│   │   ├── SystemPreferences.h
│   │   ├── _uninstall-non-interactive.sh
│   │   └── main.m
│   ├── BGMApp.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           ├── BGMXPCHelper.xcscheme
│   │           └── Background Music.xcscheme
│   ├── BGMAppTests/
│   │   ├── UITests/
│   │   │   ├── BGMApp.h
│   │   │   ├── BGMAppUITests-Info.plist
│   │   │   ├── BGMAppUITests.mm
│   │   │   └── skip-ui-tests.py
│   │   └── UnitTests/
│   │       ├── BGMAppUnitTests-Info.plist
│   │       ├── BGMMusicPlayersUnitTests.mm
│   │       ├── BGMPlayThroughRTLoggerTests.mm
│   │       ├── BGMPlayThroughTests.mm
│   │       └── Mocks/
│   │           ├── MockAudioDevice.cpp
│   │           ├── MockAudioDevice.h
│   │           ├── MockAudioObject.cpp
│   │           ├── MockAudioObject.h
│   │           ├── MockAudioObjects.cpp
│   │           ├── MockAudioObjects.h
│   │           ├── Mock_CAHALAudioDevice.cpp
│   │           ├── Mock_CAHALAudioObject.cpp
│   │           └── Mock_CAHALAudioSystemObject.cpp
│   ├── BGMThreadSafetyAnalysis.h
│   ├── BGMXPCHelper/
│   │   ├── BGMXPCHelperService.h
│   │   ├── BGMXPCHelperService.mm
│   │   ├── BGMXPCListenerDelegate.h
│   │   ├── BGMXPCListenerDelegate.m
│   │   ├── Info.plist
│   │   ├── com.bearisdriving.BGM.XPCHelper.plist.template
│   │   ├── main.m
│   │   ├── post_install.sh
│   │   └── safe_install_dir.sh
│   ├── BGMXPCHelperTests/
│   │   ├── BGMXPCHelperTests-Info.plist
│   │   └── BGMXPCHelperTests.m
│   ├── OptimizationProfiles/
│   │   └── BGMApp.profdata
│   └── PublicUtility/
│       ├── BGMDebugLogging.c
│       ├── BGMDebugLogging.h
│       ├── CAAtomic.h
│       ├── CAAutoDisposer.h
│       ├── CABitOperations.h
│       ├── CACFArray.cpp
│       ├── CACFArray.h
│       ├── CACFDictionary.cpp
│       ├── CACFDictionary.h
│       ├── CACFNumber.cpp
│       ├── CACFNumber.h
│       ├── CACFString.cpp
│       ├── CACFString.h
│       ├── CADebugMacros.cpp
│       ├── CADebugMacros.h
│       ├── CADebugPrintf.cpp
│       ├── CADebugPrintf.h
│       ├── CADebugger.cpp
│       ├── CADebugger.h
│       ├── CAException.h
│       ├── CAHALAudioDevice.cpp
│       ├── CAHALAudioDevice.h
│       ├── CAHALAudioObject.cpp
│       ├── CAHALAudioObject.h
│       ├── CAHALAudioStream.cpp
│       ├── CAHALAudioStream.h
│       ├── CAHALAudioSystemObject.cpp
│       ├── CAHALAudioSystemObject.h
│       ├── CAHostTimeBase.cpp
│       ├── CAHostTimeBase.h
│       ├── CAMutex.cpp
│       ├── CAMutex.h
│       ├── CAPThread.cpp
│       ├── CAPThread.h
│       ├── CAPropertyAddress.h
│       ├── CARingBuffer.cpp
│       └── CARingBuffer.h
├── BGMDriver/
│   ├── BGMDriver/
│   │   ├── BGM_AbstractDevice.cpp
│   │   ├── BGM_AbstractDevice.h
│   │   ├── BGM_AudibleState.cpp
│   │   ├── BGM_AudibleState.h
│   │   ├── BGM_Control.cpp
│   │   ├── BGM_Control.h
│   │   ├── BGM_Device.cpp
│   │   ├── BGM_Device.h
│   │   ├── BGM_MuteControl.cpp
│   │   ├── BGM_MuteControl.h
│   │   ├── BGM_NullDevice.cpp
│   │   ├── BGM_NullDevice.h
│   │   ├── BGM_Object.cpp
│   │   ├── BGM_Object.h
│   │   ├── BGM_PlugIn.cpp
│   │   ├── BGM_PlugIn.h
│   │   ├── BGM_PlugInInterface.cpp
│   │   ├── BGM_Stream.cpp
│   │   ├── BGM_Stream.h
│   │   ├── BGM_TaskQueue.cpp
│   │   ├── BGM_TaskQueue.h
│   │   ├── BGM_VolumeControl.cpp
│   │   ├── BGM_VolumeControl.h
│   │   ├── BGM_WrappedAudioEngine.cpp
│   │   ├── BGM_WrappedAudioEngine.h
│   │   ├── BGM_XPCHelper.h
│   │   ├── BGM_XPCHelper.m
│   │   ├── DeviceClients/
│   │   │   ├── BGM_Client.cpp
│   │   │   ├── BGM_Client.h
│   │   │   ├── BGM_ClientMap.cpp
│   │   │   ├── BGM_ClientMap.h
│   │   │   ├── BGM_ClientTasks.h
│   │   │   ├── BGM_Clients.cpp
│   │   │   └── BGM_Clients.h
│   │   ├── DeviceIcon.icns
│   │   ├── Info.plist
│   │   └── quick_install.sh
│   ├── BGMDriver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           ├── Background Music Device.xcscheme
│   │           └── PublicUtility.xcscheme
│   ├── BGMDriverTests/
│   │   ├── BGM_ClientMapTests.mm
│   │   ├── BGM_ClientsTests.mm
│   │   ├── BGM_DeviceTests.mm
│   │   └── Info.plist
│   └── PublicUtility/
│       ├── CAAtomic.h
│       ├── CAAtomicStack.h
│       ├── CAAutoDisposer.h
│       ├── CABitOperations.h
│       ├── CACFArray.cpp
│       ├── CACFArray.h
│       ├── CACFDictionary.cpp
│       ├── CACFDictionary.h
│       ├── CACFNumber.cpp
│       ├── CACFNumber.h
│       ├── CACFString.cpp
│       ├── CACFString.h
│       ├── CADebugMacros.cpp
│       ├── CADebugMacros.h
│       ├── CADebugPrintf.cpp
│       ├── CADebugPrintf.h
│       ├── CADebugger.cpp
│       ├── CADebugger.h
│       ├── CADispatchQueue.cpp
│       ├── CADispatchQueue.h
│       ├── CAException.h
│       ├── CAHostTimeBase.cpp
│       ├── CAHostTimeBase.h
│       ├── CAMutex.cpp
│       ├── CAMutex.h
│       ├── CAPThread.cpp
│       ├── CAPThread.h
│       ├── CAPropertyAddress.h
│       ├── CARingBuffer.cpp
│       ├── CARingBuffer.h
│       ├── CAVolumeCurve.cpp
│       └── CAVolumeCurve.h
├── CONTRIBUTING.md
├── DEVELOPING.md
├── Images/
│   ├── FermataIcon.tex
│   ├── VolumeIcons.tex
│   ├── generate_icon_pngs.sh
│   └── iconizer.sh
├── LICENSE
├── LICENSE-Apple-Sample-Code
├── MANUAL-INSTALL.md
├── MANUAL-UNINSTALL.md
├── README.md
├── SharedSource/
│   ├── BGMXPCProtocols.h
│   ├── BGM_TestUtils.h
│   ├── BGM_Types.h
│   ├── BGM_Utils.cpp
│   ├── BGM_Utils.h
│   └── Scripts/
│       └── set-version.sh
├── TODO.md
├── build_and_install.sh
├── package.sh
├── pkg/
│   ├── Distribution.xml.template
│   ├── ListInputDevices.swift
│   ├── README.md
│   ├── pkgbuild.plist
│   ├── postinstall
│   └── preinstall
└── uninstall.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# This file was added to get GitHub to display our source code correctly when
# it has mixed tabs and spaces. (I didn't realise the sample code BGMDriver is
# based on used tabs and it's too late to fix it now.)
#
# See http://editorconfig.org.

# This is the top-most .editorconfig file.
root = true

# Set tabs to the width of 4 spaces in C, C++, Objective-C and Objective-C++
# source files.
[*.{h,c,cpp,m,mm}]
tab_width = 4




================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

## Example bug report template

> Don't worry if you have trouble getting some of this info. Just leave it out.

**Description of the bug**
> Please don't just say it's "not working".

**Steps to reproduce**
> Steps to reproduce the bug. This usually doesn't need to be super detailed.
1. Go to '...'
2. Click on '...'
3. See error message '...'

**Versions**
> Please complete the following information.
 - Background Music: [e.g. "0.4.3" or "0.4.0-SNAPSHOT-c0ab98b". `Preferences > About Background Music`]
 - macOS: [e.g. "11.3 Beta (20E5172i)" or "Big Sur". ` > About This Mac`]

**Hardware**
> Delete this part if you think it's probably not necessary.
 - Computer: [e.g. "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)". ` > About This Mac`]
 - Audio Device: [e.g. "Built-in Output. Manufacturer: Apple Inc. Output Channels: 2 [...]". `System Information app > Hardware > Audio`]

**Debug logs**
> 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.

[Debug logs attached here](https://github.com/example/background-music-debug-logs.txt)

**Other info**
> Anything else you want to add?

---

> Tips
> (Delete this section before posting.)
>  - https://github.com/kyleneideck/BackgroundMusic#troubleshooting
>  - Try the latest SNAPSHOT version from https://github.com/kyleneideck/BackgroundMusic/releases (if it's newer than the latest non-SNAPSHOT release).
>  - If your bug is one of these common issues, consider leaving a comment or a +1 (👍) on an existing issue:
>     - Background Music currently only supports audio devices with two channels. Bluetooth devices often only have one.
>     - 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)


================================================
FILE: .github/ISSUE_TEMPLATE/other.md
================================================
---
name: Other
about: Feature request, question, support request or anything else
title: ''
labels: ''
assignees: ''

---

> 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.


================================================
FILE: .github/workflows/build-test-release.yml
================================================
# TODO: Split this into multiple .yml files? Multiple jobs?
name: Build, Test and Release

on:
  push:
    branches:
      - '*'
    tags:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  # Build and test in the same job because the UI tests expect BGMDriver to be installed.
  build-and-test:
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    strategy:
      matrix:
        # TODO: Add older macOS versions.
        os:
          - macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Work in a case-sensitive disk image.
        # This lets us catch failures that only happen on case-sensitive filesystems.
        run: |
          hdiutil create \
            -type SPARSEBUNDLE \
            -fs 'Case-sensitive Journaled HFS+' \
            -volname bgmbuild \
            -nospotlight \
            -verbose \
            -attach \
            -size 100m \
            bgmbuild.dmg
          sudo cp -r . /Volumes/bgmbuild
          cd /Volumes/bgmbuild
      - name: Install coreutils for actions/runner/issues/884 workaround.
        # See https://github.com/actions/runner/issues/884#issuecomment-1018851327
        run: brew install coreutils
      - name: Build and install Background Music.
        run: |
          # `sudo` and `tput` expect this to be set.
          export TERM=xterm-256color
          genv --default-signal=PIPE yes | sudo ./build_and_install.sh
      - name: Print the log file.
        if: always()
        run: cat build_and_install.log
      - name: Log some checksums.
        run: 'find */build/Release/*/ -type f -exec md5 {} \;'
      - name: Log the installed audio devices and their IDs.
        run: |
          system_profiler SPAudioDataType
          say -a '?'
      - name: Check the BGM dirs and files were installed.
        run: |
          # These commands fail if the dir/file isn't found.
          ls -la "/Applications/Background Music.app"
          ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
          ls -la "/usr/local/libexec/BGMXPCHelper.xpc" \
            || ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
          ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
      - name: Close BGMApp (which the install script opened).
        run: >-
          osascript -e 'tell application "Background Music" to quit'
          || killall "Background Music"
      - name: Skip the UI tests. (They don't work on GitHub Actions yet.)
        run: BGMApp/BGMAppTests/UITests/skip-ui-tests.py
      - name: Run the tests.
        run: |
          echo '::group::BGMDriver Tests'
          xcodebuild \
            -quiet \
            -workspace BGM.xcworkspace \
            -scheme 'Background Music Device' \
            test
          echo '::endgroup::'

          echo '::group::BGMXPCHelper Tests'
          xcodebuild \
            -quiet \
            -workspace BGM.xcworkspace \
            -scheme 'BGMXPCHelper' \
            test
          echo '::endgroup::'

          # Grant BGMApp authorization to use input devices.
          # This is necessary for the UI tests because accepting the "Background Music would like to
          # use the microphone" dialog programmatically isn't reliable.
          # TODO: Commented out because we would need to generate the csreq (codesign signature)
          #       value to match the BGMApp bundle the tests will run against.
          # dbPath="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
          # values="'kTCCServiceMicrophone','com.bearisdriving.BGM.App',0,2,2,1,X'FADE0C000000004800000001000000070000000800000014545ABE68FAF437700B14984BB24117EDDA1BBF2C0000000800000014386FB63B9CD6BA6E83CEDEAF4EDEE177C1FAEA92',NULL,NULL,'UNUSED',NULL,0,1652845317"
          # sqlQuery="INSERT OR IGNORE INTO access VALUES($values);"
          # sqlite3 "$dbPath" "$sqlQuery" || (echo "Failed to modify $dbPath"; exit 1)
          # # Log the added TCC.db entry.
          # sqlite3 "$dbPath" "select * from access where client like '%BGM%';"

          echo '::group::BGMApp Tests'
          # TODO: Commented out in case it uses too much CPU.
          # log stream --info \
          #   --predicate 'process == "coreaudiod" or
          #                process == "Background Music" or
          #                process == "BGMXPCHelper" or
          #                composedMessage contains[cd] "Background Music" or
          #                composedMessage contains "BGM"' > app.log &
          xcodebuild \
            -quiet \
            -workspace BGM.xcworkspace \
            -scheme 'Background Music' \
            test
          echo '::endgroup::'
      - name: Upload the test results.
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: bgm-test-results
          path: |
            /Users/runner/Library/Developer/Xcode/DerivedData/*/Logs/Test/*.xcresult
            app.log
            /Users/runner/Library/Logs/CrashReporter/*
            /Users/runner/Library/Logs/DiagnosticReports/*
      - name: Uninstall Background Music.
        run: |
          # `tput` expects this to be set.
          export TERM=xterm-256color
          genv --default-signal=PIPE yes | sudo ./uninstall.sh
      - name: Check the BGM dirs and files were removed.
        run: |
          if ls -la "/Applications/Background Music.app"; then exit 1; fi
          if ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"; then exit 1; fi
          if ls -la "/usr/local/libexec/BGMXPCHelper.xpc"; then exit 1; fi
          if ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"; then
            exit 1
          fi
          if ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"; then exit 1; fi
  release:
    runs-on: macos-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Build the .pkg installer.
        run: |
          # `sudo` and `tput` expect this to be set.
          export TERM=xterm-256color
          # If this build is for a tag with "DEBUG" in its name, build a debug package. (More
          # detailed logging, no optimization, etc.)
          if [[ "$GITHUB_REF" =~ .*DEBUG.* ]]; then
            sudo ./package.sh -d
          else
            sudo ./package.sh
          fi
      - name: Install the .pkg.
        # Delete archives/ first because it contains a copy of Background Music.app.
        # Background Music.app is "relocatable", which means that if the user moves it and then
        # installs a new version, macOS will put the new version in the same place. This makes sure
        # the installer puts Background Music.app in /Applications so the build won't fail when we
        # check that later.
        #
        # package.sh puts the archives in a zipfile next to the .pkg, so we can still upload them
        # after deleting the directory here.
        #
        # TODO: On TravisCI, this was failing for debug builds. We couldn't figure out why, so we
        #       might have to ignore that with
        #       || [[ "$GITHUB_REF" =~ .*DEBUG.* ]]
        run: |
          sudo rm -rf archives
          sudo installer \
            -pkg Background-Music-*/BackgroundMusic-*.pkg \
            -target / \
            -verbose \
            -dumplog
      - name: Print the installer logs.
        if: always()
        # This trims the start of the log to save space.
        run: grep -E -A 9999 -B 20 'Background.?Music' /var/log/install.log
      - name: Check the BGM dirs and files were installed.
        if: always()
        run: |
          ls -la "/Applications/Background Music.app"
          ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
          ls -la "/usr/local/libexec/BGMXPCHelper.xpc" \
            || ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
          ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
      - name: Upload the .pkg installer and archives.
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: pkg-installer
          path: Background-Music-*
      - name: Upload the log file from the package.sh build.
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: build-and-install-log-for-pkg
          path: build_and_install.log
# TODO: Create a GitHub release. This is the Travis YAML that was handling it:
# deploy:
#  provider: releases
#  api_key:
#    secure: j5Gd[...]
#  file_glob: true
#  file: Background-Music-*/*
#  skip_cleanup: true
#  name: $TRAVIS_TAG
#  prerelease: true
#  draft: true
#  on:
#    repo: kyleneideck/BackgroundMusic
#    tags: true
#    # TODO: Use "condition" to build master and tags?
#    condition: $DEPLOY = true

================================================
FILE: .gitignore
================================================
.DS_Store
.*.swp
/BGMDriver/BGMDriver/quick_install.conf
/build_and_install.log
.idea/
tags
cmake-build-debug/
/Background-Music-*/
BGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
Images/*.aux
Images/*.log
/archives/

# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore

## Build generated
build/
DerivedData

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata

## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa


================================================
FILE: BGM.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:BGMDriver/BGMDriver.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:BGMApp/BGMApp.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:README.md">
   </FileRef>
   <FileRef
      location = "group:MANUAL-INSTALL.md">
   </FileRef>
   <FileRef
      location = "group:MANUAL-UNINSTALL.md">
   </FileRef>
   <FileRef
      location = "group:TODO.md">
   </FileRef>
   <FileRef
      location = "group:DEVELOPING.md">
   </FileRef>
   <FileRef
      location = "group:CONTRIBUTING.md">
   </FileRef>
   <FileRef
      location = "group:LICENSE">
   </FileRef>
   <FileRef
      location = "group:LICENSE-Apple-Sample-Code">
   </FileRef>
   <FileRef
      location = "group:build_and_install.sh">
   </FileRef>
   <FileRef
      location = "group:uninstall.sh">
   </FileRef>
   <FileRef
      location = "group:package.sh">
   </FileRef>
   <FileRef
      location = "group:pkg">
   </FileRef>
   <FileRef
      location = "group:Images">
   </FileRef>
</Workspace>


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


================================================
FILE: BGMApp/BGMApp/BGMApp.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.automation.apple-events</key>
	<true/>
	<key>com.apple.security.device.audio-input</key>
	<true/>
</dict>
</plist>


================================================
FILE: BGMApp/BGMApp/BGMAppDelegate.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppDelegate.h
//  BGMApp
//
//  Copyright © 2016, 2017, 2020 Kyle Neideck
//  Copyright © 2021 Marcus Wu
//
//  Sets up and tears down the app.
//

// System Includes
#import <Cocoa/Cocoa.h>

@class BGMAudioDeviceManager;
@class BGMAppVolumesController;

// Tags for UI elements in MainMenu.xib
static NSInteger const kVolumesHeadingMenuItemTag = 3;
static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;

@interface BGMAppDelegate : NSObject <NSApplicationDelegate, NSMenuDelegate>

@property (weak) IBOutlet NSMenu* bgmMenu;

@property (weak) IBOutlet NSView* outputVolumeView;
@property (weak) IBOutlet NSTextField* outputVolumeLabel;
@property (weak) IBOutlet NSSlider* outputVolumeSlider;

@property (weak) IBOutlet NSView* systemSoundsView;
@property (weak) IBOutlet NSSlider* systemSoundsSlider;

@property (weak) IBOutlet NSView* appVolumeView;

@property (weak) IBOutlet NSPanel* aboutPanel;
@property (unsafe_unretained) IBOutlet NSTextView* aboutPanelLicenseView;

@property (weak) IBOutlet NSMenuItem* autoPauseMenuItemUnwrapped;
@property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped;

@property (readonly) BGMAudioDeviceManager* audioDevices;
@property BGMAppVolumesController* appVolumes;

@end



================================================
FILE: BGMApp/BGMApp/BGMAppDelegate.mm
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppDelegate.mm
//  BGMApp
//
//  Copyright © 2016-2022 Kyle Neideck
//  Copyright © 2021 Marcus Wu
//

// Self Include
#import "BGMAppDelegate.h"

// Local Includes
#import "BGM_Utils.h"
#import "BGMAppVolumes.h"
#import "BGMAppVolumesController.h"
#import "BGMAutoPauseMusic.h"
#import "BGMAutoPauseMenuItem.h"
#import "BGMDebugLoggingMenuItem.h"
#import "BGMMusicPlayers.h"
#import "BGMOutputDeviceMenuSection.h"
#import "BGMOutputVolumeMenuItem.h"
#import "BGMPreferencesMenu.h"
#import "BGMPreferredOutputDevices.h"
#import "BGMStatusBarItem.h"
#import "BGMSystemSoundsVolume.h"
#import "BGMTermination.h"
#import "BGMUserDefaults.h"
#import "BGMXPCListener.h"
#import "SystemPreferences.h"

// System Includes
#import <AVFoundation/AVCaptureDevice.h>


#pragma clang assume_nonnull begin

static NSString* const kOptNoPersistentData  = @"--no-persistent-data";
static NSString* const kOptShowDockIcon      = @"--show-dock-icon";

@implementation BGMAppDelegate {
    // The button in the system status bar that shows the main menu.
    BGMStatusBarItem* statusBarItem;
    
    // Only show the 'BGMXPCHelper is missing' error dialog once.
    BOOL haveShownXPCHelperErrorMessage;

    // Persistently stores user settings and data.
    BGMUserDefaults* userDefaults;

    BGMAutoPauseMusic* autoPauseMusic;
    BGMAutoPauseMenuItem* autoPauseMenuItem;
    BGMMusicPlayers* musicPlayers;
    BGMSystemSoundsVolume* systemSoundsVolume;
    BGMOutputDeviceMenuSection* outputDeviceMenuSection;
    BGMPreferencesMenu* prefsMenu;
    BGMDebugLoggingMenuItem* debugLoggingMenuItem;
    BGMXPCListener* xpcListener;
    BGMPreferredOutputDevices* preferredOutputDevices;
}

@synthesize audioDevices = audioDevices;
@synthesize appVolumes = appVolumes;

- (void) awakeFromNib {
    [super awakeFromNib];
    
    // Show BGMApp in the dock, if the command-line option for that was passed. This is used by the
    // UI tests.
    if ([NSProcessInfo.processInfo.arguments indexOfObject:kOptShowDockIcon] != NSNotFound) {
        [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    }
    
    haveShownXPCHelperErrorMessage = NO;

    // Set up audioDevices, which coordinates BGMDevice and the output device. It manages
    // playthrough, volume/mute controls, etc.
    if (![self initAudioDeviceManager]) {
        return;
    }

    // Stored user settings
    userDefaults = [self createUserDefaults];

    // Add the status bar item. (The thing you click to show BGMApp's main menu.)
    statusBarItem = [[BGMStatusBarItem alloc] initWithMenu:self.bgmMenu
                                              audioDevices:audioDevices
                                              userDefaults:userDefaults];
}

- (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
    #pragma unused (aNotification)
    
    // Log the version/build number.
    //
    // TODO: NSLog should only be used for logging errors.
    // TODO: Automatically add the commit ID to the end of the build number for unreleased builds. (In the
    //       Info.plist or something -- not here.)
    NSLog(@"BGMApp version: %@, BGMApp build number: %@",
          NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"],
          NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]);

    // Handles changing (or not changing) the output device when devices are added or removed. Must
    // be initialised before calling setBGMDeviceAsDefault.
    preferredOutputDevices =
        [[BGMPreferredOutputDevices alloc] initWithDevices:audioDevices userDefaults:userDefaults];

    // Skip this if we're compiling on a version of macOS before 10.14 as won't compile and it
    // isn't needed.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400  // MAC_OS_X_VERSION_10_14
    if (@available(macOS 10.14, *)) {
        // On macOS 10.14+ we need to get the user's permission to use input devices before we can
        // use BGMDevice for playthrough (see BGMPlayThrough), so we wait until they've given it
        // before making BGMDevice the default device. This way, if the user is playing audio when
        // they open Background Music, we won't interrupt it while we're waiting for them to click
        // OK.
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
                                 completionHandler:^(BOOL granted) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (granted) {
                    DebugMsg("BGMAppDelegate::applicationDidFinishLaunching: Permission granted");
                    [self continueLaunchAfterInputDevicePermissionGranted];
                } else {
                    NSLog(@"BGMAppDelegate::applicationDidFinishLaunching: Permission denied");
                    // If they don't accept, Background Music won't work at all and the only way to
                    // fix it is in System Preferences, so show an error dialog with instructions.
                    //
                    // TODO: It would be nice if this dialog had a shortcut to open the System
                    //       Preferences panel. See showSetDeviceAsDefaultError.
                    [self showErrorMessage:@"Background Music needs permission to use microphones."
                           informativeText:@"It uses a virtual microphone to access your system's "
                                            "audio.\n\nYou can grant the permission by going to "
                                            "System Preferences > Security and Privacy > "
                                            "Microphone and checking the box for Background Music."
                 exitAfterMessageDismissed:YES];
                }
            });
        }];
    }
    else
#endif
    {
        // We can change the device immediately on older versions of macOS because they don't
        // require user permission for input devices.
        [self continueLaunchAfterInputDevicePermissionGranted];
    }
}

- (void) continueLaunchAfterInputDevicePermissionGranted {
    // Choose an output device for BGMApp to use to play audio.
    if (![self setInitialOutputDevice]) {
        return;
    }

    // Make BGMDevice the default device.
    [self setBGMDeviceAsDefault];

    // Handle some of the unusual reasons BGMApp might have to exit, mostly crashes.
    BGMTermination::SetUpTerminationCleanUp(audioDevices);

    // Set up the rest of the UI and other external interfaces.
    musicPlayers = [[BGMMusicPlayers alloc] initWithAudioDevices:audioDevices
                                                    userDefaults:userDefaults];

    autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
                                                        musicPlayers:musicPlayers
                                                        userDefaults:userDefaults];

    [self setUpMainMenu];

    xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
                                  helperConnectionErrorHandler:^(NSError* error) {
        NSLog(@"BGMAppDelegate::continueLaunchAfterInputDevicePermissionGranted: "
              "(helperConnectionErrorHandler) BGMXPCHelper connection error: %@",
              error);
        [self showXPCHelperErrorMessage:error];
    }];
}

// Returns NO if (and only if) BGMApp is about to terminate because of a fatal error.
- (BOOL) initAudioDeviceManager {
    audioDevices = [BGMAudioDeviceManager new];

    if (!audioDevices) {
        [self showBGMDeviceNotFoundErrorMessageAndExit];
        return NO;
    }

    return YES;
}

// Returns NO if (and only if) BGMApp is about to terminate because of a fatal error.
- (BOOL) setInitialOutputDevice {
    AudioObjectID preferredDevice = [preferredOutputDevices findPreferredDevice];

    if (preferredDevice != kAudioObjectUnknown) {
        NSError* __nullable error = [audioDevices setOutputDeviceWithID:preferredDevice
                                                        revertOnFailure:NO];
        if (error) {
            // Show the error message.
            [self showFailedToSetOutputDeviceErrorMessage:BGMNN(error)
                                          preferredDevice:preferredDevice];
        }
    } else {
        // We couldn't find a device to use, so show an error message and quit.
        [self showOutputDeviceNotFoundErrorMessageAndExit];
        return NO;
    }

    return YES;
}

// Sets the "Background Music" virtual audio device (BGMDevice) as the user's default audio device.
- (void) setBGMDeviceAsDefault {
    NSError* error = [audioDevices setBGMDeviceAsOSDefault];

    if (error) {
        [self showSetDeviceAsDefaultError:error
                                  message:@"Could not set the Background Music device as your"
                                           "default audio device."
                          informativeText:@"You might be able to change it yourself."];
    }
}

- (void) menuWillOpen:(NSMenu*)menu {
    if (@available(macOS 10.16, *)) {
        // Set menu offset and check for any active menu items
        float menuOffset = 12.0;
        for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {
            if (menuItem.state == NSControlStateValueOn && menuItem.indentationLevel == 0) {
                menuOffset += 10;
                break;
            }
        }
        
        // Align volume output device and slider
        for (NSView* subview in self.outputVolumeView.subviews) {
            CGRect newSubview = subview.frame;
            newSubview.origin.x = menuOffset;
            subview.frame = newSubview;
        }

        // Align system sounds and app volumes
        double appIconTitleOffset = 0;
        for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {
            if (menuItem.view.subviews.count == 7 || menuItem.view.subviews.count == 3) {
                NSTextField* appTitle;
                NSImageView* appIcon;
                
                for (NSView* subview in menuItem.view.subviews) {
                    if (menuItem.view.subviews.count == 3) {
                        // System sounds
                        if ([subview isKindOfClass:[NSTextField class]]) {
                            appTitle = (NSTextField*)subview;
                        }
                        if ([subview isKindOfClass:[NSImageView class]]) {
                            appIcon = (NSImageView*)subview;
                        }
                    } else if (menuItem.view.subviews.count == 7) {
                        // App volumes
                        if ([subview isKindOfClass:[BGMAVM_AppNameLabel class]]) {
                            appTitle = (NSTextField*)subview;
                        }
                        if ([subview isKindOfClass:[BGMAVM_AppIcon class]]) {
                            appIcon = (NSImageView*)subview;
                        }
                    }
                }
 
                if (appIconTitleOffset == 0) {
                    appIconTitleOffset = appTitle.frame.origin.x - appIcon.frame.origin.x;
                }
                
                CGRect newAppIcon = appIcon.frame;
                newAppIcon.origin.x = menuOffset;
                appIcon.frame = newAppIcon;
                CGRect newAppTitle = appTitle.frame;
                newAppTitle.origin.x = menuOffset + appIconTitleOffset;
                appTitle.frame = newAppTitle;
            }
        }
    }
}

- (void) setUpMainMenu {
    autoPauseMenuItem =
        [[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped
                                        autoPauseMusic:autoPauseMusic
                                          musicPlayers:musicPlayers
                                          userDefaults:userDefaults];

    [self initVolumesMenuSection];

    // Output device selection.
    outputDeviceMenuSection =
            [[BGMOutputDeviceMenuSection alloc] initWithBGMMenu:self.bgmMenu
                                                   audioDevices:audioDevices
                                               preferredDevices:preferredOutputDevices];
    [audioDevices setOutputDeviceMenuSection:outputDeviceMenuSection];

    // Preferences submenu.
    prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu
                                               audioDevices:audioDevices
                                               musicPlayers:musicPlayers
                                              statusBarItem:statusBarItem
                                                 aboutPanel:self.aboutPanel
                                      aboutPanelLicenseView:self.aboutPanelLicenseView
                                               userDefaults:userDefaults];

    // Enable/disable debug logging. Hidden unless you option-click the status bar icon.
    debugLoggingMenuItem =
        [[BGMDebugLoggingMenuItem alloc] initWithMenuItem:self.debugLoggingMenuItemUnwrapped];
    [statusBarItem setDebugLoggingMenuItem:debugLoggingMenuItem];

    // Handle events about the main menu. (See the NSMenuDelegate methods below.)
    self.bgmMenu.delegate = self;
}

- (BGMUserDefaults*) createUserDefaults {
    BOOL persistentDefaults =
        [NSProcessInfo.processInfo.arguments indexOfObject:kOptNoPersistentData] == NSNotFound;
    NSUserDefaults* wrappedDefaults = persistentDefaults ? [NSUserDefaults standardUserDefaults] : nil;
    return [[BGMUserDefaults alloc] initWithDefaults:wrappedDefaults];
}

- (void) initVolumesMenuSection {
    // Create the menu item with the (main) output volume slider.
    BGMOutputVolumeMenuItem* outputVolume =
            [[BGMOutputVolumeMenuItem alloc] initWithAudioDevices:audioDevices
                                                             view:self.outputVolumeView
                                                           slider:self.outputVolumeSlider
                                                      deviceLabel:self.outputVolumeLabel];
    [audioDevices setOutputVolumeMenuItem:outputVolume];

    NSInteger headingIdx = [self.bgmMenu indexOfItemWithTag:kVolumesHeadingMenuItemTag];

    // Add it to the main menu below the "Volumes" heading.
    [self.bgmMenu insertItem:outputVolume atIndex:(headingIdx + 1)];

    // Add the volume control for system (UI) sounds to the menu.
    BGMAudioDevice uiSoundsDevice = [audioDevices bgmDevice].GetUISoundsBGMDeviceInstance();

    systemSoundsVolume =
        [[BGMSystemSoundsVolume alloc] initWithUISoundsDevice:uiSoundsDevice
                                                         view:self.systemSoundsView
                                                       slider:self.systemSoundsSlider];

    [self.bgmMenu insertItem:systemSoundsVolume.menuItem atIndex:(headingIdx + 2)];

    // Add the app volumes to the menu.
    appVolumes = [[BGMAppVolumesController alloc] initWithMenu:self.bgmMenu
                                                 appVolumeView:self.appVolumeView
                                                  audioDevices:audioDevices];
}

- (void) applicationWillTerminate:(NSNotification*)aNotification {
    #pragma unused (aNotification)
    
    DebugMsg("BGMAppDelegate::applicationWillTerminate");

    // Change the user's default output device back.
    NSError* error = [audioDevices unsetBGMDeviceAsOSDefault];
    
    if (error) {
        [self showSetDeviceAsDefaultError:error
                                  message:@"Failed to reset your system's audio output device."
                          informativeText:@"You'll have to change it yourself to get audio working again."];
    }
}

#pragma mark Error messages

- (void) showBGMDeviceNotFoundErrorMessageAndExit {
    // BGMDevice wasn't found on the system. Most likely, BGMDriver isn't installed. Show an error
    // dialog and exit.
    //
    // TODO: Check whether the driver files are in /Library/Audio/Plug-Ins/HAL? Might even want to
    //       offer to install them if not.
    [self showErrorMessage:@"Could not find the Background Music virtual audio device."
           informativeText:@"Make sure you've installed Background Music Device.driver to "
                            "/Library/Audio/Plug-Ins/HAL and restarted coreaudiod (e.g. \"sudo "
                            "killall coreaudiod\")."
 exitAfterMessageDismissed:YES];
}

- (void) showFailedToSetOutputDeviceErrorMessage:(NSError*)error
                                 preferredDevice:(BGMAudioDevice)device {
    NSLog(@"Failed to set initial output device. Error: %@", error);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSAlert* alert = [NSAlert alertWithError:BGMNN(error)];
        alert.messageText = @"Failed to set the output device.";

        NSString* __nullable name = nil;
        BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
            name = (__bridge NSString* __nullable)device.CopyName();
        });

        alert.informativeText =
                [NSString stringWithFormat:@"Could not start the device '%@'. (Error: %ld)",
                        name, error.code];

        [alert runModal];
    });
}

- (void) showOutputDeviceNotFoundErrorMessageAndExit {
    // We couldn't find any output devices. Show an error dialog and exit.
    [self showErrorMessage:@"Could not find an audio output device."
           informativeText:@"If you do have one installed, this is probably a bug. Sorry about "
                            "that. Feel free to file an issue on GitHub."
 exitAfterMessageDismissed:YES];
}

- (void) showXPCHelperErrorMessage:(NSError*)error {
    if (!haveShownXPCHelperErrorMessage) {
        haveShownXPCHelperErrorMessage = YES;
        
        // NSAlert should only be used on the main thread.
        dispatch_async(dispatch_get_main_queue(), ^{
            NSAlert* alert = [NSAlert new];
            
            // TODO: Offer to install BGMXPCHelper if it's missing.
            // TODO: Show suppression button?
            [alert setMessageText:@"Error connecting to BGMXPCHelper."];
            [alert setInformativeText:[NSString stringWithFormat:@"%s%s%@ (%lu)",
                                       "Make sure you have BGMXPCHelper installed. There are instructions in the "
                                       "README.md file.\n\n"
                                       "Background Music might still work, but it won't work as well as it could.",
                                       "\n\nDetails:\n",
                                       [error localizedDescription],
                                       [error code]]];
            [alert runModal];
        });
    }
}

- (void) showErrorMessage:(NSString*)message
          informativeText:(NSString*)informativeText
exitAfterMessageDismissed:(BOOL)fatal {
    // NSAlert should only be used on the main thread.
    dispatch_async(dispatch_get_main_queue(), ^{
        NSAlert* alert = [NSAlert new];
        [alert setMessageText:message];
        [alert setInformativeText:informativeText];

        // This crashes if built with Xcode 9.0.1, but works with versions of Xcode before 9 and
        // with 9.1.
        [alert runModal];

        if (fatal) {
            [NSApp terminate:self];
        }
    });
}

- (void) showSetDeviceAsDefaultError:(NSError*)error
                             message:(NSString*)msg
                     informativeText:(NSString*)info {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"%@ %@ Error: %@", msg, info, error);
        
        NSAlert* alert = [NSAlert alertWithError:error];
        alert.messageText = msg;
        alert.informativeText = info;
        
        [alert addButtonWithTitle:@"OK"];
        [alert addButtonWithTitle:@"Open Sound in System Preferences"];
        
        NSModalResponse buttonClicked = [alert runModal];
        
        if (buttonClicked != NSAlertFirstButtonReturn) {  // 'OK' is the first button.
            [self openSysPrefsSoundOutput];
        }
    });
}

- (void) openSysPrefsSoundOutput {
    SystemPreferencesApplication* __nullable sysPrefs =
        [SBApplication applicationWithBundleIdentifier:@"com.apple.systempreferences"];
    
    if (!sysPrefs) {
        NSLog(@"Could not open System Preferences");
        return;
    }
    
    // In System Preferences, go to the "Output" tab on the "Sound" pane.
    for (SystemPreferencesPane* pane : [sysPrefs panes]) {
        DebugMsg("BGMAppDelegate::openSysPrefsSoundOutput: pane = %s", [pane.name UTF8String]);
        
        if ([pane.id isEqualToString:@"com.apple.preference.sound"]) {
            sysPrefs.currentPane = pane;
            
            for (SystemPreferencesAnchor* anchor : [pane anchors]) {
                DebugMsg("BGMAppDelegate::openSysPrefsSoundOutput: anchor = %s", [anchor.name UTF8String]);
                
                if ([[anchor.name lowercaseString] isEqualToString:@"output"]) {
                    DebugMsg("BGMAppDelegate::openSysPrefsSoundOutput: Showing Output in Sound pane.");
                    
                    [anchor reveal];
                }
            }
        }
    }
    
    // Bring System Preferences to the foreground.
    [sysPrefs activate];
}

#pragma mark NSMenuDelegate

- (void) menuNeedsUpdate:(NSMenu*)menu {
    if ([menu isEqual:self.bgmMenu]) {
        [autoPauseMenuItem parentMenuNeedsUpdate];
    } else {
        DebugMsg("BGMAppDelegate::menuNeedsUpdate: Warning: unexpected menu. menu=%s", menu.description.UTF8String);
    }
}

- (void) menu:(NSMenu*)menu willHighlightItem:(NSMenuItem* __nullable)item {
    if ([menu isEqual:self.bgmMenu]) {
        [autoPauseMenuItem parentMenuItemWillHighlight:item];
    } else {
        DebugMsg("BGMAppDelegate::menu: Warning: unexpected menu. menu=%s", menu.description.UTF8String);
    }
}
@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAppVolumes.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppVolumes.h
//  BGMApp
//
//  Copyright © 2016, 2017 Kyle Neideck
//  Copyright © 2021 Marcus Wu
//  Copyright © 2026 TwelfthFace
//

// Local Includes
#import "BGMAppVolumesController.h"

// System Includes
#import <Cocoa/Cocoa.h>


#pragma clang assume_nonnull begin

@interface BGMAppVolumes : NSObject

- (id) initWithController:(BGMAppVolumesController*)inController
                  bgmMenu:(NSMenu*)inMenu
            appVolumeView:(NSView*)inView;

// Pass -1 for initialVolume or kAppPanNoValue for initialPan to leave the volume/pan at its default level.
- (void) insertMenuItemForApp:(NSRunningApplication*)app
                initialVolume:(int)volume
                   initialPan:(int)pan;

- (void) removeMenuItemForApp:(NSRunningApplication*)app;

- (void) removeAllAppVolumeMenuItems;

- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app;
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;

@end

// Protocol for the UI custom classes

@protocol BGMAppVolumeMenuItemSubview <NSObject>

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)item;

@end

// Custom classes for the UI elements in the app volume menu items

@interface BGMAVM_VolumeMute: NSButton <BGMAppVolumeMenuItemSubview>

- (void) bgm_syncForVolume:(int)vol;

@end


@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeMenuItemSubview>
@end

@interface BGMAVM_AppNameLabel : NSTextField <BGMAppVolumeMenuItemSubview>
@end

@interface BGMAVM_ShowMoreControlsButton : NSButton <BGMAppVolumeMenuItemSubview>
@end

@interface BGMAVM_VolumeSlider : NSSlider <BGMAppVolumeMenuItemSubview>

- (void) setRelativeVolume:(int)relativeVolume;

@end

@interface BGMAVM_PanSlider : NSSlider <BGMAppVolumeMenuItemSubview>

- (void) setPanPosition:(int)panPosition;

@end

#pragma clang assume_nonnull end




================================================
FILE: BGMApp/BGMApp/BGMAppVolumes.m
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppVolumes.m
//  BGMApp
//
//  Copyright © 2016-2020, 2026 Kyle Neideck
//  Copyright © 2017 Andrew Tonner
//  Copyright © 2021 Marcus Wu
//  Copyright © 2022 Jon Egan
//  Copyright © 2026 TwelfthFace
//

// Self Include
#import "BGMAppVolumes.h"

// Local Includes
#import "BGM_Types.h"
#import "BGM_Utils.h"
#import "BGMAppDelegate.h"

// PublicUtility Includes
#import "CADebugMacros.h"


static float const     kSlidersSnapWithin          = 5;
static CGFloat const   kAppVolumeViewInitialHeight = 20;
static NSString* const kMoreAppsMenuTitle          = @"More Apps";

@implementation BGMAppVolumes {
    BGMAppVolumesController* controller;

    NSMenu* bgmMenu;
    NSMenu* moreAppsMenu;
    
    NSView* appVolumeView;
    CGFloat appVolumeViewFullHeight;

    // The number of menu items this class has added to bgmMenu. Doesn't include the More Apps menu.
    NSInteger numMenuItems;
}

- (id) initWithController:(BGMAppVolumesController*)inController
                  bgmMenu:(NSMenu*)inMenu
            appVolumeView:(NSView*)inView {
    if ((self = [super init])) {
        controller = inController;
        bgmMenu = inMenu;
        moreAppsMenu = [[NSMenu alloc] initWithTitle:kMoreAppsMenuTitle];
        appVolumeView = inView;
        appVolumeViewFullHeight = appVolumeView.frame.size.height;
        numMenuItems = 0;

        // Add the More Apps menu to the main menu.
        NSMenuItem* moreAppsMenuItem =
            [[NSMenuItem alloc] initWithTitle:kMoreAppsMenuTitle action:nil keyEquivalent:@""];
        moreAppsMenuItem.submenu = moreAppsMenu;

        [bgmMenu insertItem:moreAppsMenuItem atIndex:([self lastMenuItemIndex] + 1)];
        numMenuItems++;

        // Put an empty menu item above the More Apps menu item to fix its top margin.
        NSMenuItem* spacer = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
        spacer.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 4)];
        spacer.hidden = YES;  // Tells accessibility clients to ignore this menu item.

        [bgmMenu insertItem:spacer atIndex:[self lastMenuItemIndex]];
        numMenuItems++;
    }
    
    return self;
}

#pragma mark UI Modifications

- (void) insertMenuItemForApp:(NSRunningApplication*)app
                initialVolume:(int)volume
                   initialPan:(int)pan {
    NSMenuItem* appVolItem = [self createBlankAppVolumeMenuItem];

    // Look through the menu item's subviews for the ones we want to set up
    for (NSView* subview in appVolItem.view.subviews) {
        if ([subview conformsToProtocol:@protocol(BGMAppVolumeMenuItemSubview)]) {
            [(NSView<BGMAppVolumeMenuItemSubview>*)subview setUpWithApp:app
                                                                context:self
                                                             controller:controller
                                                               menuItem:appVolItem];
        }
    }

    // Store the NSRunningApplication object with the menu item so when the app closes we can find the item to remove it
    appVolItem.representedObject = app;

    // Set the slider to the volume for this app if we got one from the driver
    [self setVolumeOfMenuItem:appVolItem relativeVolume:volume panPosition:pan];

    // NSMenuItem didn't implement NSAccessibility before OS X SDK 10.12.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200  // MAC_OS_X_VERSION_10_12
    if ([appVolItem respondsToSelector:@selector(setAccessibilityTitle:)]) {
        // TODO: This doesn't show up in Accessibility Inspector for me. Not sure why.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        appVolItem.accessibilityTitle = [NSString stringWithFormat:@"%@", [app localizedName]];
#pragma clang diagnostic pop
    }
#endif

    // Add the menu item to its menu.
    if (app.activationPolicy == NSApplicationActivationPolicyRegular) {
        [bgmMenu insertItem:appVolItem atIndex:[self firstMenuItemIndex]];
        numMenuItems++;
    } else if (app.activationPolicy == NSApplicationActivationPolicyAccessory) {
        [moreAppsMenu insertItem:appVolItem atIndex:0];
    }
}

- (NSMenuItem*) getMenuItemForApp:(NSRunningApplication*)app {
    NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;

    for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {
        NSMenuItem* item = [bgmMenu itemAtIndex:i];
        NSRunningApplication* itemApp = item.representedObject;
        BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);

        if ([itemApp isEqual:app]) {
            return item;
        }
    }
    for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {
        NSMenuItem* item = [moreAppsMenu itemAtIndex:i];
        NSRunningApplication* itemApp = item.representedObject;
        BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);

        if ([itemApp isEqual:app]) {
            return item;
        }
    }

    return nil;
}

- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app {
    BGMAppVolumeAndPan result = {
        .volume = -1,
        .pan = kAppPanNoValue
    };

    NSMenuItem *item = [self getMenuItemForApp:app];

    if (item == nil) {
        return result;
    }

    for (NSView* subview in item.view.subviews) {
        // Get the volume.
        if ([subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
            result.volume = [(BGMAVM_VolumeSlider*)subview intValue];
        }

        // Get the pan position.
        if ([subview isKindOfClass:[BGMAVM_PanSlider class]]) {
            result.pan = [(BGMAVM_PanSlider*)subview intValue];
        }
    }

    return result;
}

- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
    NSMenuItem *item = [self getMenuItemForApp:app];

    if (item == nil) {
        return;
    }

    for (NSView* subview in item.view.subviews) {
        // Set the volume.
        if (volumeAndPan.volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
            [(BGMAVM_VolumeSlider*)subview setRelativeVolume:volumeAndPan.volume];
        }

        // Set the pan position.
        if (volumeAndPan.pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
            [(BGMAVM_PanSlider*)subview setPanPosition:volumeAndPan.pan];
        }
    }

}

// Create a blank menu item to copy as a template.
- (NSMenuItem*) createBlankAppVolumeMenuItem {
    NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
    
    menuItem.view = appVolumeView;
    menuItem = [menuItem copy];  // So we can modify a copy of the view, rather than the template itself.
    
    return menuItem;
}

- (void) setVolumeOfMenuItem:(NSMenuItem*)menuItem relativeVolume:(int)volume panPosition:(int)pan {
    // Update the sliders.
    for (NSView* subview in menuItem.view.subviews) {
        // Set the volume.
        if (volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
            [(BGMAVM_VolumeSlider*)subview setRelativeVolume:volume];
        }

        // Set the pan position.
        if (pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
            [(BGMAVM_PanSlider*)subview setPanPosition:pan];
        }
    }
}

- (NSInteger) firstMenuItemIndex {
    return [self lastMenuItemIndex] - numMenuItems + 1;
}

- (NSInteger) lastMenuItemIndex {
    return [bgmMenu indexOfItemWithTag:kSeparatorBelowVolumesMenuItemTag] - 1;
}

- (void) removeMenuItemForApp:(NSRunningApplication*)app {
    // Subtract two extra positions to skip the More Apps menu and the spacer menu item above it.
    NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;

    // Check each app volume menu item and remove the item that controls the given app.

    // Look through the main menu.
    for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {
        NSMenuItem* item = [bgmMenu itemAtIndex:i];
        NSRunningApplication* itemApp = item.representedObject;
        BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);

        if ([itemApp isEqual:app]) {
            [bgmMenu removeItem:item];
            numMenuItems--;
            return;
        }
    }

    // Look through the More Apps menu.
    for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {
        NSMenuItem* item = [moreAppsMenu itemAtIndex:i];
        NSRunningApplication* itemApp = item.representedObject;
        BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);

        if ([itemApp isEqual:app]) {
            [moreAppsMenu removeItem:item];
            return;
        }
    }
}

- (void) showHideExtraControls:(BGMAVM_ShowMoreControlsButton*)button {
    // Show or hide an app's extra controls, currently only pan, in its App Volumes menu item.
    
    NSMenuItem* menuItem = button.cell.representedObject;
    
    BGMAssert(button, "!button");
    BGMAssert(menuItem, "!menuItem");

    CGFloat width = menuItem.view.frame.size.width;
#if DEBUG
    CGFloat height = menuItem.view.frame.size.height;
#endif

    const char* appName =
        [((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];

    // Using this function (instead of just ==) shouldn't be necessary, but just in case.
#if DEBUG
    BOOL(^nearEnough)(CGFloat x, CGFloat y) = ^BOOL(CGFloat x, CGFloat y) {
        return fabs(x - y) < 0.01;  // We don't need much precision.
    };
#endif
    
    bool allSubviewsShowing = true;
    for (NSView* subview in menuItem.view.subviews) {
        if (subview.hidden) {
            allSubviewsShowing = false;
            break;
        }
        //DebugMsg("BGMAppVolumes:: subview hash / hidden: (%lu) / (%hhd)", (unsigned long)subview.hash, subview.hidden);
    }

    if (allSubviewsShowing) {
        // Hide extra controls
        DebugMsg("BGMAppVolumes::showHideExtraControls: Hiding extra controls (%s)", appName);
        
        BGMAssert(nearEnough(height, appVolumeViewFullHeight), "Extra controls were already hidden");
        
        // Make the menu item shorter to hide the extra controls. Keep the width unchanged.
        menuItem.view.frameSize = NSMakeSize(width, kAppVolumeViewInitialHeight);
        // Turn the button upside down so the arrowhead points down.
        button.frameCenterRotation = 180.0;
        // Move the button up slightly so it aligns with the volume slider.
        [button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y - 1)];

        // Set the extra controls, and anything else below the fold, to hidden so accessibility
        // clients can skip over them.
        for (NSView* subview in menuItem.view.subviews) {
            CGFloat top = subview.frame.origin.y + subview.frame.size.height;
            if (top <= 0.0) {
                subview.hidden = YES;
            }
        }
    } else {
        // Show extra controls
        DebugMsg("BGMAppVolumes::showHideExtraControls: Showing extra controls (%s)", appName);
        
        BGMAssert(nearEnough(button.frameCenterRotation, 180.0), "Unexpected button rotation");
        BGMAssert(nearEnough(height, kAppVolumeViewInitialHeight), "Extra controls were already shown");
        
        // Make the menu item taller to show the extra controls. Keep the width unchanged.
        menuItem.view.frameSize = NSMakeSize(width, appVolumeViewFullHeight);
        // Turn the button rightside up so the arrowhead points up.
        button.frameCenterRotation = 0.0;
        // Move the button down slightly, back to its original position.
        [button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y + 1)];

        // Set all of the UI elements in the menu item to "not hidden" for accessibility clients.
        for (NSView* subview in menuItem.view.subviews) {
            subview.hidden = NO;
        }
    }
}

- (void) removeAllAppVolumeMenuItems {
    // Remove all of the menu items this class adds to the menu except for the last two, which are
    // the More Apps menu item and the invisible spacer above it.
    while (numMenuItems > 2) {
        [bgmMenu removeItemAtIndex:[self firstMenuItemIndex]];
        numMenuItems--;
    }

    // The More Apps menu only contains app volume menu items, so we can just remove everything.
    [moreAppsMenu removeAllItems];
}

@end

#pragma mark Custom Classes (IB)

// Custom classes for the UI elements in the app volume menu items

@implementation BGMAVM_AppIcon

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)menuItem {
    #pragma unused (ctx, ctrl, menuItem)
    
    self.image = app.icon;

    // Remove the icon from the accessibility hierarchy.
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000  // MAC_OS_X_VERSION_10_10
    if ([self.cell respondsToSelector:@selector(setAccessibilityElement:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        self.cell.accessibilityElement = NO;
#pragma clang diagnostic pop
    }
#endif
}

@end

@implementation BGMAVM_AppNameLabel

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)menuItem {
    #pragma unused (ctx, ctrl, menuItem)
    
    NSString* name = app.localizedName ? (NSString*)app.localizedName : @"";
    self.stringValue = name;
}

@end

@implementation BGMAVM_ShowMoreControlsButton

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)menuItem {
    #pragma unused (app, ctrl)
    
    // Set up the button that show/hide the extra controls (currently only a pan slider) for the app.
    self.cell.representedObject = menuItem;
    self.target = ctx;
    self.action = @selector(showHideExtraControls:);
    
    // The menu item starts out with the extra controls visible, so we hide them here.
    //
    // TODO: Leave them visible if any of the controls are set to non-default values. The user has no way to
    //       tell otherwise. Maybe we should also make this button look different if the controls are hidden
    //       when they have non-default values.
    [ctx showHideExtraControls:self];

    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        self.accessibilityTitle = @"More options";
#pragma clang diagnostic pop
    }
}

@end

@implementation BGMAVM_VolumeMute {
    pid_t appProcessID;
    NSString* __nullable appBundleID;
    BGMAppVolumesController* controller;
}

- (NSString*) lastNonZeroVolumeDefaultsKey {
    if (appBundleID.length > 0) {
        return [NSString stringWithFormat:@"BGMAVM_LastNonZeroVolume_%@", appBundleID];
    }
    return [NSString stringWithFormat:@"BGMAVM_LastNonZeroVolume_pid_%d", appProcessID];
}

- (BOOL) isMuted:(int)value {
    return value <= kAppRelativeVolumeMinRawValue;
}

- (int) defaultRestoreVolume {
    return (int)((kAppRelativeVolumeMaxRawValue + kAppRelativeVolumeMinRawValue) / 2);
}

- (BGMAVM_VolumeSlider* __nullable) findSiblingVolumeSlider {
    for (NSView* view in self.superview.subviews) {
        if ([view isKindOfClass:[BGMAVM_VolumeSlider class]]) {
            return (BGMAVM_VolumeSlider*)view;
        }
    }
    return nil;
}

- (void) updateButtonForVolume:(int)volume {
    BOOL muted = [self isMuted:volume];

    if ([NSImage respondsToSelector:@selector(imageWithSystemSymbolName:accessibilityDescription:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        NSString* symbol = muted ? @"speaker.slash.fill" : @"speaker.wave.2.fill";
        NSString* description = muted ? @"Unmute" : @"Mute";
        self.image = [NSImage imageWithSystemSymbolName:symbol accessibilityDescription:description];
#pragma clang diagnostic pop
        self.imagePosition = NSImageOnly;
        self.title = @"";
    } else {
        self.title = muted ? @"Unmute" : @"Mute";
    }
}

- (void) bgm_syncForVolume:(int)volume {
    [self updateButtonForVolume:volume];
}

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)menuItem {
#pragma unused (ctx, menuItem)

    controller = ctrl;
    appProcessID = app.processIdentifier;
    appBundleID = app.bundleIdentifier;

    self.target = self;
    self.action = @selector(mutePressed:);

    BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider];
    int currentVol = slider ? slider.intValue : kAppRelativeVolumeMinRawValue;
    [self updateButtonForVolume:currentVol];
}

- (IBAction) mutePressed:(id)sender {
#pragma unused(sender)

    BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider];
    if (!slider) {
        DebugMsg("Mute button: no slider found");
        return;
    }

    int currentVol = slider.intValue;
    BOOL mutedNow = [self isMuted:currentVol];

    if (!mutedNow) {
        // Store last volume
        [[NSUserDefaults standardUserDefaults] setInteger:currentVol
                                                   forKey:[self lastNonZeroVolumeDefaultsKey]];

        [slider setRelativeVolume:kAppRelativeVolumeMinRawValue];
    } else {
        NSInteger last = [[NSUserDefaults standardUserDefaults] integerForKey:[self lastNonZeroVolumeDefaultsKey]];
        int restoreVol = (int)last;

        if (restoreVol <= kAppRelativeVolumeMinRawValue ||
            restoreVol > kAppRelativeVolumeMaxRawValue) {
            restoreVol = [self defaultRestoreVolume];
        }

        [slider setRelativeVolume:restoreVol];
    }

    [controller setVolume:slider.intValue
      forAppWithProcessID:appProcessID
                 bundleID:appBundleID];

    [self updateButtonForVolume:slider.intValue];
}

@end

@implementation BGMAVM_VolumeSlider {
    // Will be set to -1 for apps without a pid
    pid_t appProcessID;
    NSString* __nullable appBundleID;
    BGMAppVolumesController* controller;

    // Keep the menu item so we can sync the mute button when the slider changes.
    __weak NSMenuItem* menuItem;
}

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)inMenuItem {
    #pragma unused (ctx)
    
    controller = ctrl;
    menuItem = inMenuItem;
    
    self.target = self;
    self.action = @selector(appVolumeChanged);
    
    appProcessID = app.processIdentifier;
    appBundleID = app.bundleIdentifier;
    
    self.maxValue = kAppRelativeVolumeMaxRawValue;
    self.minValue = kAppRelativeVolumeMinRawValue;

    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        self.accessibilityTitle = [NSString stringWithFormat:@"Volume for %@", [app localizedName]];
#pragma clang diagnostic pop
    }
}

// We have to handle snapping for volume sliders ourselves because adding a tick mark (snap point) in Interface Builder
// changes how the slider looks.
- (void) snap {
    // Snap to the 50% point.
    float midPoint = (float)((self.maxValue + self.minValue) / 2);
    if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) {
        self.floatValue = midPoint;
    }
}

- (void) setRelativeVolume:(int)relativeVolume {
    self.intValue = relativeVolume;
    [self snap];
}

- (void) appVolumeChanged {
    // TODO: This (sending updates to the driver) should probably be rate-limited. It uses a fair bit of CPU for me.
    
    DebugMsg("BGMAppVolumes::appVolumeChanged: App volume for %s (%d) changed to %d",
             appBundleID.UTF8String,
             appProcessID,
             self.intValue);
    
    [self snap];

    // The values from our sliders are in
    // [kAppRelativeVolumeMinRawValue, kAppRelativeVolumeMaxRawValue] already.
    [controller setVolume:self.intValue forAppWithProcessID:appProcessID bundleID:appBundleID];

    // Sync the mute button so it reflects muted/unmuted when the user drags the slider.
    for (NSView* subview in menuItem.view.subviews) {
        if ([subview isKindOfClass:[BGMAVM_VolumeMute class]]) {
            [(BGMAVM_VolumeMute*)subview bgm_syncForVolume:self.intValue];
        }
    }
}

@end

@implementation BGMAVM_PanSlider {
    // Will be set to -1 for apps without a pid
    pid_t appProcessID;
    NSString* __nullable appBundleID;
    BGMAppVolumesController* controller;
}

- (void) setUpWithApp:(NSRunningApplication*)app
              context:(BGMAppVolumes*)ctx
           controller:(BGMAppVolumesController*)ctrl
             menuItem:(NSMenuItem*)menuItem {
    #pragma unused (ctx, menuItem)
    
    controller = ctrl;
    
    self.target = self;
    self.action = @selector(appPanPositionChanged);
    
    appProcessID = app.processIdentifier;
    appBundleID = app.bundleIdentifier;
    
    self.minValue = kAppPanLeftRawValue;
    self.maxValue = kAppPanRightRawValue;

    if ([self respondsToSelector:@selector(setAccessibilityTitle:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        self.accessibilityTitle = [NSString stringWithFormat:@"Pan for %@", [app localizedName]];
#pragma clang diagnostic pop
    }
}

- (void) setPanPosition:(int)panPosition {
    self.intValue = panPosition;
}

- (void) appPanPositionChanged {
    // TODO: This (sending updates to the driver) should probably be rate-limited. It uses a fair bit of CPU for me.
    
    DebugMsg("BGMAppVolumes::appPanPositionChanged: App pan position for %s changed to %d", appBundleID.UTF8String, self.intValue);

    // The values from our sliders are in [kAppPanLeftRawValue, kAppPanRightRawValue] already.
    [controller setPanPosition:self.intValue forAppWithProcessID:appProcessID bundleID:appBundleID];
}

@end



================================================
FILE: BGMApp/BGMApp/BGMAppVolumesController.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppVolumesController.h
//  BGMApp
//
//  Copyright © 2017 Kyle Neideck
//  Copyright © 2021 Marcus Wu
//

// Local Includes
#import "BGMAudioDeviceManager.h"

// System Includes
#import <Cocoa/Cocoa.h>


#pragma clang assume_nonnull begin

typedef struct BGMAppVolumeAndPan {
    int volume;
    int pan;
} BGMAppVolumeAndPan;

@interface BGMAppVolumesController : NSObject

- (id) initWithMenu:(NSMenu*)menu
      appVolumeView:(NSView*)view
       audioDevices:(BGMAudioDeviceManager*)audioDevices;

// See BGMBackgroundMusicDevice::SetAppVolume.
- (void)  setVolume:(SInt32)volume
forAppWithProcessID:(pid_t)processID
           bundleID:(NSString* __nullable)bundleID;

// See BGMBackgroundMusicDevice::SetPanVolume.
- (void) setPanPosition:(SInt32)pan
    forAppWithProcessID:(pid_t)processID
               bundleID:(NSString* __nullable)bundleID;

- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app;
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAppVolumesController.mm
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppVolumesController.mm
//  BGMApp
//
//  Copyright © 2017, 2018 Kyle Neideck
//  Copyright © 2017 Andrew Tonner
//  Copyright © 2021 Marcus Wu
//

// Self Include
#import "BGMAppVolumesController.h"

// Local Includes
#import "BGM_Types.h"
#import "BGM_Utils.h"
#import "BGMAppVolumes.h"

// PublicUtility Includes
#import "CACFArray.h"
#import "CACFDictionary.h"
#import "CACFString.h"

// System Includes
#include <libproc.h>


#pragma clang assume_nonnull begin

@implementation BGMAppVolumesController {
    // The App Volumes UI.
    BGMAppVolumes* appVolumes;
    BGMAudioDeviceManager* audioDevices;
}

#pragma mark Initialisation

- (id) initWithMenu:(NSMenu*)menu
      appVolumeView:(NSView*)view
       audioDevices:(BGMAudioDeviceManager*)devices {
    if ((self = [super init])) {
        audioDevices = devices;
        appVolumes = [[BGMAppVolumes alloc] initWithController:self
                                                       bgmMenu:menu
                                                 appVolumeView:view];

        // Create the menu items for controlling app volumes.
        NSArray<NSRunningApplication*>* apps = [[NSWorkspace sharedWorkspace] runningApplications];
        [self insertMenuItemsForApps:apps];

        // Register for notifications when the user opens or closes apps, so we can update the menu.
        auto opts = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [[NSWorkspace sharedWorkspace] addObserver:self
                                        forKeyPath:@"runningApplications"
                                           options:opts
                                           context:nil];
    }

    return self;
}

- (void) dealloc {
    [[NSWorkspace sharedWorkspace] removeObserver:self
                                       forKeyPath:@"runningApplications"
                                          context:nil];
}

// Adds a volume control menu item for each given app.
- (void) insertMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {
    NSAssert([NSThread isMainThread], @"insertMenuItemsForApps is not thread safe");

    // TODO: Handle the C++ exceptions this method can throw. They can cause crashes because this
    //       method is called in a KVO handler.

    // Get the app volumes currently set on the device
    CACFArray volumesFromBGMDevice([audioDevices bgmDevice].GetAppVolumes(), false);

    for (NSRunningApplication* app in apps) {
        if ([self shouldBeIncludedInMenu:app]) {
            BGMAppVolumeAndPan initial = [self getVolumeAndPanForApp:app
                                                         fromVolumes:volumesFromBGMDevice];
            [appVolumes insertMenuItemForApp:app
                               initialVolume:initial.volume
                                  initialPan:initial.pan];
        }
    }
}

- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app {
    return [appVolumes getVolumeAndPanForApp:app];
}

- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
    [appVolumes setVolumeAndPan:volumeAndPan forApp:app];
    if (volumeAndPan.volume != -1) {
        [self setVolume:volumeAndPan.volume forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
    }
    if (volumeAndPan.pan != kAppPanNoValue) {
        [self setPanPosition:volumeAndPan.pan forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
    }
}

- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app
                                 fromVolumes:(const CACFArray&)volumes {
    BGMAppVolumeAndPan volumeAndPan = {
        .volume = -1,
        .pan = kAppPanNoValue
    };

    for (UInt32 i = 0; i < volumes.GetNumberItems(); i++) {
        CACFDictionary appVolume(false);
        volumes.GetCACFDictionary(i, appVolume);

        // Match the app to the volume/pan by pid or bundle ID.
        CACFString bundleID;
        bundleID.DontAllowRelease();
        appVolume.GetCACFString(CFSTR(kBGMAppVolumesKey_BundleID), bundleID);

        pid_t pid;
        appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), pid);

        if ((app.processIdentifier == pid) ||
            [app.bundleIdentifier isEqualToString:(__bridge NSString*)bundleID.GetCFString()]) {
            // Found a match, so read the volume and pan.
            appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), volumeAndPan.volume);
            appVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), volumeAndPan.pan);
            break;
        }
    }

    return volumeAndPan;
}

- (BOOL) shouldBeIncludedInMenu:(NSRunningApplication*)app {
    // Ignore hidden apps and Background Music itself.
    // TODO: Would it be better to only show apps that are registered as HAL clients?
    BOOL isHidden = app.activationPolicy != NSApplicationActivationPolicyRegular &&
                    app.activationPolicy != NSApplicationActivationPolicyAccessory;

    NSString* bundleID = app.bundleIdentifier;
    BOOL isBGMApp = bundleID && [@kBGMAppBundleID isEqualToString:BGMNN(bundleID)];

    return !isHidden && !isBGMApp;
}

- (void) removeMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {
    NSAssert([NSThread isMainThread], @"removeMenuItemsForApps is not thread safe");

    for (NSRunningApplication* app in apps) {
        [appVolumes removeMenuItemForApp:app];
    }
}

#pragma mark Accessors

- (void)  setVolume:(SInt32)volume
forAppWithProcessID:(pid_t)processID
           bundleID:(NSString* __nullable)bundleID {
    // Update the app's volume.
    audioDevices.bgmDevice.SetAppVolume(volume, processID, (__bridge_retained CFStringRef)bundleID);

    // If this volume is for FaceTime, set the volume for the avconferenced process as well. This
    // works around FaceTime not playing its own audio. It plays UI sounds through
    // systemsoundserverd and call audio through avconferenced.
    //
    // This isn't ideal because other apps might play audio through avconferenced, but I don't see a
    // good way we could find out which app is actually playing the audio. We could probably figure
    // it out from reading avconferenced's logs, at least, if it turns out to be important. See
    // https://github.com/kyleneideck/BackgroundMusic/issues/139.
    if ([bundleID isEqual:@"com.apple.FaceTime"]) {
        [self setAvconferencedVolume:volume];
    }
}

- (void) setAvconferencedVolume:(SInt32)volume {
    // TODO: This volume will be lost if avconferenced is restarted.
    pid_t pids[1024];
    size_t procCount = proc_listallpids(pids, 1024);
    char path[PROC_PIDPATHINFO_MAXSIZE];

    for (int i = 0; i < procCount; i++) {
        pid_t pid = pids[i];

        if (proc_pidpath(pid, path, sizeof(path)) > 0 &&
            strncmp(path, "/usr/libexec/avconferenced", sizeof(path)) == 0) {
            DebugMsg("Setting avconferenced volume: %d", volume);
            audioDevices.bgmDevice.SetAppVolume(volume, pid, nullptr);
            return;
        }
    }

    LogWarning("Failed to set avconferenced volume.");
}

- (void) setPanPosition:(SInt32)pan
    forAppWithProcessID:(pid_t)processID
               bundleID:(NSString* __nullable)bundleID {
    audioDevices.bgmDevice.SetAppPanPosition(pan,
                                             processID,
                                             (__bridge_retained CFStringRef)bundleID);
}

#pragma mark KVO

- (void) observeValueForKeyPath:(NSString* __nullable)keyPath
                       ofObject:(id __nullable)object
                         change:(NSDictionary* __nullable)change
                        context:(void* __nullable)context
{
    #pragma unused (object, context)

    // KVO callback for the apps currently running on the system. Adds/removes the associated menu
    // items.
    if (keyPath && change && [keyPath isEqualToString:@"runningApplications"]) {
        NSArray<NSRunningApplication*>* newApps = change[NSKeyValueChangeNewKey];
        NSArray<NSRunningApplication*>* oldApps = change[NSKeyValueChangeOldKey];

        int changeKind = [change[NSKeyValueChangeKindKey] intValue];

        switch (changeKind) {
            case NSKeyValueChangeInsertion:
                [self insertMenuItemsForApps:newApps];
                break;

            case NSKeyValueChangeRemoval:
                [self removeMenuItemsForApps:oldApps];
                break;

            case NSKeyValueChangeReplacement:
                [self removeMenuItemsForApps:oldApps];
                [self insertMenuItemsForApps:newApps];
                break;

            case NSKeyValueChangeSetting:
                [appVolumes removeAllAppVolumeMenuItems];
                [self insertMenuItemsForApps:newApps];
                break;
        }
    }
}

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAppWatcher.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppWatcher.h
//  BGMApp
//
//  Copyright © 2019 Kyle Neideck
//
//  Calls callback functions when a given application is launched or terminated. Starts watching
//  after being initialised, stops after being destroyed.
//

// System Includes
#import <Foundation/Foundation.h>


#pragma clang assume_nonnull begin

@interface BGMAppWatcher : NSObject

// appLaunched will be called when the application is launched and appTerminated will be called when
// it's terminated. Background apps, status bar apps, etc. are ignored.
- (instancetype) initWithBundleID:(NSString*)bundleID
                      appLaunched:(void(^)(void))appLaunched
                    appTerminated:(void(^)(void))appTerminated;

// With this constructor, when an application is launched or terminated, isMatchingBundleID will be
// called first to decide whether or not the callback should be called.
- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched
                       appTerminated:(void(^)(void))appTerminated
                  isMatchingBundleID:(BOOL(^)(NSString* appBundleID))isMatchingBundleID;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAppWatcher.m
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAppWatcher.m
//  BGMApp
//
//  Copyright © 2019 Kyle Neideck
//

// Self Include
#import "BGMAppWatcher.h"

// System Includes
#import <Cocoa/Cocoa.h>


#pragma clang assume_nonnull begin

@implementation BGMAppWatcher {
    // Tokens for the notification observers so we can remove them in dealloc.
    id<NSObject> didLaunchToken;
    id<NSObject> didTerminateToken;
}

- (instancetype) initWithBundleID:(NSString*)bundleID
                      appLaunched:(void(^)(void))appLaunched
                    appTerminated:(void(^)(void))appTerminated {
    return [self initWithAppLaunched:appLaunched
                       appTerminated:appTerminated
                  isMatchingBundleID:^BOOL(NSString* appBundleID) {
                      return [bundleID isEqualToString:appBundleID];
                  }];
}

- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched
                       appTerminated:(void(^)(void))appTerminated
                  isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID
{
    if ((self = [super init])) {
        NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;

        didLaunchToken =
            [center addObserverForName:NSWorkspaceDidLaunchApplicationNotification
                                object:nil
                                 queue:nil
                            usingBlock:^(NSNotification* notification) {
                                if ([BGMAppWatcher shouldBeHandled:notification
                                                isMatchingBundleID:isMatchingBundleID]) {
                                    appLaunched();
                                }
                            }];

        didTerminateToken =
            [center addObserverForName:NSWorkspaceDidTerminateApplicationNotification
                                object:nil
                                 queue:nil
                            usingBlock:^(NSNotification* notification) {
                                if ([BGMAppWatcher shouldBeHandled:notification
                                                isMatchingBundleID:isMatchingBundleID]) {
                                    appTerminated();
                                }
                            }];
    }

    return self;
}

// Returns YES if we should call the app launch/termination callback for this NSNotification.
+ (BOOL) shouldBeHandled:(NSNotification*)notification
      isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID {
    NSString* __nullable notifiedBundleID =
        [notification.userInfo[NSWorkspaceApplicationKey] bundleIdentifier];

    // Ignore the notification if the app doesn't have a bundle ID or isMatchingBundleID returns NO.
    return notifiedBundleID && isMatchingBundleID((NSString*)notifiedBundleID);
}

- (void) dealloc {
    // Remove the application launch/termination observers.
    NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;

    if (didLaunchToken) {
        [center removeObserver:didLaunchToken];
        didLaunchToken = nil;
    }

    if (didTerminateToken) {
        [center removeObserver:didTerminateToken];
        didTerminateToken = nil;
    }
}

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAudioDevice.cpp
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAudioDevice.cpp
//  BGMApp
//
//  Copyright © 2017 Kyle Neideck
//

// Self Include
#include "BGMAudioDevice.h"

// Local Includes
#include "BGM_Types.h"

// System Includes
#include <AudioToolbox/AudioServices.h>


#pragma mark Construction/Destruction

BGMAudioDevice::BGMAudioDevice(AudioObjectID inAudioDevice)
:
    CAHALAudioDevice(inAudioDevice)
{
}

BGMAudioDevice::BGMAudioDevice(CFStringRef inUID)
:
    CAHALAudioDevice(inUID)
{
}

BGMAudioDevice::BGMAudioDevice(const CAHALAudioDevice& inDevice)
:
    BGMAudioDevice(inDevice.GetObjectID())
{
}

BGMAudioDevice::~BGMAudioDevice()
{
}

bool    BGMAudioDevice::CanBeOutputDeviceInBGMApp() const
{
    CFStringRef uid = CopyDeviceUID();
    assert(uid != nullptr);

    bool isNullDevice = CFEqual(uid, CFSTR(kBGMNullDeviceUID));
    CFRelease(uid);

    bool hasOutputChannels = GetTotalNumberChannels(/* inIsInput = */ false) > 0;
    bool canBeDefault = CanBeDefaultDevice(/* inIsInput = */ false, /* inIsSystem = */ false);

    return !IsBGMDeviceInstance() &&
            !isNullDevice &&
            !IsHidden() &&
            hasOutputChannels &&
            canBeDefault;
}

#pragma mark Available Controls

bool    BGMAudioDevice::HasSettableMasterVolume(AudioObjectPropertyScope inScope) const
{
    return HasVolumeControl(inScope, kMasterChannel) &&
        VolumeControlIsSettable(inScope, kMasterChannel);
}

bool    BGMAudioDevice::HasSettableVirtualMasterVolume(AudioObjectPropertyScope inScope) const
{
    AudioObjectPropertyAddress virtualMasterVolumeAddress = {
        kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
        inScope,
        kAudioObjectPropertyElementMaster
    };

    // TODO: Replace these calls deprecated AudioToolbox functions. There are more below.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    Boolean virtualMasterVolumeIsSettable;
    OSStatus err = AudioHardwareServiceIsPropertySettable(GetObjectID(),
                                                          &virtualMasterVolumeAddress,
                                                          &virtualMasterVolumeIsSettable);
    virtualMasterVolumeIsSettable &= (err == kAudioServicesNoError);

    bool hasVirtualMasterVolume =
        AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterVolumeAddress);
#pragma clang diagnostic pop

    return hasVirtualMasterVolume && virtualMasterVolumeIsSettable;
}

bool    BGMAudioDevice::HasSettableMasterMute(AudioObjectPropertyScope inScope) const
{
    return HasMuteControl(inScope, kMasterChannel) &&
        MuteControlIsSettable(inScope, kMasterChannel);
}

#pragma mark Control Values Accessors

void    BGMAudioDevice::CopyMuteFrom(const BGMAudioDevice inDevice,
                                     AudioObjectPropertyScope inScope)
{
    // TODO: Support for devices that have per-channel mute controls but no master mute control
    if(HasSettableMasterMute(inScope) && inDevice.HasMuteControl(inScope, kMasterChannel))
    {
        SetMuteControlValue(inScope,
                            kMasterChannel,
                            inDevice.GetMuteControlValue(inScope, kMasterChannel));
    }
}

void    BGMAudioDevice::CopyVolumeFrom(const BGMAudioDevice inDevice,
                                       AudioObjectPropertyScope inScope)
{
    // Get the volume of the other device.
    bool didGetVolume = false;
    Float32 volume = FLT_MIN;

    if(inDevice.HasVolumeControl(inScope, kMasterChannel))
    {
        volume = inDevice.GetVolumeControlScalarValue(inScope, kMasterChannel);
        didGetVolume = true;
    }

    // Use the average channel volume of the other device if it has no master volume.
    if(!didGetVolume)
    {
        UInt32 numChannels =
            inDevice.GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);
        volume = 0;

        for(UInt32 channel = 1; channel <= numChannels; channel++)
        {
            if(inDevice.HasVolumeControl(inScope, channel))
            {
                volume += inDevice.GetVolumeControlScalarValue(inScope, channel);
                didGetVolume = true;
            }
        }

        if(numChannels > 0)  // Avoid divide by zero.
        {
            volume /= numChannels;
        }
    }

    // Set the volume of this device.
    if(didGetVolume && volume != FLT_MIN)
    {
        bool didSetVolume = false;

        try
        {
            didSetVolume = SetMasterVolumeScalar(inScope, volume);
        }
        catch(CAException e)
        {
            OSStatus err = e.GetError();
            char err4CC[5] = CA4CCToCString(err);
            CFStringRef uid = CopyDeviceUID();
            LogWarning("BGMAudioDevice::CopyVolumeFrom: CAException '%s' trying to set master "
                       "volume of %s",
                       err4CC,
                       CFStringGetCStringPtr(uid, kCFStringEncodingUTF8));
            CFRelease(uid);
        }

        if(!didSetVolume)
        {
            // Couldn't find a master volume control to set, so try to find a virtual one
            Float32 virtualMasterVolume;
            bool success = inDevice.GetVirtualMasterVolumeScalar(inScope, virtualMasterVolume);
            if(success)
            {
                didSetVolume = SetVirtualMasterVolumeScalar(inScope, virtualMasterVolume);
            }
        }

        if(!didSetVolume)
        {
            // Couldn't set a master or virtual master volume, so as a fallback try to set each
            // channel individually.
            UInt32 numChannels = GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);
            for(UInt32 channel = 1; channel <= numChannels; channel++)
            {
                if(HasVolumeControl(inScope, channel) && VolumeControlIsSettable(inScope, channel))
                {
                    SetVolumeControlScalarValue(inScope, channel, volume);
                }
            }
        }
    }
}

bool    BGMAudioDevice::SetMasterVolumeScalar(AudioObjectPropertyScope inScope, Float32 inVolume)
{
    if(HasSettableMasterVolume(inScope))
    {
        SetVolumeControlScalarValue(inScope, kMasterChannel, inVolume);
        return true;
    }

    return false;
}

bool    BGMAudioDevice::GetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,
                                                     Float32& outVirtualMasterVolume) const
{
    AudioObjectPropertyAddress virtualMasterVolumeAddress = {
        kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
        inScope,
        kAudioObjectPropertyElementMaster
    };

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    if(!AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterVolumeAddress))
    {
        return false;
    }
#pragma clang diagnostic pop

    UInt32 virtualMasterVolumePropertySize = sizeof(Float32);
    return kAudioServicesNoError == AHSGetPropertyData(GetObjectID(),
                                                       &virtualMasterVolumeAddress,
                                                       &virtualMasterVolumePropertySize,
                                                       &outVirtualMasterVolume);
}

bool    BGMAudioDevice::SetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,
                                                     Float32 inVolume)
{
    // TODO: For me, setting the virtual master volume sets all the device's channels to the same volume, meaning you can't
    //       keep any channels quieter than the others. The expected behaviour is to scale the channel volumes
    //       proportionally. So to do this properly I think we'd have to store BGMDevice's previous volume and calculate
    //       each channel's new volume from its current volume and the distance between BGMDevice's old and new volumes.
    //
    //       The docs kAudioHardwareServiceDeviceProperty_VirtualMasterVolume for say
    //           "If the device has individual channel volume controls, this property will apply to those identified by the
    //           device's preferred multi-channel layout (or preferred stereo pair if the device is stereo only). Note that
    //           this control maintains the relative balance between all the channels it affects.
    //       so I'm not sure why that's not working here. As a workaround we take the to device's (virtual master) balance
    //       before changing the volume and set it back after, but of course that'll only work for stereo devices.

    bool didSetVolume = false;

    if(HasSettableVirtualMasterVolume(inScope))
    {
        // Not sure why, but setting the virtual master volume sets all channels to the same volume. As a workaround, we store
        // the current balance here so we can reset it after setting the volume.
        Float32 virtualMasterBalance;
        bool didGetVirtualMasterBalance = GetVirtualMasterBalance(inScope, virtualMasterBalance);

        AudioObjectPropertyAddress virtualMasterVolumeAddress = {
            kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
            inScope,
            kAudioObjectPropertyElementMaster
        };

        didSetVolume = (kAudioServicesNoError == AHSSetPropertyData(GetObjectID(),
                                                                    &virtualMasterVolumeAddress,
                                                                    sizeof(Float32),
                                                                    &inVolume));

        // Reset the balance
        AudioObjectPropertyAddress virtualMasterBalanceAddress = {
            kAudioHardwareServiceDeviceProperty_VirtualMainBalance,
            inScope,
            kAudioObjectPropertyElementMaster
        };

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        if(didSetVolume &&
           didGetVirtualMasterBalance &&
           AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterBalanceAddress))
        {
            Boolean balanceIsSettable;
            OSStatus err = AudioHardwareServiceIsPropertySettable(GetObjectID(),
                                                                  &virtualMasterBalanceAddress,
                                                                  &balanceIsSettable);
            if(err == kAudioServicesNoError && balanceIsSettable)
            {
                AHSSetPropertyData(GetObjectID(),
                                   &virtualMasterBalanceAddress,
                                   sizeof(Float32),
                                   &virtualMasterBalance);
            }
        }
#pragma clang diagnostic pop
    }

    return didSetVolume;
}

bool    BGMAudioDevice::GetVirtualMasterBalance(AudioObjectPropertyScope inScope,
                                                Float32& outVirtualMasterBalance) const
{
    AudioObjectPropertyAddress virtualMasterBalanceAddress = {
        kAudioHardwareServiceDeviceProperty_VirtualMainBalance,
        inScope,
        kAudioObjectPropertyElementMaster
    };

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    if(!AudioHardwareServiceHasProperty(GetObjectID(), &virtualMasterBalanceAddress))
    {
        return false;
    }
#pragma clang diagnostic pop

    UInt32 virtualMasterVolumePropertySize = sizeof(Float32);
    return kAudioServicesNoError == AHSGetPropertyData(GetObjectID(),
                                                       &virtualMasterBalanceAddress,
                                                       &virtualMasterVolumePropertySize,
                                                       &outVirtualMasterBalance);
}

#pragma mark Implementation

bool    BGMAudioDevice::IsBGMDevice(bool inIncludeUISoundsInstance) const
{
    bool isBGMDevice = false;

    if(GetObjectID() != kAudioObjectUnknown)
    {
        // Check the device's UID to see whether it's BGMDevice.
        CFStringRef uid = CopyDeviceUID();
        if (uid == nullptr) {
            return isBGMDevice;
        }

        isBGMDevice =
            CFEqual(uid, CFSTR(kBGMDeviceUID)) ||
                    (inIncludeUISoundsInstance && CFEqual(uid, CFSTR(kBGMDeviceUID_UISounds)));

        CFRelease(uid);
    }

    return isBGMDevice;
}

// static
OSStatus    BGMAudioDevice::AHSGetPropertyData(AudioObjectID inObjectID,
                                               const AudioObjectPropertyAddress* inAddress,
                                               UInt32* ioDataSize,
                                               void* outData)
{
    // The docs for AudioHardwareServiceGetPropertyData specifically allow passing NULL for
    // inQualifierData as we do here, but it's declared in an assume_nonnull section so we have to
    // disable the warning here. I'm not sure why inQualifierData isn't __nullable. I'm assuming
    // it's either a backwards compatibility thing or just a bug.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    // The non-depreciated version of this (and the setter below) doesn't seem to support devices
    // other than the default
    return AudioHardwareServiceGetPropertyData(inObjectID, inAddress, 0, NULL, ioDataSize, outData);
#pragma clang diagnostic pop
}

// static
OSStatus    BGMAudioDevice::AHSSetPropertyData(AudioObjectID inObjectID,
                                               const AudioObjectPropertyAddress* inAddress,
                                               UInt32 inDataSize,
                                               const void* inData)
{
    // See the explanation about these pragmas in AHSGetPropertyData
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    return AudioHardwareServiceSetPropertyData(inObjectID, inAddress, 0, NULL, inDataSize, inData);
#pragma clang diagnostic pop
}



================================================
FILE: BGMApp/BGMApp/BGMAudioDevice.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//  BGMAudioDevice.h
//  BGMApp
//
//  Copyright © 2017, 2020 Kyle Neideck
//
//  A HAL audio device. Note that this class's only state is the AudioObjectID of the device.
//

#ifndef BGMApp__BGMAudioDevice
#define BGMApp__BGMAudioDevice

// PublicUtility Includes
#include "CAHALAudioDevice.h"


class BGMAudioDevice
:
    public CAHALAudioDevice
{

#pragma mark Construction/Destruction

public:
                       BGMAudioDevice(AudioObjectID inAudioDevice);
    /*!
     Creates a BGMAudioDevice with the Audio Object ID of the device whose UID is inUID or, if no
     such device is found, kAudioObjectUnknown.

     @throws CAException If the HAL returns an error when queried for the device's ID.
     @see kAudioPlugInPropertyTranslateUIDToDevice in AudioHardwareBase.h.
     */
                       BGMAudioDevice(CFStringRef inUID);
                       BGMAudioDevice(const CAHALAudioDevice& inDevice);
    virtual            ~BGMAudioDevice();

#if defined(__OBJC__)

    // Hack/workaround for Objective-C classes so we don't have to use pointers for instance
    // variables.
                       BGMAudioDevice() : BGMAudioDevice(kAudioObjectUnknown) { }

#endif /* defined(__OBJC__) */

                       operator AudioObjectID() const { return GetObjectID(); }

    /*!
     @return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice, not
             the instance used for UI sounds.)
     @throws CAException If the HAL returns an error when queried.
     */
    bool               IsBGMDevice() const { return IsBGMDevice(false); };
    /*!
     @return True if this device is either the main instance of BGMDevice (the device named
             "Background Music") or the instance used for UI sounds (the device named "Background
             Music (UI Sounds)").
     @throws CAException If the HAL returns an error when queried.
     */
    bool               IsBGMDeviceInstance() const { return IsBGMDevice(true); };

    /*!
     @return True if this device can be set as the output device in BGMApp.
     @throws CAException If the HAL returns an error when queried.
     */
    bool               CanBeOutputDeviceInBGMApp() const;

#pragma mark Available Controls

    bool               HasSettableMasterVolume(AudioObjectPropertyScope inScope) const;
    bool               HasSettableVirtualMasterVolume(AudioObjectPropertyScope inScope) const;
    bool               HasSettableMasterMute(AudioObjectPropertyScope inScope) const;

#pragma mark Control Values Accessors

    void               CopyMuteFrom(const BGMAudioDevice inDevice,
                                    AudioObjectPropertyScope inScope);
    void               CopyVolumeFrom(const BGMAudioDevice inDevice,
                                      AudioObjectPropertyScope inScope);

    bool               SetMasterVolumeScalar(AudioObjectPropertyScope inScope, Float32 inVolume);
    
    bool               GetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,
                                                    Float32& outVirtualMasterVolume) const;
    bool               SetVirtualMasterVolumeScalar(AudioObjectPropertyScope inScope,
                                                    Float32 inVolume);

    bool               GetVirtualMasterBalance(AudioObjectPropertyScope inScope,
                                               Float32& outVirtualMasterBalance) const;

#pragma mark Implementation

private:
    bool               IsBGMDevice(bool inIncludingUISoundsInstance) const;

    static OSStatus    AHSGetPropertyData(AudioObjectID inObjectID,
                                          const AudioObjectPropertyAddress* inAddress,
                                          UInt32* ioDataSize,
                                          void* outData);
    static OSStatus    AHSSetPropertyData(AudioObjectID inObjectID,
                                          const AudioObjectPropertyAddress* inAddress,
                                          UInt32 inDataSize,
                                          const void* inData);

};

#endif /* BGMApp__BGMAudioDevice */



================================================
FILE: BGMApp/BGMApp/BGMAudioDeviceManager.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAudioDeviceManager.h
//  BGMApp
//
//  Copyright © 2016-2018 Kyle Neideck
//
//  Manages BGMDevice and the output device. Sets the system's current default device as the output
//  device on init, then starts playthrough and mirroring the devices' controls.
//

#if defined(__cplusplus)

// Local Includes
#import "BGMBackgroundMusicDevice.h"

// PublicUtility Includes
#import "CAHALAudioDevice.h"

#endif /* defined(__cplusplus) */

// System Includes
#import <Foundation/Foundation.h>
#import <CoreAudio/AudioHardwareBase.h>

// Forward Declarations
@class BGMOutputVolumeMenuItem;
@class BGMOutputDeviceMenuSection;


#pragma clang assume_nonnull begin

static const int kBGMErrorCode_OutputDeviceNotFound = 1;
static const int kBGMErrorCode_ReturningEarly       = 2;

@interface BGMAudioDeviceManager : NSObject

// Returns nil if BGMDevice isn't installed.
- (instancetype) init;

// Set the BGMOutputVolumeMenuItem to be notified when the output device is changed.
- (void) setOutputVolumeMenuItem:(BGMOutputVolumeMenuItem*)item;

// Set the BGMOutputDeviceMenuSection to be notified when the output device is changed.
- (void) setOutputDeviceMenuSection:(BGMOutputDeviceMenuSection*)menuSection;

// Set BGMDevice as the default audio device for all processes
- (NSError* __nullable) setBGMDeviceAsOSDefault;
// Replace BGMDevice as the default device with the output device
- (NSError* __nullable) unsetBGMDeviceAsOSDefault;

#ifdef __cplusplus
// The virtual device published by BGMDriver.
- (BGMBackgroundMusicDevice) bgmDevice;

// The device BGMApp will play audio through, making it, from the user's perspective, the system's
// default output device.
- (CAHALAudioDevice) outputDevice;
#endif

- (BOOL) isOutputDevice:(AudioObjectID)deviceID;
- (BOOL) isOutputDataSource:(UInt32)dataSourceID;

// Set the audio output device that BGMApp uses.
//
// Returns an error if the output device couldn't be changed. If revertOnFailure is true in that case,
// this method will attempt to set the output device back to the original device. If it fails to
// revert, an additional error will be included in the error's userInfo with the key "revertError".
//
// Both errors' codes will be the code of the exception that caused the failure, if any, generally one
// of the error constants from AudioHardwareBase.h.
//
// Blocks while the old device stops IO (if there was one).
- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID
                              revertOnFailure:(BOOL)revertOnFailure;

// As above, but also sets the new output device's data source. See kAudioDevicePropertyDataSource in
// AudioHardware.h.
- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID
                                 dataSourceID:(UInt32)dataSourceID
                              revertOnFailure:(BOOL)revertOnFailure;

// Start playthrough synchronously. Blocks until IO has started on the output device and playthrough
// is running. See BGMPlayThrough.
//
// Returns one of the error codes defined by this class or BGMPlayThrough, or an AudioHardware error
// code received from the HAL.
- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice;

// When the output device is changed, BGMAudioDeviceManager will send the ID of the new output
// device to BGMXPCHelper through this connection.
- (void) setBGMXPCHelperConnection:(NSXPCConnection* __nullable)connection;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAudioDeviceManager.mm
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAudioDeviceManager.mm
//  BGMApp
//
//  Copyright © 2016-2018 Kyle Neideck
//

// Self Include
#import "BGMAudioDeviceManager.h"

// Local Includes
#import "BGM_Types.h"
#import "BGM_Utils.h"
#import "BGMAudioDevice.h"
#import "BGMDeviceControlSync.h"
#import "BGMOutputDeviceMenuSection.h"
#import "BGMOutputVolumeMenuItem.h"
#import "BGMPlayThrough.h"
#import "BGMXPCProtocols.h"

// PublicUtility Includes
#import "CAAtomic.h"
#import "CAAutoDisposer.h"
#import "CAHALAudioSystemObject.h"


#pragma clang assume_nonnull begin

@implementation BGMAudioDeviceManager {
    // This ivar is a pointer so that BGMBackgroundMusicDevice's constructor doesn't get called
    // during [BGMAudioDeviceManager alloc] when the ivars are initialised. It queries the HAL for
    // BGMDevice's AudioObject ID, which might throw a CAException, most likely because BGMDevice
    // isn't installed.
    //
    // That would be the only way for [BGMAudioDeviceManager alloc] to throw a CAException, so we
    // could wrap that call in a try/catch block instead, but it would make the code a bit
    // confusing.
    BGMBackgroundMusicDevice* bgmDevice;
    BGMAudioDevice outputDevice;
    
    BGMDeviceControlSync deviceControlSync;
    BGMPlayThrough playThrough;
    BGMPlayThrough playThrough_UISounds;

    // A connection to BGMXPCHelper so we can send it the ID of the output device.
    NSXPCConnection* __nullable bgmXPCHelperConnection;

    BGMOutputVolumeMenuItem* __nullable outputVolumeMenuItem;
    BGMOutputDeviceMenuSection* __nullable outputDeviceMenuSection;

    NSRecursiveLock* stateLock;
}

#pragma mark Construction/Destruction

- (instancetype) init {
    if ((self = [super init])) {
        stateLock = [NSRecursiveLock new];
        bgmXPCHelperConnection = nil;
        outputVolumeMenuItem = nil;
        outputDeviceMenuSection = nil;
        outputDevice = kAudioObjectUnknown;

        try {
            bgmDevice = new BGMBackgroundMusicDevice;
        } catch (const CAException& e) {
            LogError("BGMAudioDeviceManager::init: BGMDevice not found. (%d)", e.GetError());
            self = nil;
            return self;
        }
    }
    
    return self;
}

- (void) dealloc {
    @try {
        [stateLock lock];

        if (bgmDevice) {
            delete bgmDevice;
            bgmDevice = nullptr;
        }
    } @finally {
        [stateLock unlock];
    }
}

- (void) setOutputVolumeMenuItem:(BGMOutputVolumeMenuItem*)item {
    outputVolumeMenuItem = item;
}

- (void) setOutputDeviceMenuSection:(BGMOutputDeviceMenuSection*)menuSection {
    outputDeviceMenuSection = menuSection;
}

#pragma mark Systemwide Default Device

// Note that there are two different "default" output devices on OS X: "output" and "system output". See
// kAudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h.

- (NSError* __nullable) setBGMDeviceAsOSDefault {
    try {
        // Intentionally avoid taking stateLock before making calls to the HAL. See
        // startPlayThroughSync.
        CAMemoryBarrier();
        bgmDevice->SetAsOSDefault();
    } catch (const CAException& e) {
        BGMLogExceptionIn("BGMAudioDeviceManager::setBGMDeviceAsOSDefault", e);
        return [NSError errorWithDomain:@kBGMAppBundleID code:e.GetError() userInfo:nil];
    }

    return nil;
}

- (NSError* __nullable) unsetBGMDeviceAsOSDefault {
    // Copy the devices so we can call the HAL without holding stateLock. See startPlayThroughSync.
    BGMBackgroundMusicDevice* bgmDeviceCopy;
    AudioDeviceID outputDeviceID;

    @try {
        [stateLock lock];
        bgmDeviceCopy = bgmDevice;
        outputDeviceID = outputDevice.GetObjectID();
    } @finally {
        [stateLock unlock];
    }

    if (outputDeviceID == kAudioObjectUnknown) {
        return [NSError errorWithDomain:@kBGMAppBundleID
                                   code:kBGMErrorCode_OutputDeviceNotFound
                               userInfo:nil];
    }

    try {
        bgmDeviceCopy->UnsetAsOSDefault(outputDeviceID);
    } catch (const CAException& e) {
        BGMLogExceptionIn("BGMAudioDeviceManager::unsetBGMDeviceAsOSDefault", e);
        return [NSError errorWithDomain:@kBGMAppBundleID code:e.GetError() userInfo:nil];
    }
    
    return nil;
}

#pragma mark Accessors

- (BGMBackgroundMusicDevice) bgmDevice {
    return *bgmDevice;
}

- (CAHALAudioDevice) outputDevice {
    return outputDevice;
}

- (BOOL) isOutputDevice:(AudioObjectID)deviceID {
    @try {
        [stateLock lock];
        return deviceID == outputDevice.GetObjectID();
    } @finally {
        [stateLock unlock];
    }
}

- (BOOL) isOutputDataSource:(UInt32)dataSourceID {
    BOOL isOutputDataSource = NO;

    @try {
        [stateLock lock];
        
        try {
            AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;
            UInt32 channel = 0;
            
            isOutputDataSource =
                    outputDevice.HasDataSourceControl(scope, channel) &&
                            (dataSourceID == outputDevice.GetCurrentDataSourceID(scope, channel));
        } catch (const CAException& e) {
            BGMLogException(e);
        }
    } @finally {
        [stateLock unlock];
    }

    return isOutputDataSource;
}

#pragma mark Output Device

- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID
                              revertOnFailure:(BOOL)revertOnFailure {
    return [self setOutputDeviceWithIDImpl:deviceID
                              dataSourceID:nil
                           revertOnFailure:revertOnFailure];
}

- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID
                                 dataSourceID:(UInt32)dataSourceID
                              revertOnFailure:(BOOL)revertOnFailure {
    return [self setOutputDeviceWithIDImpl:deviceID
                              dataSourceID:&dataSourceID
                           revertOnFailure:revertOnFailure];
}

- (NSError* __nullable) setOutputDeviceWithIDImpl:(AudioObjectID)newDeviceID
                                     dataSourceID:(UInt32* __nullable)dataSourceID
                                  revertOnFailure:(BOOL)revertOnFailure {
    DebugMsg("BGMAudioDeviceManager::setOutputDeviceWithIDImpl: Setting output device. newDeviceID=%u",
             newDeviceID);
    
    @try {
        [stateLock lock];

        AudioDeviceID currentDeviceID = outputDevice.GetObjectID();  // (Doesn't throw.)

        try {
            [self setOutputDeviceWithIDImpl:newDeviceID
                               dataSourceID:dataSourceID
                            currentDeviceID:currentDeviceID];
        } catch (const CAException& e) {
            BGMAssert(e.GetError() != kAudioHardwareNoError,
                      "CAException with kAudioHardwareNoError");
            
            return [self failedToSetOutputDevice:newDeviceID
                                       errorCode:e.GetError()
                                        revertTo:(revertOnFailure ? &currentDeviceID : nullptr)];
        } catch (...) {
            return [self failedToSetOutputDevice:newDeviceID
                                       errorCode:kAudioHardwareUnspecifiedError
                                        revertTo:(revertOnFailure ? &currentDeviceID : nullptr)];
        }

        // Tell other classes and BGMXPCHelper that we changed the output device.
        [self propagateOutputDeviceChange];
    } @finally {
        [stateLock unlock];
    }

    return nil;
}

// Throws CAException.
- (void) setOutputDeviceWithIDImpl:(AudioObjectID)newDeviceID
                      dataSourceID:(UInt32* __nullable)dataSourceID
                   currentDeviceID:(AudioObjectID)currentDeviceID {
    if (newDeviceID != currentDeviceID) {
        BGMAudioDevice newOutputDevice(newDeviceID);
        [self setOutputDeviceForPlaythroughAndControlSync:newOutputDevice];
        outputDevice = newOutputDevice;
    }

    // Set the output device to use the new data source.
    if (dataSourceID) {
        // TODO: If this fails, ideally we'd still start playthrough and return an error, but not
        //       revert the device. It would probably be a bit awkward, though.
        [self setDataSource:*dataSourceID device:outputDevice];
    }

    if (newDeviceID != currentDeviceID) {
        // We successfully changed to the new device. Start playthrough on it, since audio might be
        // playing. (If we only changed the data source, playthrough will already be running if it
        // needs to be.)
        playThrough.Start();
        playThrough_UISounds.Start();
        // But stop playthrough if audio isn't playing, since it uses CPU.
        playThrough.StopIfIdle();
        playThrough_UISounds.StopIfIdle();
    }

    CFStringRef outputDeviceUID = outputDevice.CopyDeviceUID();
    DebugMsg("BGMAudioDeviceManager::setOutputDeviceWithIDImpl: Set output device to %s (%d)",
             CFStringGetCStringPtr(outputDeviceUID, kCFStringEncodingUTF8),
             outputDevice.GetObjectID());
    CFRelease(outputDeviceUID);
}

// Changes the output device that playthrough plays audio to and that BGMDevice's controls are
// kept in sync with. Throws CAException.
- (void) setOutputDeviceForPlaythroughAndControlSync:(const BGMAudioDevice&)newOutputDevice {
    // Deactivate playthrough rather than stopping it so it can't be started by HAL notifications
    // while we're updating deviceControlSync.
    playThrough.Deactivate();
    playThrough_UISounds.Deactivate();

    deviceControlSync.SetDevices(*bgmDevice, newOutputDevice);
    deviceControlSync.Activate();

    // Stream audio from BGMDevice to the new output device. This blocks while the old device stops
    // IO.
    playThrough.SetDevices(bgmDevice, &newOutputDevice);
    playThrough.Activate();

    // TODO: Support setting different devices as the default output device and the default system
    //       output device the way OS X does?
    BGMAudioDevice uiSoundsDevice = bgmDevice->GetUISoundsBGMDeviceInstance();
    playThrough_UISounds.SetDevices(&uiSoundsDevice, &newOutputDevice);
    playThrough_UISounds.Activate();
}

- (void) setDataSource:(UInt32)dataSourceID device:(BGMAudioDevice&)device {
    BGMLogAndSwallowExceptions("BGMAudioDeviceManager::setDataSource", ([&] {
        AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;
        UInt32 channel = 0;

        if (device.DataSourceControlIsSettable(scope, channel)) {
            DebugMsg("BGMAudioDeviceManager::setOutputDeviceWithID: Setting dataSourceID=%u",
                     dataSourceID);
            
            device.SetCurrentDataSourceByID(scope, channel, dataSourceID);
        }
    }));
}

- (void) propagateOutputDeviceChange {
    // Tell BGMXPCHelper that the output device has changed.
    [self sendOutputDeviceToBGMXPCHelper];

    // Update the menu item for the volume of the output device.
    [outputVolumeMenuItem outputDeviceDidChange];
    [outputDeviceMenuSection outputDeviceDidChange];
}

- (NSError*) failedToSetOutputDevice:(AudioDeviceID)deviceID
                           errorCode:(OSStatus)errorCode
                            revertTo:(AudioDeviceID*)revertTo {
    // Using LogWarning from PublicUtility instead of NSLog here crashes from a bad access. Not sure why.
    // TODO: Possibly caused by a bug in CADebugMacros.cpp. See commit ab9d4cd.
    NSLog(@"BGMAudioDeviceManager::failedToSetOutputDevice: Couldn't set device with ID %u as output device. "
          "%s%d. %@",
          deviceID,
          "Error: ", errorCode,
          (revertTo ? [NSString stringWithFormat:@"Will attempt to revert to the previous device. "
                                                  "Previous device ID: %u.", *revertTo] : @""));
    
    NSDictionary* __nullable info = nil;
    
    if (revertTo) {
        // Try to reactivate the original device listener and playthrough. (Sorry about the mutual recursion.)
        NSError* __nullable revertError = [self setOutputDeviceWithID:*revertTo revertOnFailure:NO];
        
        if (revertError) {
            info = @{ @"revertError": (NSError*)revertError };
        }
    } else {
        // TODO: Handle this error better in callers. Maybe show an error dialog and try to set the original
        //       default device as the output device.
        NSLog(@"BGMAudioDeviceManager::failedToSetOutputDevice: Failed to revert to the previous device.");
    }
    
    return [NSError errorWithDomain:@kBGMAppBundleID code:errorCode userInfo:info];
}

- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice {
    // We can only try for stateLock because setOutputDeviceWithID might have already taken it, then made a
    // HAL request to BGMDevice and be waiting for the response. Some of the requests setOutputDeviceWithID
    // makes to BGMDevice block in the HAL if another thread is in BGM_Device::StartIO.
    //
    // Since BGM_Device::StartIO calls this method (via XPC), waiting for setOutputDeviceWithID to release
    // stateLock could cause deadlocks. Instead we return early with an error code that BGMDriver knows to
    // ignore, since the output device is (almost certainly) being changed and we can't avoid dropping frames
    // while the output device starts up.
    OSStatus err;
    BOOL gotLock;
    
    @try {
        gotLock = [stateLock tryLock];

        BOOL isBigSur = NO;
        if (@available(macOS 11.0, *)) {
            isBigSur = YES;
        }

        // Always start playthrough asynchronously. Temp workaround for deadlock on Big Sur.
        if (!isBigSur && gotLock) {
            BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);

            // Playthrough might not have been notified that BGMDevice is starting yet, so make sure
            // playthrough is starting. This way we won't drop any frames while waiting for the HAL to send
            // that notification. We can't be completely sure this is safe from deadlocking, though, since
            // CoreAudio is closed-source.
            //
            // TODO: Test this on older OS X versions. Differences in the CoreAudio implementations could
            //       cause deadlocks.
            BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::startPlayThroughSync",
                                          "Starting playthrough", [&] {
                pt.Start();
            });

            err = pt.WaitForOutputDeviceToStart();
            BGMAssert(err != BGMPlayThrough::kDeviceNotStarting, "Playthrough didn't start");
        } else {
            LogWarning("BGMAudioDeviceManager::startPlayThroughSync: Didn't get state lock. Returning "
                       "early with kBGMErrorCode_ReturningEarly.");
            err = kBGMErrorCode_ReturningEarly;

            dispatch_async(BGMGetDispatchQueue_PriorityUserInteractive(), ^{
                @try {
                    [stateLock lock];

                    BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);
                    
                    BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::startPlayThroughSync",
                                                  "Starting playthrough (dispatched)", [&] {
                        pt.Start();
                    });

                    BGMLogAndSwallowExceptions("BGMAudioDeviceManager::startPlayThroughSync", [&] {
                        pt.StopIfIdle();
                    });
                } @finally {
                    [stateLock unlock];
                }
            });
        }
    } @finally {
        if (gotLock) {
            [stateLock unlock];
        }
    }
    
    return err;
}

#pragma mark BGMXPCHelper Communication

- (void) setBGMXPCHelperConnection:(NSXPCConnection* __nullable)connection {
    bgmXPCHelperConnection = connection;

    // Tell BGMXPCHelper which device is the output device, since it might not be up-to-date.
    [self sendOutputDeviceToBGMXPCHelper];
}

- (void) sendOutputDeviceToBGMXPCHelper {
    NSXPCConnection* __nullable connection = bgmXPCHelperConnection;

    if (connection)
    {
        id<BGMXPCHelperXPCProtocol> helperProxy =
                [connection remoteObjectProxyWithErrorHandler:^(NSError* error) {
                    // We could wait a bit and try again, but it isn't that important.
                    NSLog(@"BGMAudioDeviceManager::sendOutputDeviceToBGMXPCHelper: Connection"
                           "error: %@", error);
                }];

        [helperProxy setOutputDeviceToMakeDefaultOnAbnormalTermination:outputDevice.GetObjectID()];
    }
}

@end

#pragma clang assume_nonnull end




================================================
FILE: BGMApp/BGMApp/BGMAutoPauseMenuItem.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAutoPauseMenuItem.h
//  BGMApp
//
//  Copyright © 2016 Kyle Neideck
//

// Local Includes
#import "BGMAutoPauseMusic.h"
#import "BGMMusicPlayers.h"
#import "BGMUserDefaults.h"

// System Includes
#import <Cocoa/Cocoa.h>


#pragma clang assume_nonnull begin

@interface BGMAutoPauseMenuItem : NSObject

- (instancetype) initWithMenuItem:(NSMenuItem*)item
                   autoPauseMusic:(BGMAutoPauseMusic*)autoPause
                     musicPlayers:(BGMMusicPlayers*)players
                     userDefaults:(BGMUserDefaults*)defaults;

// Handle events passed along by the delegate (NSMenuDelegate) of the menu containing this menu item.
- (void) parentMenuNeedsUpdate;
- (void) parentMenuItemWillHighlight:(NSMenuItem* __nullable)item;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAutoPauseMenuItem.m
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAutoPauseMenuItem.m
//  BGMApp
//
//  Copyright © 2016, 2019 Kyle Neideck
//  Copyright © 2016 Tanner Hoke
//

// Self Include
#import "BGMAutoPauseMenuItem.h"

// Local Includes
#import "BGMAppWatcher.h"


#pragma clang assume_nonnull begin

static NSString* const kMenuItemTitleFormat = @"Auto-pause %@";
static NSString* const kMenuItemDisabledToolTipFormat = @"%@ doesn't appear to be running.";

// Wait time to disable/enable the auto-pause menu item, in seconds.
static SInt64 const kMenuItemUpdateWaitTime = 1;

@implementation BGMAutoPauseMenuItem {
    BGMUserDefaults* userDefaults;
    NSMenuItem* menuItem;
    BGMAutoPauseMusic* autoPauseMusic;
    BGMMusicPlayers* musicPlayers;
    BGMAppWatcher* appWatcher;
}

- (instancetype) initWithMenuItem:(NSMenuItem*)item
                   autoPauseMusic:(BGMAutoPauseMusic*)autoPause
                     musicPlayers:(BGMMusicPlayers*)players
                     userDefaults:(BGMUserDefaults*)defaults {
    if ((self = [super init])) {
        menuItem = item;
        autoPauseMusic = autoPause;
        musicPlayers = players;
        userDefaults = defaults;
        
        // Enable/disable auto-pause to match the user's preferences setting.
        if (userDefaults.autoPauseMusicEnabled) {
            menuItem.state = NSOnState;
            [autoPauseMusic enable];
        } else {
            menuItem.state = NSOffState;
            [autoPauseMusic disable];
        }
        
        // Toggle auto-pause when the menu item is clicked.
        menuItem.target = self;
        menuItem.action = @selector(toggleAutoPauseMusic);

        [self initMenuItemTitle];
    }
    
    return self;
}

- (void) initMenuItemTitle {
    // Set the initial text, tool-tip, state, etc.
    [self updateMenuItemTitle];

    // Avoid retain cycles in case we ever want to destroy instances of this class.
    BGMAutoPauseMenuItem* __weak weakSelf = self;

    // Add a callback that enables/disables the Auto-pause Music menu item when the music player
    // is launched/terminated.
    void (^callback)(void) = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMenuItemUpdateWaitTime * NSEC_PER_SEC),
                       dispatch_get_main_queue(),
                       ^{
                           BGMAutoPauseMenuItem* strongSelf = weakSelf;
                           [strongSelf updateMenuItemTitle];
                       });
    };

    appWatcher = [[BGMAppWatcher alloc] initWithAppLaunched:callback
                                              appTerminated:callback
                                         isMatchingBundleID:^BOOL(NSString* appBundleID) {
        BGMAutoPauseMenuItem* strongSelf = weakSelf;
        NSString* __nullable playerBundleID =
                strongSelf->musicPlayers.selectedMusicPlayer.bundleID;
        return playerBundleID && [appBundleID isEqualToString:(NSString*)playerBundleID];
    }];
}

- (void) toggleAutoPauseMusic {
    // The menu item was clicked.
    
    if (menuItem.state == NSOnState) {
        menuItem.state = NSOffState;
        [autoPauseMusic disable];
    } else {
        menuItem.state = NSOnState;
        [autoPauseMusic enable];
    }
    
    // Persist the change in the user's preferences.
    userDefaults.autoPauseMusicEnabled = (menuItem.state == NSOnState);
}

- (void) updateMenuItemTitle {
    [self updateMenuItemTitleWithHighlight:menuItem.isHighlighted];
}

- (void) updateMenuItemTitleWithHighlight:(BOOL)highlight {
    // Set the title of the Auto-pause Music menu item, including the name of the selected music player.
    NSString* musicPlayerName = musicPlayers.selectedMusicPlayer.name;
    menuItem.title = [NSString stringWithFormat:kMenuItemTitleFormat, musicPlayerName];
    
    // Make the Auto-pause Music menu item appear disabled if the application is not running.
    //
    // We don't actually disable it just in case the user decides to disable auto-pause and their music player isn't
    // running. E.g. someone who only recently installed Background Music and doesn't want to use auto-pause at all.
    if (musicPlayers.selectedMusicPlayer.running) {
        menuItem.attributedTitle = nil;
        menuItem.toolTip = nil;
    } else {
        // Hardcode the text colour grey to match disabled menu items (unless the menu item is highlighted, in which
        // case use white).
        //
        // I couldn't figure out a way to do this without hardcoding the colours. There's no colour constant for this,
        // except possibly disabledControlTextColor, which just leaves the text black for me. I also couldn't get the
        // colours from the built-in NSColorLists.
        //
        // TODO: Can we make the tick mark grey as well?
        NSString* __nullable appleInterfaceStyle =
            [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
        BOOL darkMode = [appleInterfaceStyle isEqualToString:@"Dark"];
        NSColor* textColor = [NSColor colorWithHue:0
                                        saturation:0
                                        brightness:(highlight ? 1 : (darkMode ? 0.25 : 0.75))
                                             alpha:1];
        
        NSDictionary* attributes = @{ NSFontAttributeName: [NSFont menuBarFontOfSize:0],  // Default font size
                                      NSForegroundColorAttributeName: textColor };
        NSAttributedString* pseudoDisabledTitle = [[NSAttributedString alloc] initWithString:menuItem.title
                                                                                  attributes:attributes];
        menuItem.attributedTitle = pseudoDisabledTitle;

        menuItem.toolTip = [NSString stringWithFormat:kMenuItemDisabledToolTipFormat, musicPlayerName];
    }
}

#pragma mark Parent menu events

- (void) parentMenuNeedsUpdate {
    [self updateMenuItemTitle];
}

- (void) parentMenuItemWillHighlight:(NSMenuItem* __nullable)item {
    // Used to make the auto-pause menu item's text white when it's highlighted and change it back after.
    //
    // TODO: If you click the auto-pause menu item while it's disabled, it will initially appear highlighted next time
    //       you open the main menu.
    
    // If item is nil or any other menu item, the auto-pause menu item will be unhighlighted.
    BOOL willHighlightMenuItem = [item isEqual:menuItem];
    
    // Only update the menu item if it's changing (from highlighted to unhighlighted or vice versa) to save a little
    // CPU.
    if (willHighlightMenuItem != menuItem.highlighted) {
        [self updateMenuItemTitleWithHighlight:willHighlightMenuItem];
    }
}

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAutoPauseMusic.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAutoPauseMusic.h
//  BGMApp
//
//  Copyright © 2016 Kyle Neideck
//
//  When enabled, BGMAutoPauseMusic listens for notifications from BGMDevice to tell when music is playing and
//  pauses the music player if other audio starts.
//

// Local Includes
#import "BGMAudioDeviceManager.h"
#import "BGMMusicPlayers.h"
#import "BGMUserDefaults.h"

// System Includes
#import <Foundation/Foundation.h>


#pragma clang assume_nonnull begin

@interface BGMAutoPauseMusic : NSObject

- (id) initWithAudioDevices:(BGMAudioDeviceManager*)inAudioDevices musicPlayers:(BGMMusicPlayers*)inMusicPlayers userDefaults:(BGMUserDefaults*)inUserDefaults;

- (void) enable;
- (void) disable;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMAutoPauseMusic.mm
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMAutoPauseMusic.m
//  BGMApp
//
//  Copyright © 2016, 2017 Kyle Neideck
//

// Self Include
#import "BGMAutoPauseMusic.h"

// Local Includes
#include "BGM_Types.h"
#import "BGMMusicPlayer.h"

// STL Includes
#import <algorithm>  // std::max, std::min

// System Includes
#include <CoreAudio/AudioHardware.h>
#include <mach/mach_time.h>


// We multiply the time spent paused by this factor to calculate the delay before we consider unpausing.
static Float32 const kUnpauseDelayWeightingFactor = 0.1f;

@implementation BGMAutoPauseMusic {
    BOOL enabled;
    
    BGMAudioDeviceManager* audioDevices;
    BGMMusicPlayers* musicPlayers;
    BGMUserDefaults* userDefaults;
    
    dispatch_queue_t listenerQueue;
    // Have to keep track of the listener block we add so we can remove it later.
    AudioObjectPropertyListenerBlock listenerBlock;
    
    dispatch_queue_t pauseUnpauseMusicQueue;
    
    // True if BGMApp has paused musicPlayer and hasn't unpaused it yet. (Will be out of sync with the music player app if the
    // user has unpaused it themselves.)
    BOOL wePaused;
    // The times, in absolute time, that the BGMDevice last changed its audible state to silent...
    UInt64 wentSilent;
    // ...and to audible.
    UInt64 wentAudible;
}

- (id) initWithAudioDevices:(BGMAudioDeviceManager*)inAudioDevices musicPlayers:(BGMMusicPlayers*)inMusicPlayers userDefaults:(BGMUserDefaults*)inUserDefaults {
    if ((self = [super init])) {
        audioDevices = inAudioDevices;
        musicPlayers = inMusicPlayers;
        userDefaults = inUserDefaults;
        
        enabled = NO;
        wePaused = NO;
        
        dispatch_queue_attr_t attr;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        if (&dispatch_queue_attr_make_with_qos_class) {
            attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
        } else {
            // OS X 10.9 fallback
            attr = DISPATCH_QUEUE_SERIAL;
        }
#pragma clang diagnostic pop

        listenerQueue = dispatch_queue_create("com.bearisdriving.BGM.AutoPauseMusic.Listener", attr);
        pauseUnpauseMusicQueue = dispatch_queue_create("com.bearisdriving.BGM.AutoPauseMusic.PauseUnpauseMusic", attr);
        
        [self initListenerBlock];
    }
    
    return self;
}

- (void) dealloc {
    [self disable];
}

- (void) initListenerBlock {
    // To avoid retain cycle
    __unsafe_unretained BGMAutoPauseMusic* weakSelf = self;
    
    listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress * _Nonnull inAddresses) {
        // inAddresses "may contain addresses for properties for which the listener is not signed up to receive notifications",
        // so we have to check them all
        for (int i = 0; i < inNumberAddresses; i++) {
            if (inAddresses[i].mSelector == kAudioDeviceCustomPropertyDeviceAudibleState) {
                BGMDeviceAudibleState audibleState = [weakSelf deviceAudibleState];
                
#if DEBUG
                const char audibleStateStr[5] = CA4CCToCString(audibleState);
                DebugMsg("BGMAutoPauseMusic::initListenerBlock: kAudioDeviceCustomPropertyDeviceAudibleState property changed to '%s'",
                         audibleStateStr);
#endif
                
                // TODO: We shouldn't assume this block will only get called when BGMDevice's audible state changes. (Even if
                //       the Core Audio docs did specify that, there's no reason not to be fault tolerant.)
                if (audibleState == kBGMDeviceIsAudible) {
                    [weakSelf queuePauseBlock];
                } else if (audibleState == kBGMDeviceIsSilent) {
                    [weakSelf queueUnpauseBlock];
                } else if (audibleState == kBGMDeviceIsSilentExceptMusic) {
                    // If we pause the music player and then the user unpauses it before the other audio stops, we need to set
                    // wePaused to false at some point before the other audio starts again so we know we should pause
                    DebugMsg("BGMAutoPauseMusic: Device is silent except music, resetting wePaused flag");
                    wePaused = NO;
                }
                // TODO: Add a fourth audible state, something like "AudibleAndMusicPlaying", and check it here to
                //       handle the user unpausing and then repausing music while also playing other audio?
            }
        }
    };
}

- (BGMDeviceAudibleState) deviceAudibleState {
    return [audioDevices bgmDevice].GetAudibleState();
}

- (void) queuePauseBlock {
    UInt64 now = mach_absolute_time();
    wentAudible = now;
    UInt64 startedPauseDelay = now;
    
    UInt64 pauseDelayMS = userDefaults.pauseDelayMS;
    
    // If pause delay is 0, pause immediately (no delay)
    if (pauseDelayMS == 0) {
        DebugMsg("BGMAutoPauseMusic::queuePauseBlock: Pause delay is 0, pausing immediately");
        
        // Pause immediately if device is audible and we haven't already paused
        if (!wePaused && ([self deviceAudibleState] == kBGMDeviceIsAudible)) {
            wePaused = ([musicPlayers.selectedMusicPlayer pause] || wePaused);
        }
        return;
    }
    
    UInt64 pauseDelayNSec = pauseDelayMS * NSEC_PER_MSEC;
    
    DebugMsg("BGMAutoPauseMusic::queuePauseBlock: Dispatching pause block at %llu", now);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, pauseDelayNSec),
                   pauseUnpauseMusicQueue,
                   ^{
                       BOOL stillAudible = ([self deviceAudibleState] == kBGMDeviceIsAudible);
                       
                       DebugMsg("BGMAutoPauseMusic::queuePauseBlock: Running pause block dispatched at %llu.%s wentAudible=%llu",
                                startedPauseDelay,
                                stillAudible ? "" : " Not pausing because the device isn't audible.",
                                wentAudible);
                       
                       // Pause if this is the most recent pause block and the device is still audible, which means the audible
                       // state hasn't changed since this block was queued. Also set wePaused to true if the player wasn't
                       // already paused.
                       if (!wePaused && (startedPauseDelay == wentAudible) && stillAudible) {
                           wePaused = ([musicPlayers.selectedMusicPlayer pause] || wePaused);
                       }
                   });
}

- (void) queueUnpauseBlock {
    UInt64 now = mach_absolute_time();
    wentSilent = now;
    UInt64 startedUnpauseDelay = now;
    
    // Get user-configurable max delay
    UInt64 maxUnpauseDelayMS = userDefaults.maxUnpauseDelayMS;
    
    // If max unpause delay is 0, unpause immediately (no delay)
    if (maxUnpauseDelayMS == 0) {
        DebugMsg("BGMAutoPauseMusic::queueUnpauseBlock: Max unpause delay is 0, unpausing immediately");
        
        // Unpause immediately if we were the one who paused and device is still silent
        BGMDeviceAudibleState currentState = [self deviceAudibleState];
        DebugMsg("BGMAutoPauseMusic::queueUnpauseBlock: Immediate unpause - wePaused=%s, currentState=%s", 
                 wePaused ? "YES" : "NO", 
                 currentState == kBGMDeviceIsSilent ? "Silent" : 
                 (currentState == kBGMDeviceIsAudible ? "Audible" : "SilentExceptMusic"));
        
        if (wePaused && (currentState == kBGMDeviceIsSilent)) {
            wePaused = NO;
            [musicPlayers.selectedMusicPlayer unpause];
        }
        return;
    }
    
    // Unpause sooner if we've only been paused for a short time. This is so a notification sound causing an auto-pause is
    // less of an interruption.
    //
    // TODO: Fading in and out would make short pauses a lot less jarring because, if they were short enough, we wouldn't
    //       actually pause the music player. So you'd hear a dip in the music's volume rather than a gap.
    UInt64 unpauseDelayNsec =
        static_cast<UInt64>((wentSilent - wentAudible) * kUnpauseDelayWeightingFactor);
    
    // Convert from absolute time to nanos.
    mach_timebase_info_data_t info;
    mach_timebase_info(&info);
    unpauseDelayNsec = unpauseDelayNsec * info.numer / info.denom;
    
    // Clamp using user-configurable max delay and calculated min delay.
    UInt64 maxUnpauseDelayNSec = maxUnpauseDelayMS * NSEC_PER_MSEC;
    UInt64 minUnpauseDelayNSec = maxUnpauseDelayNSec / 10;
    unpauseDelayNsec = std::min(maxUnpauseDelayNSec, unpauseDelayNsec);
    unpauseDelayNsec = std::max(minUnpauseDelayNSec, unpauseDelayNsec);
    
    DebugMsg("BGMAutoPauseMusic::queueUnpauseBlock: Dispatched unpause block at %llu. unpauseDelayNsec=%llu",
             now,
             unpauseDelayNsec);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, unpauseDelayNsec),
                   pauseUnpauseMusicQueue,
                   ^{
                       BGMDeviceAudibleState currentState = [self deviceAudibleState];
                       BOOL stillSilent = (currentState == kBGMDeviceIsSilent);
                       BOOL isLatestUnpause = (startedUnpauseDelay == wentSilent);
                       
                       DebugMsg("BGMAutoPauseMusic::queueUnpauseBlock: Running unpause block dispatched at %llu. wePaused=%s, isLatest=%s, currentState=%s, wentSilent=%llu",
                                startedUnpauseDelay,
                                wePaused ? "YES" : "NO",
                                isLatestUnpause ? "YES" : "NO",
                                currentState == kBGMDeviceIsSilent ? "Silent" : 
                                (currentState == kBGMDeviceIsAudible ? "Audible" : "SilentExceptMusic"),
                                wentSilent);
                       
                       // Unpause if we were the one who paused. Also check that this is the most recent unpause block and the
                       // device is still silent, which means the audible state hasn't changed since this block was queued.
                       if (wePaused && isLatestUnpause && stillSilent) {
                           DebugMsg("BGMAutoPauseMusic::queueUnpauseBlock: Unpausing music player");
                           wePaused = NO;
                           [musicPlayers.selectedMusicPlayer unpause];
                       }
                   });
}

- (void) enable {
    if (!enabled) {
        [audioDevices bgmDevice].AddPropertyListenerBlock(kBGMAudibleStateAddress, listenerQueue, listenerBlock);
        enabled = YES;
    }
}

- (void) disable {
    if (enabled) {
        [audioDevices bgmDevice].RemovePropertyListenerBlock(kBGMAudibleStateAddress, listenerQueue, listenerBlock);
        enabled = NO;
    }
}

@end



================================================
FILE: BGMApp/BGMApp/BGMBackgroundMusicDevice.cpp
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMBackgroundMusicDevice.cpp
//  BGMApp
//
//  Copyright © 2016-2019 Kyle Neideck
//  Copyright © 2017 Andrew Tonner
//

// Self Include
#include "BGMBackgroundMusicDevice.h"

// Local Includes
#include "BGM_Types.h"
#include "BGM_Utils.h"

// PublicUtility Includes
#include "CADebugMacros.h"
#include "CAHALAudioSystemObject.h"
#include "CACFArray.h"
#include "CACFDictionary.h"

// STL Includes
#include <map>


#pragma clang assume_nonnull begin

#pragma mark Construction/Destruction

BGMBackgroundMusicDevice::BGMBackgroundMusicDevice()
:
    BGMAudioDevice(CFSTR(kBGMDeviceUID)),
    mUISoundsBGMDevice(CFSTR(kBGMDeviceUID_UISounds))
{
    if((GetObjectID() == kAudioObjectUnknown) || (mUISoundsBGMDevice == kAudioObjectUnknown))
    {
        LogError("BGMBackgroundMusicDevice::BGMBackgroundMusicDevice: Error getting BGMDevice ID");
        Throw(CAException(kAudioHardwareIllegalOperationError));
    }
};

BGMBackgroundMusicDevice::~BGMBackgroundMusicDevice()
{
}

#pragma mark Systemwide Default Device

void BGMBackgroundMusicDevice::SetAsOSDefault()
{
    DebugMsg("BGMBackgroundMusicDevice::SetAsOSDefault: Setting the system's default audio device "
             "to BGMDevice");

    CAHALAudioSystemObject audioSystem;

    AudioDeviceID defaultDevice = audioSystem.GetDefaultAudioDevice(false, false);
    AudioDeviceID systemDefaultDevice = audioSystem.GetDefaultAudioDevice(false, true);

    if(systemDefaultDevice == defaultDevice)
    {
        // The default system device is the same as the default device, so change both of them.
        //
        // Use the UI sounds instance of BGMDevice because the default system output device is the
        // device "to use for system related sound". The allows BGMDriver to tell when the audio it
        // receives is UI-related.
        audioSystem.SetDefaultAudioDevice(false, true, mUISoundsBGMDevice);
    }

    audioSystem.SetDefaultAudioDevice(false, false, GetObjectID());
}

void BGMBackgroundMusicDevice::UnsetAsOSDefault(AudioDeviceID inOutputDeviceID)
{
    CAHALAudioSystemObject audioSystem;

    // Set BGMApp's output device as OS X's default output device.
    bool bgmDeviceIsDefault =
            (audioSystem.GetDefaultAudioDevice(false, false) == GetObjectID());

    if(bgmDeviceIsDefault)
    {
        DebugMsg("BGMBackgroundMusicDevice::UnsetAsOSDefault: Setting the system's default output "
                 "device back to device %d", inOutputDeviceID);

        audioSystem.SetDefaultAudioDevice(false, false, inOutputDeviceID);
    }

    // Set BGMApp's output device as OS X's default system output device.
    bool bgmDeviceIsSystemDefault =
            (audioSystem.GetDefaultAudioDevice(false, true) == mUISoundsBGMDevice);

    // If we changed the default system output device to BGMDevice, which we only do if it's set to
    // the same device as the default output device, change it back to the previous device.
    if(bgmDeviceIsSystemDefault)
    {
        DebugMsg("BGMBackgroundMusicDevice::UnsetAsOSDefault: Setting the system's default system "
                 "output device back to device %d", inOutputDeviceID);

        audioSystem.SetDefaultAudioDevice(false, true, inOutputDeviceID);
    }
}

#pragma mark App Volumes

CFArrayRef BGMBackgroundMusicDevice::GetAppVolumes() const
{
    CFTypeRef appVolumes = GetPropertyData_CFType(kBGMAppVolumesAddress);

    ThrowIfNULL(appVolumes,
                CAException(kAudioHardwareIllegalOperationError),
                "BGMBackgroundMusicDevice::GetAppVolumes: !appVolumes");
    ThrowIf(CFGetTypeID(appVolumes) != CFArrayGetTypeID(),
            CAException(kAudioHardwareIllegalOperationError),
            "BGMBackgroundMusicDevice::GetAppVolumes: Expected CFArray value");

    return static_cast<CFArrayRef>(appVolumes);
}

void BGMBackgroundMusicDevice::SetAppVolume(SInt32 inVolume,
                                            pid_t inAppProcessID,
                                            CFStringRef __nullable inAppBundleID)
{
    BGMAssert((kAppRelativeVolumeMinRawValue <= inVolume) &&
                      (inVolume <= kAppRelativeVolumeMaxRawValue),
              "BGMBackgroundMusicDevice::SetAppVolume: Volume out of bounds");

    // Clamp the volume to [kAppRelativeVolumeMinRawValue, kAppPanRightRawValue].
    inVolume = std::max(kAppRelativeVolumeMinRawValue, inVolume);
    inVolume = std::min(kAppRelativeVolumeMaxRawValue, inVolume);

    SendAppVolumeOrPanToBGMDevice(inVolume,
                                  CFSTR(kBGMAppVolumesKey_RelativeVolume),
                                  inAppProcessID,
                                  inAppBundleID);
}

void BGMBackgroundMusicDevice::SetAppPanPosition(SInt32 inPanPosition,
                                                 pid_t inAppProcessID,
                                                 CFStringRef __nullable inAppBundleID)
{
    BGMAssert((kAppPanLeftRawValue <= inPanPosition) && (inPanPosition <= kAppPanRightRawValue),
              "BGMBackgroundMusicDevice::SetAppPanPosition: Pan position out of bounds");

    // Clamp the pan position to [kAppPanLeftRawValue, kAppPanRightRawValue].
    inPanPosition = std::max(kAppPanLeftRawValue, inPanPosition);
    inPanPosition = std::min(kAppPanRightRawValue, inPanPosition);

    SendAppVolumeOrPanToBGMDevice(inPanPosition,
                                  CFSTR(kBGMAppVolumesKey_PanPosition),
                                  inAppProcessID,
                                  inAppBundleID);
}

void BGMBackgroundMusicDevice::SendAppVolumeOrPanToBGMDevice(SInt32 inNewValue,
                                                             CFStringRef inVolumeTypeKey,
                                                             pid_t inAppProcessID,
                                                             CFStringRef __nullable inAppBundleID)
{
    CACFArray appVolumeChanges(true);

    auto addVolumeChange = [&] (pid_t pid, CFStringRef bundleID)
    {
        CACFDictionary appVolumeChange(true);

        appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), pid);
        appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID), bundleID);
        appVolumeChange.AddSInt32(inVolumeTypeKey, inNewValue);

        appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());
    };

    addVolumeChange(inAppProcessID, inAppBundleID);

    // Add the same change for each process the app is responsible for.
    for(CACFString responsibleBundleID : ResponsibleBundleIDsOf(CACFString(inAppBundleID)))
    {
        // Send -1 as the PID so this volume will only ever be matched by bundle ID.
        addVolumeChange(-1, responsibleBundleID.GetCFString());
    }

    CFPropertyListRef changesPList = appVolumeChanges.AsPropertyList();

    // Send the change to BGMDevice.
    SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);

    // Also send it to the instance of BGMDevice that handles UI sounds.
    mUISoundsBGMDevice.SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);
}

// This is a temporary solution that lets us control the volumes of some multiprocess apps, i.e.
// apps that play their audio from a process with a different bundle ID.
//
// We can't just check the child processes of the apps' main processes because they're usually
// created with launchd rather than being actual child processes. There's a private API to get the
// processes that an app is "responsible for", so we'll try to use it in the proper fix and only use
// this list if the API doesn't work.
//
// static
std::vector<CACFString>
BGMBackgroundMusicDevice::ResponsibleBundleIDsOf(CACFString inParentBundleID)
{
    if(!inParentBundleID.IsValid())
    {
        return {};
    }

    std::map<CACFString, std::vector<CACFString>> bundleIDMap = {
        // Finder
        { "com.apple.finder",
            { "com.apple.quicklook.ui.helper",
              "com.apple.quicklook.QuickLookUIService" } },
        // Safari
        { "com.apple.Safari", { "com.apple.WebKit.WebContent" } },
        // Firefox
        { "org.mozilla.firefox", { "org.mozilla.plugincontainer" } },
        // Firefox Nightly
        { "org.mozilla.nightly", { "org.mozilla.plugincontainer" } },
        // VMWare Fusion
        { "com.vmware.fusion", { "com.vmware.vmware-vmx" } },
        // Parallels
        { "com.parallels.desktop.console", { "com.parallels.vm" } },
        // MPlayer OSX Extended
        { "hu.mplayerhq.mplayerosx.extended",
                { "ch.sttz.mplayerosx.extended.binaries.officialsvn" } },
        // Discord
        { "com.hnc.Discord",
            { "com.hnc.Discord.helper",
              "com.hnc.Discord.helper.Renderer",
              "com.hnc.Discord.helper.Plugin" } },
        // Brave
        { "com.brave.Browser",
            { "com.brave.Browser.helper",
              "com.brave.Browser.helper.plugin" } },
        // Skype
        { "com.skype.skype", { "com.skype.skype.Helper" } },
        // Google Chrome
        { "com.google.Chrome", { "com.google.Chrome.helper" } },
        // Microsoft Edge
        { "com.microsoft.edgemac", { "com.microsoft.edgemac.helper" } },
        // Arc
        { "company.thebrowser.Browser", { "company.thebrowser.browser.helper" } }
    };

    // Parallels' VM "dock helper" apps have bundle IDs like
    // com.parallels.winapp.87f6bfc236d64d70a81c47f6243add4c.f5a25fdede514f7aa0a475a1873d3287.fs
    if(inParentBundleID.StartsWith(CFSTR("com.parallels.winapp.")))
    {
        return { "com.parallels.vm" };
    }

    return bundleIDMap[inParentBundleID];
}

#pragma mark Audible State

BGMDeviceAudibleState BGMBackgroundMusicDevice::GetAudibleState() const
{
    CFTypeRef propertyDataRef = GetPropertyData_CFType(kBGMAudibleStateAddress);

    ThrowIfNULL(propertyDataRef,
                CAException(kAudioHardwareIllegalOperationError),
                "BGMBackgroundMusicDevice::GetAudibleState: !propertyDataRef");

    ThrowIf(CFGetTypeID(propertyDataRef) != CFNumberGetTypeID(),
            CAException(kAudioHardwareIllegalOperationError),
            "BGMBackgroundMusicDevice::GetAudibleState: Property was not a CFNumber");

    CFNumberRef audibleStateRef = static_cast<CFNumberRef>(propertyDataRef);

    BGMDeviceAudibleState audibleState;
    Boolean success = CFNumberGetValue(audibleStateRef, kCFNumberSInt32Type, &audibleState);
    CFRelease(audibleStateRef);

    ThrowIf(!success,
            CAException(kAudioHardwareIllegalOperationError),
            "BGMBackgroundMusicDevice::GetMusicPlayerProcessID: CFNumberGetValue failed");

    return audibleState;
}

#pragma mark Music Player

pid_t BGMBackgroundMusicDevice::GetMusicPlayerProcessID() const
{
    CFTypeRef propertyDataRef = GetPropertyData_CFType(kBGMMusicPlayerProcessIDAddress);

    ThrowIfNULL(propertyDataRef,
                CAException(kAudioHardwareIllegalOperationError),
                "BGMBackgroundMusicDevice::GetMusicPlayerProcessID: !propertyDataRef");

    ThrowIf(CFGetTypeID(propertyDataRef) != CFNumberGetTypeID(),
            CAException(kAudioHardwareIllegalOperationError),
            "BGMBackgroundMusicDevice::GetMusicPlayerProcessID: Property was not a CFNumber");

    CFNumberRef pidRef = static_cast<CFNumberRef>(propertyDataRef);

    pid_t pid;
    Boolean success = CFNumberGetValue(pidRef, kCFNumberIntType, &pid);
    CFRelease(pidRef);

    ThrowIf(!success,
            CAException(kAudioHardwareIllegalOperationError),
            "BGMBackgroundMusicDevice::GetMusicPlayerProcessID: CFNumberGetValue failed");

    return pid;
}

CFStringRef BGMBackgroundMusicDevice::GetMusicPlayerBundleID() const
{
    CFStringRef bundleID = GetPropertyData_CFString(kBGMMusicPlayerBundleIDAddress);

    ThrowIfNULL(bundleID,
                CAException(kAudioHardwareIllegalOperationError),
                "BGMBackgroundMusicDevice::GetMusicPlayerBundleID: !bundleID");

    return bundleID;
}

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMBackgroundMusicDevice.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMBackgroundMusicDevice.h
//  BGMApp
//
//  Copyright © 2017 Kyle Neideck
//
//  The interface to BGMDevice, the main virtual device published by BGMDriver, and the second
//  instance of that device, which handles UI-related audio. In most cases, users of this class
//  should be able to think of it as representing a single device.
//
//  BGMDevice is the device that appears as "Background Music" in programs that list the output
//  devices, e.g. System Preferences. It receives the system's audio, processes it and sends it to
//  BGMApp by publishing an input stream. BGMApp then plays the audio on the user's real output
//  device.
//
//  See BGMDriver/BGMDriver/BGM_Device.h.
//

#ifndef BGMApp__BGMBackgroundMusicDevice
#define BGMApp__BGMBackgroundMusicDevice

// Superclass Includes
#include "BGMAudioDevice.h"

// Local Includes
#include "BGM_Types.h"

// PublicUtility Includes
#include "CACFString.h"

// STL Includes
#include <vector>


#pragma clang assume_nonnull begin

class BGMBackgroundMusicDevice
:
    public BGMAudioDevice
{

#pragma mark Construction/Destruction

public:
    /*!
     @throws CAException If BGMDevice is not found or the HAL returns an error when queried for
                         BGMDevice's current Audio Object ID.
     */
                        BGMBackgroundMusicDevice();
    virtual            ~BGMBackgroundMusicDevice();

#pragma mark Systemwide Default Device

public:
    /*!
     Set BGMDevice as the default audio device for all processes.

     @throws CAException If the HAL responds with an error.
     */
    void                SetAsOSDefault();
    /*!
     Replace BGMDevice as the default device with the output device.

     @throws CAException If the HAL responds with an error.
     */
    void                UnsetAsOSDefault(AudioDeviceID inOutputDeviceID);

#pragma mark App Volumes

public:
    /*!
     @return The current value of BGMDevice's kAudioDeviceCustomPropertyAppVolumes property. See
             BGM_Types.h.
     @throws CAException If the HAL returns an error or a non-array type. Callers are responsible
                         for validating and type-checking the values contained in the array.
     */
    CFArrayRef          GetAppVolumes() const;
    /*!
     @param inVolume A value between kAppRelativeVolumeMinRawValue and kAppRelativeVolumeMaxRawValue
                     from BGM_Types.h. See kBGMAppVolumesKey_RelativeVolume in BGM_Types.h.
     @param inAppProcessID The ID of app's main process (or the process it uses to play audio, if
                           you've managed to figure that out). If an app has multiple audio
                           processes, you can just set the volume for each of them. Pass -1 to omit
                           this param.
     @param inAppBundleID The app's bundle ID. Pass null to omit this param.
     @throws CAException If the HAL returns an error when this function sends the volume change to
                         BGMDevice.
     */
    void                SetAppVolume(SInt32 inVolume,
                                     pid_t inAppProcessID,
                                     CFStringRef __nullable inAppBundleID);
    /*!
     @param inPanPosition A value between kAppPanLeftRawValue and kAppPanRightRawValue from
                          BGM_Types.h. A negative value has a higher proportion of left channel, and
                          a positive value has a higher proportion of right channel.
     @param inAppProcessID The ID of app's main process (or the process it uses to play audio, if
                           you've managed to figure that out). If an app has multiple audio
                           processes, you can just set the pan position for each of them. Pass -1 to
                           omit this param.
     @param inAppBundleID The app's bundle ID. Pass null to omit this param.
     @throws CAException If the HAL returns an error when this function sends the pan position
                         change to BGMDevice.
     */
    void                SetAppPanPosition(SInt32 inPanPosition,
                                          pid_t inAppProcessID,
                                          CFStringRef __nullable inAppBundleID);

private:
    void                SendAppVolumeOrPanToBGMDevice(SInt32 inNewValue,
                                                      CFStringRef inVolumeTypeKey,
                                                      pid_t inAppProcessID,
                                                      CFStringRef __nullable inAppBundleID);

    static std::vector<CACFString>
                        ResponsibleBundleIDsOf(CACFString inParentBundleID);

#pragma mark Audible State

public:
    /*!
     @return BGMDevice's current "audible state", which can be either silent, silent except for the
             user's music player or audible, meaning a program other than the music player is
             playing audio.
     @throws CAException If the HAL returns an error or invalid data when queried.
     @see kAudioDeviceCustomPropertyDeviceAudibleState in BGM_Types.h.
     */
    BGMDeviceAudibleState GetAudibleState() const;

#pragma mark Music Player

public:
    /*!
     @return The value of BGMDevice's property for the selected music player's process ID. Zero if
             the property is unset. (We assume kernel_task will never be the user's music player.)
     @throws CAException If the HAL returns an error or an invalid PID when queried.
     @see kAudioDeviceCustomPropertyMusicPlayerProcessID in BGM_Types.h.
     */
    virtual pid_t       GetMusicPlayerProcessID() const;
    /*!
     Set the value of BGMDevice's property for the selected music player's process ID. Pass zero to
     unset the property. Setting this property will unset the bundle ID version of the property.

     @throws CAException If the HAL returns an error.
     @see kAudioDeviceCustomPropertyMusicPlayerProcessID in BGM_Types.h.
     */
    virtual void        SetMusicPlayerProcessID(CFNumberRef inProcessID) {
                            SetPropertyData_CFType(kBGMMusicPlayerProcessIDAddress, inProcessID); }
    /*!
     @return The value of BGMDevice's property for the selected music player's bundle ID. The empty
             string if the property is unset.
     @throws CAException If the HAL returns an error or an invalid bundle ID when queried.
     @see kAudioDeviceCustomPropertyMusicPlayerBundleID in BGM_Types.h.
     */
    virtual CFStringRef GetMusicPlayerBundleID() const;
    /*!
     Set the value of BGMDevice's property for the selected music player's bundle ID. Pass the empty
     string to unset the property. Setting this property will unset the process ID version of the
     property.

     @throws CAException If the HAL returns an error.
     @see kAudioDeviceCustomPropertyMusicPlayerBundleID in BGM_Types.h.
     */
    virtual void        SetMusicPlayerBundleID(CFStringRef inBundleID) {
                            SetPropertyData_CFString(kBGMMusicPlayerBundleIDAddress, inBundleID); }

#pragma mark UI Sounds Instance

public:
    /*! @return The instance of BGMDevice that handles UI sounds. */
    BGMAudioDevice      GetUISoundsBGMDeviceInstance() { return mUISoundsBGMDevice; }

private:
    /*! The instance of BGMDevice that handles UI sounds. */
    BGMAudioDevice      mUISoundsBGMDevice;

};

#pragma clang assume_nonnull end

#endif /* BGMApp__BGMBackgroundMusicDevice */



================================================
FILE: BGMApp/BGMApp/BGMDebugLoggingMenuItem.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMDebugLoggingMenuItem.h
//  BGMApp
//
//  Copyright © 2020 Kyle Neideck
//
//  A menu item in the main menu that enables/disables debug logging. Only visible if you hold the
//  option down when you click the status bar icon to reveal the main menu.
//
//  TODO: It would be better to have this menu item in the Preferences menu (maybe in an Advanced
//        section) and always visible, but first we'd need to add something that tells the user how
//        to view the log messages. Or better yet, something that automatically opens them.
//

// System Includes
#import <Cocoa/Cocoa.h>


#pragma clang assume_nonnull begin

@interface BGMDebugLoggingMenuItem : NSObject

- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem;

// True if the main menu is showing hidden items/options because the user held the option key when
// they clicked the icon. This class makes the debug logging menu item visible if this property has
// been set true or if debug logging is enabled.
@property (nonatomic) BOOL menuShowingExtraOptions;

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMDebugLoggingMenuItem.m
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMDebugLoggingMenuItem.m
//  BGMApp
//
//  Copyright © 2020 Kyle Neideck
//

// Self Include
#import "BGMDebugLoggingMenuItem.h"

// PublicUtility Includes
#import "BGMDebugLogging.h"
#import "CADebugMacros.h"


#pragma clang assume_nonnull begin

@implementation BGMDebugLoggingMenuItem {
    NSMenuItem* _menuItem;
    BOOL _menuShowingExtraOptions;
}

- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem {
    if ((self = [super init])) {
        _menuItem = menuItem;
        _menuItem.state =
                BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;

        [self setMenuShowingExtraOptions:NO];

        // Enable/disable debug logging when the menu item is clicked.
        menuItem.target = self;
        menuItem.action = @selector(toggleDebugLogging);
    }

    return self;
}

- (void) setMenuShowingExtraOptions:(BOOL)showingExtra {
    _menuShowingExtraOptions = showingExtra;
    _menuItem.hidden = !BGMDebugLoggingIsEnabled() && !showingExtra;

    DebugMsg("BGMDebugLoggingMenuItem::menuShowingExtraOptions: %s the menu item",
             _menuItem.hidden ? "Hiding" : "Showing");
}

- (void) toggleDebugLogging {
    BGMSetDebugLoggingEnabled(!BGMDebugLoggingIsEnabled());
    _menuItem.state = BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;

    DebugMsg("BGMDebugLoggingMenuItem::toggleDebugLogging: Debug logging %s",
             BGMDebugLoggingIsEnabled() ? "enabled" : "disabled");
}

@end

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMDeviceControlSync.cpp
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMDeviceControlSync.cpp
//  BGMApp
//
//  Copyright © 2016, 2017 Kyle Neideck
//

// Self Include
#include "BGMDeviceControlSync.h"

// Local Includes
#include "BGM_Types.h"
#include "BGM_Utils.h"

// PublicUtility Includes
#include "CAPropertyAddress.h"


#pragma clang assume_nonnull begin

static const AudioObjectPropertyAddress kMutePropertyAddress =
    { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMaster };

static const AudioObjectPropertyAddress kVolumePropertyAddress =
    { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMaster };

#pragma mark Construction/Destruction

BGMDeviceControlSync::BGMDeviceControlSync(AudioObjectID inBGMDevice,
                                           AudioObjectID inOutputDevice,
                                           CAHALAudioSystemObject inAudioSystem)
:
    mBGMDevice(inBGMDevice),
    mOutputDevice(inOutputDevice),
    mAudioSystem(inAudioSystem),
    mBGMDeviceControlsList(inBGMDevice)
{
}

BGMDeviceControlSync::~BGMDeviceControlSync()
{
    BGMLogAndSwallowExceptions("BGMDeviceControlSync::~BGMDeviceControlSync", [&] {
        CAMutex::Locker locker(mMutex);

        Deactivate();
    });
}

void    BGMDeviceControlSync::Activate()
{
    CAMutex::Locker locker(mMutex);

    ThrowIf((mBGMDevice.GetObjectID() == kAudioObjectUnknown || mOutputDevice.GetObjectID() == kAudioObjectUnknown),
            BGM_DeviceNotSetException(),
            "BGMDeviceControlSync::Activate: Both the output device and BGMDevice must be set to start synchronizing their controls");

    if(!mActive)
    {
        DebugMsg("BGMDeviceControlSync::Activate: Activating control sync");

        // Disable BGMDevice controls that the output device doesn't have and reenable any that were
        // disabled for the previous output device.
        //
        // Continue anyway if this fails because it's better to have extra/missing controls than to
        // be unable to use the device.
        BGMLogAndSwallowExceptionsMsg("BGMDeviceControlSync::Activate", "Controls list", [&] {
            bool wasUpdated = mBGMDeviceControlsList.MatchControlsListOf(mOutputDevice);
            if(wasUpdated)
            {
                mBGMDeviceControlsList.PropagateControlListChange();
            }
        });

        // Init BGMDevice controls to match output device
        mBGMDevice.CopyVolumeFrom(mOutputDevice, kAudioObjectPropertyScopeOutput);
        mBGMDevice.CopyMuteFrom(mOutputDevice, kAudioObjectPropertyScopeOutput);

        // Register listeners for volume and mute values
        mBGMDevice.AddPropertyListener(kVolumePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);
        
        try
        {
            mBGMDevice.AddPropertyListener(kMutePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);
        }
        catch(CAException)
        {
            CATry
            mBGMDevice.RemovePropertyListener(kVolumePropertyAddress, &BGMDeviceControlSync::BGMDeviceListenerProc, this);
            CACatch
            
            throw;
        }
        
        mActive = true;
    }
    else
    {
        DebugMsg("BGMDeviceControlSync::Activate: Already active");
    }
}

void    BGMDeviceControlSync::Deactivate()
{
    CAMutex::Locker locker(mMutex);

    if(mActive)
    {
        DebugMsg("BGMDeviceControlSync::Deactivate: Deactivating control sync");

        // Deregister listeners
        if(mBGMDevice.GetObjectID() != kAudioDeviceUnknown)
        {
            BGMLogAndSwallowExceptions("BGMDeviceControlSync::Deactivate", [&] {
                mBGMDevice.RemovePropertyListener(kVolumePropertyAddress,
                                                  &BGMDeviceControlSync::BGMDeviceListenerProc,
                                                  this);
            });

            BGMLogAndSwallowExceptions("BGMDeviceControlSync::Deactivate", [&] {
                mBGMDevice.RemovePropertyListener(kMutePropertyAddress,
                                                  &BGMDeviceControlSync::BGMDeviceListenerProc,
                                                  this);
            });
        }

        mActive = false;
    }
    else
    {
        DebugMsg("BGMDeviceControlSync::Deactivate: Not active");
    }
}

#pragma mark Accessors

void    BGMDeviceControlSync::SetDevices(AudioObjectID inBGMDevice, AudioObjectID inOutputDevice)
{
    CAMutex::Locker locker(mMutex);

    bool wasActive = mActive;

    Deactivate();

    mBGMDevice = inBGMDevice;
    mBGMDeviceControlsList.SetBGMDevice(inBGMDevice);
    mOutputDevice = inOutputDevice;
    
    if(wasActive)
    {
        Activate();
    }
}

#pragma mark Listener Procs

// static
OSStatus    BGMDeviceControlSync::BGMDeviceListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses, void* __nullable inClientData)
{
    // refCon (reference context) is the instance that registered this listener proc.
    BGMDeviceControlSync* refCon = static_cast<BGMDeviceControlSync*>(inClientData);

    auto checkState = [&] {
        if(!refCon)
        {
            LogError("BGMDeviceControlSync::BGMDeviceListenerProc: !refCon");
            return false;
        }

        if(!refCon->mActive ||
           (refCon->mBGMDevice.GetObjectID() == kAudioObjectUnknown) ||
           (refCon->mOutputDevice.GetObjectID() == kAudioObjectUnknown))
        {
            return false;
        }

        if(inObjectID != refCon->mBGMDevice.GetObjectID())
        {
            LogError("BGMDeviceControlSync::BGMDeviceListenerProc: notified about audio object other than BGMDevice");
            return false;
        }
        
        return true;
    };

    for(int i = 0; i < inNumberAddresses; i++)
    {
        AudioObjectPropertyScope scope = inAddresses[i].mScope;
        
        switch(inAddresses[i].mSelector)
        {
            case kAudioDevicePropertyVolumeScalar:
                {
                    CAMutex::Locker locker(refCon->mMutex);

                    // Update the output device's volume.
                    if(checkState())
                    {
                        refCon->mOutputDevice.CopyVolumeFrom(refCon->mBGMDevice, scope);
                    }
                }
                break;
                
            case kAudioDevicePropertyMute:
                {
                    CAMutex::Locker locker(refCon->mMutex);

                    // Update the output device's mute control. Note that this also runs when you
                    // change the volume (on BGMDevice).
                    if(checkState())
                    {
                        refCon->mOutputDevice.CopyMuteFrom(refCon->mBGMDevice, scope);
                    }
                }
                break;
        }
    }

    // "The return value [of an AudioObjectPropertyListenerProc] is currently unused and should always be 0."
    return 0;
}

#pragma clang assume_nonnull end



================================================
FILE: BGMApp/BGMApp/BGMDeviceControlSync.h
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMDeviceControlSync.h
//  BGMApp
//
//  Copyright © 2016, 2017 Kyle Neideck
//
//  Synchronises BGMDevice's controls (just volume and mute currently) with the output device's
//  controls. This allows the user to control the output device normally while BGMDevice is set as
//  the default device.
//
//  BGMDeviceControlSync disables any BGMDevice controls that the output device doesn't also have.
//  When the value of one of BGMDevice's controls is changed, BGMDeviceControlSync copies the new
//  value to the output device.
//
//  Thread safe.
//

#ifndef BGMApp__BGMDeviceControlSync
#define BGMApp__BGMDeviceControlSync

// Local Includes
#include "BGMAudioDevice.h"
#include "BGMDeviceControlsList.h"

// PublicUtility Includes
#include "CAHALAudioSystemObject.h"
#include "CAMutex.h"

// System Includes
#include <AudioToolbox/AudioServices.h>


#pragma clang assume_nonnull begin

class BGMDeviceControlSync
{

#pragma mark Construction/Destruction

public:
                        BGMDeviceControlSync(AudioObjectID inBGMDevice,
                                             AudioObjectID inOutputDevice,
                                             CAHALAudioSystemObject inAudioSystem
                                                     = CAHALAudioSystemObject());
                        ~BGMDeviceControlSync();
                        // Disallow copying
                        BGMDeviceControlSync(const BGMDeviceControlSync&) = delete;
                        BGMDeviceControlSync& operator=(const BGMDeviceControlSync&) = delete;

#ifdef __OBJC__
                        // Only intended as a convenience for Objective-C instance vars
                        BGMDeviceControlSync()
                        : BGMDeviceControlSync(kAudioObjectUnknown, kAudioObjectUnknown) { };
#endif

    /*!
     Begin synchronising BGMDevice's controls with the output device's.

     @throws BGM_DeviceNotSetException if BGMDevice isn't set.
     @throws CAException if the HAL or one of the devices returns an error when this function
                         registers for device property notifications or when it copies the current
                         values of the output device's controls to BGMDevice. This
                         BGMDeviceControlSync will remain inactive if this function throws.
     */
    void                Activate();
    /*! Stop synchronising BGMDevice's controls with the output device's. */
    void                Deactivate();

#pragma mark Accessors

    /*!
     Set the IDs of BGMDevice and the output device to synchronise with.

     @throws BGM_DeviceNotSetException if BGMDevice isn't set.
     @throws CAException if the HAL or one of the new devices returns an error while restarting
                         synchronisation. This BGMDeviceControlSync will be deactivated if this
                         function throws, but its devices will still be set.
     */
    void                SetDevices(AudioObjectID inBGMDevice, AudioObjectID inOutputDevice);

#pragma mark Listener Procs
    
private:
    /*! Receives HAL notifications about the BGMDevice properties this class listens to. */
    static OSStatus     BGMDeviceListenerProc(AudioObjectID inObjectID,
                                              UInt32 inNumberAddresses,
                                              const AudioObjectPropertyAddress* inAddresses,
                                              void* __nullable inClientData);
    
private:
    CAMutex             mMutex         { "Device Control Sync" };
    bool                mActive        = false;

    CAHALAudioSystemObject mAudioSystem;
    
    BGMAudioDevice      mBGMDevice     { (AudioObjectID)kAudioObjectUnknown };
    BGMAudioDevice      mOutputDevice  { (AudioObjectID)kAudioObjectUnknown };

    BGMDeviceControlsList mBGMDeviceControlsList;
    
};

#pragma clang assume_nonnull end

#endif /* BGMApp__BGMDeviceControlSync */



================================================
FILE: BGMApp/BGMApp/BGMDeviceControlsList.cpp
================================================
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.

//
//  BGMDeviceControlsList.cpp
//  BGMApp
//
//  Copyright © 2017 Kyle Neideck
//

// Self Include
#include "BGMDeviceControlsList.h"

// Local Includes
#include "BGM_Types.h"
#include "BGM_Utils.h"

// PublicUtility Includes
#include "CAPropertyAddress.h"
#include "CACFArray.h"


#pragma clang assume_nonnull begin

static const SInt64 kToggleDeviceInitialDelay = 50 * NSEC_PER_MSEC;
static const SInt64 kToggleDeviceBackDelay    = 500 * NSEC_PER_MSEC;
static const SInt64 kDisableNullDeviceDelay   = 500 * NSEC_PER_MSEC;
static const SInt64 kDisableNullDeviceTimeout = 5000 * NSEC_PER_MSEC;

#pragma mark Construction/Destruction

BGMDeviceControlsList::BGMDeviceControlsList(AudioObjectID inBGMDevice,
                                             CAHALAudioSystemObject inAudioSystem)
:
    mBGMDevice(inBGMDevice),
    mAudioSystem(inAudioSystem)
{
    BGMAssert((mBGMDevice.IsBGMDevice() || mBGMDevice.GetObjectID() == kAudioObjectUnknown),
              "BGMDeviceControlsList::BGMDeviceControlsList: Given device is not BGMDevice");

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
    mCanToggleDeviceOnSystem = (&dispatch_block_wait &&
                                &dispatch_block_cancel &&
                                &dispatch_block_testcancel &&
                                &dispatch_queue_attr_make_with_qos_class);
#pragma clang diagnostic pop
}

BGMDeviceControlsList::~BGMDeviceControlsList()
{
    CAMutex::Locker locker(mMutex);

    if(!mDeviceTogglingInitialised)
    {
        return;
    }

    if(mListenerQueue && mListenerBlock)
    {
        BGMLogAndSwallowExceptions("BGMDeviceControlsList::~BGMDeviceControlsList", ([&] {
            mAudioSystem.RemovePropertyListenerBlock(
                    CAPropertyAddress(kAudioHardwarePropertyDevices),
                    mListenerQueue,
                    mListenerBlock);
        }));
    }

    // If we're in the middle of toggling the default device, block until we've finished.
    if(mDisableNullDeviceBlock && mDeviceToggleState != ToggleState::NotToggling)
    {
        DebugMsg("BGMDeviceControlsList::~BGMDeviceControlsList: Waiting for device toggle");

        // Copy the reference so we can unlock the mutex and allow any remaining blocks to run.
        dispatch_block_t disableNullDeviceBlock = mDisableNullDeviceBlock;

        CAMutex::Unlocker unlocker(mMutex);

        // Note that if mDisableNullDeviceBlock is currently running this will return after it
        // finishes and if it's already run this will return immediately. So we don't have to
        // worry about ending up waiting for mDisableNullDeviceBlock when it isn't queued.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        long timedOut = dispatch_block_wait(disableNullDeviceBlock, kDisableNullDeviceTimeout);
#pragma clang diagnostic pop

        if(timedOut)
        {
            LogWarning("BGMDeviceControlsList::~BGMDeviceControlsList: Device toggle timed out");
        }
    }

    mDeviceToggleState = ToggleState::NotToggling;

    DestroyBlock(mDeviceToggleBlock);
    DestroyBlock(mDeviceToggleBackBlock);
    DestroyBlock(mDisableNullDeviceBlock);

    if(mListenerBlock)
    {
        Block_release(mListenerBlock);
    }

    if(mListenerQueue)
    {
        dispatch_release(BGM_Utils::NN(mListenerQueue));
    }
}

#pragma mark Accessors

void    BGMDeviceControlsList::SetBGMDevice(AudioObjectID inBGMDeviceID)
{
    CAMutex::Locker locker(mMutex);

    mBGMDevice = inBGMDeviceID;

    BGMAssert(mBGMDevice.IsBGMDevice(),
              "BGMDeviceControlsList::SetBGMDevice: Given device is not BGMDevice");
}

#pragma mark Update Controls List

bool    BGMDeviceControlsList::MatchControlsListOf(AudioObjectID inDeviceID)
{
    CAMutex::Locker locker(mMutex);

    if(!mBGMDevice.IsBGMDevice())
    {
        LogWarning("BGMDeviceControlsList::MatchControlsListOf: BGMDevice ID not set");
        return false;
    }

    // If the output device doesn't have a control that BGMDevice does, disable it on BGMDevice so
    // the system's audio UI isn't confusing.

    // No need to change input controls.
    AudioObjectPropertyScope inScope = kAudioObjectPropertyScopeOutput;

    // Check which of BGMDevice's controls are currently enabled. We need to know whether we're
    // actually enabling/disabling any controls so we know whether we need to call
    // PropagateControlListChange afterward.
    CFTypeRef __nullable enabledControlsRef =
        mBGMDevice.GetPropertyData_CFType(kBGMEnabledOutputControlsAddress);

    ThrowIf(!enabledControlsRef || (CFGetTypeID(enabledControlsRef) != CFArrayGetTypeID()),
            CAException(kAudioHardwareIllegalOperationError),
            "BGMDeviceControlsList::MatchControlsListOf: Expected a CFArray for "
            "kAudioDeviceCustomPropertyEnabledOutputControls");

    CACFArray enabledControls(static_cast<CFArrayRef>(enabledControlsRef), true);

    BGMAssert(enabledControls.GetNumberItems() == 2,
              "BGMDeviceControlsList::MatchControlsListOf: Expected 2 array elements for "
              "kAudioDeviceCustomPropertyEnabledOutputControls");

    bool volumeEnabled;
    bool didGetBool = enabledControls.GetBool(kBGMEnabledOutputControlsIndex_Volume, volumeEnabled);
    ThrowIf(!didGetBool,
            CAException(kAudioHardwareIllegalOperationError),
            "BGMDeviceControlsList::MatchControlsListOf: Expected volume element of "
            "kAudioDeviceCustomPropertyEnabledOutputControls to be a CFBoolean");

    bool muteEnabled;
    didGetBool = enabledControls.GetBool(kBGMEnabledOutputControlsIndex_Mute, muteEnabled);
    ThrowIf(!didGetBool,
            CAException(kAudioHardwareIllegalOperationError),
            "BGMDeviceControlsList::MatchControlsListOf: Expected mute element of "
            "kAudioDeviceCustomPropertyEnabledOutputControls to be a CFBoolean");

    DebugMsg("BGMDeviceControlsList::MatchControlsListOf: BGMDevice has volume %s, mute %s",
             (volumeEnabled ? "enabled" : "disabled"),
             (muteEnabled ? "enabled" : "disabled"));

    // Check which controls the other device has.
    BGMAudioDevice device(inDeviceID);
    bool hasMute = device.HasSettableMasterMute(inScope);

    bool hasVolume =
        device.HasSettableMasterVolume(inScope) || device.HasSettableVirtualMasterVolume(inScope);

    if(!hasVolume)
    {
        // Check for per-channel volume controls.
        UInt32 numChannels =
            device.GetTotalNumberChannels(inScope == kAudioObjectPropertyScopeInput);

        for(UInt32 channel = 1; channel <= numChannels; channel++)
        {
            BGMLogAndSwallowExceptionsMsg("BGMDeviceControlsList::MatchControlsListOf",
                                          "Checking for channel volume controls",
                                          ([&] {
                hasVolume =
                    (device.HasVolumeControl(inScope, channel)
                            && device.VolumeControlIsSettable(inScope, channel));
            }));

            if(hasVolume)
            {
                break;
            }
        }
    }

    // Tell BGMDevice to enable/disable its controls to match the output device.
    bool deviceUpdated = false;

    CACFArray newEnabledControls;
    newEnabledControls.SetCFMutableArrayFromCopy(enabledControls.GetCFArray());

    // Update volume.
    if(volumeEnabled != hasVolume)
    {
        DebugMsg("BGMDeviceControlsList::MatchControlsListOf: %s BGMDevice volume control.",
                 hasVolume ? "Enabling" : "Disabling");

        newEnabledControls.SetBool(kBGMEnabledOutputControlsIndex_Volume, hasVolume);
        deviceUpdated = true;
    }

    // Update mute.
    if(muteEnabled != hasMute)
    {
        DebugMsg("BGMDeviceControlsList::MatchControlsListOf: %s BGMDevice mute control.",
                 hasMute ? "Enabling" : "Disabling");

        newEnabledControls.SetBool(kBGMEnabledOutputControlsIndex_Mute, hasMute);
        deviceUpdated = true;
    }

    if(deviceUpdated)
    {
        mBGMDevice.SetPropertyData_CFType(kBGMEnabledOutputControlsAddress,
                                          newEnabledControls.GetCFMutableArray());
    }

    return deviceUpdated;
}

void    BGMDeviceControlsList::PropagateControlListChange()
{
    CAMutex::Locker locker(mMutex);

    if((mBGMDevice == kAudioObjectUnknown) || !mCanToggleDeviceOnSystem)
    {
        return;
    }

    InitDeviceToggling();

    // Leave the default device alone if the user has changed it since launching BGMApp.
    bool bgmDeviceIsDefault = true;

    BGMLogAndSwallowExceptions("BGMDeviceControlsList::PropagateControlListChange", ([&] {
        bgmDeviceIsDefault =
            (mBGMDevice.GetObjectID() == mAudioSystem.GetDefaultAudioDevice(false, false));
    }));

    if(bgmDeviceIsDefault)
    {
        mDeviceToggleState = ToggleState::SettingNullDeviceAsDefault;

        // We'll get a notification from the HAL after the Null Device is enabled. Then we can
        // temporarily make it the default device, which gets other programs to notice that
        // BGMDevice's controls have changed.
        try
        {
            CAMutex::Unlocker unlocker(mMutex);
            SetNullDeviceEnabled(true);
        }
        catch (...)
        {
            mDeviceToggleState = ToggleState::NotToggling;
            LogError("BGMDeviceControlsList::PropagateControlListChange: Could not enable the Null "
                     "Device");
            throw;
        }
    }
}

#pragma mark Implementation

void    BGMDeviceControlsList::InitDeviceToggling()
{
    CAMutex::Locker locker(mMutex);

    if(mDeviceTogglingInitialised || !mCanToggleDeviceOnSystem)
    {
        return;
    }

    BGMAssert(mBGMDevice.IsBGMDevice(),
              "BGMDeviceControlsList::InitDeviceToggling: mBGMDevice device is not set to "
              "BGMDevice's ID");

    // Register a listener to find out when the Null Device becomes available/unavailable. See
    // ToggleDefaultDevice.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
    dispatch_queue_attr_t attr =
        dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
#pragma clang diagnostic pop
    mListenerQueue = dispatch_queue_create("com.bearisdriving.BGM.BGMDeviceControlsList", attr);

    auto listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {
        // Ignore the notification if we're not toggling the default device, which would just mean
        // the default device has been changed for an unrelated reason.
        if(mDeviceToggleState == ToggleState::NotToggling)
        {
            return;
        }

        for(int i = 0; i < inNumberAddresses; i++)
        {
            switch(inAddresses[i].mSelector)
            {
                case kAudioHardwarePropertyDevices:
                    {
                        CAMutex::Locker innerLocker(mMutex);

                        DebugMsg("BGMDeviceControlsList::InitDeviceToggling: Got "
                                 "kAudioHardwarePropertyDevices");

                        // Cancel the previous block in case it hasn't run yet.
                        DestroyBlock(mDeviceToggleBlock);

                        mDeviceToggleBlock = CreateDeviceToggleBlock();

                        // Changing the default device too quickly after enabling the Null Device
                        // seems to cause problems with some programs. Not sure why.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
                        if(mDeviceToggleBlock)
                        {
                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                                         kToggleDeviceInitialDelay),
                                           dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
                                           BGM_Utils::NN(mDeviceToggleBlock));
                        }
#pragma clang diagnostic pop
                    }
                    break;

                default:
                    break;
            }
        }
    };

    mListenerBlock = Block_copy(listenerBlock);

    BGMLogAndSwallowExceptions("BGMDeviceControlsList::InitDeviceToggling", [&] {
        mAudioSystem.AddPropertyListenerBlock(CAPropertyAddress(kAudioHardwarePropertyDevices),
                                              mListenerQueue,
                    
Download .txt
gitextract_t8bnn9v3/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── other.md
│   └── workflows/
│       └── build-test-release.yml
├── .gitignore
├── BGM.xcworkspace/
│   └── contents.xcworkspacedata
├── BGMApp/
│   ├── BGMApp/
│   │   ├── BGMApp-Debug.entitlements
│   │   ├── BGMApp.entitlements
│   │   ├── BGMAppDelegate.h
│   │   ├── BGMAppDelegate.mm
│   │   ├── BGMAppVolumes.h
│   │   ├── BGMAppVolumes.m
│   │   ├── BGMAppVolumesController.h
│   │   ├── BGMAppVolumesController.mm
│   │   ├── BGMAppWatcher.h
│   │   ├── BGMAppWatcher.m
│   │   ├── BGMAudioDevice.cpp
│   │   ├── BGMAudioDevice.h
│   │   ├── BGMAudioDeviceManager.h
│   │   ├── BGMAudioDeviceManager.mm
│   │   ├── BGMAutoPauseMenuItem.h
│   │   ├── BGMAutoPauseMenuItem.m
│   │   ├── BGMAutoPauseMusic.h
│   │   ├── BGMAutoPauseMusic.mm
│   │   ├── BGMBackgroundMusicDevice.cpp
│   │   ├── BGMBackgroundMusicDevice.h
│   │   ├── BGMDebugLoggingMenuItem.h
│   │   ├── BGMDebugLoggingMenuItem.m
│   │   ├── BGMDeviceControlSync.cpp
│   │   ├── BGMDeviceControlSync.h
│   │   ├── BGMDeviceControlsList.cpp
│   │   ├── BGMDeviceControlsList.h
│   │   ├── BGMOutputDeviceMenuSection.h
│   │   ├── BGMOutputDeviceMenuSection.mm
│   │   ├── BGMOutputVolumeMenuItem.h
│   │   ├── BGMOutputVolumeMenuItem.mm
│   │   ├── BGMPlayThrough.cpp
│   │   ├── BGMPlayThrough.h
│   │   ├── BGMPlayThroughRTLogger.cpp
│   │   ├── BGMPlayThroughRTLogger.h
│   │   ├── BGMPreferredOutputDevices.h
│   │   ├── BGMPreferredOutputDevices.mm
│   │   ├── BGMStatusBarItem.h
│   │   ├── BGMStatusBarItem.mm
│   │   ├── BGMSystemSoundsVolume.h
│   │   ├── BGMSystemSoundsVolume.mm
│   │   ├── BGMTermination.h
│   │   ├── BGMTermination.mm
│   │   ├── BGMUserDefaults.h
│   │   ├── BGMUserDefaults.m
│   │   ├── BGMVolumeChangeListener.cpp
│   │   ├── BGMVolumeChangeListener.h
│   │   ├── BGMXPCListener.h
│   │   ├── BGMXPCListener.mm
│   │   ├── Base.lproj/
│   │   │   └── MainMenu.xib
│   │   ├── Images.xcassets/
│   │   │   ├── AirPlayIcon.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── FermataIcon.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume0.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume1.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Volume2.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── Volume3.imageset/
│   │   │       └── Contents.json
│   │   ├── Info.plist
│   │   ├── LICENSE
│   │   ├── Music Players/
│   │   │   ├── BGMDecibel.h
│   │   │   ├── BGMDecibel.m
│   │   │   ├── BGMGooglePlayMusicDesktopPlayer.h
│   │   │   ├── BGMGooglePlayMusicDesktopPlayer.m
│   │   │   ├── BGMGooglePlayMusicDesktopPlayerConnection.h
│   │   │   ├── BGMGooglePlayMusicDesktopPlayerConnection.m
│   │   │   ├── BGMHermes.h
│   │   │   ├── BGMHermes.m
│   │   │   ├── BGMMusic.h
│   │   │   ├── BGMMusic.m
│   │   │   ├── BGMMusicPlayer.h
│   │   │   ├── BGMMusicPlayer.m
│   │   │   ├── BGMMusicPlayers.h
│   │   │   ├── BGMMusicPlayers.mm
│   │   │   ├── BGMScriptingBridge.h
│   │   │   ├── BGMScriptingBridge.m
│   │   │   ├── BGMSpotify.h
│   │   │   ├── BGMSpotify.m
│   │   │   ├── BGMSwinsian.h
│   │   │   ├── BGMSwinsian.m
│   │   │   ├── BGMVLC.h
│   │   │   ├── BGMVLC.m
│   │   │   ├── BGMVOX.h
│   │   │   ├── BGMVOX.m
│   │   │   ├── BGMiTunes.h
│   │   │   ├── BGMiTunes.m
│   │   │   ├── Decibel.h
│   │   │   ├── GooglePlayMusicDesktopPlayer.js
│   │   │   ├── Hermes.h
│   │   │   ├── Music.h
│   │   │   ├── Spotify.h
│   │   │   ├── Swinsian.h
│   │   │   ├── VLC.h
│   │   │   ├── VOX.h
│   │   │   └── iTunes.h
│   │   ├── Preferences/
│   │   │   ├── BGMAboutPanel.h
│   │   │   ├── BGMAboutPanel.m
│   │   │   ├── BGMAutoPauseMusicPrefs.h
│   │   │   ├── BGMAutoPauseMusicPrefs.mm
│   │   │   ├── BGMPreferencesMenu.h
│   │   │   └── BGMPreferencesMenu.mm
│   │   ├── Scripting/
│   │   │   ├── BGMASApplication.h
│   │   │   ├── BGMASApplication.m
│   │   │   ├── BGMASOutputDevice.h
│   │   │   ├── BGMASOutputDevice.mm
│   │   │   ├── BGMApp.sdef
│   │   │   ├── BGMAppDelegate+AppleScript.h
│   │   │   └── BGMAppDelegate+AppleScript.mm
│   │   ├── SystemPreferences.h
│   │   ├── _uninstall-non-interactive.sh
│   │   └── main.m
│   ├── BGMApp.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           ├── BGMXPCHelper.xcscheme
│   │           └── Background Music.xcscheme
│   ├── BGMAppTests/
│   │   ├── UITests/
│   │   │   ├── BGMApp.h
│   │   │   ├── BGMAppUITests-Info.plist
│   │   │   ├── BGMAppUITests.mm
│   │   │   └── skip-ui-tests.py
│   │   └── UnitTests/
│   │       ├── BGMAppUnitTests-Info.plist
│   │       ├── BGMMusicPlayersUnitTests.mm
│   │       ├── BGMPlayThroughRTLoggerTests.mm
│   │       ├── BGMPlayThroughTests.mm
│   │       └── Mocks/
│   │           ├── MockAudioDevice.cpp
│   │           ├── MockAudioDevice.h
│   │           ├── MockAudioObject.cpp
│   │           ├── MockAudioObject.h
│   │           ├── MockAudioObjects.cpp
│   │           ├── MockAudioObjects.h
│   │           ├── Mock_CAHALAudioDevice.cpp
│   │           ├── Mock_CAHALAudioObject.cpp
│   │           └── Mock_CAHALAudioSystemObject.cpp
│   ├── BGMThreadSafetyAnalysis.h
│   ├── BGMXPCHelper/
│   │   ├── BGMXPCHelperService.h
│   │   ├── BGMXPCHelperService.mm
│   │   ├── BGMXPCListenerDelegate.h
│   │   ├── BGMXPCListenerDelegate.m
│   │   ├── Info.plist
│   │   ├── com.bearisdriving.BGM.XPCHelper.plist.template
│   │   ├── main.m
│   │   ├── post_install.sh
│   │   └── safe_install_dir.sh
│   ├── BGMXPCHelperTests/
│   │   ├── BGMXPCHelperTests-Info.plist
│   │   └── BGMXPCHelperTests.m
│   ├── OptimizationProfiles/
│   │   └── BGMApp.profdata
│   └── PublicUtility/
│       ├── BGMDebugLogging.c
│       ├── BGMDebugLogging.h
│       ├── CAAtomic.h
│       ├── CAAutoDisposer.h
│       ├── CABitOperations.h
│       ├── CACFArray.cpp
│       ├── CACFArray.h
│       ├── CACFDictionary.cpp
│       ├── CACFDictionary.h
│       ├── CACFNumber.cpp
│       ├── CACFNumber.h
│       ├── CACFString.cpp
│       ├── CACFString.h
│       ├── CADebugMacros.cpp
│       ├── CADebugMacros.h
│       ├── CADebugPrintf.cpp
│       ├── CADebugPrintf.h
│       ├── CADebugger.cpp
│       ├── CADebugger.h
│       ├── CAException.h
│       ├── CAHALAudioDevice.cpp
│       ├── CAHALAudioDevice.h
│       ├── CAHALAudioObject.cpp
│       ├── CAHALAudioObject.h
│       ├── CAHALAudioStream.cpp
│       ├── CAHALAudioStream.h
│       ├── CAHALAudioSystemObject.cpp
│       ├── CAHALAudioSystemObject.h
│       ├── CAHostTimeBase.cpp
│       ├── CAHostTimeBase.h
│       ├── CAMutex.cpp
│       ├── CAMutex.h
│       ├── CAPThread.cpp
│       ├── CAPThread.h
│       ├── CAPropertyAddress.h
│       ├── CARingBuffer.cpp
│       └── CARingBuffer.h
├── BGMDriver/
│   ├── BGMDriver/
│   │   ├── BGM_AbstractDevice.cpp
│   │   ├── BGM_AbstractDevice.h
│   │   ├── BGM_AudibleState.cpp
│   │   ├── BGM_AudibleState.h
│   │   ├── BGM_Control.cpp
│   │   ├── BGM_Control.h
│   │   ├── BGM_Device.cpp
│   │   ├── BGM_Device.h
│   │   ├── BGM_MuteControl.cpp
│   │   ├── BGM_MuteControl.h
│   │   ├── BGM_NullDevice.cpp
│   │   ├── BGM_NullDevice.h
│   │   ├── BGM_Object.cpp
│   │   ├── BGM_Object.h
│   │   ├── BGM_PlugIn.cpp
│   │   ├── BGM_PlugIn.h
│   │   ├── BGM_PlugInInterface.cpp
│   │   ├── BGM_Stream.cpp
│   │   ├── BGM_Stream.h
│   │   ├── BGM_TaskQueue.cpp
│   │   ├── BGM_TaskQueue.h
│   │   ├── BGM_VolumeControl.cpp
│   │   ├── BGM_VolumeControl.h
│   │   ├── BGM_WrappedAudioEngine.cpp
│   │   ├── BGM_WrappedAudioEngine.h
│   │   ├── BGM_XPCHelper.h
│   │   ├── BGM_XPCHelper.m
│   │   ├── DeviceClients/
│   │   │   ├── BGM_Client.cpp
│   │   │   ├── BGM_Client.h
│   │   │   ├── BGM_ClientMap.cpp
│   │   │   ├── BGM_ClientMap.h
│   │   │   ├── BGM_ClientTasks.h
│   │   │   ├── BGM_Clients.cpp
│   │   │   └── BGM_Clients.h
│   │   ├── DeviceIcon.icns
│   │   ├── Info.plist
│   │   └── quick_install.sh
│   ├── BGMDriver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           ├── Background Music Device.xcscheme
│   │           └── PublicUtility.xcscheme
│   ├── BGMDriverTests/
│   │   ├── BGM_ClientMapTests.mm
│   │   ├── BGM_ClientsTests.mm
│   │   ├── BGM_DeviceTests.mm
│   │   └── Info.plist
│   └── PublicUtility/
│       ├── CAAtomic.h
│       ├── CAAtomicStack.h
│       ├── CAAutoDisposer.h
│       ├── CABitOperations.h
│       ├── CACFArray.cpp
│       ├── CACFArray.h
│       ├── CACFDictionary.cpp
│       ├── CACFDictionary.h
│       ├── CACFNumber.cpp
│       ├── CACFNumber.h
│       ├── CACFString.cpp
│       ├── CACFString.h
│       ├── CADebugMacros.cpp
│       ├── CADebugMacros.h
│       ├── CADebugPrintf.cpp
│       ├── CADebugPrintf.h
│       ├── CADebugger.cpp
│       ├── CADebugger.h
│       ├── CADispatchQueue.cpp
│       ├── CADispatchQueue.h
│       ├── CAException.h
│       ├── CAHostTimeBase.cpp
│       ├── CAHostTimeBase.h
│       ├── CAMutex.cpp
│       ├── CAMutex.h
│       ├── CAPThread.cpp
│       ├── CAPThread.h
│       ├── CAPropertyAddress.h
│       ├── CARingBuffer.cpp
│       ├── CARingBuffer.h
│       ├── CAVolumeCurve.cpp
│       └── CAVolumeCurve.h
├── CONTRIBUTING.md
├── DEVELOPING.md
├── Images/
│   ├── FermataIcon.tex
│   ├── VolumeIcons.tex
│   ├── generate_icon_pngs.sh
│   └── iconizer.sh
├── LICENSE
├── LICENSE-Apple-Sample-Code
├── MANUAL-INSTALL.md
├── MANUAL-UNINSTALL.md
├── README.md
├── SharedSource/
│   ├── BGMXPCProtocols.h
│   ├── BGM_TestUtils.h
│   ├── BGM_Types.h
│   ├── BGM_Utils.cpp
│   ├── BGM_Utils.h
│   └── Scripts/
│       └── set-version.sh
├── TODO.md
├── build_and_install.sh
├── package.sh
├── pkg/
│   ├── Distribution.xml.template
│   ├── ListInputDevices.swift
│   ├── README.md
│   ├── pkgbuild.plist
│   ├── postinstall
│   └── preinstall
└── uninstall.sh
Download .txt
SYMBOL INDEX (726 symbols across 123 files)

FILE: BGMApp/BGMApp/BGMAppVolumesController.h
  type BGMAppVolumeAndPan (line 33) | typedef struct BGMAppVolumeAndPan {

FILE: BGMApp/BGMApp/BGMAudioDevice.cpp
  function OSStatus (line 360) | OSStatus    BGMAudioDevice::AHSGetPropertyData(AudioObjectID inObjectID,
  function OSStatus (line 379) | OSStatus    BGMAudioDevice::AHSSetPropertyData(AudioObjectID inObjectID,

FILE: BGMApp/BGMApp/BGMAudioDevice.h
  function class (line 31) | class BGMAudioDevice

FILE: BGMApp/BGMApp/BGMBackgroundMusicDevice.cpp
  function CFArrayRef (line 119) | CFArrayRef BGMBackgroundMusicDevice::GetAppVolumes() const
  function BGMDeviceAudibleState (line 270) | BGMDeviceAudibleState BGMBackgroundMusicDevice::GetAudibleState() const
  function pid_t (line 297) | pid_t BGMBackgroundMusicDevice::GetMusicPlayerProcessID() const
  function CFStringRef (line 322) | CFStringRef BGMBackgroundMusicDevice::GetMusicPlayerBundleID() const

FILE: BGMApp/BGMApp/BGMBackgroundMusicDevice.h
  function class (line 52) | class BGMBackgroundMusicDevice

FILE: BGMApp/BGMApp/BGMDeviceControlSync.cpp
  function OSStatus (line 172) | OSStatus    BGMDeviceControlSync::BGMDeviceListenerProc(AudioObjectID in...

FILE: BGMApp/BGMApp/BGMDeviceControlSync.h
  function BGMAudioDevice (line 112) | BGMAudioDevice      mOutputDevice  { (AudioObjectID)kAudioObjectUnknown };

FILE: BGMApp/BGMApp/BGMDeviceControlsList.cpp
  function dispatch_block_t (line 432) | dispatch_block_t __nullable BGMDeviceControlsList::CreateDeviceToggleBlo...
  function dispatch_block_t (line 458) | dispatch_block_t __nullable BGMDeviceControlsList::CreateDeviceToggleBac...
  function dispatch_block_t (line 505) | dispatch_block_t __nullable BGMDeviceControlsList::CreateDisableNullDevi...

FILE: BGMApp/BGMApp/BGMDeviceControlsList.h
  type ToggleState (line 119) | enum ToggleState

FILE: BGMApp/BGMApp/BGMPlayThrough.cpp
  function CACatch (line 165) | CACatch
  function CACatch (line 194) | CACatch
  function OSStatus (line 518) | OSStatus    BGMPlayThrough::WaitForOutputDeviceToStart() noexcept
  function OSStatus (line 650) | OSStatus    BGMPlayThrough::Stop()
  function OSStatus (line 813) | OSStatus    BGMPlayThrough::BGMDeviceListenerProc(AudioObjectID inObjectID,
  function OSStatus (line 946) | OSStatus    BGMPlayThrough::InputDeviceIOProc(AudioObjectID           in...
  function OSStatus (line 1010) | OSStatus    BGMPlayThrough::OutputDeviceIOProc(AudioObjectID           i...

FILE: BGMApp/BGMApp/BGMPlayThrough.h
  type class (line 161) | enum class
  function UInt64 (line 213) | UInt64              mLastNotifiedIOStoppedOnBGMDevice { 0 }
  function UInt64 (line 219) | UInt64              mToldOutputDeviceToStartAt { 0 }
  function Float64 (line 229) | Float64             mInToOutSampleOffset { 0.0 };

FILE: BGMApp/BGMApp/BGMPlayThroughRTLogger.cpp
  function semaphore_t (line 67) | semaphore_t BGMPlayThroughRTLogger::CreateSemaphore()

FILE: BGMApp/BGMApp/BGMPlayThroughRTLogger.h
  function LogExceptionStoppingIOProc (line 83) | void                    LogExceptionStoppingIOProc(const char* inCallerN...
  function LogExceptionStoppingIOProc (line 88) | void                    LogExceptionStoppingIOProc(const char* inCallerN...
  function LogIfRingBufferError_Fetch (line 105) | void                    LogIfRingBufferError_Fetch(CARingBufferError inE...
  function LogIfRingBufferError_Store (line 110) | void                    LogIfRingBufferError_Store(CARingBufferError inE...
  function mNumDebugMessagesLogged (line 216) | int                     mNumDebugMessagesLogged { 0 }
  function mNumWarningMessagesLogged (line 217) | int                     mNumWarningMessagesLogged { 0 }
  function mNumErrorMessagesLogged (line 218) | int                     mNumErrorMessagesLogged { 0 }

FILE: BGMApp/BGMApp/BGMTermination.h
  function class (line 46) | class BGMTermination

FILE: BGMApp/BGMApp/BGMVolumeChangeListener.h
  function class (line 38) | class BGMVolumeChangeListener

FILE: BGMApp/BGMApp/Music Players/Decibel.h
  type DecibelSaveOptions (line 14) | enum DecibelSaveOptions {
  type DecibelSaveOptions (line 19) | typedef enum DecibelSaveOptions DecibelSaveOptions;
  type DecibelPrintingErrorHandling (line 21) | enum DecibelPrintingErrorHandling {
  type DecibelPrintingErrorHandling (line 25) | typedef enum DecibelPrintingErrorHandling DecibelPrintingErrorHandling;
  type DecibelShuffleMode (line 27) | enum DecibelShuffleMode {
  type DecibelShuffleMode (line 33) | typedef enum DecibelShuffleMode DecibelShuffleMode;
  type DecibelRepeatMode (line 35) | enum DecibelRepeatMode {
  type DecibelRepeatMode (line 42) | typedef enum DecibelRepeatMode DecibelRepeatMode;

FILE: BGMApp/BGMApp/Music Players/Hermes.h
  type HermesPlayerStates (line 15) | enum HermesPlayerStates {
  type HermesPlayerStates (line 20) | typedef enum HermesPlayerStates HermesPlayerStates;

FILE: BGMApp/BGMApp/Music Players/Music.h
  type MusicEKnd (line 14) | enum MusicEKnd {
  type MusicEKnd (line 19) | typedef enum MusicEKnd MusicEKnd;
  type MusicEnum (line 21) | enum MusicEnum {
  type MusicEnum (line 25) | typedef enum MusicEnum MusicEnum;
  type MusicEPlS (line 27) | enum MusicEPlS {
  type MusicEPlS (line 34) | typedef enum MusicEPlS MusicEPlS;
  type MusicERpt (line 36) | enum MusicERpt {
  type MusicERpt (line 41) | typedef enum MusicERpt MusicERpt;
  type MusicEShM (line 43) | enum MusicEShM {
  type MusicEShM (line 48) | typedef enum MusicEShM MusicEShM;
  type MusicESrc (line 50) | enum MusicESrc {
  type MusicESrc (line 60) | typedef enum MusicESrc MusicESrc;
  type MusicESrA (line 62) | enum MusicESrA {
  type MusicESrA (line 70) | typedef enum MusicESrA MusicESrA;
  type MusicESpK (line 72) | enum MusicESpK {
  type MusicESpK (line 80) | typedef enum MusicESpK MusicESpK;
  type MusicEMdK (line 82) | enum MusicEMdK {
  type MusicEMdK (line 87) | typedef enum MusicEMdK MusicEMdK;
  type MusicERtK (line 89) | enum MusicERtK {
  type MusicERtK (line 93) | typedef enum MusicERtK MusicERtK;
  type MusicEAPD (line 95) | enum MusicEAPD {
  type MusicEAPD (line 104) | typedef enum MusicEAPD MusicEAPD;
  type MusicEClS (line 106) | enum MusicEClS {
  type MusicEClS (line 119) | typedef enum MusicEClS MusicEClS;

FILE: BGMApp/BGMApp/Music Players/Spotify.h
  type SpotifyEPlS (line 14) | enum SpotifyEPlS {
  type SpotifyEPlS (line 19) | typedef enum SpotifyEPlS SpotifyEPlS;

FILE: BGMApp/BGMApp/Music Players/Swinsian.h
  type SwinsianSaveOptions (line 14) | enum SwinsianSaveOptions {
  type SwinsianSaveOptions (line 19) | typedef enum SwinsianSaveOptions SwinsianSaveOptions;
  type SwinsianPlayerState (line 21) | enum SwinsianPlayerState {
  type SwinsianPlayerState (line 26) | typedef enum SwinsianPlayerState SwinsianPlayerState;

FILE: BGMApp/BGMApp/Music Players/VLC.h
  type VLCSavo (line 14) | enum VLCSavo {
  type VLCSavo (line 19) | typedef enum VLCSavo VLCSavo;
  type VLCEnum (line 21) | enum VLCEnum {
  type VLCEnum (line 25) | typedef enum VLCEnum VLCEnum;

FILE: BGMApp/BGMApp/Music Players/iTunes.h
  type iTunesEKnd (line 14) | enum iTunesEKnd {
  type iTunesEKnd (line 19) | typedef enum iTunesEKnd iTunesEKnd;
  type iTunesEnum (line 21) | enum iTunesEnum {
  type iTunesEnum (line 25) | typedef enum iTunesEnum iTunesEnum;
  type iTunesEPlS (line 27) | enum iTunesEPlS {
  type iTunesEPlS (line 34) | typedef enum iTunesEPlS iTunesEPlS;
  type iTunesERpt (line 36) | enum iTunesERpt {
  type iTunesERpt (line 41) | typedef enum iTunesERpt iTunesERpt;
  type iTunesEVSz (line 43) | enum iTunesEVSz {
  type iTunesEVSz (line 48) | typedef enum iTunesEVSz iTunesEVSz;
  type iTunesESrc (line 50) | enum iTunesESrc {
  type iTunesESrc (line 59) | typedef enum iTunesESrc iTunesESrc;
  type iTunesESrA (line 61) | enum iTunesESrA {
  type iTunesESrA (line 69) | typedef enum iTunesESrA iTunesESrA;
  type iTunesESpK (line 71) | enum iTunesESpK {
  type iTunesESpK (line 84) | typedef enum iTunesESpK iTunesESpK;
  type iTunesEVdK (line 86) | enum iTunesEVdK {
  type iTunesEVdK (line 93) | typedef enum iTunesEVdK iTunesEVdK;
  type iTunesERtK (line 95) | enum iTunesERtK {
  type iTunesERtK (line 99) | typedef enum iTunesERtK iTunesERtK;
  type iTunesEAPD (line 101) | enum iTunesEAPD {
  type iTunesEAPD (line 108) | typedef enum iTunesEAPD iTunesEAPD;

FILE: BGMApp/BGMApp/SystemPreferences.h
  type SystemPreferencesSavo (line 11) | enum SystemPreferencesSavo {
  type SystemPreferencesSavo (line 16) | typedef enum SystemPreferencesSavo SystemPreferencesSavo;
  type SystemPreferencesEnum (line 18) | enum SystemPreferencesEnum {
  type SystemPreferencesEnum (line 22) | typedef enum SystemPreferencesEnum SystemPreferencesEnum;

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp
  function CACFString (line 42) | CACFString MockAudioDevice::GetPlayerBundleID() const

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.h
  function class (line 40) | class MockAudioDevice

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp
  function AudioObjectID (line 33) | AudioObjectID MockAudioObject::GetObjectID() const

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.h
  function class (line 40) | class MockAudioObject

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.h
  function class (line 39) | class MockAudioObjects

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioDevice.cpp
  function UInt32 (line 65) | UInt32	CAHALAudioDevice::GetIOBufferSize() const
  function AudioDeviceIOProcID (line 80) | AudioDeviceIOProcID	CAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc i...
  function Float64 (line 89) | Float64	CAHALAudioDevice::GetNominalSampleRate() const
  function CFStringRef (line 99) | CFStringRef    CAHALAudioDevice::CopyDeviceUID() const
  function CFStringRef (line 112) | CFStringRef	CAHALAudioDevice::CopyModelUID() const
  function CFStringRef (line 117) | CFStringRef	CAHALAudioDevice::CopyConfigurationApplicationBundleID() const
  function CFURLRef (line 122) | CFURLRef	CAHALAudioDevice::CopyIconLocation() const
  function UInt32 (line 127) | UInt32	CAHALAudioDevice::GetTransportType() const
  function OSStatus (line 142) | OSStatus	CAHALAudioDevice::GetDevicePlugInStatus() const
  function pid_t (line 152) | pid_t	CAHALAudioDevice::GetHogModeOwner() const
  function UInt32 (line 202) | UInt32	CAHALAudioDevice::GetNumberRelatedAudioDevices() const
  function AudioObjectID (line 212) | AudioObjectID	CAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIn...
  function UInt32 (line 217) | UInt32	CAHALAudioDevice::GetNumberStreams(bool inIsInput) const
  function AudioObjectID (line 227) | AudioObjectID	CAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 ...
  function UInt32 (line 232) | UInt32	CAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const
  function UInt32 (line 252) | UInt32	CAHALAudioDevice::GetLatency(bool inIsInput) const
  function UInt32 (line 257) | UInt32	CAHALAudioDevice::GetSafetyOffset(bool inIsInput) const
  function UInt32 (line 267) | UInt32	CAHALAudioDevice::GetClockDomain() const
  function Float64 (line 272) | Float64	CAHALAudioDevice::GetActualSampleRate() const
  function UInt32 (line 277) | UInt32	CAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const
  function UInt32 (line 307) | UInt32	CAHALAudioDevice::GetMaximumVariableIOBufferSize() const
  function Float32 (line 347) | Float32	CAHALAudioDevice::GetIOCycleUsage() const
  function Float32 (line 382) | Float32	CAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropert...
  function Float32 (line 387) | Float32	CAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectProper...
  function Float32 (line 402) | Float32	CAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObj...
  function Float32 (line 407) | Float32	CAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObj...
  function Float32 (line 422) | Float32	CAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectProp...
  function Float32 (line 427) | Float32	CAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPro...
  function Float32 (line 442) | Float32	CAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(Audio...
  function Float32 (line 447) | Float32	CAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(Audio...
  function Float32 (line 502) | Float32	CAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertySc...
  function UInt32 (line 577) | UInt32	CAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope...
  function UInt32 (line 587) | UInt32	CAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectProper...
  function UInt32 (line 597) | UInt32	CAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectProper...
  function CFStringRef (line 602) | CFStringRef	CAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropert...
  function UInt32 (line 617) | UInt32	CAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectProperty...
  function UInt32 (line 627) | UInt32	CAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectP...
  function UInt32 (line 637) | UInt32	CAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectP...
  function CFStringRef (line 642) | CFStringRef	CAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPr...
  function UInt32 (line 657) | UInt32	CAHALAudioDevice::GetCurrentClockSourceID() const
  function UInt32 (line 667) | UInt32	CAHALAudioDevice::GetNumberAvailableClockSources() const
  function UInt32 (line 677) | UInt32	CAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) ...
  function CFStringRef (line 682) | CFStringRef	CAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const
  function UInt32 (line 687) | UInt32	CAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioObject.cpp
  function AudioObjectID (line 48) | AudioObjectID	CAHALAudioObject::GetObjectID() const
  function UInt32 (line 113) | UInt32	CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAd...
  function AudioClassID (line 144) | AudioClassID	CAHALAudioObject::GetClassID() const
  function AudioObjectID (line 149) | AudioObjectID	CAHALAudioObject::GetOwnerObjectID() const
  function CFStringRef (line 154) | CFStringRef	CAHALAudioObject::CopyOwningPlugInBundleID() const
  function CFStringRef (line 159) | CFStringRef	CAHALAudioObject::CopyName() const
  function CFStringRef (line 164) | CFStringRef	CAHALAudioObject::CopyManufacturer() const
  function CFStringRef (line 169) | CFStringRef	CAHALAudioObject::CopyNameForElement(AudioObjectPropertyScop...
  function CFStringRef (line 174) | CFStringRef	CAHALAudioObject::CopyCategoryNameForElement(AudioObjectProp...
  function CFStringRef (line 179) | CFStringRef	CAHALAudioObject::CopyNumberNameForElement(AudioObjectProper...
  function UInt32 (line 189) | UInt32	CAHALAudioObject::GetNumberOwnedObjects(AudioClassID inClass) const
  function AudioObjectID (line 199) | AudioObjectID	CAHALAudioObject::GetOwnedObjectByIndex(AudioClassID inCla...

FILE: BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioSystemObject.cpp
  function AudioObjectID (line 43) | AudioObjectID	CAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef i...
  function UInt32 (line 59) | UInt32	CAHALAudioSystemObject::GetNumberAudioDevices() const
  function AudioObjectID (line 69) | AudioObjectID	CAHALAudioSystemObject::GetAudioDeviceAtIndex(UInt32 inInd...
  function AudioObjectID (line 79) | AudioObjectID	CAHALAudioSystemObject::GetDefaultAudioDevice(bool inIsInp...
  function AudioObjectID (line 89) | AudioObjectID	CAHALAudioSystemObject::GetAudioPlugInForBundleID(CFString...

FILE: BGMApp/PublicUtility/BGMDebugLogging.c
  function BGMDebugLoggingIsEnabled (line 39) | int BGMDebugLoggingIsEnabled(void)
  function BGMSetDebugLoggingEnabled (line 44) | void BGMSetDebugLoggingEnabled(int inEnabled)

FILE: BGMApp/PublicUtility/CAAtomic.h
  function CAMemoryBarrier (line 71) | inline void CAMemoryBarrier()
  function SInt32 (line 80) | inline SInt32 CAAtomicAdd32Barrier(SInt32 theAmt, volatile SInt32* theVa...
  function SInt32 (line 93) | inline SInt32 CAAtomicOr32Barrier(UInt32 theMask, volatile UInt32* theVa...
  function SInt32 (line 107) | inline SInt32 CAAtomicAnd32Barrier(UInt32 theMask, volatile UInt32* theV...
  function CAAtomicCompareAndSwap32Barrier (line 121) | inline bool CAAtomicCompareAndSwap32Barrier(SInt32 oldValue, SInt32 newV...
  function SInt32 (line 135) | inline SInt32 CAAtomicIncrement32(volatile SInt32* theValue)
  function SInt32 (line 144) | inline SInt32 CAAtomicDecrement32(volatile SInt32* theValue)
  function SInt32 (line 153) | inline SInt32 CAAtomicIncrement32Barrier(volatile SInt32* theValue)
  function SInt32 (line 162) | inline SInt32 CAAtomicDecrement32Barrier(volatile SInt32* theValue)
  function CAAtomicTestAndClearBarrier (line 171) | inline bool CAAtomicTestAndClearBarrier(int bitToClear, void* theAddress)
  function CAAtomicTestAndClear (line 181) | inline bool CAAtomicTestAndClear(int bitToClear, void* theAddress)
  function CAAtomicTestAndSetBarrier (line 191) | inline bool CAAtomicTestAndSetBarrier(int bitToSet, void* theAddress)
  function CAAtomicAdd32Barrier (line 209) | inline int32_t CAAtomicAdd32Barrier(int32_t theAmt, volatile int32_t* th...
  function CAAtomicOr32Barrier (line 214) | inline int32_t CAAtomicOr32Barrier(uint32_t theMask, volatile uint32_t* ...
  function CAAtomicAnd32Barrier (line 219) | inline int32_t CAAtomicAnd32Barrier(uint32_t theMask, volatile uint32_t*...
  function CAAtomicCompareAndSwap32Barrier (line 224) | inline bool CAAtomicCompareAndSwap32Barrier(int32_t oldValue, int32_t ne...
  function CAAtomicIncrement32 (line 229) | inline int32_t CAAtomicIncrement32(volatile int32_t* theValue)
  function CAAtomicDecrement32 (line 234) | inline int32_t CAAtomicDecrement32(volatile int32_t* theValue)
  function CAAtomicIncrement32Barrier (line 239) | inline int32_t CAAtomicIncrement32Barrier(volatile int32_t* theValue)
  function CAAtomicDecrement32Barrier (line 244) | inline int32_t CAAtomicDecrement32Barrier(volatile int32_t* theValue)
  function CAAtomicCompareAndSwap64Barrier (line 254) | inline bool CAAtomicCompareAndSwap64Barrier( int64_t __oldValue, int64_t...
  function CAAtomicCompareAndSwapPtrBarrier (line 260) | inline bool CAAtomicCompareAndSwapPtrBarrier(void *__oldValue, void *__n...
  type CASpinLock (line 279) | typedef int32_t CASpinLock;
  function CASpinLockLock (line 288) | inline void    CASpinLockLock( volatile CASpinLock *__lock )
  function CASpinLockUnlock (line 298) | inline void    CASpinLockUnlock( volatile CASpinLock *__lock )
  function CASpinLockTry (line 307) | inline bool    CASpinLockTry( volatile CASpinLock *__lock )

FILE: BGMApp/PublicUtility/CAAutoDisposer.h
  function explicit (line 100) | explicit CAPtrRef(T* ptr) : ptr_(ptr) {}
  function explicit (line 113) | explicit CAAutoFree(T* ptr) : ptr_(ptr) {}
  function reallocBytes (line 149) | void reallocBytes(size_t numBytes)
  function reallocItems (line 154) | void reallocItems(size_t numItems)
  function explicit (line 253) | explicit CAAutoDelete(T* ptr) : ptr_(ptr) {}
  function explicit (line 354) | explicit CAAutoArrayDelete(T* ptr) : ptr_(ptr) {}
  function ptr_ (line 363) | CAAutoArrayDelete(size_t n) : ptr_(new T[n]) {}
  function alloc (line 367) | void alloc(size_t numItems)

FILE: BGMApp/PublicUtility/CABitOperations.h
  function UInt32 (line 60) | inline UInt32 IsPowerOfTwo(UInt32 x)
  function UInt32 (line 95) | inline UInt32 CountLeadingZeroesLong(UInt64 arg)
  function UInt32 (line 116) | inline UInt32 CountTrailingZeroes(UInt32 x)
  function UInt32 (line 122) | inline UInt32 CountLeadingOnes(UInt32 x)
  function UInt32 (line 128) | inline UInt32 CountTrailingOnes(UInt32 x)
  function UInt32 (line 134) | inline UInt32 NumBits(UInt32 x)
  function UInt32 (line 140) | inline UInt32 Log2Ceil(UInt32 x)
  function UInt32 (line 146) | inline UInt32 Log2Floor(UInt32 x)
  function UInt32 (line 152) | inline UInt32 NextPowerOfTwo(UInt32 x)
  function UInt32 (line 158) | inline UInt32 CountOnes(UInt32 x)
  function UInt32 (line 167) | inline UInt32 CountZeroes(UInt32 x)
  function UInt32 (line 173) | inline UInt32 LSBitPos(UInt32 x)
  function UInt32 (line 179) | inline UInt32 LSBit(UInt32 x)
  function UInt32 (line 185) | inline UInt32 MSBitPos(UInt32 x)
  function UInt32 (line 191) | inline UInt32 MSBit(UInt32 x)
  function UInt32 (line 197) | inline UInt32 DivInt(UInt32 numerator, UInt32 denominator)

FILE: BGMApp/PublicUtility/CACFArray.h
  function class (line 76) | class CACFArray
  function Release (line 94) | void				Release()																	{ if(mRelease && (mCFArray != NULL)) {...
  function ShouldRelease (line 103) | void				ShouldRelease(bool inRelease)												{ mRelease = inRelease; }
  function RemoveItem (line 120) | void				RemoveItem(const void* inItem)												{ UInt32 theIndex; if(...
  function RemoveItemAtIndex (line 122) | void				RemoveItemAtIndex(UInt32 inIndex)											{ if(CanModify()) { ...
  function Clear (line 123) | void				Clear()																		{ if(CanModify()) { CFArrayRemoveAllVal...
  function Sort (line 124) | void				Sort(CFComparatorFunction inCompareFunction)								{ if(CanModi...
  function SortNumbers (line 125) | void				SortNumbers()																{ Sort((CFComparatorFunction)CFNumb...
  function SortStrings (line 126) | void				SortStrings()																{ Sort((CFComparatorFunction)CFStri...

FILE: BGMApp/PublicUtility/CACFDictionary.cpp
  function UInt32 (line 68) | UInt32	CACFDictionary::Size () const

FILE: BGMApp/PublicUtility/CACFDictionary.h
  function class (line 72) | class CACFDictionary
  function Release (line 89) | void					Release()																{ if(mRelease && (mCFDictionary != NUL...
  function ShouldRelease (line 98) | void					ShouldRelease(bool inRelease)											{ mRelease = inRelease; }
  function CFMutableDictionaryRef (line 104) | CFMutableDictionaryRef	GetMutableDict()														{ return mCFDiction...
  function OSStatus (line 111) | OSStatus				GetDictIfMutable(CFMutableDictionaryRef& outDict) const					...
  function UInt32 (line 116) | UInt32					Size() const;
  function Clear (line 163) | void					Clear()																	{ if(CanModify()) { CFDictionaryRemoveA...
  function Show (line 165) | void					Show()																	{ CFShow(mCFDictionary); }

FILE: BGMApp/PublicUtility/CACFNumber.cpp
  function Float32 (line 57) | Float32	CACFNumber::GetFixed32() const
  function Float64 (line 71) | Float64	CACFNumber::GetFixed64() const

FILE: BGMApp/PublicUtility/CACFNumber.h
  function class (line 66) | class	CACFBoolean
  function Release (line 80) | void			Release() { if(mWillRelease && (mCFBoolean != NULL)) { CFRelease(...
  function DontAllowRelease (line 88) | void			DontAllowRelease() { mWillRelease = false; }
  function IsValid (line 89) | bool			IsValid() { return mCFBoolean != NULL; }
  function GetBoolean (line 96) | bool			GetBoolean() const { bool theAnswer = false; if(mCFBoolean != NUL...
  function class (line 105) | class	CACFNumber
  function Release (line 124) | void		Release() { if(mWillRelease && (mCFNumber != NULL)) { CFRelease(mC...
  function DontAllowRelease (line 132) | void		DontAllowRelease() { mWillRelease = false; }
  function SInt8 (line 140) | SInt8		GetSInt8() const { SInt8 theAnswer = 0; if(mCFNumber != NULL) { C...
  function UInt32 (line 142) | UInt32		GetUInt32() const { UInt32 theAnswer = 0; if(mCFNumber != NULL) ...
  function Float32 (line 144) | Float32		GetFixed32() const;

FILE: BGMApp/PublicUtility/CACFString.cpp
  function UInt32 (line 57) | UInt32	CACFString::GetStringByteLength(CFStringRef inCFString, CFStringE...

FILE: BGMApp/PublicUtility/CACFString.h
  function class (line 73) | class	CACFString
  function AssignWithoutRetain (line 87) | void				AssignWithoutRetain(CFStringRef inCFString) { if (inCFString != ...
  function Release (line 91) | void				Release() { if(mWillRelease && (mCFString != NULL)) { CFRelease(...
  function DontAllowRelease (line 99) | void				DontAllowRelease() { mWillRelease = false; }
  function IsEqualTo (line 101) | bool				IsEqualTo(CFStringRef inString) const { bool theAnswer = false; ...
  function StartsWith (line 102) | bool				StartsWith(CFStringRef inString) const { bool theAnswer = false;...
  function EndsWith (line 103) | bool				EndsWith(CFStringRef inString) const { bool theAnswer = false; i...
  function CFStringRef (line 109) | const CFStringRef*	GetPointerToStorage() const	{ return &mCFString; }
  function UInt32 (line 111) | UInt32				GetLength() const { UInt32 theAnswer = 0; if(mCFString != NULL...
  function UInt32 (line 112) | UInt32				GetByteLength(CFStringEncoding inEncoding = kCFStringEncodingU...
  function GetCString (line 113) | void				GetCString(char* outString, UInt32& ioStringSize, CFStringEncodi...
  function GetUnicodeString (line 114) | void				GetUnicodeString(UInt16* outString, UInt32& ioStringSize) const ...
  function SInt32 (line 115) | SInt32				GetAsInteger() { return GetAsInteger(mCFString); }
  function Float64 (line 116) | Float64				GetAsFloat64() { return GetAsFloat64(mCFString); }
  function UInt32 (line 118) | static UInt32		GetStringLength(CFStringRef inCFString)  { UInt32 theAnsw...
  function SInt32 (line 122) | static SInt32		GetAsInteger(CFStringRef inCFString) { SInt32 theAnswer =...
  function Float64 (line 123) | static Float64		GetAsFloat64(CFStringRef inCFString) { Float64 theAnswer...
  function class (line 152) | class	CACFMutableString
  function Release (line 168) | void				Release() { if(mWillRelease && (mCFMutableString != NULL)) { CFR...
  function DontAllowRelease (line 176) | void				DontAllowRelease() { mWillRelease = false; }
  function IsValid (line 177) | bool				IsValid() { return mCFMutableString != NULL; }
  function IsEqualTo (line 178) | bool				IsEqualTo(CFStringRef inString) const { bool theAnswer = false; ...
  function StartsWith (line 179) | bool				StartsWith(CFStringRef inString) const { bool theAnswer = false;...
  function EndsWith (line 180) | bool				EndsWith(CFStringRef inString) const { bool theAnswer = false; i...
  function Append (line 181) | void				Append(CFStringRef inString) { if(mCFMutableString != NULL) { CF...
  function UInt32 (line 187) | UInt32				GetLength() const { UInt32 theAnswer = 0; if(mCFMutableString ...
  function UInt32 (line 188) | UInt32				GetByteLength(CFStringEncoding inEncoding = kCFStringEncodingU...
  function GetCString (line 189) | void				GetCString(char* outString, UInt32& ioStringSize, CFStringEncodi...
  function GetUnicodeString (line 190) | void				GetUnicodeString(UInt16* outString, UInt32& ioStringSize) const ...
  function SInt32 (line 191) | SInt32				GetAsInteger() { return CACFString::GetAsInteger(mCFMutableStr...
  function Float64 (line 192) | Float64				GetAsFloat64() { return CACFString::GetAsFloat64(mCFMutableSt...

FILE: BGMApp/PublicUtility/CADebugMacros.cpp
  function DebugPrint (line 81) | void	DebugPrint(const char *fmt, ...)
  function LogError (line 90) | void	LogError(const char *fmt, ...)
  function vLogError (line 98) | void    vLogError(const char *fmt, va_list args)
  function LogWarning (line 113) | void	LogWarning(const char *fmt, ...)
  function vLogWarning (line 121) | void    vLogWarning(const char *fmt, va_list args)

FILE: BGMApp/PublicUtility/CADebugPrintf.cpp
  function CAWin32DebugPrintf (line 84) | int	CAWin32DebugPrintf(char* inFormat, ...)
  function OpenDebugPrintfSideFile (line 103) | void OpenDebugPrintfSideFile()

FILE: BGMApp/PublicUtility/CADebugger.cpp
  function CAIsDebuggerAttached (line 63) | bool CAIsDebuggerAttached(void)
  function CADebuggerStop (line 83) | void	CADebuggerStop(void)

FILE: BGMApp/PublicUtility/CAException.h
  function class (line 64) | class CAException

FILE: BGMApp/PublicUtility/CAHALAudioDevice.cpp
  function CFStringRef (line 80) | CFStringRef	CAHALAudioDevice::CopyDeviceUID() const
  function CFStringRef (line 92) | CFStringRef	CAHALAudioDevice::CopyModelUID() const
  function CFStringRef (line 98) | CFStringRef	CAHALAudioDevice::CopyConfigurationApplicationBundleID() const
  function CFURLRef (line 104) | CFURLRef	CAHALAudioDevice::CopyIconLocation() const
  function UInt32 (line 113) | UInt32	CAHALAudioDevice::GetTransportType() const
  function OSStatus (line 131) | OSStatus	CAHALAudioDevice::GetDevicePlugInStatus() const
  function pid_t (line 149) | pid_t	CAHALAudioDevice::GetHogModeOwner() const
  function UInt32 (line 236) | UInt32	CAHALAudioDevice::GetNumberRelatedAudioDevices() const
  function AudioObjectID (line 265) | AudioObjectID	CAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIn...
  function UInt32 (line 281) | UInt32	CAHALAudioDevice::GetNumberStreams(bool inIsInput) const
  function AudioObjectID (line 297) | AudioObjectID	CAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 ...
  function UInt32 (line 313) | UInt32	CAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const
  function UInt32 (line 365) | UInt32	CAHALAudioDevice::GetLatency(bool inIsInput) const
  function UInt32 (line 371) | UInt32	CAHALAudioDevice::GetSafetyOffset(bool inIsInput) const
  function UInt32 (line 383) | UInt32	CAHALAudioDevice::GetClockDomain() const
  function Float64 (line 389) | Float64	CAHALAudioDevice::GetActualSampleRate() const
  function Float64 (line 405) | Float64	CAHALAudioDevice::GetNominalSampleRate() const
  function UInt32 (line 420) | UInt32	CAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const
  function UInt32 (line 476) | UInt32	CAHALAudioDevice::GetIOBufferSize() const
  function UInt32 (line 494) | UInt32	CAHALAudioDevice::GetMaximumVariableIOBufferSize() const
  function AudioDeviceIOProcID (line 516) | AudioDeviceIOProcID	CAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc i...
  function Float32 (line 600) | Float32	CAHALAudioDevice::GetIOCycleUsage() const
  function Float32 (line 655) | Float32	CAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropert...
  function Float32 (line 664) | Float32	CAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectProper...
  function Float32 (line 685) | Float32	CAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObj...
  function Float32 (line 694) | Float32	CAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObj...
  function Float32 (line 715) | Float32	CAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectProp...
  function Float32 (line 724) | Float32	CAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPro...
  function Float32 (line 745) | Float32	CAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(Audio...
  function Float32 (line 754) | Float32	CAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(Audio...
  function Float32 (line 833) | Float32	CAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertySc...
  function UInt32 (line 944) | UInt32	CAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope...
  function UInt32 (line 960) | UInt32	CAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectProper...
  function UInt32 (line 981) | UInt32	CAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectProper...
  function CFStringRef (line 994) | CFStringRef	CAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropert...
  function UInt32 (line 1016) | UInt32	CAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectProperty...
  function UInt32 (line 1032) | UInt32	CAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectP...
  function UInt32 (line 1053) | UInt32	CAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectP...
  function CFStringRef (line 1066) | CFStringRef	CAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPr...
  function UInt32 (line 1088) | UInt32	CAHALAudioDevice::GetCurrentClockSourceID() const
  function UInt32 (line 1104) | UInt32	CAHALAudioDevice::GetNumberAvailableClockSources() const
  function UInt32 (line 1125) | UInt32	CAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) ...
  function CFStringRef (line 1138) | CFStringRef	CAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const
  function UInt32 (line 1148) | UInt32	CAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const

FILE: BGMApp/PublicUtility/CAHALAudioDevice.h
  function HasModelUID (line 79) | bool				HasModelUID() const;

FILE: BGMApp/PublicUtility/CAHALAudioObject.cpp
  function AudioObjectID (line 74) | AudioObjectID	CAHALAudioObject::GetObjectID() const
  function AudioClassID (line 84) | AudioClassID	CAHALAudioObject::GetClassID() const
  function AudioObjectID (line 102) | AudioObjectID	CAHALAudioObject::GetOwnerObjectID() const
  function CFStringRef (line 121) | CFStringRef	CAHALAudioObject::CopyOwningPlugInBundleID() const
  function CFStringRef (line 140) | CFStringRef	CAHALAudioObject::CopyName() const
  function CFStringRef (line 159) | CFStringRef	CAHALAudioObject::CopyManufacturer() const
  function CFStringRef (line 178) | CFStringRef	CAHALAudioObject::CopyNameForElement(AudioObjectPropertyScop...
  function CFStringRef (line 197) | CFStringRef	CAHALAudioObject::CopyCategoryNameForElement(AudioObjectProp...
  function CFStringRef (line 216) | CFStringRef	CAHALAudioObject::CopyNumberNameForElement(AudioObjectProper...
  function UInt32 (line 245) | UInt32	CAHALAudioObject::GetNumberOwnedObjects(AudioClassID inClass) const
  function AudioObjectID (line 293) | AudioObjectID	CAHALAudioObject::GetOwnedObjectByIndex(AudioClassID inCla...
  function UInt32 (line 343) | UInt32	CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAd...

FILE: BGMApp/PublicUtility/CAHALAudioObject.h
  function AudioClassID (line 83) | AudioClassID				GetClassID() const;
  function RemovePropertyListenerBlock (line 149) | inline void	CAHALAudioObject::RemovePropertyListenerBlock(const AudioObj...

FILE: BGMApp/PublicUtility/CAHALAudioStream.cpp
  function UInt32 (line 74) | UInt32	CAHALAudioStream::GetDirection() const
  function UInt32 (line 80) | UInt32	CAHALAudioStream::GetTerminalType() const
  function UInt32 (line 86) | UInt32	CAHALAudioStream::GetStartingChannel() const
  function UInt32 (line 92) | UInt32	CAHALAudioStream::GetLatency() const
  function UInt32 (line 111) | UInt32	CAHALAudioStream::GetNumberAvailableVirtualFormats() const
  function UInt32 (line 154) | UInt32	CAHALAudioStream::GetNumberAvailablePhysicalFormats() const

FILE: BGMApp/PublicUtility/CAHALAudioStream.h
  function class (line 61) | class CAHALAudioStream

FILE: BGMApp/PublicUtility/CAHALAudioSystemObject.cpp
  function UInt32 (line 74) | UInt32	CAHALAudioSystemObject::GetNumberAudioDevices() const
  function AudioObjectID (line 90) | AudioObjectID	CAHALAudioSystemObject::GetAudioDeviceAtIndex(UInt32 inInd...
  function AudioObjectID (line 106) | AudioObjectID	CAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef i...
  function AudioObjectPropertySelector (line 143) | static inline AudioObjectPropertySelector	CAHALAudioSystemObject_Calcula...
  function AudioObjectID (line 157) | AudioObjectID	CAHALAudioSystemObject::GetDefaultAudioDevice(bool inIsInp...
  function AudioObjectID (line 173) | AudioObjectID	CAHALAudioSystemObject::GetAudioPlugInForBundleID(CFString...

FILE: BGMApp/PublicUtility/CAHALAudioSystemObject.h
  function class (line 61) | class CAHALAudioSystemObject

FILE: BGMApp/PublicUtility/CAHostTimeBase.cpp
  type mach_timebase_info (line 73) | struct mach_timebase_info

FILE: BGMApp/PublicUtility/CAHostTimeBase.h
  function class (line 83) | class	CAHostTimeBase
  function UInt64 (line 120) | inline UInt64	CAHostTimeBase::GetTheCurrentTime()
  function UInt64 (line 150) | inline UInt64	CAHostTimeBase::ConvertToNanos(UInt64 inHostTime)
  function UInt64 (line 165) | inline UInt64	CAHostTimeBase::ConvertFromNanos(UInt64 inNanos)
  function UInt64 (line 180) | inline UInt64	CAHostTimeBase::GetCurrentTimeInNanos()
  function UInt64 (line 185) | inline UInt64	CAHostTimeBase::AbsoluteHostDeltaToNanos(UInt64 inStartTim...
  function SInt64 (line 201) | inline SInt64	CAHostTimeBase::HostDeltaToNanos(UInt64 inStartTime, UInt6...
  function UInt64 (line 219) | inline UInt64	CAHostTimeBase::MultiplyByRatio(UInt64 inMuliplicand, UInt...

FILE: BGMApp/PublicUtility/CAMutex.h
  function virtual (line 113) | virtual bool	IsFree() const;
  function class (line 154) | class Unlocker
  function class (line 170) | class SCOPED_CAPABILITY Tryer {

FILE: BGMApp/PublicUtility/CAPThread.cpp
  function UInt32 (line 141) | UInt32	CAPThread::GetScheduledPriority()
  function UInt32 (line 155) | UInt32	CAPThread::GetScheduledPriority(NativeThread thread)
  function UInt32 (line 328) | UInt32 CAPThread::getScheduledPriority(pthread_t inThread, int inPriorit...
  function UInt32 (line 375) | UInt32 WINAPI	CAPThread::Entry(CAPThread* inCAPThread)
  function Boolean (line 408) | Boolean CompareAndSwap(UInt32 inOldValue, UInt32 inNewValue, UInt32* inO...

FILE: BGMApp/PublicUtility/CAPThread.h
  function class (line 77) | class	CAPThread

FILE: BGMApp/PublicUtility/CAPropertyAddress.h
  function AudioObjectPropertyAddress (line 76) | struct CAPropertyAddress
  function AudioObjectPropertyAddress (line 87) | CAPropertyAddress(const AudioObjectPropertyAddress& inAddress)										...
  function AudioObjectPropertyAddress (line 88) | CAPropertyAddress(const CAPropertyAddress& inAddress)																			...
  function IsLessThanAddress (line 95) | static bool			IsLessThanAddress(const AudioObjectPropertyAddress& inAddr...
  function IsCongruentSelector (line 96) | static bool			IsCongruentSelector(AudioObjectPropertySelector inSelector...
  function IsCongruentScope (line 97) | static bool			IsCongruentScope(AudioObjectPropertyScope inScope1, AudioO...
  function IsCongruentElement (line 98) | static bool			IsCongruentElement(AudioObjectPropertyElement inElement1, ...
  function IsCongruentAddress (line 99) | static bool			IsCongruentAddress(const AudioObjectPropertyAddress& inAdd...
  function IsCongruentLessThanAddress (line 100) | static bool			IsCongruentLessThanAddress(const AudioObjectPropertyAddres...
  function class (line 109) | class   CAPropertyAddressList
  function class (line 166) | class	CAPropertyAddressListVector
  function HasAnyNonEmptyItems (line 202) | inline bool	CAPropertyAddressListVector::HasAnyNonEmptyItems() const
  function HasAnyItemsWithAddress (line 212) | inline bool	CAPropertyAddressListVector::HasAnyItemsWithAddress(const Au...
  function HasAnyItemsWithExactAddress (line 222) | inline bool	CAPropertyAddressListVector::HasAnyItemsWithExactAddress(con...
  function CAPropertyAddressList (line 232) | inline const CAPropertyAddressList*	CAPropertyAddressListVector::GetItem...
  function CAPropertyAddressList (line 247) | inline CAPropertyAddressList*	CAPropertyAddressListVector::GetItemByToke...
  function CAPropertyAddressList (line 262) | inline const CAPropertyAddressList*	CAPropertyAddressListVector::GetItem...
  function CAPropertyAddressList (line 277) | inline CAPropertyAddressList*	CAPropertyAddressListVector::GetItemByIntT...

FILE: BGMApp/PublicUtility/CARingBuffer.cpp
  function ZeroRange (line 112) | inline void ZeroRange(Byte **buffers, int nchannels, int offset, int nby...
  function StoreABL (line 120) | inline void StoreABL(Byte **buffers, int destOffset, const AudioBufferLi...
  function FetchABL (line 132) | inline void FetchABL(AudioBufferList *abl, int destOffset, Byte **buffer...
  function ZeroABL (line 144) | inline void ZeroABL(AudioBufferList *abl, int destOffset, int nbytes)
  function CARingBufferError (line 156) | CARingBufferError	CARingBuffer::Store(const AudioBufferList *abl, UInt32...
  function CARingBufferError (line 225) | CARingBufferError	CARingBuffer::GetTimeBounds(SampleTime &startTime, Sam...
  function CARingBufferError (line 243) | CARingBufferError	CARingBuffer::ClipTimeBounds(SampleTime& startRead, Sa...
  function CARingBufferError (line 262) | CARingBufferError	CARingBuffer::Fetch(AudioBufferList *abl, UInt32 nFram...

FILE: BGMApp/PublicUtility/CARingBuffer.h
  type SInt32 (line 63) | typedef SInt32 CARingBufferError;
  function class (line 68) | class CARingBuffer {

FILE: BGMDriver/BGMDriver/BGM_AbstractDevice.cpp
  function UInt32 (line 125) | UInt32    BGM_AbstractDevice::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_AbstractDevice.h
  function class (line 32) | class BGM_AbstractDevice

FILE: BGMDriver/BGMDriver/BGM_AudibleState.cpp
  function BGMDeviceAudibleState (line 48) | BGMDeviceAudibleState   BGM_AudibleState::GetState() const noexcept

FILE: BGMDriver/BGMDriver/BGM_Control.cpp
  function UInt32 (line 96) | UInt32  BGM_Control::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_Control.h
  function class (line 35) | class BGM_Control

FILE: BGMDriver/BGMDriver/BGM_Device.cpp
  function BGM_Device (line 61) | BGM_Device&	BGM_Device::GetInstance()
  function BGM_Device (line 67) | BGM_Device&	BGM_Device::GetUISoundsInstance()
  function UInt32 (line 249) | UInt32	BGM_Device::GetPropertyDataSize(AudioObjectID inObjectID, pid_t i...
  function UInt32 (line 389) | UInt32	BGM_Device::Device_GetPropertyDataSize(AudioObjectID inObjectID, ...
  function ValidateAppVolumesProperty (line 1007) | static void ValidateAppVolumesProperty(const CACFArray& inAppVolumes)
  function Float64 (line 1673) | Float64	BGM_Device::GetSampleRate() const
  function BGM_Object (line 1723) | BGM_Object&  BGM_Device::GetOwnedObjectByID(AudioObjectID inObjectID)
  function BGM_Object (line 1729) | const BGM_Object&  BGM_Device::GetOwnedObjectByID(AudioObjectID inObject...
  function UInt32 (line 1754) | UInt32	BGM_Device::GetNumberOfSubObjects() const
  function UInt32 (line 1759) | UInt32	BGM_Device::GetNumberOfOutputSubObjects() const
  function UInt32 (line 1764) | UInt32	BGM_Device::GetNumberOfOutputControls() const
  function kern_return_t (line 1876) | kern_return_t	BGM_Device::_HW_StartIO()
  function Float64 (line 1904) | Float64	BGM_Device::_HW_GetSampleRate() const
  function kern_return_t (line 1914) | kern_return_t	BGM_Device::_HW_SetSampleRate(Float64 inNewSampleRate)
  function UInt32 (line 1924) | UInt32	BGM_Device::_HW_GetRingBufferFrameSize() const

FILE: BGMDriver/BGMDriver/BGM_Device.h
  function class (line 54) | class BGM_Device

FILE: BGMDriver/BGMDriver/BGM_MuteControl.cpp
  function UInt32 (line 101) | UInt32  BGM_MuteControl::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_MuteControl.h
  function class (line 39) | class BGM_MuteControl

FILE: BGMDriver/BGMDriver/BGM_NullDevice.cpp
  function BGM_NullDevice (line 47) | BGM_NullDevice&    BGM_NullDevice::GetInstance()
  function UInt32 (line 171) | UInt32    BGM_NullDevice::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_NullDevice.h
  function class (line 58) | class BGM_NullDevice

FILE: BGMDriver/BGMDriver/BGM_Object.cpp
  function UInt32 (line 108) | UInt32	BGM_Object::GetPropertyDataSize(AudioObjectID inObjectID, pid_t i...

FILE: BGMDriver/BGMDriver/BGM_Object.h
  function class (line 113) | class BGM_Object

FILE: BGMDriver/BGMDriver/BGM_PlugIn.cpp
  function BGM_PlugIn (line 47) | BGM_PlugIn& BGM_PlugIn::GetInstance()
  function UInt32 (line 132) | UInt32	BGM_PlugIn::GetPropertyDataSize(AudioObjectID inObjectID, pid_t i...

FILE: BGMDriver/BGMDriver/BGM_PlugIn.h
  function class (line 40) | class BGM_PlugIn
  function Host_PropertiesChanged (line 63) | static void						Host_PropertiesChanged(AudioObjectID inObjectID, UInt32...
  function Host_RequestDeviceConfigurationChange (line 64) | static void						Host_RequestDeviceConfigurationChange(AudioObjectID inD...

FILE: BGMDriver/BGMDriver/BGM_PlugInInterface.cpp
  function BGM_Object (line 102) | static BGM_Object& BGM_LookUpOwnerObject(AudioObjectID inObjectID)
  function BGM_AbstractDevice (line 131) | static BGM_AbstractDevice& BGM_LookUpDevice(AudioObjectID inObjectID)
  function HRESULT (line 177) | static HRESULT	BGM_QueryInterface(void* inDriver, REFIID inUUID, LPVOID*...
  function ULONG (line 225) | static ULONG	BGM_AddRef(void* inDriver)
  function ULONG (line 243) | static ULONG	BGM_Release(void* inDriver)
  function OSStatus (line 266) | static OSStatus	BGM_Initialize(AudioServerPlugInDriverRef inDriver, Audi...
  function OSStatus (line 304) | static OSStatus	BGM_CreateDevice(AudioServerPlugInDriverRef inDriver, CF...
  function OSStatus (line 315) | static OSStatus	BGM_DestroyDevice(AudioServerPlugInDriverRef inDriver, A...
  function OSStatus (line 326) | static OSStatus	BGM_AddDeviceClient(AudioServerPlugInDriverRef inDriver,...
  function OSStatus (line 362) | static OSStatus	BGM_RemoveDeviceClient(AudioServerPlugInDriverRef inDriv...
  function OSStatus (line 398) | static OSStatus	BGM_PerformDeviceConfigurationChange(AudioServerPlugInDr...
  function OSStatus (line 437) | static OSStatus	BGM_AbortDeviceConfigurationChange(AudioServerPlugInDriv...
  function Boolean (line 471) | static Boolean	BGM_HasProperty(AudioServerPlugInDriverRef inDriver, Audi...
  function OSStatus (line 500) | static OSStatus	BGM_IsPropertySettable(AudioServerPlugInDriverRef inDriv...
  function OSStatus (line 539) | static OSStatus	BGM_GetPropertyDataSize(AudioServerPlugInDriverRef inDri...
  function OSStatus (line 577) | static OSStatus	BGM_GetPropertyData(AudioServerPlugInDriverRef inDriver,...
  function OSStatus (line 616) | static OSStatus	BGM_SetPropertyData(AudioServerPlugInDriverRef inDriver,...
  function OSStatus (line 663) | static OSStatus	BGM_StartIO(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 700) | static OSStatus	BGM_StopIO(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 734) | static OSStatus	BGM_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 784) | static OSStatus	BGM_WillDoIOOperation(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 833) | static OSStatus	BGM_BeginIOOperation(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 878) | static OSStatus	BGM_DoIOOperation(AudioServerPlugInDriverRef inDriver,
  function OSStatus (line 929) | static OSStatus	BGM_EndIOOperation(AudioServerPlugInDriverRef inDriver,

FILE: BGMDriver/BGMDriver/BGM_Stream.cpp
  function UInt32 (line 127) | UInt32    BGM_Stream::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_Stream.h
  function class (line 43) | class BGM_Stream

FILE: BGMDriver/BGMDriver/BGM_TaskQueue.cpp
  function UInt32 (line 137) | UInt32  BGM_TaskQueue::NanosToAbsoluteTime(UInt32 inNanos)
  function UInt64 (line 195) | UInt64    BGM_TaskQueue::QueueSync(BGM_TaskID inTaskID, bool inRunOnReal...

FILE: BGMDriver/BGMDriver/BGM_TaskQueue.h
  function class (line 57) | class BGM_TaskQueue

FILE: BGMDriver/BGMDriver/BGM_VolumeControl.cpp
  function UInt32 (line 129) | UInt32  BGM_VolumeControl::GetPropertyDataSize(AudioObjectID inObjectID,

FILE: BGMDriver/BGMDriver/BGM_VolumeControl.h
  function class (line 36) | class BGM_VolumeControl

FILE: BGMDriver/BGMDriver/BGM_WrappedAudioEngine.cpp
  function UInt64 (line 29) | UInt64	BGM_WrappedAudioEngine::GetSampleRate() const
  function kern_return_t (line 34) | kern_return_t BGM_WrappedAudioEngine::SetSampleRate(Float64 inNewSampleR...
  function UInt32 (line 41) | UInt32 BGM_WrappedAudioEngine::GetSampleBufferFrameSize() const

FILE: BGMDriver/BGMDriver/BGM_WrappedAudioEngine.h
  function class (line 37) | class BGM_WrappedAudioEngine

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_Client.h
  function class (line 42) | class BGM_Client

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp
  function BGM_Client (line 94) | BGM_Client    BGM_ClientMap::RemoveClient(UInt32 inClientID)
  function CACFArray (line 213) | CACFArray   BGM_ClientMap::CopyClientRelativeVolumesAsAppVolumes(CAVolum...
  function ShowSetRelativeVolumeMessage (line 276) | void ShowSetRelativeVolumeMessage(pid_t inAppPID, BGM_Client* theClient) {
  function ShowSetRelativeVolumeMessage (line 285) | void ShowSetRelativeVolumeMessage(CACFString inAppBundleID, BGM_Client* ...

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h
  function class (line 72) | class BGM_ClientMap

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_ClientTasks.h
  function class (line 39) | class BGM_ClientTasks

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp
  function Float32 (line 300) | Float32 BGM_Clients::GetClientRelativeVolumeRT(UInt32 inClientID) const
  function SInt32 (line 307) | SInt32 BGM_Clients::GetClientPanPositionRT(UInt32 inClientID) const

FILE: BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h
  function CACFString (line 141) | CACFString                          mMusicPlayerBundleIDProperty { "" };

FILE: BGMDriver/PublicUtility/CAAtomic.h
  function CAMemoryBarrier (line 71) | inline void CAMemoryBarrier()
  function SInt32 (line 80) | inline SInt32 CAAtomicAdd32Barrier(SInt32 theAmt, volatile SInt32* theVa...
  function SInt32 (line 93) | inline SInt32 CAAtomicOr32Barrier(UInt32 theMask, volatile UInt32* theVa...
  function SInt32 (line 107) | inline SInt32 CAAtomicAnd32Barrier(UInt32 theMask, volatile UInt32* theV...
  function CAAtomicCompareAndSwap32Barrier (line 121) | inline bool CAAtomicCompareAndSwap32Barrier(SInt32 oldValue, SInt32 newV...
  function SInt32 (line 135) | inline SInt32 CAAtomicIncrement32(volatile SInt32* theValue)
  function SInt32 (line 144) | inline SInt32 CAAtomicDecrement32(volatile SInt32* theValue)
  function SInt32 (line 153) | inline SInt32 CAAtomicIncrement32Barrier(volatile SInt32* theValue)
  function SInt32 (line 162) | inline SInt32 CAAtomicDecrement32Barrier(volatile SInt32* theValue)
  function CAAtomicTestAndClearBarrier (line 171) | inline bool CAAtomicTestAndClearBarrier(int bitToClear, void* theAddress)
  function CAAtomicTestAndClear (line 181) | inline bool CAAtomicTestAndClear(int bitToClear, void* theAddress)
  function CAAtomicTestAndSetBarrier (line 191) | inline bool CAAtomicTestAndSetBarrier(int bitToSet, void* theAddress)
  function CAAtomicAdd32Barrier (line 208) | inline int32_t CAAtomicAdd32Barrier(int32_t theAmt, volatile int32_t* th...
  function CAAtomicOr32Barrier (line 213) | inline int32_t CAAtomicOr32Barrier(uint32_t theMask, volatile uint32_t* ...
  function CAAtomicAnd32Barrier (line 218) | inline int32_t CAAtomicAnd32Barrier(uint32_t theMask, volatile uint32_t*...
  function CAAtomicCompareAndSwap32Barrier (line 223) | inline bool CAAtomicCompareAndSwap32Barrier(int32_t oldValue, int32_t ne...
  function CAAtomicIncrement32 (line 228) | inline int32_t CAAtomicIncrement32(volatile int32_t* theValue)
  function CAAtomicDecrement32 (line 233) | inline int32_t CAAtomicDecrement32(volatile int32_t* theValue)
  function CAAtomicIncrement32Barrier (line 238) | inline int32_t CAAtomicIncrement32Barrier(volatile int32_t* theValue)
  function CAAtomicDecrement32Barrier (line 243) | inline int32_t CAAtomicDecrement32Barrier(volatile int32_t* theValue)
  function CAAtomicCompareAndSwap64Barrier (line 253) | inline bool CAAtomicCompareAndSwap64Barrier( int64_t __oldValue, int64_t...
  function CAAtomicCompareAndSwapPtrBarrier (line 259) | inline bool CAAtomicCompareAndSwapPtrBarrier(void *__oldValue, void *__n...
  type CASpinLock (line 277) | typedef int32_t CASpinLock;
  function CASpinLockLock (line 286) | inline void    CASpinLockLock( volatile CASpinLock *__lock )
  function CASpinLockUnlock (line 296) | inline void    CASpinLockUnlock( volatile CASpinLock *__lock )
  function CASpinLockTry (line 305) | inline bool    CASpinLockTry( volatile CASpinLock *__lock )

FILE: BGMDriver/PublicUtility/CAAtomicStack.h
  function push_NA (line 68) | void	push_NA(T *item)
  function T (line 74) | T *		pop_NA()
  function T (line 84) | T *		head() { return mHead; }
  function push_atomic (line 87) | void	push_atomic(T *item)
  function push_multiple_atomic (line 96) | void	push_multiple_atomic(T *item)
  function T (line 111) | T *		pop_atomic_single_reader()
  function T (line 124) | T *		pop_atomic()
  function T (line 139) | T *		pop_all()
  function T (line 149) | T*		pop_all_reversed()
  function compare_and_swap (line 161) | static bool	compare_and_swap(T *oldvalue, T *newvalue, T **pvalue)
  function class (line 187) | class CAAtomicStack {
  function push_atomic (line 194) | void	push_atomic(void *p) { OSAtomicEnqueue(&mHead, p, mNextPtrOffset); }
  function push_NA (line 195) | void	push_NA(void *p) { push_atomic(p); }
  function push_atomic (line 215) | void	push_atomic(T *item) {
  function push_NA (line 222) | void	push_NA(T *item) { push_atomic(item); }
  function T (line 224) | T *		pop_atomic() { return (T *)OSAtomicDequeue(&mHead, mNextPtrOffset); }
  function T (line 225) | T *		pop_atomic_single_reader() { return pop_atomic(); }
  function T (line 226) | T *		pop_NA() { return pop_atomic(); }

FILE: BGMDriver/PublicUtility/CAAutoDisposer.h
  function explicit (line 100) | explicit CAPtrRef(T* ptr) : ptr_(ptr) {}
  function explicit (line 113) | explicit CAAutoFree(T* ptr) : ptr_(ptr) {}
  function reallocBytes (line 149) | void reallocBytes(size_t numBytes)
  function reallocItems (line 154) | void reallocItems(size_t numItems)
  function explicit (line 253) | explicit CAAutoDelete(T* ptr) : ptr_(ptr) {}
  function explicit (line 354) | explicit CAAutoArrayDelete(T* ptr) : ptr_(ptr) {}
  function ptr_ (line 363) | CAAutoArrayDelete(size_t n) : ptr_(new T[n]) {}
  function alloc (line 367) | void alloc(size_t numItems)

FILE: BGMDriver/PublicUtility/CABitOperations.h
  function UInt32 (line 60) | inline UInt32 IsPowerOfTwo(UInt32 x)
  function UInt32 (line 95) | inline UInt32 CountLeadingZeroesLong(UInt64 arg)
  function UInt32 (line 116) | inline UInt32 CountTrailingZeroes(UInt32 x)
  function UInt32 (line 122) | inline UInt32 CountLeadingOnes(UInt32 x)
  function UInt32 (line 128) | inline UInt32 CountTrailingOnes(UInt32 x)
  function UInt32 (line 134) | inline UInt32 NumBits(UInt32 x)
  function UInt32 (line 140) | inline UInt32 Log2Ceil(UInt32 x)
  function UInt32 (line 146) | inline UInt32 Log2Floor(UInt32 x)
  function UInt32 (line 152) | inline UInt32 NextPowerOfTwo(UInt32 x)
  function UInt32 (line 158) | inline UInt32 CountOnes(UInt32 x)
  function UInt32 (line 167) | inline UInt32 CountZeroes(UInt32 x)
  function UInt32 (line 173) | inline UInt32 LSBitPos(UInt32 x)
  function UInt32 (line 179) | inline UInt32 LSBit(UInt32 x)
  function UInt32 (line 185) | inline UInt32 MSBitPos(UInt32 x)
  function UInt32 (line 191) | inline UInt32 MSBit(UInt32 x)
  function UInt32 (line 197) | inline UInt32 DivInt(UInt32 numerator, UInt32 denominator)

FILE: BGMDriver/PublicUtility/CACFArray.h
  function class (line 76) | class CACFArray
  function Release (line 94) | void				Release()																	{ if(mRelease && (mCFArray != NULL)) {...
  function ShouldRelease (line 103) | void				ShouldRelease(bool inRelease)												{ mRelease = inRelease; }
  function RemoveItem (line 120) | void				RemoveItem(const void* inItem)												{ UInt32 theIndex; if(...
  function RemoveItemAtIndex (line 122) | void				RemoveItemAtIndex(UInt32 inIndex)											{ if(CanModify()) { ...
  function Clear (line 123) | void				Clear()																		{ if(CanModify()) { CFArrayRemoveAllVal...
  function Sort (line 124) | void				Sort(CFComparatorFunction inCompareFunction)								{ if(CanModi...
  function SortNumbers (line 125) | void				SortNumbers()																{ Sort((CFComparatorFunction)CFNumb...
  function SortStrings (line 126) | void				SortStrings()																{ Sort((CFComparatorFunction)CFStri...

FILE: BGMDriver/PublicUtility/CACFDictionary.cpp
  function UInt32 (line 68) | UInt32	CACFDictionary::Size () const

FILE: BGMDriver/PublicUtility/CACFDictionary.h
  function class (line 72) | class CACFDictionary
  function Release (line 89) | void					Release()																{ if(mRelease && (mCFDictionary != NUL...
  function ShouldRelease (line 98) | void					ShouldRelease(bool inRelease)											{ mRelease = inRelease; }
  function CFMutableDictionaryRef (line 104) | CFMutableDictionaryRef	GetMutableDict()														{ return mCFDiction...
  function OSStatus (line 111) | OSStatus				GetDictIfMutable(CFMutableDictionaryRef& outDict) const					...
  function UInt32 (line 116) | UInt32					Size() const;
  function Clear (line 163) | void					Clear()																	{ if(CanModify()) { CFDictionaryRemoveA...
  function Show (line 165) | void					Show()																	{ CFShow(mCFDictionary); }

FILE: BGMDriver/PublicUtility/CACFNumber.cpp
  function Float32 (line 57) | Float32	CACFNumber::GetFixed32() const
  function Float64 (line 71) | Float64	CACFNumber::GetFixed64() const

FILE: BGMDriver/PublicUtility/CACFNumber.h
  function class (line 66) | class	CACFBoolean
  function Release (line 80) | void			Release() { if(mWillRelease && (mCFBoolean != NULL)) { CFRelease(...
  function DontAllowRelease (line 88) | void			DontAllowRelease() { mWillRelease = false; }
  function IsValid (line 89) | bool			IsValid() { return mCFBoolean != NULL; }
  function GetBoolean (line 96) | bool			GetBoolean() const { bool theAnswer = false; if(mCFBoolean != NUL...
  function class (line 105) | class	CACFNumber
  function Release (line 124) | void		Release() { if(mWillRelease && (mCFNumber != NULL)) { CFRelease(mC...
  function DontAllowRelease (line 132) | void		DontAllowRelease() { mWillRelease = false; }
  function SInt8 (line 140) | SInt8		GetSInt8() const { SInt8 theAnswer = 0; if(mCFNumber != NULL) { C...
  function UInt32 (line 142) | UInt32		GetUInt32() const { UInt32 theAnswer = 0; if(mCFNumber != NULL) ...
  function Float32 (line 144) | Float32		GetFixed32() const;

FILE: BGMDriver/PublicUtility/CACFString.cpp
  function UInt32 (line 57) | UInt32	CACFString::GetStringByteLength(CFStringRef inCFString, CFStringE...

FILE: BGMDriver/PublicUtility/CACFString.h
  function class (line 73) | class	CACFString
  function AssignWithoutRetain (line 85) | void				AssignWithoutRetain(CFStringRef inCFString) { if (inCFString != ...
  function Release (line 89) | void				Release() { if(mWillRelease && (mCFString != NULL)) { CFRelease(...
  function DontAllowRelease (line 97) | void				DontAllowRelease() { mWillRelease = false; }
  function IsEqualTo (line 99) | bool				IsEqualTo(CFStringRef inString) const { bool theAnswer = false; ...
  function StartsWith (line 100) | bool				StartsWith(CFStringRef inString) const { bool theAnswer = false;...
  function EndsWith (line 101) | bool				EndsWith(CFStringRef inString) const { bool theAnswer = false; i...
  function CFStringRef (line 107) | const CFStringRef*	GetPointerToStorage() const	{ return &mCFString; }
  function UInt32 (line 109) | UInt32				GetLength() const { UInt32 theAnswer = 0; if(mCFString != NULL...
  function UInt32 (line 110) | UInt32				GetByteLength(CFStringEncoding inEncoding = kCFStringEncodingU...
  function GetCString (line 111) | void				GetCString(char* outString, UInt32& ioStringSize, CFStringEncodi...
  function GetUnicodeString (line 112) | void				GetUnicodeString(UInt16* outString, UInt32& ioStringSize) const ...
  function SInt32 (line 113) | SInt32				GetAsInteger() { return GetAsInteger(mCFString); }
  function Float64 (line 114) | Float64				GetAsFloat64() { return GetAsFloat64(mCFString); }
  function UInt32 (line 116) | static UInt32		GetStringLength(CFStringRef inCFString)  { UInt32 theAnsw...
  function SInt32 (line 120) | static SInt32		GetAsInteger(CFStringRef inCFString) { SInt32 theAnswer =...
  function Float64 (line 121) | static Float64		GetAsFloat64(CFStringRef inCFString) { Float64 theAnswer...
  function class (line 136) | class	CACFMutableString
  function Release (line 152) | void				Release() { if(mWillRelease && (mCFMutableString != NULL)) { CFR...
  function DontAllowRelease (line 160) | void				DontAllowRelease() { mWillRelease = false; }
  function IsValid (line 161) | bool				IsValid() { return mCFMutableString != NULL; }
  function IsEqualTo (line 162) | bool				IsEqualTo(CFStringRef inString) const { bool theAnswer = false; ...
  function StartsWith (line 163) | bool				StartsWith(CFStringRef inString) const { bool theAnswer = false;...
  function EndsWith (line 164) | bool				EndsWith(CFStringRef inString) const { bool theAnswer = false; i...
  function Append (line 165) | void				Append(CFStringRef inString) { if(mCFMutableString != NULL) { CF...
  function UInt32 (line 171) | UInt32				GetLength() const { UInt32 theAnswer = 0; if(mCFMutableString ...
  function UInt32 (line 172) | UInt32				GetByteLength(CFStringEncoding inEncoding = kCFStringEncodingU...
  function GetCString (line 173) | void				GetCString(char* outString, UInt32& ioStringSize, CFStringEncodi...
  function GetUnicodeString (line 174) | void				GetUnicodeString(UInt16* outString, UInt32& ioStringSize) const ...
  function SInt32 (line 175) | SInt32				GetAsInteger() { return CACFString::GetAsInteger(mCFMutableStr...
  function Float64 (line 176) | Float64				GetAsFloat64() { return CACFString::GetAsFloat64(mCFMutableSt...

FILE: BGMDriver/PublicUtility/CADebugMacros.cpp
  function DebugPrint (line 57) | void	DebugPrint(const char *fmt, ...)
  function LogError (line 66) | void	LogError(const char *fmt, ...)
  function LogWarning (line 92) | void	LogWarning(const char *fmt, ...)

FILE: BGMDriver/PublicUtility/CADebugPrintf.cpp
  function CAWin32DebugPrintf (line 61) | int	CAWin32DebugPrintf(char* inFormat, ...)
  function OpenDebugPrintfSideFile (line 77) | void OpenDebugPrintfSideFile()

FILE: BGMDriver/PublicUtility/CADebugger.cpp
  function CAIsDebuggerAttached (line 63) | bool CAIsDebuggerAttached(void)
  function CADebuggerStop (line 83) | void	CADebuggerStop(void)

FILE: BGMDriver/PublicUtility/CADispatchQueue.cpp
  function CADispatchQueue (line 418) | CADispatchQueue&	CADispatchQueue::GetGlobalSerialQueue()

FILE: BGMDriver/PublicUtility/CADispatchQueue.h
  function class (line 97) | class CADispatchQueue
  function CADispatchQueue (line 182) | inline CADispatchQueue::EventSource::EventSource()
  function CADispatchQueue (line 213) | inline CADispatchQueue::EventSource::~EventSource()
  function Retain (line 218) | inline void	CADispatchQueue::EventSource::Retain()
  function Release (line 226) | inline void	CADispatchQueue::EventSource::Release()

FILE: BGMDriver/PublicUtility/CAException.h
  function class (line 64) | class CAException

FILE: BGMDriver/PublicUtility/CAHostTimeBase.cpp
  type mach_timebase_info (line 73) | struct mach_timebase_info

FILE: BGMDriver/PublicUtility/CAHostTimeBase.h
  function class (line 83) | class	CAHostTimeBase
  function UInt64 (line 120) | inline UInt64	CAHostTimeBase::GetTheCurrentTime()
  function UInt64 (line 150) | inline UInt64	CAHostTimeBase::ConvertToNanos(UInt64 inHostTime)
  function UInt64 (line 165) | inline UInt64	CAHostTimeBase::ConvertFromNanos(UInt64 inNanos)
  function UInt64 (line 180) | inline UInt64	CAHostTimeBase::GetCurrentTimeInNanos()
  function UInt64 (line 185) | inline UInt64	CAHostTimeBase::AbsoluteHostDeltaToNanos(UInt64 inStartTim...
  function SInt64 (line 201) | inline SInt64	CAHostTimeBase::HostDeltaToNanos(UInt64 inStartTime, UInt6...
  function UInt64 (line 219) | inline UInt64	CAHostTimeBase::MultiplyByRatio(UInt64 inMuliplicand, UInt...

FILE: BGMDriver/PublicUtility/CAMutex.h
  function class (line 73) | class	CAMutex
  function class (line 126) | class Unlocker
  function class (line 142) | class Tryer {

FILE: BGMDriver/PublicUtility/CAPThread.cpp
  function UInt32 (line 141) | UInt32	CAPThread::GetScheduledPriority()
  function UInt32 (line 155) | UInt32	CAPThread::GetScheduledPriority(NativeThread thread)
  function UInt32 (line 328) | UInt32 CAPThread::getScheduledPriority(pthread_t inThread, int inPriorit...
  function UInt32 (line 375) | UInt32 WINAPI	CAPThread::Entry(CAPThread* inCAPThread)
  function Boolean (line 408) | Boolean CompareAndSwap(UInt32 inOldValue, UInt32 inNewValue, UInt32* inO...

FILE: BGMDriver/PublicUtility/CAPThread.h
  function class (line 77) | class	CAPThread

FILE: BGMDriver/PublicUtility/CAPropertyAddress.h
  function AudioObjectPropertyAddress (line 76) | struct CAPropertyAddress
  function AudioObjectPropertyAddress (line 87) | CAPropertyAddress(const AudioObjectPropertyAddress& inAddress)										...
  function AudioObjectPropertyAddress (line 88) | CAPropertyAddress(const CAPropertyAddress& inAddress)																			...
  function IsLessThanAddress (line 95) | static bool			IsLessThanAddress(const AudioObjectPropertyAddress& inAddr...
  function IsCongruentSelector (line 96) | static bool			IsCongruentSelector(AudioObjectPropertySelector inSelector...
  function IsCongruentScope (line 97) | static bool			IsCongruentScope(AudioObjectPropertyScope inScope1, AudioO...
  function IsCongruentElement (line 98) | static bool			IsCongruentElement(AudioObjectPropertyElement inElement1, ...
  function IsCongruentAddress (line 99) | static bool			IsCongruentAddress(const AudioObjectPropertyAddress& inAdd...
  function IsCongruentLessThanAddress (line 100) | static bool			IsCongruentLessThanAddress(const AudioObjectPropertyAddres...
  type EqualTo (line 106) | struct EqualTo
  function const (line 108) | bool	operator()(const AudioObjectPropertyAddress& inAddress1, const Audi...
  type LessThan (line 111) | struct LessThan
  function const (line 113) | bool	operator()(const AudioObjectPropertyAddress& inAddress1, const Audi...
  type CongruentEqualTo (line 116) | struct CongruentEqualTo
  function const (line 118) | bool	operator()(const AudioObjectPropertyAddress& inAddress1, const Audi...
  type CongruentLessThan (line 121) | struct CongruentLessThan
  function const (line 123) | bool	operator()(const AudioObjectPropertyAddress& inAddress1, const Audi...
  function class (line 135) | class   CAPropertyAddressList
  function class (line 195) | class	CAPropertyAddressListVector
  function HasAnyNonEmptyItems (line 231) | inline bool	CAPropertyAddressListVector::HasAnyNonEmptyItems() const
  function HasAnyItemsWithAddress (line 241) | inline bool	CAPropertyAddressListVector::HasAnyItemsWithAddress(const Au...
  function HasAnyItemsWithExactAddress (line 251) | inline bool	CAPropertyAddressListVector::HasAnyItemsWithExactAddress(con...
  function CAPropertyAddressList (line 261) | inline const CAPropertyAddressList*	CAPropertyAddressListVector::GetItem...
  function CAPropertyAddressList (line 276) | inline CAPropertyAddressList*	CAPropertyAddressListVector::GetItemByToke...
  function CAPropertyAddressList (line 291) | inline const CAPropertyAddressList*	CAPropertyAddressListVector::GetItem...
  function CAPropertyAddressList (line 306) | inline CAPropertyAddressList*	CAPropertyAddressListVector::GetItemByIntT...

FILE: BGMDriver/PublicUtility/CARingBuffer.cpp
  function ZeroRange (line 112) | inline void ZeroRange(Byte **buffers, int nchannels, int offset, int nby...
  function StoreABL (line 120) | inline void StoreABL(Byte **buffers, int destOffset, const AudioBufferLi...
  function FetchABL (line 132) | inline void FetchABL(AudioBufferList *abl, int destOffset, Byte **buffer...
  function ZeroABL (line 144) | inline void ZeroABL(AudioBufferList *abl, int destOffset, int nbytes)
  function CARingBufferError (line 156) | CARingBufferError	CARingBuffer::Store(const AudioBufferList *abl, UInt32...
  function CARingBufferError (line 225) | CARingBufferError	CARingBuffer::GetTimeBounds(SampleTime &startTime, Sam...
  function CARingBufferError (line 243) | CARingBufferError	CARingBuffer::ClipTimeBounds(SampleTime& startRead, Sa...
  function CARingBufferError (line 262) | CARingBufferError	CARingBuffer::Fetch(AudioBufferList *abl, UInt32 nFram...

FILE: BGMDriver/PublicUtility/CARingBuffer.h
  type SInt32 (line 63) | typedef SInt32 CARingBufferError;
  function class (line 68) | class CARingBuffer {

FILE: BGMDriver/PublicUtility/CAVolumeCurve.cpp
  function SInt32 (line 74) | SInt32	CAVolumeCurve::GetMinimumRaw() const
  function SInt32 (line 87) | SInt32	CAVolumeCurve::GetMaximumRaw() const
  function Float32 (line 101) | Float32	CAVolumeCurve::GetMinimumDB() const
  function Float32 (line 114) | Float32	CAVolumeCurve::GetMaximumDB() const
  function SInt32 (line 306) | SInt32	CAVolumeCurve::ConvertDBToRaw(Float32 inDB) const
  function Float32 (line 365) | Float32	CAVolumeCurve::ConvertRawToDB(SInt32 inRaw) const
  function Float32 (line 415) | Float32	CAVolumeCurve::ConvertRawToScalar(SInt32 inRaw) const
  function Float32 (line 441) | Float32	CAVolumeCurve::ConvertDBToScalar(Float32 inDB) const
  function SInt32 (line 448) | SInt32	CAVolumeCurve::ConvertScalarToRaw(Float32 inScalar) const
  function Float32 (line 477) | Float32	CAVolumeCurve::ConvertScalarToDB(Float32 inScalar) const

FILE: BGMDriver/PublicUtility/CAVolumeCurve.h
  type CARawPoint (line 65) | struct CARawPoint
  type CADBPoint (line 85) | struct CADBPoint
  function class (line 109) | class CAVolumeCurve

FILE: SharedSource/BGM_TestUtils.h
  function catch (line 46) | catch (ExpectedException)

FILE: SharedSource/BGM_Types.h
  type BGMDeviceAudibleState (line 130) | enum BGMDeviceAudibleState
  function class (line 234) | class BGM_InvalidClientException : public std::runtime_error {
  function class (line 239) | class BGM_InvalidClientPIDException : public std::runtime_error {
  function class (line 244) | class BGM_InvalidClientRelativeVolumeException : public std::runtime_err...
  function class (line 249) | class BGM_InvalidClientPanPositionException : public std::runtime_error {
  function class (line 254) | class BGM_DeviceNotSetException : public std::runtime_error {

FILE: SharedSource/BGM_Utils.cpp
  function dispatch_queue_t (line 37) | dispatch_queue_t BGMGetDispatchQueue_PriorityUserInteractive()
  type BGM_Utils (line 61) | namespace BGM_Utils
    function LogIfMachError (line 73) | bool LogIfMachError(const char* callerName,
    function ThrowIfMachError (line 91) | void ThrowIfMachError(const char* callerName,
    function OSStatus (line 101) | OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
    function OSStatus (line 109) | OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
    function LogException (line 118) | void LogException(const char* __nullable fileName,
    function LogUnexpectedException (line 134) | void LogUnexpectedException(const char* __nullable fileName,
    function OSStatus (line 144) | OSStatus LogUnexpectedExceptions(const char* callerName,
    function OSStatus (line 150) | OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
    function OSStatus (line 158) | OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
    function OSStatus (line 169) | static OSStatus LogAndSwallowExceptions(const char* __nullable fileName,

FILE: SharedSource/BGM_Utils.h
  function namespace (line 153) | namespace BGM_Utils
Condensed preview — 289 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,464K chars).
[
  {
    "path": ".editorconfig",
    "chars": 430,
    "preview": "# This file was added to get GitHub to display our source code correctly when\n# it has mixed tabs and spaces. (I didn't "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 2492,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n## Example bug "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "chars": 244,
    "preview": "---\nname: Other\nabout: Feature request, question, support request or anything else\ntitle: ''\nlabels: ''\nassignees: ''\n\n-"
  },
  {
    "path": ".github/workflows/build-test-release.yml",
    "chars": 8894,
    "preview": "# TODO: Split this into multiple .yml files? Multiple jobs?\nname: Build, Test and Release\n\non:\n  push:\n    branches:\n   "
  },
  {
    "path": ".gitignore",
    "chars": 618,
    "preview": ".DS_Store\n.*.swp\n/BGMDriver/BGMDriver/quick_install.conf\n/build_and_install.log\n.idea/\ntags\ncmake-build-debug/\n/Backgrou"
  },
  {
    "path": "BGM.xcworkspace/contents.xcworkspacedata",
    "chars": 1093,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:BGMDriver/BGMD"
  },
  {
    "path": "BGMApp/BGMApp/BGMApp-Debug.entitlements",
    "chars": 498,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMApp/BGMApp.entitlements",
    "chars": 311,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppDelegate.h",
    "chars": 1943,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppDelegate.mm",
    "chars": 22582,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumes.h",
    "chars": 2681,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumes.m",
    "chars": 23037,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumesController.h",
    "chars": 1775,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppVolumesController.mm",
    "chars": 9547,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppWatcher.h",
    "chars": 1848,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAppWatcher.m",
    "chars": 3960,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDevice.cpp",
    "chars": 14754,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDevice.h",
    "chars": 4851,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDeviceManager.h",
    "chars": 4157,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAudioDeviceManager.mm",
    "chars": 17435,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMenuItem.h",
    "chars": 1495,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMenuItem.m",
    "chars": 7409,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMusic.h",
    "chars": 1424,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMAutoPauseMusic.mm",
    "chars": 11588,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMBackgroundMusicDevice.cpp",
    "chars": 12668,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMBackgroundMusicDevice.h",
    "chars": 8166,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDebugLoggingMenuItem.h",
    "chars": 1785,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDebugLoggingMenuItem.m",
    "chars": 2231,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlSync.cpp",
    "chars": 7754,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlSync.h",
    "chars": 4647,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlsList.cpp",
    "chars": 20365,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMDeviceControlsList.h",
    "chars": 5339,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputDeviceMenuSection.h",
    "chars": 1487,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputDeviceMenuSection.mm",
    "chars": 16620,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputVolumeMenuItem.h",
    "chars": 1485,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm",
    "chars": 12904,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThrough.cpp",
    "chars": 52183,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThrough.h",
    "chars": 10520,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThroughRTLogger.cpp",
    "chars": 17010,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPlayThroughRTLogger.h",
    "chars": 9228,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPreferredOutputDevices.h",
    "chars": 2437,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMPreferredOutputDevices.mm",
    "chars": 21983,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMStatusBarItem.h",
    "chars": 2395,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMStatusBarItem.mm",
    "chars": 11079,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMSystemSoundsVolume.h",
    "chars": 2199,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMSystemSoundsVolume.mm",
    "chars": 3148,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMTermination.h",
    "chars": 3028,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMTermination.mm",
    "chars": 6351,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMUserDefaults.h",
    "chars": 2629,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMUserDefaults.m",
    "chars": 10043,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMVolumeChangeListener.cpp",
    "chars": 3892,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMVolumeChangeListener.h",
    "chars": 1722,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMXPCListener.h",
    "chars": 1412,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/BGMXPCListener.mm",
    "chars": 10688,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Base.lproj/MainMenu.xib",
    "chars": 36520,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/AirPlayIcon.imageset/Contents.json",
    "chars": 304,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"AirPlay.pdf\",\n      \"scale\" : \"1x\"\n    },\n    {\n"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1280,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"appicon_16.png\",\n      \"scale\""
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/FermataIcon.imageset/Contents.json",
    "chars": 154,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"FermataIcon.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\""
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Contents.json",
    "chars": 151,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume0.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Contents.json",
    "chars": 151,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume1.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Contents.json",
    "chars": 151,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume2.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1"
  },
  {
    "path": "BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Contents.json",
    "chars": 151,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"filename\" : \"Volume3.pdf\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1"
  },
  {
    "path": "BGMApp/BGMApp/Info.plist",
    "chars": 2006,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMApp/LICENSE",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMDecibel.h",
    "chars": 899,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMDecibel.m",
    "chars": 2888,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayer.h",
    "chars": 1740,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayer.m",
    "chars": 13411,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayerConnection.h",
    "chars": 2396,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMGooglePlayMusicDesktopPlayerConnection.m",
    "chars": 17793,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMHermes.h",
    "chars": 897,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMHermes.m",
    "chars": 2999,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusic.h",
    "chars": 1000,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusic.m",
    "chars": 2988,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayer.h",
    "chars": 7212,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayer.m",
    "chars": 3997,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayers.h",
    "chars": 2246,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMMusicPlayers.mm",
    "chars": 11144,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMScriptingBridge.h",
    "chars": 2696,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMScriptingBridge.m",
    "chars": 7286,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSpotify.h",
    "chars": 899,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSpotify.m",
    "chars": 3151,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSwinsian.h",
    "chars": 971,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMSwinsian.m",
    "chars": 3095,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVLC.h",
    "chars": 891,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVLC.m",
    "chars": 3234,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVOX.h",
    "chars": 891,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMVOX.m",
    "chars": 2747,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMiTunes.h",
    "chars": 1151,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/BGMiTunes.m",
    "chars": 2990,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Decibel.h",
    "chars": 7810,
    "preview": "/*\n * Decibel.h\n *\n * Generated with\n * sdef /Applications/Decibel.app | sdp -fh --basename Decibel\n */\n\n#import <AppKit"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/GooglePlayMusicDesktopPlayer.js",
    "chars": 6959,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Hermes.h",
    "chars": 3143,
    "preview": "/*\n * Hermes.h\n *\n * Generated with\n * sdef /Applications/Hermes.app | sdp -fh --basename Hermes\n */\n\n#import <AppKit/Ap"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Music.h",
    "chars": 23433,
    "preview": "/*\n * Music.h\n *\n * Generated with\n * sdef /System/Applications/Music.app | sdp -fh --basename Music\n */\n\n#import <AppKi"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Spotify.h",
    "chars": 2921,
    "preview": "/*\n * Spotify.h\n *\n * Generated with\n * sdef /Applications/Spotify.app | sdp -fh --basename Spotify\n */\n\n#import <AppKit"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/Swinsian.h",
    "chars": 9790,
    "preview": "/*\n * Swinsian.h\n *\n * Generated with\n * sdef /Applications/Swinsian.app | sdp -fh --basename Swinsian\n */\n\n#import <App"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/VLC.h",
    "chars": 9346,
    "preview": "/*\n * VLC.h\n *\n * Generated with\n * sdef /Applications/VLC.app | sdp -fh --basename VLC\n */\n\n#import <AppKit/AppKit.h>\n#"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/VOX.h",
    "chars": 2618,
    "preview": "/*\n * VOX.h\n *\n * Generated with\n * sdef /Applications/VOX.app | sdp -fh --basename VOX\n */\n\n#import <AppKit/AppKit.h>\n#"
  },
  {
    "path": "BGMApp/BGMApp/Music Players/iTunes.h",
    "chars": 23104,
    "preview": "/*\n * iTunes.h\n *\n * Generated with\n * sdef /Applications/iTunes.app | sdp -fh --basename iTunes\n */\n\n#import <AppKit/Ap"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAboutPanel.h",
    "chars": 1130,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAboutPanel.m",
    "chars": 6882,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAutoPauseMusicPrefs.h",
    "chars": 1223,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMAutoPauseMusicPrefs.mm",
    "chars": 4152,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMPreferencesMenu.h",
    "chars": 1606,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Preferences/BGMPreferencesMenu.mm",
    "chars": 11207,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASApplication.h",
    "chars": 1526,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASApplication.m",
    "chars": 2830,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASOutputDevice.h",
    "chars": 1443,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMASOutputDevice.mm",
    "chars": 2699,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMApp.sdef",
    "chars": 4184,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE dictionary SYSTEM \"file://localhost/System/Library/DTDs/sdef.dtd\">\n<dic"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h",
    "chars": 1477,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm",
    "chars": 3931,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp/SystemPreferences.h",
    "chars": 9470,
    "preview": "/*\n * SystemPreferences.h\n */\n\n#import <AppKit/AppKit.h>\n#import <ScriptingBridge/ScriptingBridge.h>\n\n\n@class SystemPref"
  },
  {
    "path": "BGMApp/BGMApp/_uninstall-non-interactive.sh",
    "chars": 6679,
    "preview": "#!/bin/bash\n# vim: tw=120:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redi"
  },
  {
    "path": "BGMApp/BGMApp/main.m",
    "chars": 921,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/project.pbxproj",
    "chars": 145851,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 47;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/xcshareddata/xcschemes/BGMXPCHelper.xcscheme",
    "chars": 5284,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "BGMApp/BGMApp.xcodeproj/xcshareddata/xcschemes/Background Music.xcscheme",
    "chars": 6275,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMApp.h",
    "chars": 749,
    "preview": "/*\n * BGMApp.h\n *\n * Generated with\n * sdef \"/Applications/Background Music.app\" | sdp -fh --basename BGMApp\n */\n\n#impor"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMAppUITests-Info.plist",
    "chars": 680,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/BGMAppUITests.mm",
    "chars": 10014,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UITests/skip-ui-tests.py",
    "chars": 1306,
    "preview": "#!/usr/bin/env python3\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redistri"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist",
    "chars": 680,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMMusicPlayersUnitTests.mm",
    "chars": 7754,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMPlayThroughRTLoggerTests.mm",
    "chars": 5581,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/BGMPlayThroughTests.mm",
    "chars": 3218,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp",
    "chars": 1595,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioDevice.h",
    "chars": 2174,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp",
    "chars": 1031,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObject.h",
    "chars": 1703,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp",
    "chars": 3384,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.h",
    "chars": 3279,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioDevice.cpp",
    "chars": 20756,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioObject.cpp",
    "chars": 6762,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioSystemObject.cpp",
    "chars": 2479,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMThreadSafetyAnalysis.h",
    "chars": 18540,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCHelperService.h",
    "chars": 1235,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCHelperService.mm",
    "chars": 11399,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCListenerDelegate.h",
    "chars": 1342,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/BGMXPCListenerDelegate.m",
    "chars": 3693,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/Info.plist",
    "chars": 994,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template",
    "chars": 3025,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMXPCHelper/main.m",
    "chars": 3994,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/BGMXPCHelper/post_install.sh",
    "chars": 8182,
    "preview": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redi"
  },
  {
    "path": "BGMApp/BGMXPCHelper/safe_install_dir.sh",
    "chars": 7815,
    "preview": "#!/bin/bash\n# vim: tw=100:\n\n# This file is part of Background Music.\n#\n# Background Music is free software: you can redi"
  },
  {
    "path": "BGMApp/BGMXPCHelperTests/BGMXPCHelperTests-Info.plist",
    "chars": 733,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "BGMApp/BGMXPCHelperTests/BGMXPCHelperTests.m",
    "chars": 2602,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/BGMDebugLogging.c",
    "chars": 1500,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/BGMDebugLogging.h",
    "chars": 2389,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/CAAtomic.h",
    "chars": 10922,
    "preview": "/*\n     File: CAAtomic.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This App"
  },
  {
    "path": "BGMApp/PublicUtility/CAAutoDisposer.h",
    "chars": 12187,
    "preview": "/*\n     File: CAAutoDisposer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  Th"
  },
  {
    "path": "BGMApp/PublicUtility/CABitOperations.h",
    "chars": 6196,
    "preview": "/*\n     File: CABitOperations.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  T"
  },
  {
    "path": "BGMApp/PublicUtility/CACFArray.cpp",
    "chars": 18108,
    "preview": "/*\n     File: CACFArray.cpp\n Abstract: CACFArray.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supp"
  },
  {
    "path": "BGMApp/PublicUtility/CACFArray.h",
    "chars": 10408,
    "preview": "/*\n     File: CACFArray.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Ap"
  },
  {
    "path": "BGMApp/PublicUtility/CACFDictionary.cpp",
    "chars": 15345,
    "preview": "/*\n     File: CACFDictionary.cpp\n Abstract: CACFDictionary.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple softwa"
  },
  {
    "path": "BGMApp/PublicUtility/CACFDictionary.h",
    "chars": 10280,
    "preview": "/*\n     File: CACFDictionary.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  Th"
  },
  {
    "path": "BGMApp/PublicUtility/CACFNumber.cpp",
    "chars": 3662,
    "preview": "/*\n     File: CACFNumber.cpp\n Abstract: CACFNumber.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is su"
  },
  {
    "path": "BGMApp/PublicUtility/CACFNumber.h",
    "chars": 7938,
    "preview": "/*\n     File: CACFNumber.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This A"
  },
  {
    "path": "BGMApp/PublicUtility/CACFString.cpp",
    "chars": 4230,
    "preview": "/*\n     File: CACFString.cpp\n Abstract: CACFString.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is su"
  },
  {
    "path": "BGMApp/PublicUtility/CACFString.h",
    "chars": 13837,
    "preview": "/*\n     File: CACFString.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This A"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugMacros.cpp",
    "chars": 4490,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugMacros.h",
    "chars": 21025,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugPrintf.cpp",
    "chars": 4430,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugPrintf.h",
    "chars": 4438,
    "preview": "/*\n     File: CADebugPrintf.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  Thi"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugger.cpp",
    "chars": 3542,
    "preview": "/*\n     File: CADebugger.cpp\n Abstract: CADebugger.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is su"
  },
  {
    "path": "BGMApp/PublicUtility/CADebugger.h",
    "chars": 3276,
    "preview": "/*\n     File: CADebugger.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This A"
  },
  {
    "path": "BGMApp/PublicUtility/CAException.h",
    "chars": 3525,
    "preview": "/*\n     File: CAException.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This "
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioDevice.cpp",
    "chars": 44035,
    "preview": "/*\n     File: CAHALAudioDevice.cpp\n Abstract: CAHALAudioDevice.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple so"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioDevice.h",
    "chars": 13856,
    "preview": "/*\n     File: CAHALAudioDevice.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  "
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioObject.cpp",
    "chars": 12689,
    "preview": "/*\n     File: CAHALAudioObject.cpp\n Abstract: CAHALAudioObject.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple so"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioObject.h",
    "chars": 11353,
    "preview": "/*\n     File: CAHALAudioObject.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  "
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioStream.cpp",
    "chars": 7349,
    "preview": "/*\n     File: CAHALAudioStream.cpp\n Abstract: CAHALAudioStream.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple so"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioStream.h",
    "chars": 4249,
    "preview": "/*\n     File: CAHALAudioStream.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  "
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioSystemObject.cpp",
    "chars": 7646,
    "preview": "/*\n     File: CAHALAudioSystemObject.cpp\n Abstract: CAHALAudioSystemObject.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  T"
  },
  {
    "path": "BGMApp/PublicUtility/CAHALAudioSystemObject.h",
    "chars": 3863,
    "preview": "/*\n     File: CAHALAudioSystemObject.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORT"
  },
  {
    "path": "BGMApp/PublicUtility/CAHostTimeBase.cpp",
    "chars": 4476,
    "preview": "/*\n     File: CAHostTimeBase.cpp\n Abstract: CAHostTimeBase.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple softwa"
  },
  {
    "path": "BGMApp/PublicUtility/CAHostTimeBase.h",
    "chars": 7314,
    "preview": "/*\n     File: CAHostTimeBase.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  Th"
  },
  {
    "path": "BGMApp/PublicUtility/CAMutex.cpp",
    "chars": 12835,
    "preview": "/*\n     File: CAMutex.cpp\n Abstract: CAMutex.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supplied"
  },
  {
    "path": "BGMApp/PublicUtility/CAMutex.h",
    "chars": 6493,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMApp/PublicUtility/CAPThread.cpp",
    "chars": 14725,
    "preview": "/*\n     File: CAPThread.cpp\n Abstract: CAPThread.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software is supp"
  },
  {
    "path": "BGMApp/PublicUtility/CAPThread.h",
    "chars": 7443,
    "preview": "/*\n     File: CAPThread.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Ap"
  },
  {
    "path": "BGMApp/PublicUtility/CAPropertyAddress.h",
    "chars": 17265,
    "preview": "/*\n     File: CAPropertyAddress.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT: "
  },
  {
    "path": "BGMApp/PublicUtility/CARingBuffer.cpp",
    "chars": 10096,
    "preview": "/*\n     File: CARingBuffer.cpp\n Abstract: CARingBuffer.h\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This Apple software i"
  },
  {
    "path": "BGMApp/PublicUtility/CARingBuffer.h",
    "chars": 5365,
    "preview": "/*\n     File: CARingBuffer.h\n Abstract: Part of CoreAudio Utility Classes\n  Version: 1.1\n \n Disclaimer: IMPORTANT:  This"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AbstractDevice.cpp",
    "chars": 16744,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AbstractDevice.h",
    "chars": 5246,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AudibleState.cpp",
    "chars": 8427,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_AudibleState.h",
    "chars": 3522,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Control.cpp",
    "chars": 6362,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Control.h",
    "chars": 3332,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Device.cpp",
    "chars": 85023,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Device.h",
    "chars": 11725,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_MuteControl.cpp",
    "chars": 7757,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_MuteControl.h",
    "chars": 3842,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_NullDevice.cpp",
    "chars": 19066,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_NullDevice.h",
    "chars": 8265,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Object.cpp",
    "chars": 6144,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_Object.h",
    "chars": 9136,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  },
  {
    "path": "BGMDriver/BGMDriver/BGM_PlugIn.cpp",
    "chars": 14771,
    "preview": "// This file is part of Background Music.\n//\n// Background Music is free software: you can redistribute it and/or\n// mod"
  }
]

// ... and 89 more files (download for full content)

About this extraction

This page contains the full source code of the kyleneideck/BackgroundMusic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 289 files (2.2 MB), approximately 596.5k tokens, and a symbol index with 726 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!