From f64cf41f8a4ebe3888ca64b633254d02a9a677fb Mon Sep 17 00:00:00 2001 From: Kyle Neideck Date: Tue, 26 Dec 2017 23:04:20 +1100 Subject: [PATCH] Add a volume slider for system sounds. System sounds are UI-related sounds like mail notifications or terminal bells. Xcode 9.2 doesn't support saving .xib files in Xcode 7 format any more, so building Background Music now requires Xcode 8 or above. Also, fix some of the tooltips that would only work if BGMApp was the foreground app, which it shouldn't be. --- BGMApp/BGMApp.xcodeproj/project.pbxproj | 12 +- BGMApp/BGMApp/BGMAppDelegate.h | 7 + BGMApp/BGMApp/BGMAppDelegate.mm | 51 ++++--- BGMApp/BGMApp/BGMAppVolumes.m | 2 + BGMApp/BGMApp/BGMAudioDeviceManager.mm | 2 +- BGMApp/BGMApp/BGMOutputVolumeMenuItem.h | 4 + BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm | 41 ++--- BGMApp/BGMApp/BGMSystemSoundsVolume.h | 56 +++++++ BGMApp/BGMApp/BGMSystemSoundsVolume.mm | 88 +++++++++++ BGMApp/BGMApp/Base.lproj/MainMenu.xib | 144 +++++++++++------- BGMDriver/BGMDriver.xcodeproj/project.pbxproj | 4 + BGMDriver/BGMDriver/BGM_AbstractDevice.cpp | 7 +- BGMDriver/BGMDriver/BGM_Control.h | 8 +- BGMDriver/BGMDriver/BGM_Device.cpp | 68 ++++++--- BGMDriver/BGMDriver/BGM_Device.h | 6 - BGMDriver/BGMDriver/BGM_MuteControl.h | 3 +- BGMDriver/BGMDriver/BGM_PlugInInterface.cpp | 1 + BGMDriver/BGMDriver/BGM_VolumeControl.cpp | 132 +++++++++++++--- BGMDriver/BGMDriver/BGM_VolumeControl.h | 65 +++++++- .../BGMDriver/DeviceClients/BGM_Client.h | 2 +- .../BGMDriver/DeviceClients/BGM_Clients.cpp | 2 +- DEVELOPING.md | 20 +-- README.md | 2 +- SharedSource/BGM_Types.h | 1 + build_and_install.sh | 3 +- 25 files changed, 567 insertions(+), 164 deletions(-) create mode 100644 BGMApp/BGMApp/BGMSystemSoundsVolume.h create mode 100644 BGMApp/BGMApp/BGMSystemSoundsVolume.mm diff --git a/BGMApp/BGMApp.xcodeproj/project.pbxproj b/BGMApp/BGMApp.xcodeproj/project.pbxproj index 2582561..b1cbce6 100644 --- a/BGMApp/BGMApp.xcodeproj/project.pbxproj +++ b/BGMApp/BGMApp.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ 1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; }; 1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; }; 1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; }; + 1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; }; + 1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; }; 1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; }; 1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; }; 1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; }; @@ -251,6 +253,8 @@ 1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlSync.h; sourceTree = ""; }; 1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = ""; }; 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = ""; }; + 1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMSystemSoundsVolume.h; sourceTree = ""; }; + 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMSystemSoundsVolume.mm; sourceTree = ""; }; 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = ""; }; 1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = ""; }; 1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputVolumeMenuItem.h; sourceTree = ""; }; @@ -286,7 +290,7 @@ 1CE7064A1BF1EC0600BFC06D /* BGMOutputDevicePrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMOutputDevicePrefs.h; path = Preferences/BGMOutputDevicePrefs.h; sourceTree = ""; }; 1CE7064B1BF1EC0600BFC06D /* BGMOutputDevicePrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMOutputDevicePrefs.mm; path = Preferences/BGMOutputDevicePrefs.mm; sourceTree = ""; }; 1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = UnitTests/Mock_CAHALAudioSystemObject.cpp; sourceTree = ""; }; - 1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = ""; }; 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = ""; }; 1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMAudioDevice.cpp; sourceTree = ""; }; @@ -314,7 +318,7 @@ 2743CA1C1D86DA9B0089613B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 275343BF1DFD01BC00DF3858 /* SystemPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemPreferences.h; sourceTree = ""; }; 2769728B1CAFCEE8007A2F7C /* post_install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = post_install.sh; path = BGMXPCHelper/post_install.sh; sourceTree = SOURCE_ROOT; }; - 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */ = {isa = PBXFileReference; explicitFileType = text.xml; fileEncoding = 1; name = com.bearisdriving.BGM.XPCHelper.plist.template; path = BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template; sourceTree = SOURCE_ROOT; }; + 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */ = {isa = PBXFileReference; explicitFileType = text.xml; fileEncoding = 4; name = com.bearisdriving.BGM.XPCHelper.plist.template; path = BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template; sourceTree = SOURCE_ROOT; }; 276972901CB16008007A2F7C /* safe_install_dir.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = safe_install_dir.sh; path = BGMXPCHelper/safe_install_dir.sh; sourceTree = SOURCE_ROOT; }; 2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Utils.h; path = ../SharedSource/BGM_Utils.h; sourceTree = ""; }; 277170141CA24D7C00AB34B4 /* BGMXPCListenerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCListenerDelegate.h; path = BGMXPCHelper/BGMXPCListenerDelegate.h; sourceTree = SOURCE_ROOT; }; @@ -532,6 +536,8 @@ 1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */, 1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */, 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */, + 1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */, + 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */, 1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */, 1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */, 1CD410D21F9EDDAD0070A094 /* BGMAppVolumesController.h */, @@ -928,6 +934,7 @@ buildActionMask = 2147483647; files = ( 1C86DA6A1F91EE3B000C8CCF /* CAPThread.cpp in Sources */, + 1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */, 1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */, 1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */, 1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */, @@ -982,6 +989,7 @@ buildActionMask = 2147483647; files = ( 1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */, + 1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */, 1CC6593D1F91DEB400B0CCDC /* BGMTermination.mm in Sources */, 1CD410D51F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */, 1CD989571ECFFD250014BBBF /* CAHostTimeBase.cpp in Sources */, diff --git a/BGMApp/BGMApp/BGMAppDelegate.h b/BGMApp/BGMApp/BGMAppDelegate.h index 641e9c7..dca4f3f 100644 --- a/BGMApp/BGMApp/BGMAppDelegate.h +++ b/BGMApp/BGMApp/BGMAppDelegate.h @@ -36,12 +36,19 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4; @interface BGMAppDelegate : NSObject @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 (readonly) BGMAudioDeviceManager* audioDevices; diff --git a/BGMApp/BGMApp/BGMAppDelegate.mm b/BGMApp/BGMApp/BGMAppDelegate.mm index 394ae23..7b2acb3 100644 --- a/BGMApp/BGMApp/BGMAppDelegate.mm +++ b/BGMApp/BGMApp/BGMAppDelegate.mm @@ -24,11 +24,12 @@ #import "BGMAppDelegate.h" // Local Includes -#import "BGM_Types.h" +#import "BGM_Utils.h" #import "BGMUserDefaults.h" #import "BGMMusicPlayers.h" #import "BGMAutoPauseMusic.h" #import "BGMAutoPauseMenuItem.h" +#import "BGMSystemSoundsVolume.h" #import "BGMAppVolumesController.h" #import "BGMPreferencesMenu.h" #import "BGMXPCListener.h" @@ -55,6 +56,7 @@ static float const kStatusBarIconPadding = 0.25; BGMAutoPauseMusic* autoPauseMusic; BGMAutoPauseMenuItem* autoPauseMenuItem; BGMMusicPlayers* musicPlayers; + BGMSystemSoundsVolume* systemSoundsVolume; BGMAppVolumesController* appVolumes; BGMPreferencesMenu* prefsMenu; BGMXPCListener* xpcListener; @@ -172,23 +174,8 @@ static float const kStatusBarIconPadding = 0.25; [self showXPCHelperErrorMessage:error]; }]; - // 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]; + [self initVolumesMenuSection]; - // Add it to the main menu below the "Volumes" heading. - [self.bgmMenu insertItem:outputVolume - atIndex:([self.bgmMenu indexOfItemWithTag:kVolumesHeadingMenuItemTag] + 1)]; - - - appVolumes = [[BGMAppVolumesController alloc] initWithMenu:self.bgmMenu - appVolumeView:self.appVolumeView - audioDevices:audioDevices]; - prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu audioDevices:audioDevices musicPlayers:musicPlayers @@ -227,6 +214,36 @@ static float const kStatusBarIconPadding = 0.25; return YES; } +- (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) diff --git a/BGMApp/BGMApp/BGMAppVolumes.m b/BGMApp/BGMApp/BGMAppVolumes.m index 02539a5..c7a7cd9 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.m +++ b/BGMApp/BGMApp/BGMAppVolumes.m @@ -142,10 +142,12 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps"; - (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 != -1 && [subview isKindOfClass:[BGMAVM_PanSlider class]]) { [(BGMAVM_PanSlider*)subview setPanPosition:pan]; } diff --git a/BGMApp/BGMApp/BGMAudioDeviceManager.mm b/BGMApp/BGMApp/BGMAudioDeviceManager.mm index c76117f..d8aece3 100644 --- a/BGMApp/BGMApp/BGMAudioDeviceManager.mm +++ b/BGMApp/BGMApp/BGMAudioDeviceManager.mm @@ -186,7 +186,7 @@ #pragma mark Systemwide Default Device // Note that there are two different "default" output devices on OS X: "output" and "system output". See -// AudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h. +// kAudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h. - (NSError* __nullable) setBGMDeviceAsOSDefault { // Copy bgmDevice so we can call the HAL without holding stateLock. See startPlayThroughSync. diff --git a/BGMApp/BGMApp/BGMOutputVolumeMenuItem.h b/BGMApp/BGMApp/BGMOutputVolumeMenuItem.h index 9a4926f..bbedef4 100644 --- a/BGMApp/BGMApp/BGMOutputVolumeMenuItem.h +++ b/BGMApp/BGMApp/BGMOutputVolumeMenuItem.h @@ -27,6 +27,8 @@ #import +#pragma clang assume_nonnull begin + @interface BGMOutputVolumeMenuItem : NSMenuItem // A menu item with a slider for controlling the volume of the output device. Similar to the one in @@ -42,3 +44,5 @@ @end +#pragma clang assume_nonnull end + diff --git a/BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm b/BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm index c5bc975..87fd87f 100644 --- a/BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm +++ b/BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm @@ -35,38 +35,38 @@ #import +#pragma clang assume_nonnull begin + const float kSliderEpsilon = 1e-10f; const AudioObjectPropertyScope kScope = kAudioDevicePropertyScopeOutput; NSString* const __nonnull kGenericOutputDeviceName = @"Output Device"; @implementation BGMOutputVolumeMenuItem { BGMAudioDeviceManager* audioDevices; - NSTextField* outputVolumeLabel; + NSTextField* deviceLabel; NSSlider* volumeSlider; } -// TODO: Update the UI when the output device is changed. // TODO: Show the output device's icon next to its name. -// TODO: Disable the slider if the output device doesn't have a volume control. // TODO: Should the menu (bgmMenu) hide after you change the output volume slider, like the normal // menu bar volume slider does? // TODO: Move the output devices from Preferences to the main menu so they're slightly easier to // access? -// TODO: Update the screenshot in the README. +// TODO: Update the screenshot in the README at some point. - (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices view:(NSView*)view slider:(NSSlider*)slider deviceLabel:(NSTextField*)label { if ((self = [super initWithTitle:@"" action:nil keyEquivalent:@""])) { audioDevices = devices; - outputVolumeLabel = label; + deviceLabel = label; volumeSlider = slider; // Apply our custom view from MainMenu.xib. self.view = view; [self initSlider]; - [self setOutputVolumeLabel]; + [self updateLabelAndToolTip]; } return self; @@ -150,15 +150,16 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device"; - (void) outputDeviceDidChange { dispatch_async(dispatch_get_main_queue(), ^{ // Update the label to use the name of the new output device. - [self setOutputVolumeLabel]; + [self updateLabelAndToolTip]; // Set the slider to the volume of the new device. [self updateVolumeSlider]; }); } -// Sets the label to the name of the output device. Falls back to a generic name if the device -// returns an error when queried. -- (void) setOutputVolumeLabel { +// Sets the label to the output device's name or, if it has one, its current datasource. If it has a +// datasource, the device's name is set as this menu item's tooltip. Falls back to a generic name if +// the device returns an error when queried. +- (void) updateLabelAndToolTip { BGMAudioDevice device = audioDevices.outputDevice; BOOL didSetLabel = NO; @@ -167,34 +168,37 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device"; // The device has datasources, so use the current datasource's name like macOS does. UInt32 dataSourceID = device.GetCurrentDataSourceID(kScope, kMasterChannel); - outputVolumeLabel.stringValue = + deviceLabel.stringValue = (__bridge_transfer NSString*)device.CopyDataSourceNameForID(kScope, kMasterChannel, dataSourceID); didSetLabel = YES; // So we know not to change the text if setting the tooltip fails. - outputVolumeLabel.toolTip = (__bridge_transfer NSString*)device.CopyName(); + // Set the tooltip of the menu item (the container) rather than the label because menu + // items' tooltips will still appear when a different app is focused and, as far as I + // know, BGMApp should never be the foreground app. + self.toolTip = (__bridge_transfer NSString*)device.CopyName(); } else { - outputVolumeLabel.stringValue = (__bridge_transfer NSString*)device.CopyName(); + deviceLabel.stringValue = (__bridge_transfer NSString*)device.CopyName(); } } catch (const CAException& e) { BGMLogException(e); // The device returned an error, so set the label to a generic device name, since we don't // want to leave it set to the previous device's name. - outputVolumeLabel.toolTip = nil; + self.toolTip = nil; if (!didSetLabel) { - outputVolumeLabel.stringValue = kGenericOutputDeviceName; + deviceLabel.stringValue = kGenericOutputDeviceName; } } // Take the label out of the accessibility hierarchy, which also moves the slider up a level. #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 // MAC_OS_X_VERSION_10_10 - if ([outputVolumeLabel.cell respondsToSelector:@selector(setAccessibilityElement:)]) { + if ([deviceLabel.cell respondsToSelector:@selector(setAccessibilityElement:)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" - outputVolumeLabel.cell.accessibilityElement = NO; + deviceLabel.cell.accessibilityElement = NO; #pragma clang diagnostic pop } #endif @@ -227,3 +231,6 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device"; @end +#pragma clang assume_nonnull end + + diff --git a/BGMApp/BGMApp/BGMSystemSoundsVolume.h b/BGMApp/BGMApp/BGMSystemSoundsVolume.h new file mode 100644 index 0000000..ab86398 --- /dev/null +++ b/BGMApp/BGMApp/BGMSystemSoundsVolume.h @@ -0,0 +1,56 @@ +// 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 . + +// +// BGMSystemSoundsVolume.h +// BGMApp +// +// Copyright © 2017 Kyle Neideck +// +// The menu item with the volume slider that controls system-related sounds. The slider is used to +// set the volume of the instance of BGMDevice that system sounds are played on, i.e. the audio +// device returned by BGMBackgroundMusicDevice::GetUISoundsBGMDeviceInstance. +// +// System sounds are any sounds played using the audio device macOS is set to use as the device +// "for system related sound from the alert sound to digital call progress". See +// kAudioHardwarePropertyDefaultSystemOutputDevice in AudioHardware.h. They can be played by any +// app, though most apps use systemsoundserverd to play their system sounds, which means BGMDriver +// can't tell which app is actually playing the sounds. +// + +// Local Includes +#import "BGMAudioDevice.h" + +// System Includes +#import + + +#pragma clang assume_nonnull begin + +@interface BGMSystemSoundsVolume : NSObject + +// The volume level of uiSoundsDevice will be used to set the slider's initial position and will be +// updated when the user moves the slider. view and slider are the UI elements from MainMenu.xib. +- (instancetype) initWithUISoundsDevice:(BGMAudioDevice)uiSoundsDevice + view:(NSView*)view + slider:(NSSlider*)slider; + +// The menu item with the volume slider for system sounds. +@property (readonly) NSMenuItem* menuItem; + +@end + +#pragma clang assume_nonnull end + diff --git a/BGMApp/BGMApp/BGMSystemSoundsVolume.mm b/BGMApp/BGMApp/BGMSystemSoundsVolume.mm new file mode 100644 index 0000000..44f4f58 --- /dev/null +++ b/BGMApp/BGMApp/BGMSystemSoundsVolume.mm @@ -0,0 +1,88 @@ +// 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 . + +// +// BGMSystemSoundsVolume.mm +// BGMApp +// +// Copyright © 2017 Kyle Neideck +// + +// Self Include +#import "BGMSystemSoundsVolume.h" + +// Local Includes +#import "BGM_Types.h" +#import "BGM_Utils.h" + + +#pragma clang assume_nonnull begin + +NSString* const kMenuItemToolTip = + @"Alerts, notification sounds, etc. Usually short. Can be played by any app."; + +@implementation BGMSystemSoundsVolume { + BGMAudioDevice uiSoundsDevice; + NSSlider* volumeSlider; +} + +- (instancetype) initWithUISoundsDevice:(BGMAudioDevice)inUISoundsDevice + view:(NSView*)inView + slider:(NSSlider*)inSlider { + if ((self = [super init])) { + uiSoundsDevice = inUISoundsDevice; + volumeSlider = inSlider; + + _menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + _menuItem.toolTip = kMenuItemToolTip; + + // Apply our custom view from MainMenu.xib. It's very similar to the one for app volumes. + _menuItem.view = inView; + + try { + volumeSlider.floatValue = + uiSoundsDevice.GetVolumeControlScalarValue(kAudioObjectPropertyScopeOutput, + kMasterChannel); + } catch (const CAException& e) { + BGMLogException(e); + volumeSlider.floatValue = 1.0f; // Full volume + } + + volumeSlider.target = self; + volumeSlider.action = @selector(systemSoundsSliderChanged:); + } + + return self; +} + +- (void) systemSoundsSliderChanged:(id)sender { + #pragma unused(sender) + + float sliderLevel = volumeSlider.floatValue; + + BGMAssert((sliderLevel >= 0.0f) && (sliderLevel <= 1.0f), "Invalid value from slider"); + DebugMsg("BGMSystemSoundsVolume::systemSoundsSliderChanged: UI sounds volume: %f", sliderLevel); + + BGMLogAndSwallowExceptions("BGMSystemSoundsVolume::systemSoundsSliderChanged", ([&] { + uiSoundsDevice.SetVolumeControlScalarValue(kAudioObjectPropertyScopeOutput, + kMasterChannel, + sliderLevel); + })); +} + +@end + +#pragma clang assume_nonnull end + diff --git a/BGMApp/BGMApp/Base.lproj/MainMenu.xib b/BGMApp/BGMApp/Base.lproj/MainMenu.xib index f82aad1..743a361 100644 --- a/BGMApp/BGMApp/Base.lproj/MainMenu.xib +++ b/BGMApp/BGMApp/Base.lproj/MainMenu.xib @@ -1,9 +1,10 @@ - + - - + + + @@ -23,6 +24,8 @@ + + @@ -111,7 +114,7 @@ - + @@ -120,7 +123,7 @@ - + @@ -130,7 +133,7 @@ - + @@ -211,10 +214,10 @@ - + - - + + @@ -232,7 +235,7 @@ - + @@ -271,7 +274,7 @@ - + @@ -281,60 +284,89 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + YnBsaXN0MDDUAQIDBAUGPT5YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK4HCBMU GR4fIyQrLjE3OlUkbnVsbNUJCgsMDQ4PEBESVk5TU2l6ZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVw c1dOU0NvbG9ygAKADRIgwwAAgAOAC1Z7MSwgMX3SFQoWGFpOUy5vYmplY3RzoReABIAK0hUKGh2iGxyA -BYAGgAkQANIgCiEiXxAUTlNUSUZGUmVwcmVzZW50YXRpb26AB4AITxEIrE1NACoAAAAKAAAADgEAAAMA -AAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAERAAQA -AAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEWAAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMA -AAABAAEAAAFSAAMAAAABAAEAAAFTAAMAAAACAAEAAYdzAAcAAAf0AAAAuAAAAAAAAAf0YXBwbAIgAABt -bnRyR1JBWVhZWiAH0AACAA4ADAAAAABhY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYA -AQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVk -ZXNjAAAAwAAAAG9kc2NtAAABMAAABmZjcHJ0AAAHmAAAADh3dHB0AAAH0AAAABRrVFJDAAAH5AAAAA5k -ZXNjAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAAAAAAAAAAFUdlbmVyaWMgR3JheSBQcm9m -aWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAA -AAAfAAAADHNrU0sAAAAqAAABhGVuVVMAAAAoAAABrmNhRVMAAAAsAAAB1nZpVk4AAAAsAAACAnB0QlIA -AAAqAAACLnVrVUEAAAAsAAACWGZyRlUAAAAqAAAChGh1SFUAAAAuAAACrnpoVFcAAAAQAAAC3G5iTk8A -AAAsAAAC7GtvS1IAAAAYAAADGGNzQ1oAAAAkAAADMGhlSUwAAAAgAAADVHJvUk8AAAAkAAADdGRlREUA -AAA6AAADmGl0SVQAAAAuAAAD0nN2U0UAAAAuAAAEAHpoQ04AAAAQAAAELmphSlAAAAAWAAAEPmVsR1IA -AAAkAAAEVHB0UE8AAAA4AAAEeG5sTkwAAAAqAAAEsGVzRVMAAAAoAAAE2nRoVEgAAAAkAAAFAnRyVFIA -AAAiAAAFJmZpRkkAAAAsAAAFSGhySFIAAAA6AAAFdHBsUEwAAAA2AAAFrnJ1UlUAAAAmAAAF5GFyRUcA -AAAoAAAGCmRhREsAAAA0AAAGMgBWAWEAZQBvAGIAZQBjAG4A/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkA -bABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAFAAcgBvAGYAaQBsAGUAUABlAHIAZgBpAGwAIABkAGUA -IABnAHIAaQBzACAAZwBlAG4A6AByAGkAYwBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBtACAA -QwBoAHUAbgBnAFAAZQByAGYAaQBsACAAQwBpAG4AegBhACAARwBlAG4A6QByAGkAYwBvBBcEMAQzBDAE -OwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAEcAcgBhAHkAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIA -aQBxAHUAZQAgAGcAcgBpAHMAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/AByAGsAZQAgAHAAcgBvAGYA -aQBskBp1KHBwlo6Ccl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QB0AG8AbgBlAHAAcgBvAGYA -aQBsx3y8GAAgAEcAcgBhAHkAINUEuFzTDMd8AE8AYgBlAGMAbgD9ACABYQBlAGQA/QAgAHAAcgBvAGYA -aQBsBeQF6AXVBeQF2QXcACAARwByAGEAeQAgBdsF3AXcBdkAUAByAG8AZgBpAGwAIABnAHIAaQAgAGcA -ZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBmAGUAbgAtAFAA -cgBvAGYAaQBsAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwBHAGUA -bgBlAHIAaQBzAGsAIABnAHIA5QBzAGsAYQBsAGUAcAByAG8AZgBpAGxmbpAacHBepmPPj/Blh072TgCC -LDCwMOwwpDDXMO0w1TChMKQw6wOTA7UDvQO5A7oDzAAgA8ADwQO/A8YDrwO7ACADswO6A8EDuQBQAGUA -cgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGMAaQBuAHoAZQBuAHQAbwBzAEEAbABnAGUA -bQBlAGUAbgAgAGcAcgBpAGoAcwBwAHIAbwBmAGkAZQBsAFAAZQByAGYAaQBsACAAZwByAGkAcwAgAGcA -ZQBuAOkAcgBpAGMAbw5CDhsOIw5EDh8OJQ5MDioONQ5ADhcOMg4XDjEOSA4nDkQOGwBHAGUAbgBlAGwA -IABHAHIAaQAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAHAAcgBvAGYA -aQBpAGwAaQBHAGUAbgBlAHIAaQENAGsAaQAgAHAAcgBvAGYAaQBsACAAcwBpAHYAaQBoACAAdABvAG4A -bwB2AGEAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABzAHoAYQByAG8BWwBjAGkE -HgQxBEkEOAQ5ACAEQQQ1BEAESwQ5ACAEPwRABD4ERAQ4BDsETAZFBkQGQQAgBioGOQYxBkoGQQAgAEcA -cgBhAHkAIAYnBkQGOQYnBkUARwBlAG4AZQByAGUAbAAgAGcAcgDlAHQAbwBuAGUAYgBlAHMAawByAGkA -dgBlAGwAcwBlAAB0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdodHMgcmVz -ZXJ2ZWQuAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAAAAABAc0AANIlJicoWiRjbGFzc25hbWVY -JGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoycpKlpOU0ltYWdlUmVwWE5TT2JqZWN00iUmLC1XTlNB -cnJheaIsKtIlJi8wXk5TTXV0YWJsZUFycmF5oy8sKtMyMwo0NTZXTlNXaGl0ZVxOU0NvbG9yU3BhY2VE -MCAwABADgAzSJSY4OVdOU0NvbG9yojgq0iUmOzxXTlNJbWFnZaI7Kl8QD05TS2V5ZWRBcmNoaXZlctE/ -QFRyb290gAEACAARABoAIwAtADIANwBGAEwAVwBeAGUAcgB5AIEAgwCFAIoAjACOAJUAmgClAKcAqQCr -ALAAswC1ALcAuQC7AMAA1wDZANsJiwmQCZsJpAm3CbsJxgnPCdQJ3AnfCeQJ8wn3Cf4KBgoTChgKGgoc -CiEKKQosCjEKOQo8Ck4KUQpWAAAAAAAAAgEAAAAAAAAAQQAAAAAAAAAAAAAAAAAAClg +BYAGgAkQANIgCiEiXxAUTlNUSUZGUmVwcmVzZW50YXRpb26AB4AITxEIxE1NACoAAAAKAAAAEAEAAAMA +AAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEKAAMA +AAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEWAAMAAAABAAEAAAEXAAQA +AAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFTAAMAAAACAAEAAYdzAAcA +AAf0AAAA0AAAAAAAAAf0YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAAAABhY3NwQVBQTAAAAABu +b25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAABmZjcHJ0AAAHmAAAADh3 +dHB0AAAH0AAAABRrVFJDAAAH5AAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAA +AAAAAAAAFUdlbmVyaWMgR3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNrU0sAAAAqAAABhGVuVVMAAAAoAAABrmNhRVMA +AAAsAAAB1nZpVk4AAAAsAAACAnB0QlIAAAAqAAACLnVrVUEAAAAsAAACWGZyRlUAAAAqAAAChGh1SFUA +AAAuAAACrnpoVFcAAAAQAAAC3G5iTk8AAAAsAAAC7GtvS1IAAAAYAAADGGNzQ1oAAAAkAAADMGhlSUwA +AAAgAAADVHJvUk8AAAAkAAADdGRlREUAAAA6AAADmGl0SVQAAAAuAAAD0nN2U0UAAAAuAAAEAHpoQ04A +AAAQAAAELmphSlAAAAAWAAAEPmVsR1IAAAAkAAAEVHB0UE8AAAA4AAAEeG5sTkwAAAAqAAAEsGVzRVMA +AAAoAAAE2nRoVEgAAAAkAAAFAnRyVFIAAAAiAAAFJmZpRkkAAAAsAAAFSGhySFIAAAA6AAAFdHBsUEwA +AAA2AAAFrnJ1UlUAAAAmAAAF5GFyRUcAAAAoAAAGCmRhREsAAAA0AAAGMgBWAWEAZQBvAGIAZQBjAG4A +/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAFAAcgBvAGYA +aQBsAGUAUABlAHIAZgBpAGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6AByAGkAYwBDHqUAdQAgAGgA +7ABuAGgAIABNAOAAdQAgAHgA4QBtACAAQwBoAHUAbgBnAFAAZQByAGYAaQBsACAAQwBpAG4AegBhACAA +RwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAEcAcgBhAHkA +UAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAGcAcgBpAHMAwQBsAHQAYQBsAOEAbgBvAHMA +IABzAHoA/AByAGsAZQAgAHAAcgBvAGYAaQBskBp1KHBwlo6Ccl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsA +IABnAHIA5QB0AG8AbgBlAHAAcgBvAGYAaQBsx3y8GAAgAEcAcgBhAHkAINUEuFzTDMd8AE8AYgBlAGMA +bgD9ACABYQBlAGQA/QAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAARwByAGEAeQAgBdsF3AXcBdkA +UAByAG8AZgBpAGwAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAA +RwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBvAGYAaQBsAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkA +bwAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QBzAGsAYQBsAGUAcAByAG8A +ZgBpAGxmbpAacHBepmPPj/Blh072TgCCLDCwMOwwpDDXMO0w1TChMKQw6wOTA7UDvQO5A7oDzAAgA8AD +wQO/A8YDrwO7ACADswO6A8EDuQBQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGMA +aQBuAHoAZQBuAHQAbwBzAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwBwAHIAbwBmAGkAZQBsAFAA +ZQByAGYAaQBsACAAZwByAGkAcwAgAGcAZQBuAOkAcgBpAGMAbw5CDhsOIw5EDh8OJQ5MDioONQ5ADhcO +Mg4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABHAHIAaQAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA +bgAgAGgAYQByAG0AYQBhAHAAcgBvAGYAaQBpAGwAaQBHAGUAbgBlAHIAaQENAGsAaQAgAHAAcgBvAGYA +aQBsACAAcwBpAHYAaQBoACAAdABvAG4AbwB2AGEAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8A +ZgBpAGwAIABzAHoAYQByAG8BWwBjAGkEHgQxBEkEOAQ5ACAEQQQ1BEAESwQ5ACAEPwRABD4ERAQ4BDsE +TAZFBkQGQQAgBioGOQYxBkoGQQAgAEcAcgBhAHkAIAYnBkQGOQYnBkUARwBlAG4AZQByAGUAbAAgAGcA +cgDlAHQAbwBuAGUAYgBlAHMAawByAGkAdgBlAGwAcwBlAAB0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFw +cGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAA +AAABAc0AANIlJicoWiRjbGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoycpKlpOU0lt +YWdlUmVwWE5TT2JqZWN00iUmLC1XTlNBcnJheaIsKtIlJi8wXk5TTXV0YWJsZUFycmF5oy8sKtMyMwo0 +NTZXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzSJSY4OVdOU0NvbG9yojgq0iUmOzxXTlNJbWFn +ZaI7Kl8QD05TS2V5ZWRBcmNoaXZlctE/QFRyb290gAEACAARABoAIwAtADIANwBGAEwAVwBeAGUAcgB5 +AIEAgwCFAIoAjACOAJUAmgClAKcAqQCrALAAswC1ALcAuQC7AMAA1wDZANsJowmoCbMJvAnPCdMJ3gnn +CewJ9An3CfwKCwoPChYKHgorCjAKMgo0CjkKQQpECkkKUQpUCmYKaQpuAAAAAAAAAgEAAAAAAAAAQQAA +AAAAAAAAAAAAAAAACnA diff --git a/BGMDriver/BGMDriver.xcodeproj/project.pbxproj b/BGMDriver/BGMDriver.xcodeproj/project.pbxproj index 2116d06..eaeed42 100644 --- a/BGMDriver/BGMDriver.xcodeproj/project.pbxproj +++ b/BGMDriver/BGMDriver.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 1C7010761F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */; }; 1C7010791F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; }; 1C70107A1F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; }; + 1C780FEF1FEE78E800497FAD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C780FEE1FEE78E800497FAD /* Accelerate.framework */; }; 1C8034DD1BDD073B00668E00 /* BGM_ClientsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */; }; 1CA2A9E21E8D1D08007A76A4 /* BGM_Stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */; }; 1CB8B36E1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */; }; @@ -105,6 +106,7 @@ 1C7010741F05ED5100D8CCDC /* BGM_AudibleState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_AudibleState.h; sourceTree = ""; }; 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_VolumeControl.cpp; sourceTree = ""; }; 1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_VolumeControl.h; sourceTree = ""; }; + 1C780FEE1FEE78E800497FAD /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 1C8034DA1BDD073B00668E00 /* BGMDriverTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMDriverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_ClientsTests.mm; sourceTree = ""; }; 1C8034DE1BDD073B00668E00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -175,6 +177,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1C780FEF1FEE78E800497FAD /* Accelerate.framework in Frameworks */, 2743C9E61D7EF8E00089613B /* libPublicUtility.a in Frameworks */, 2795973E1C9847CF00A002FB /* Foundation.framework in Frameworks */, 1CB8B3761BBBD924000E2DD1 /* CoreAudio.framework in Frameworks */, @@ -324,6 +327,7 @@ 2743CA241D86E2E80089613B /* Frameworks */ = { isa = PBXGroup; children = ( + 1C780FEE1FEE78E800497FAD /* Accelerate.framework */, 1CB8B3741BBBD924000E2DD1 /* CoreAudio.framework */, 1CB8B3751BBBD924000E2DD1 /* CoreFoundation.framework */, 2795973D1C9847CF00A002FB /* Foundation.framework */, diff --git a/BGMDriver/BGMDriver/BGM_AbstractDevice.cpp b/BGMDriver/BGMDriver/BGM_AbstractDevice.cpp index 51fa881..0ad1933 100644 --- a/BGMDriver/BGMDriver/BGM_AbstractDevice.cpp +++ b/BGMDriver/BGMDriver/BGM_AbstractDevice.cpp @@ -241,8 +241,8 @@ void BGM_AbstractDevice::GetPropertyData(AudioObjectID inObjectID, case kAudioDevicePropertyZeroTimeStampPeriod: case kAudioDevicePropertyNominalSampleRate: case kAudioDevicePropertyAvailableNominalSampleRates: - // Crash debug builds if a concrete device delegates a required property that can't be - // handled here or in BGM_Object (the parent of this class). + // Should be unreachable. Reaching this point would mean a concrete device has delegated + // a required property that can't be handled by this class or its parent, BGM_Object. // // See BGM_Device for info about these properties. // @@ -250,7 +250,8 @@ void BGM_AbstractDevice::GetPropertyData(AudioObjectID inObjectID, BGMAssert(false, "BGM_AbstractDevice::GetPropertyData: Property %u not handled in subclass", inAddress.mSelector); - + // Throw in release builds. + Throw(CAException(kAudioHardwareIllegalOperationError)); case kAudioDevicePropertyTransportType: // This value represents how the device is attached to the system. This can be diff --git a/BGMDriver/BGMDriver/BGM_Control.h b/BGMDriver/BGMDriver/BGM_Control.h index fc4e922..349e7d7 100644 --- a/BGMDriver/BGMDriver/BGM_Control.h +++ b/BGMDriver/BGMDriver/BGM_Control.h @@ -22,7 +22,6 @@ // An AudioObject that represents a user-controllable aspect of a device or stream, such as volume // or balance. // -// #ifndef BGMDriver__BGM_Control #define BGMDriver__BGM_Control @@ -43,9 +42,10 @@ protected: AudioClassID inClassID, AudioClassID inBaseClassID, AudioObjectID inOwnerObjectID, - AudioObjectPropertyScope inScope, - AudioObjectPropertyElement inElement - = kAudioObjectPropertyElementMaster); + AudioObjectPropertyScope inScope = + kAudioObjectPropertyScopeOutput, + AudioObjectPropertyElement inElement = + kAudioObjectPropertyElementMaster); #pragma mark Property Operations diff --git a/BGMDriver/BGMDriver/BGM_Device.cpp b/BGMDriver/BGMDriver/BGM_Device.cpp index 4e6cf2d..d38e032 100644 --- a/BGMDriver/BGMDriver/BGM_Device.cpp +++ b/BGMDriver/BGMDriver/BGM_Device.cpp @@ -71,6 +71,8 @@ void BGM_Device::StaticInitializer() { try { + // The main instance, usually referred to in the code as "BGMDevice". This is the device + // that appears in System Preferences as "Background Music". sInstance = new BGM_Device(kObjectID_Device, CFSTR(kDeviceName), CFSTR(kBGMDeviceUID), @@ -81,12 +83,27 @@ void BGM_Device::StaticInitializer() kObjectID_Mute_Output_Master); sInstance->Activate(); + // The instance for system (UI) sounds. sUISoundsInstance = new BGM_Device(kObjectID_Device_UI_Sounds, CFSTR(kDeviceName_UISounds), CFSTR(kBGMDeviceUID_UISounds), CFSTR(kBGMDeviceModelUID_UISounds), kObjectID_Stream_Input_UI_Sounds, - kObjectID_Stream_Output_UI_Sounds); + kObjectID_Stream_Output_UI_Sounds, + kObjectID_Volume_Output_Master_UI_Sounds, + kAudioObjectUnknown); // No mute control. + + // Set up the UI sounds device's volume control. + BGM_VolumeControl& theUISoundsVolumeControl = sUISoundsInstance->mVolumeControl; + // Default to full volume. + theUISoundsVolumeControl.SetVolumeScalar(1.0f); + // Make the volume curve a bit steeper than the default. + theUISoundsVolumeControl.GetVolumeCurve().SetTransferFunction(CAVolumeCurve::kPow4Over1Curve); + // Apply the volume to the device's output stream. The main instance of BGM_Device doesn't + // apply volume to its audio because BGMApp changes the real output device's volume directly + // instead. + theUISoundsVolumeControl.SetWillApplyVolumeToAudio(true); + sUISoundsInstance->Activate(); } catch(...) @@ -101,24 +118,6 @@ void BGM_Device::StaticInitializer() } } -BGM_Device::BGM_Device(AudioObjectID inObjectID, - const CFStringRef __nonnull inDeviceName, - const CFStringRef __nonnull inDeviceUID, - const CFStringRef __nonnull inDeviceModelUID, - AudioObjectID inInputStreamID, - AudioObjectID inOutputStreamID) -: - BGM_Device(inObjectID, - inDeviceName, - inDeviceUID, - inDeviceModelUID, - inInputStreamID, - inOutputStreamID, - kAudioObjectUnknown, - kAudioObjectUnknown) -{ -} - BGM_Device::BGM_Device(AudioObjectID inObjectID, const CFStringRef __nonnull inDeviceName, const CFStringRef __nonnull inDeviceUID, @@ -139,8 +138,8 @@ BGM_Device::BGM_Device(AudioObjectID inObjectID, mInputStream(inInputStreamID, inObjectID, false, kSampleRateDefault), mOutputStream(inOutputStreamID, inObjectID, false, kSampleRateDefault), mAudibleState(), - mVolumeControl(inOutputVolumeControlID, GetObjectID(), kAudioObjectPropertyScopeOutput), - mMuteControl(inOutputMuteControlID, GetObjectID(), kAudioObjectPropertyScopeOutput) + mVolumeControl(inOutputVolumeControlID, GetObjectID()), + mMuteControl(inOutputMuteControlID, GetObjectID()) { // Initialises the loopback clock with the default sample rate and, if there is one, sets the wrapped device to the same sample rate SetSampleRate(kSampleRateDefault); @@ -674,6 +673,8 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient CAException(kAudioHardwareBadPropertySizeError), "BGM_Device::GetPropertyData: not enough space for the return value of " "kAudioDevicePropertyDeviceCanBeDefaultDevice for the device"); + // TODO: Add a field for this and set it in BGM_Device::StaticInitializer so we don't + // have to handle a specific instance differently here. *reinterpret_cast(outData) = (GetObjectID() == kObjectID_Device_UI_Sounds ? 0 : 1); outDataSize = sizeof(UInt32); break; @@ -1299,12 +1300,16 @@ void BGM_Device::WillDoIOOperation(UInt32 inOperationID, bool& outWillDo, bool& outWillDo = true; outWillDoInPlace = true; break; - + + case kAudioServerPlugInIOOperationProcessMix: + outWillDo = mVolumeControl.WillApplyVolumeToAudioRT(); + outWillDoInPlace = true; + break; + case kAudioServerPlugInIOOperationCycle: case kAudioServerPlugInIOOperationConvertInput: case kAudioServerPlugInIOOperationProcessInput: case kAudioServerPlugInIOOperationMixOutput: - case kAudioServerPlugInIOOperationProcessMix: case kAudioServerPlugInIOOperationConvertMix: default: outWillDo = false; @@ -1362,6 +1367,23 @@ void BGM_Device::DoIOOperation(AudioObjectID inStreamObjectID, UInt32 inClientID ApplyClientRelativeVolume(inClientID, inIOBufferFrameSize, ioMainBuffer); break; + case kAudioServerPlugInIOOperationProcessMix: + { + // Check the arguments. + ThrowIfNULL(ioMainBuffer, + CAException(kAudioHardwareIllegalOperationError), + "BGM_Device::DoIOOperation: Buffer for " + "kAudioServerPlugInIOOperationProcessMix must not be null"); + + CAMutex::Locker theIOLocker(mIOMutex); + + // We ask to do this IO operation so this device can apply its own volume to the + // stream. Currently, only the UI sounds device does. + mVolumeControl.ApplyVolumeToAudioRT(reinterpret_cast(ioMainBuffer), + inIOBufferFrameSize); + } + break; + case kAudioServerPlugInIOOperationWriteMix: { CAMutex::Locker theIOLocker(mIOMutex); diff --git a/BGMDriver/BGMDriver/BGM_Device.h b/BGMDriver/BGMDriver/BGM_Device.h index 6aa5087..0a75cf1 100644 --- a/BGMDriver/BGMDriver/BGM_Device.h +++ b/BGMDriver/BGMDriver/BGM_Device.h @@ -69,12 +69,6 @@ protected: const CFStringRef __nonnull inDeviceUID, const CFStringRef __nonnull inDeviceModelUID, AudioObjectID inInputStreamID, - AudioObjectID inOutputStreamID); - BGM_Device(AudioObjectID inObjectID, - const CFStringRef __nonnull inDeviceName, - const CFStringRef __nonnull inDeviceUID, - const CFStringRef __nonnull inDeviceModelUID, - AudioObjectID inInputStreamID, AudioObjectID inOutputStreamID, AudioObjectID inOutputVolumeControlID, AudioObjectID inOutputMuteControlID); diff --git a/BGMDriver/BGMDriver/BGM_MuteControl.h b/BGMDriver/BGMDriver/BGM_MuteControl.h index dd6007c..c343d30 100644 --- a/BGMDriver/BGMDriver/BGM_MuteControl.h +++ b/BGMDriver/BGMDriver/BGM_MuteControl.h @@ -46,7 +46,8 @@ class BGM_MuteControl public: BGM_MuteControl(AudioObjectID inObjectID, AudioObjectID inOwnerObjectID, - AudioObjectPropertyScope inScope, + AudioObjectPropertyScope inScope = + kAudioObjectPropertyScopeOutput, AudioObjectPropertyElement inElement = kAudioObjectPropertyElementMaster); diff --git a/BGMDriver/BGMDriver/BGM_PlugInInterface.cpp b/BGMDriver/BGMDriver/BGM_PlugInInterface.cpp index 4d76bd1..c526fea 100644 --- a/BGMDriver/BGMDriver/BGM_PlugInInterface.cpp +++ b/BGMDriver/BGMDriver/BGM_PlugInInterface.cpp @@ -116,6 +116,7 @@ static BGM_Object& BGM_LookUpOwnerObject(AudioObjectID inObjectID) case kObjectID_Device_UI_Sounds: case kObjectID_Stream_Input_UI_Sounds: case kObjectID_Stream_Output_UI_Sounds: + case kObjectID_Volume_Output_Master_UI_Sounds: return BGM_Device::GetUISoundsInstance(); case kObjectID_Device_Null: diff --git a/BGMDriver/BGMDriver/BGM_VolumeControl.cpp b/BGMDriver/BGMDriver/BGM_VolumeControl.cpp index 32a58f5..e0e33dc 100644 --- a/BGMDriver/BGMDriver/BGM_VolumeControl.cpp +++ b/BGMDriver/BGMDriver/BGM_VolumeControl.cpp @@ -31,12 +31,14 @@ #include "CAException.h" #include "CADebugMacros.h" #include "CADispatchQueue.h" +#include "BGM_Utils.h" // STL Includes #include // System Includes #include +#include #pragma clang assume_nonnull begin @@ -56,10 +58,12 @@ BGM_VolumeControl::BGM_VolumeControl(AudioObjectID inObjectID, inElement), mMutex("Volume Control"), mVolumeRaw(kDefaultMinRawVolume), + mAmplitudeGain(0.0f), mMinVolumeRaw(kDefaultMinRawVolume), mMaxVolumeRaw(kDefaultMaxRawVolume), mMinVolumeDb(kDefaultMinDbVolume), - mMaxVolumeDb(kDefaultMaxDbVolume) + mMaxVolumeDb(kDefaultMaxDbVolume), + mWillApplyVolumeToAudio(false) { // Setup the volume curve with the one range mVolumeCurve.AddRange(mMinVolumeRaw, mMaxVolumeRaw, mMinVolumeDb, mMaxVolumeDb); @@ -290,42 +294,28 @@ void BGM_VolumeControl::SetPropertyData(AudioObjectID inObjectID, switch(inAddress.mSelector) { case kAudioLevelControlPropertyScalarValue: - // For the scalar volume, we clamp the new value to [0, 1]. Note that if this - // value changes, it implies that the dB value changed too. { ThrowIf(inDataSize != sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "BGM_VolumeControl::SetPropertyData: wrong size for the data for " "kAudioLevelControlPropertyScalarValue"); - // Read the new scalar volume and clamp it. + // Read the new scalar volume. Float32 theNewVolumeScalar = *reinterpret_cast(inData); - theNewVolumeScalar = std::min(1.0f, std::max(0.0f, theNewVolumeScalar)); - - // Store the new volume. - SInt32 theNewVolumeRaw = mVolumeCurve.ConvertScalarToRaw(theNewVolumeScalar); - SetVolumeRaw(theNewVolumeRaw); + SetVolumeScalar(theNewVolumeScalar); } break; case kAudioLevelControlPropertyDecibelValue: - // For the dB value, we first convert it to a raw value since that is how - // the value is tracked. Note that if this value changes, it implies that the - // scalar value changes as well. { ThrowIf(inDataSize != sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "BGM_VolumeControl::SetPropertyData: wrong size for the data for " "kAudioLevelControlPropertyDecibelValue"); - // Read the new volume in dB and clamp it. + // Read the new volume in dB. Float32 theNewVolumeDb = *reinterpret_cast(inData); - theNewVolumeDb = - std::min(mMaxVolumeDb, std::max(mMinVolumeDb, theNewVolumeDb)); - - // Store the new volume. - SInt32 theNewVolumeRaw = mVolumeCurve.ConvertDBToRaw(theNewVolumeDb); - SetVolumeRaw(theNewVolumeRaw); + SetVolumeDb(theNewVolumeDb); } break; @@ -341,6 +331,75 @@ void BGM_VolumeControl::SetPropertyData(AudioObjectID inObjectID, }; } +#pragma mark Accessors + +void BGM_VolumeControl::SetVolumeScalar(Float32 inNewVolumeScalar) +{ + // For the scalar volume, we clamp the new value to [0, 1]. Note that if this value changes, it + // implies that the dB value changes too. + inNewVolumeScalar = std::min(1.0f, std::max(0.0f, inNewVolumeScalar)); + + // Store the new volume. + SInt32 theNewVolumeRaw = mVolumeCurve.ConvertScalarToRaw(inNewVolumeScalar); + SetVolumeRaw(theNewVolumeRaw); +} + +void BGM_VolumeControl::SetVolumeDb(Float32 inNewVolumeDb) +{ + // For the dB value, we first convert it to a raw value since that is how the value is tracked. + // Note that if this value changes, it implies that the scalar value changes as well. + + // Clamp the new volume. + inNewVolumeDb = std::min(mMaxVolumeDb, std::max(mMinVolumeDb, inNewVolumeDb)); + + // Store the new volume. + SInt32 theNewVolumeRaw = mVolumeCurve.ConvertDBToRaw(inNewVolumeDb); + SetVolumeRaw(theNewVolumeRaw); +} + +void BGM_VolumeControl::SetWillApplyVolumeToAudio(bool inWillApplyVolumeToAudio) +{ + mWillApplyVolumeToAudio = inWillApplyVolumeToAudio; +} + +#pragma mark IO Operations + +bool BGM_VolumeControl::WillApplyVolumeToAudioRT() const +{ + return mWillApplyVolumeToAudio; +} + +void BGM_VolumeControl::ApplyVolumeToAudioRT(Float32* ioBuffer, UInt32 inBufferFrameSize) const +{ + ThrowIf(!mWillApplyVolumeToAudio, + CAException(kAudioHardwareIllegalOperationError), + "BGM_VolumeControl::ApplyVolumeToAudioRT: This control doesn't process audio data"); + + // Don't bother if the change is very unlikely to be perceptible. + if((mAmplitudeGain < 0.99f) || (mAmplitudeGain > 1.01f)) + { + // Apply the amount of gain/loss for the current volume to the audio signal by multiplying + // each sample. This call to vDSP_vsmul is equivalent to + // + // for(UInt32 i = 0; i < inBufferFrameSize * 2; i++) + // { + // ioBuffer[i] *= mAmplitudeGain; + // } + // + // but a bit faster on processors with newer SIMD instructions. However, it shouldn't take + // more than a few microseconds either way. (Unless some of the samples were subnormal + // numbers for some reason.) + // + // It would be a tiny bit faster still to not do this in-place, i.e. use separate input and + // output buffers, but then we'd have to copy the data into the output buffer when the + // volume is at 1.0. With our current use of this class, most people will leave the volume + // at 1.0, so it wouldn't be worth it. + vDSP_vsmul(ioBuffer, 1, &mAmplitudeGain, ioBuffer, 1, inBufferFrameSize * 2); + } +} + +#pragma mark Implementation + void BGM_VolumeControl::SetVolumeRaw(SInt32 inNewVolumeRaw) { CAMutex::Locker theLocker(mMutex); @@ -353,6 +412,41 @@ void BGM_VolumeControl::SetVolumeRaw(SInt32 inNewVolumeRaw) { mVolumeRaw = inNewVolumeRaw; + // CAVolumeCurve deals with volumes in three different scales: scalar, dB and raw. Raw + // volumes are the number of steps along the dB curve, so dB and raw volumes are linearly + // related. + // + // macOS uses the scalar volume to set the position of its volume sliders for the + // device. We have to set the scalar volume to the position of our volume slider for a + // device (more specifically, a linear mapping of it onto [0,1]) or macOS's volume sliders + // or it will work differently to our own. + // + // When we set a new slider position as the device's scalar volume, we convert it to raw + // with CAVolumeCurve::ConvertScalarToRaw, which will "undo the curve". However, we haven't + // applied the curve at that point. + // + // So, to actually apply the curve, we use CAVolumeCurve::ConvertRawToScalar to get the + // linear slider position back, map it onto the range of raw volumes and use + // CAVolumeCurve::ConvertRawToScalar again to apply the curve. + // + // It might be that we should be using CAVolumeCurve with transfer functions x^n where + // 0 < n < 1, but a lot more of the transfer functions it supports have n >= 1, including + // the default one. So I'm a bit confused. + // + // TODO: I think this means the dB volume we report will be wrong. It also makes the code + // pretty confusing. + Float32 theSliderPosition = mVolumeCurve.ConvertRawToScalar(mVolumeRaw); + + // TODO: This assumes the control should never boost the signal. (So, technically, it never + // actually applies gain, only loss.) + SInt32 theRawRange = mMaxVolumeRaw - mMinVolumeRaw; + SInt32 theSliderPositionInRawSteps = static_cast(theSliderPosition * theRawRange); + theSliderPositionInRawSteps += mMinVolumeRaw; + + mAmplitudeGain = mVolumeCurve.ConvertRawToScalar(theSliderPositionInRawSteps); + + BGMAssert((mAmplitudeGain >= 0.0f) && (mAmplitudeGain <= 1.0f), "Gain not in [0,1]"); + // Send notifications. CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ AudioObjectPropertyAddress theChangedProperties[2]; diff --git a/BGMDriver/BGMDriver/BGM_VolumeControl.h b/BGMDriver/BGMDriver/BGM_VolumeControl.h index cb9c041..196cf30 100644 --- a/BGMDriver/BGMDriver/BGM_VolumeControl.h +++ b/BGMDriver/BGMDriver/BGM_VolumeControl.h @@ -43,7 +43,8 @@ class BGM_VolumeControl public: BGM_VolumeControl(AudioObjectID inObjectID, AudioObjectID inOwnerObjectID, - AudioObjectPropertyScope inScope, + AudioObjectPropertyScope inScope = + kAudioObjectPropertyScopeOutput, AudioObjectPropertyElement inElement = kAudioObjectPropertyElementMaster); @@ -76,6 +77,63 @@ public: UInt32 inDataSize, const void* inData); +#pragma mark Accessors + + /*! + @return The curve used by this control to convert volume values from scalar into signal gain + and/or decibels. A continuous 2D function. + */ + CAVolumeCurve& GetVolumeCurve() { return mVolumeCurve; } + + /*! + Set the volume of this control to a given position along its volume curve. (See + GetVolumeCurve.) + + Passing 1.0 sets the volume to the maximum and 0.0 sets it to the minimum. The gain/loss the + control applies (and/or reports to apply) to the audio it controls is given by the y-position + of the curve at the x-position inNewVolumeScalar. + + In general, since the control's volume curve will be applied to the given value, it should be + linearly related to a volume input by the user. + + @param inNewVolumeScalar The volume to set. Will be clamped to [0.0, 1.0]. + */ + void SetVolumeScalar(Float32 inNewVolumeScalar); + /*! + Set the volume of this control in decibels. + + @param inNewVolumeDb The volume to set. Will be clamped to the minimum/maximum dB volumes of + the control. See GetVolumeCurve. + */ + void SetVolumeDb(Float32 inNewVolumeDb); + + /*! + Set this volume control to apply its volume to audio data, which allows clients to call + ApplyVolumeToAudioRT. When this is set true, WillApplyVolumeToAudioRT will return true. Set to + false initially. + */ + void SetWillApplyVolumeToAudio(bool inWillApplyVolumeToAudio); + +#pragma mark IO Operations + + /*! + @return True if clients should use ApplyVolumeToAudioRT to apply this volume control's volume + to their audio data while doing IO. + */ + bool WillApplyVolumeToAudioRT() const; + /*! + Apply this volume control's volume to the samples in ioBuffer. That is, increase/decrease the + volumes of the samples by the current volume of this control. + + @param ioBuffer The audio sample buffer to process. + @param inBufferFrameSize The number of sample frames in ioBuffer. The audio is assumed to be in + stereo, i.e. two samples per frame. (Though, hopefully we'll support + more at some point.) + @throws CAException If SetWillApplyVolumeToAudio hasn't been used to set this control to apply + its volume to audio data. + */ + void ApplyVolumeToAudioRT(Float32* ioBuffer, UInt32 inBufferFrameSize) const; + #pragma mark Implementation protected: @@ -96,6 +154,11 @@ private: Float32 mMaxVolumeDb; CAVolumeCurve mVolumeCurve; + // The gain (or loss) to apply to an audio signal to increase/decrease its volume by the current + // volume of this control. + Float32 mAmplitudeGain; + + bool mWillApplyVolumeToAudio; }; diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h index 4e68f6f..b603037 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h @@ -69,7 +69,7 @@ public: bool mIsMusicPlayer = false; // The client's volume relative to other clients. In the range [0.0, 4.0], defaults to 1.0 (unchanged). - // mRelativeVolumeCurve is applied this this value when it's set. + // mRelativeVolumeCurve is applied to this value when it's set. Float32 mRelativeVolume = 1.0; // The client's pan position, in the range [-100, 100] where -100 is left and 100 is right diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp index 34ddd5d..95d8a07 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp @@ -300,7 +300,7 @@ Float32 BGM_Clients::GetClientRelativeVolumeRT(UInt32 inClientID) const { BGM_Client theClient; bool didGetClient = mClientMap.GetClientRT(inClientID, &theClient); - return (didGetClient ? theClient.mRelativeVolume : 1.0); + return (didGetClient ? theClient.mRelativeVolume : 1.0f); } SInt32 BGM_Clients::GetClientPanPositionRT(UInt32 inClientID) const diff --git a/DEVELOPING.md b/DEVELOPING.md index c4a6b70..247bca8 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -6,17 +6,17 @@ The codebase is split into two projects: BGMDriver, a [userspace](https://en.wik Audio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) [HAL](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingAudioDrivers/AudioOnMacOSX/AudioOnMacOSX.html#//apple_ref/doc/uid/TP30000730-TPXREF104) [plugin](https://developer.apple.com/library/prerelease/content/samplecode/AudioDriverExamples/Listings/ReadMe_txt.html) -that publishes the virtual audio device, and BGMApp, which handles the UI, passing audio from the virtual device to the -real output device and a few other things. The virtual device is usually referred to as "BGMDevice" in the code. Any -code shared between the two projects is kept in the `SharedSource` dir. +that publishes the virtual audio device[1](#f1), and BGMApp, which handles the UI, passing audio from +the virtual device to the real output device and a few other things. The virtual device is usually referred to as +"BGMDevice" in the code. Any code shared between the two projects is kept in the `SharedSource` dir. ## Summary -From the user's perspective, BGMDevice appears as one input device and one output device, both named "Background Music -Device". They're shown in `System Preferences > Sound` along with the real audio devices. +From the user's perspective, BGMDevice appears as one input device and one output device, both named "Background Music". +They're shown in `System Preferences > Sound` along with the real audio devices. When you start BGMApp, it sets BGMDevice as your system's default output device so the system (i.e. Core Audio) will -start sending all[1](#f1) your audio data to BGMDriver. BGMDriver plays that audio on BGMDevice's +start sending all[2](#f2) your audio data to BGMDriver. BGMDriver plays that audio on BGMDevice's input stream, and the user can record it by selecting the Background Music device in QuickTime the same way they'd select a microphone. @@ -216,8 +216,10 @@ Scheme...`, select the Background Music scheme, and add the environment var in R ---- -[1] All, unless you're playing audio through a program that's set to always use a specific device, or one -that doesn't switch to the new default device right away. The latter would usually be a bug in that program and I doubt -we could do anything about it. [↩](#a1) +[1] It actually publishes two devices -- the main one and one for UI-related sounds, but you probably +only need to know about the main one. [↩](#a1) + +[2] All, unless you're playing audio through a program that's set to always use a specific device or, +for some reason, doesn't switch to the new default device right away. [↩](#a2) diff --git a/README.md b/README.md index aec18a3..76fbcb3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ the Background Music device. You can create the aggregate device using the Audio ## Install from source Building should take less than a minute, but you'll need [Xcode](https://developer.apple.com/xcode/download/) version -7 or higher. +8 or higher. If you're comfortable with it, you can just paste the following at a Terminal prompt. diff --git a/SharedSource/BGM_Types.h b/SharedSource/BGM_Types.h index 7dcb883..02abd70 100644 --- a/SharedSource/BGM_Types.h +++ b/SharedSource/BGM_Types.h @@ -72,6 +72,7 @@ enum kObjectID_Device_UI_Sounds = 9, // Belongs to kObjectID_PlugIn kObjectID_Stream_Input_UI_Sounds = 10, // Belongs to kObjectID_Device_UI_Sounds kObjectID_Stream_Output_UI_Sounds = 11, // Belongs to kObjectID_Device_UI_Sounds + kObjectID_Volume_Output_Master_UI_Sounds = 12, // Belongs to kObjectID_Device_UI_Sounds }; // AudioObjectPropertyElement docs: "Elements are numbered sequentially where 0 represents the diff --git a/build_and_install.sh b/build_and_install.sh index 6399061..88b5030 100755 --- a/build_and_install.sh +++ b/build_and_install.sh @@ -148,8 +148,7 @@ if ! [[ -x "${XCODEBUILD}" ]]; then XCODEBUILD=$(/usr/bin/xcrun --find xcodebuild &2>>${LOG_FILE} || true) fi -# TODO: Update this when/if Xcode 6 is supported. -RECOMMENDED_MIN_XCODE_VERSION=7 +RECOMMENDED_MIN_XCODE_VERSION=8 usage() { echo "Usage: $0 [options]" >&2