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.
This commit is contained in:
Kyle Neideck 2017-12-26 23:04:20 +11:00
parent bd90399ce3
commit f64cf41f8a
No known key found for this signature in database
GPG key ID: CAA8D9B8E39EC18C
25 changed files with 567 additions and 164 deletions

View file

@ -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 = "<group>"; };
1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = "<group>"; };
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = "<group>"; };
1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMSystemSoundsVolume.h; sourceTree = "<group>"; };
1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMSystemSoundsVolume.mm; sourceTree = "<group>"; };
1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = "<group>"; };
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputVolumeMenuItem.h; sourceTree = "<group>"; };
@ -286,7 +290,7 @@
1CE7064A1BF1EC0600BFC06D /* BGMOutputDevicePrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMOutputDevicePrefs.h; path = Preferences/BGMOutputDevicePrefs.h; sourceTree = "<group>"; };
1CE7064B1BF1EC0600BFC06D /* BGMOutputDevicePrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMOutputDevicePrefs.mm; path = Preferences/BGMOutputDevicePrefs.mm; sourceTree = "<group>"; };
1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = UnitTests/Mock_CAHALAudioSystemObject.cpp; sourceTree = "<group>"; };
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = "<group>"; };
1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = "<group>"; };
1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMAudioDevice.cpp; sourceTree = "<group>"; };
@ -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 = "<group>"; };
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 = "<group>"; };
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 */,

View file

@ -36,12 +36,19 @@ 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 (readonly) BGMAudioDeviceManager* audioDevices;

View file

@ -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)

View file

@ -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];
}

View file

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

View file

@ -27,6 +27,8 @@
#import <Cocoa/Cocoa.h>
#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

View file

@ -35,38 +35,38 @@
#import <CoreAudio/AudioHardware.h>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
//
// 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 <Cocoa/Cocoa.h>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
//
// 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

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<development version="7000" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<development version="8000" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -23,6 +24,8 @@
<outlet property="outputVolumeLabel" destination="wfC-C6-SLv" id="Nuf-mo-osG"/>
<outlet property="outputVolumeSlider" destination="9Ru-Sc-dqC" id="wv0-Md-BwF"/>
<outlet property="outputVolumeView" destination="JOz-H1-mj9" id="xeJ-fk-NMI"/>
<outlet property="systemSoundsSlider" destination="gyd-WV-2ju" id="NEe-5W-EI5"/>
<outlet property="systemSoundsView" destination="dBD-CE-4dw" id="4SD-Z1-akp"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
@ -111,7 +114,7 @@
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField identifier="PanLeft" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9jc-9i-jw2">
<textField identifier="PanLeft" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9jc-9i-jw2">
<rect key="frame" x="162" y="-1" width="12" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="L" id="hgE-7A-bez">
@ -120,7 +123,7 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField identifier="PanRight" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1lZ-hX-6Kl">
<textField identifier="PanRight" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1lZ-hX-6Kl">
<rect key="frame" x="228" y="-1" width="12" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="R" id="lzr-NO-0Na">
@ -130,7 +133,7 @@
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="117.5" y="-117.5"/>
<point key="canvasLocation" x="117" y="-45"/>
</customView>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="Cf4-3V-gl1" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES"/>
@ -211,10 +214,10 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" id="Cdb-RA-YK0">
<rect key="frame" x="1" y="1" width="565" height="243"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" id="LSG-PF-cl8">
<rect key="frame" x="-6" y="0.0" width="576.5" height="243"/>
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="LSG-PF-cl8">
<rect key="frame" x="-6" y="0.0" width="577" height="243"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<size key="minSize" width="565" height="243"/>
@ -232,7 +235,7 @@
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="qCC-lY-zQ6">
<rect key="frame" x="-15" y="1" width="16" height="0.0"/>
<rect key="frame" x="550" y="1" width="16" height="243"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
@ -271,7 +274,7 @@
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" tickMarkPosition="above" sliderType="linear" id="MzM-fe-nKb"/>
<accessibility description="Output Volume" help="Sets the volume of your audio output device." identifier="Output Volume"/>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wfC-C6-SLv">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wfC-C6-SLv">
<rect key="frame" x="20" y="25" width="226" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Volume" id="60O-ju-B5C">
@ -281,60 +284,89 @@
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="117.5" y="-216.5"/>
<point key="canvasLocation" x="117" y="-219"/>
</customView>
<customView id="dBD-CE-4dw" userLabel="Output Volume View">
<rect key="frame" x="0.0" y="0.0" width="269" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<slider identifier="System Sounds Volume" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gyd-WV-2ju" userLabel="Output Volume Slider">
<rect key="frame" x="163" y="0.0" width="74" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="VDn-d8-XK3"/>
<accessibility description="System Sounds Volume" help="Volume of alerts, notification sounds, etc. Usually short. Can be played by any app."/>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iKs-df-Hp6">
<rect key="frame" x="42" y="1" width="86" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="System Sounds" id="ATK-L8-s8z">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView identifier="SystemSoundsIcon" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="V2D-eM-8yN">
<rect key="frame" x="20" y="1" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSComputer" id="8Xp-9S-hOz"/>
</imageView>
</subviews>
<point key="canvasLocation" x="116.5" y="-133"/>
</customView>
</objects>
<resources>
<image name="FermataIcon" width="284" height="284"/>
<image name="NSComputer" width="32" height="32"/>
<image name="buttonCell:IXo-C7-3uE:image" width="1" height="1">
<mutableData key="keyedArchiveRepresentation">
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
</mutableData>
</image>
</resources>

View file

@ -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 = "<group>"; };
1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_VolumeControl.cpp; sourceTree = "<group>"; };
1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_VolumeControl.h; sourceTree = "<group>"; };
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 = "<group>"; };
1C8034DE1BDD073B00668E00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -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 */,

View file

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

View file

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

View file

@ -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<UInt32*>(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<Float32*>(ioMainBuffer),
inIOBufferFrameSize);
}
break;
case kAudioServerPlugInIOOperationWriteMix:
{
CAMutex::Locker theIOLocker(mIOMutex);

View file

@ -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);

View file

@ -46,7 +46,8 @@ class BGM_MuteControl
public:
BGM_MuteControl(AudioObjectID inObjectID,
AudioObjectID inOwnerObjectID,
AudioObjectPropertyScope inScope,
AudioObjectPropertyScope inScope =
kAudioObjectPropertyScopeOutput,
AudioObjectPropertyElement inElement =
kAudioObjectPropertyElementMaster);

View file

@ -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:

View file

@ -31,12 +31,14 @@
#include "CAException.h"
#include "CADebugMacros.h"
#include "CADispatchQueue.h"
#include "BGM_Utils.h"
// STL Includes
#include <algorithm>
// System Includes
#include <CoreAudio/AudioHardwareBase.h>
#include <Accelerate/Accelerate.h>
#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<const Float32*>(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<const Float32*>(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<SInt32>(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];

View file

@ -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;
};

View file

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

View file

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

View file

@ -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<sup id="a1">[1](#f1)</sup>, 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<sup id="a1">[1](#f1)</sup> your audio data to BGMDriver. BGMDriver plays that audio on BGMDevice's
start sending all<sup id="a2">[2](#f2)</sup> 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
----
<b id="f1">[1]</b> 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)
<b id="f1">[1]</b> 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)
<b id="f2">[2]</b> 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)

View file

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

View file

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

View file

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