mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-10 06:34:22 +00:00
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:
parent
bd90399ce3
commit
f64cf41f8a
25 changed files with 567 additions and 164 deletions
|
@ -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 */,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
56
BGMApp/BGMApp/BGMSystemSoundsVolume.h
Normal file
56
BGMApp/BGMApp/BGMSystemSoundsVolume.h
Normal 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
|
||||
|
88
BGMApp/BGMApp/BGMSystemSoundsVolume.mm
Normal file
88
BGMApp/BGMApp/BGMSystemSoundsVolume.mm
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -46,7 +46,8 @@ class BGM_MuteControl
|
|||
public:
|
||||
BGM_MuteControl(AudioObjectID inObjectID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyScope inScope =
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
AudioObjectPropertyElement inElement =
|
||||
kAudioObjectPropertyElementMaster);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue