Move the pan sliders into an "extra controls" section of the menu items.

Also add centre tick marks and "L"/"R" (left/right) labels to them.

The idea is to eventually include extra controls like an equalizer, recording
apps, hiding/ignoring apps, routing apps, etc.

Also, remove the left margin from the App Volumes menu items. Even macOS isn't
consistent about including that margin, as far as I can tell.
This commit is contained in:
Kyle Neideck
2017-01-21 17:04:01 +11:00
parent a62fae6fd1
commit cdea147010
8 changed files with 263 additions and 124 deletions
+9 -17
View File
@@ -35,40 +35,32 @@
// Protocol for the UI custom classes
@protocol BGMAppVolumeSubview <NSObject>
@protocol BGMAppVolumeMenuItemSubview <NSObject>
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx;
@end
@protocol BGMAppPanSubview <NSObject>
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx;
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)item;
@end
// Custom classes for the UI elements in the app volume menu items
@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeSubview>
@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeMenuItemSubview>
@end
@interface BGMAVM_AppNameLabel : NSTextField <BGMAppVolumeSubview>
@interface BGMAVM_AppNameLabel : NSTextField <BGMAppVolumeMenuItemSubview>
@end
@interface BGMAVM_VolumeSlider : NSSlider <BGMAppVolumeSubview>
@interface BGMAVM_ShowMoreControlsButton : NSButton <BGMAppVolumeMenuItemSubview>
@end
@interface BGMAVM_VolumeSlider : NSSlider <BGMAppVolumeMenuItemSubview>
- (void) setRelativeVolume:(NSNumber*)relativeVolume;
@end
@interface BGMAVM_PanSlider : NSSlider <BGMAppPanSubview>
@interface BGMAVM_PanSlider : NSSlider <BGMAppVolumeMenuItemSubview>
- (void) setPanPosition:(NSNumber*)panPosition;
@end
@interface BGMAVM_PanSliderCell : NSSliderCell
- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped;
@end
+124 -74
View File
@@ -17,7 +17,8 @@
// BGMAppVolumes.m
// BGMApp
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2016, 2017 Kyle Neideck
// Copyright © 2017 Andrew Tonner
//
// Self Include
@@ -25,6 +26,7 @@
// BGM Includes
#include "BGM_Types.h"
#include "BGM_Utils.h"
// PublicUtility Includes
#include "CACFDictionary.h"
@@ -32,14 +34,20 @@
#include "CACFString.h"
static NSInteger const kAppVolumesMenuItemTag = 3;
// Tags for UI elements in MainMenu.xib
static NSInteger const kAppVolumesHeadingMenuItemTag = 3;
static NSInteger const kSeparatorBelowAppVolumesMenuItemTag = 4;
static float const kSlidersSnapWithin = 5;
static CGFloat const kAppVolumeViewInitialHeight = 20;
@implementation BGMAppVolumes {
NSMenu* bgmMenu;
NSView* appVolumeView;
CGFloat appVolumeViewFullHeight;
BGMAudioDeviceManager* audioDevices;
}
@@ -47,6 +55,7 @@ static float const kSlidersSnapWithin = 5;
if ((self = [super init])) {
bgmMenu = menu;
appVolumeView = view;
appVolumeViewFullHeight = appVolumeView.frame.size.height;
audioDevices = devices;
// Create the menu items for controlling app volumes
@@ -66,23 +75,26 @@ static float const kSlidersSnapWithin = 5;
[[NSWorkspace sharedWorkspace] removeObserver:self forKeyPath:@"runningApplications" context:nil];
}
#pragma mark UI Modifications
- (void) insertMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {
NSAssert([NSThread isMainThread], @"insertMenuItemsForApps is not thread safe");
#ifndef NS_BLOCK_ASSERTIONS // If assertions are enabled
NSInteger numMenuItemsBeforeInsert =
[bgmMenu indexOfItemWithTag:kSeparatorBelowAppVolumesMenuItemTag] - [bgmMenu indexOfItemWithTag:kAppVolumesMenuItemTag] - 1;
auto numMenuItems = [&self]() {
NSInteger headingIdx = [bgmMenu indexOfItemWithTag:kAppVolumesHeadingMenuItemTag];
NSInteger separatorIdx = [bgmMenu indexOfItemWithTag:kSeparatorBelowAppVolumesMenuItemTag];
return separatorIdx - headingIdx - 1;
};
NSInteger numMenuItemsBeforeInsert = numMenuItems();
NSUInteger numApps = 0;
#endif
// Create a blank menu item to copy as a template
NSMenuItem* blankItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
blankItem.view = appVolumeView;
// Get the app volumes currently set on the device
CACFArray appVolumesOnDevice((CFArrayRef)[audioDevices bgmDevice].GetPropertyData_CFType(kBGMAppVolumesAddress), false);
NSInteger index = [bgmMenu indexOfItemWithTag:kAppVolumesMenuItemTag] + 1;
NSInteger index = [bgmMenu indexOfItemWithTag:kAppVolumesHeadingMenuItemTag] + 1;
// Add a volume-control menu item for each app
for (NSRunningApplication* app in apps) {
@@ -95,15 +107,12 @@ static float const kSlidersSnapWithin = 5;
numApps++;
#endif
NSMenuItem* appVolItem = [blankItem copy];
NSMenuItem* appVolItem = [self createBlankAppVolumeMenuItem];
// Look through the menu item's subviews for the ones we want to set up
for (NSView* subview in appVolItem.view.subviews) {
if ([subview conformsToProtocol:@protocol(BGMAppVolumeSubview)]) {
[subview performSelector:@selector(setUpWithApp:context:) withObject:app withObject:self];
}
if ([subview conformsToProtocol:@protocol(BGMAppPanSubview)]) {
[subview performSelector:@selector(setUpWithApp:context:) withObject:app withObject:self];
if ([subview conformsToProtocol:@protocol(BGMAppVolumeMenuItemSubview)]) {
[(NSView<BGMAppVolumeMenuItemSubview>*)subview setUpWithApp:app context:self menuItem:appVolItem];
}
}
@@ -116,21 +125,27 @@ static float const kSlidersSnapWithin = 5;
[bgmMenu insertItem:appVolItem atIndex:index];
}
#ifndef NS_BLOCK_ASSERTIONS // If assertions are enabled
NSInteger numMenuItemsAfterInsert =
[bgmMenu indexOfItemWithTag:kSeparatorBelowAppVolumesMenuItemTag] - [bgmMenu indexOfItemWithTag:kAppVolumesMenuItemTag] - 1;
NSAssert3(numMenuItemsAfterInsert == (numMenuItemsBeforeInsert + numApps),
@"Did not add the expected number of menu items. numMenuItemsBeforeInsert=%ld numMenuItemsAfterInsert=%ld numAppsToAdd=%lu",
NSAssert3(numMenuItems() == (numMenuItemsBeforeInsert + numApps),
@"Added more/fewer menu items than there were apps. Items before: %ld, items after: %ld, apps: %lu",
(long)numMenuItemsBeforeInsert,
(long)numMenuItemsAfterInsert,
(long)numMenuItems(),
(unsigned long)numApps);
#endif
}
// Create a blank menu item to copy as a template.
- (NSMenuItem*) createBlankAppVolumeMenuItem {
NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
menuItem.view = appVolumeView;
menuItem = [menuItem copy]; // So we can modify a copy of the view, rather than the template itself.
return menuItem;
}
- (void) removeMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {
NSAssert([NSThread isMainThread], @"removeMenuItemsForApps is not thread safe");
NSInteger firstItemIndex = [bgmMenu indexOfItemWithTag:kAppVolumesMenuItemTag] + 1;
NSInteger firstItemIndex = [bgmMenu indexOfItemWithTag:kAppVolumesHeadingMenuItemTag] + 1;
NSInteger lastItemIndex = [bgmMenu indexOfItemWithTag:kSeparatorBelowAppVolumesMenuItemTag] - 1;
// Check each app volume menu item, removing the items that control one of the given apps
@@ -188,6 +203,55 @@ static float const kSlidersSnapWithin = 5;
}
}
- (void) showHideExtraControls:(BGMAVM_ShowMoreControlsButton*)button {
// Show or hide an app's extra controls, currently only pan, in its App Volumes menu item.
NSMenuItem* menuItem = button.cell.representedObject;
BGMAssert(button, "!button");
BGMAssert(menuItem, "!menuItem");
CGFloat width = menuItem.view.frame.size.width;
CGFloat height = menuItem.view.frame.size.height;
#if DEBUG
const char* appName = [((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];
#endif
auto nearEnough = [](CGFloat x, CGFloat y) { // Shouldn't be necessary, but just in case.
return fabs(x - y) < 0.01; // We don't need much precision.
};
if (nearEnough(button.frameCenterRotation, 0.0)) {
// Hide extra controls
DebugMsg("BGMAppVolumes::showHideExtraControls: Hiding extra controls (%s)", appName);
BGMAssert(nearEnough(height, appVolumeViewFullHeight), "Extra controls were already hidden");
// Make the menu item shorter to hide the extra controls. Keep the width unchanged.
menuItem.view.frameSize = { width, kAppVolumeViewInitialHeight };
// Turn the button upside down so the arrowhead points down.
button.frameCenterRotation = 180.0;
// Move the button up slightly so it aligns with the volume slider.
[button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y - 1)];
} else {
// Show extra controls
DebugMsg("BGMAppVolumes::showHideExtraControls: Showing extra controls (%s)", appName);
BGMAssert(nearEnough(button.frameCenterRotation, 180.0), "Unexpected button rotation");
BGMAssert(nearEnough(height, kAppVolumeViewInitialHeight), "Extra controls were already shown");
// Make the menu item taller to show the extra controls. Keep the width unchanged.
menuItem.view.frameSize = { width, appVolumeViewFullHeight };
// Turn the button rightside up so the arrowhead points up.
button.frameCenterRotation = 0.0;
// Move the button down slightly, back to it's original position.
[button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y + 1)];
}
}
#pragma mark KVO
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
#pragma unused (object, context)
@@ -220,6 +284,8 @@ static float const kSlidersSnapWithin = 5;
}
}
#pragma mark BGMDevice Communication
- (void) sendVolumeChangeToBGMDevice:(SInt32)newVolume appProcessID:(pid_t)appProcessID appBundleID:(NSString*)appBundleID {
CACFDictionary appVolumeChange(true);
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), appProcessID);
@@ -246,14 +312,17 @@ static float const kSlidersSnapWithin = 5;
[audioDevices bgmDevice].SetPropertyData_CFType(kBGMAppVolumesAddress, appVolumeChanges.AsPropertyList());
}
@end
#pragma mark Custom Classes (IB)
// Custom classes for the UI elements in the app volume menu items
@implementation BGMAVM_AppIcon
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx {
#pragma unused (ctx)
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
#pragma unused (ctx, menuItem)
self.image = app.icon;
}
@@ -262,8 +331,8 @@ static float const kSlidersSnapWithin = 5;
@implementation BGMAVM_AppNameLabel
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx {
#pragma unused (ctx)
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
#pragma unused (ctx, menuItem)
NSString* name = app.localizedName ? (NSString*)app.localizedName : @"";
self.stringValue = name;
@@ -271,6 +340,26 @@ static float const kSlidersSnapWithin = 5;
@end
@implementation BGMAVM_ShowMoreControlsButton
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
#pragma unused (app)
// Set up the button that show/hide the extra controls (currently only a pan slider) for the app.
self.cell.representedObject = menuItem;
self.target = ctx;
self.action = @selector(showHideExtraControls:);
// The menu item starts out with the extra controls visible, so we hide them here.
//
// TODO: Leave them visible if any of the controls are set to non-default values. The user has no way to
// tell otherwise. Maybe we should also make this button look different if the controls are hidden
// when they have non-default values.
[ctx showHideExtraControls:self];
}
@end
@implementation BGMAVM_VolumeSlider {
// Will be set to -1 for apps without a pid
pid_t appProcessID;
@@ -278,7 +367,9 @@ static float const kSlidersSnapWithin = 5;
BGMAppVolumes* context;
}
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx {
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
#pragma unused (menuItem)
context = ctx;
self.target = self;
@@ -291,8 +382,10 @@ static float const kSlidersSnapWithin = 5;
self.minValue = kAppRelativeVolumeMinRawValue;
}
// We have to handle snapping for volume sliders ourselves because adding a tick mark (snap point) in Interface Builder
// changes how the slider looks.
- (void) snap {
// Snap to the 50% point
// Snap to the 50% point.
float midPoint = static_cast<float>((self.maxValue + self.minValue) / 2);
if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) {
self.floatValue = midPoint;
@@ -323,26 +416,9 @@ static float const kSlidersSnapWithin = 5;
BGMAppVolumes* context;
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder: coder];
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
#pragma unused (menuItem)
if(self) {
NSSliderCell * oldCell = [self cell];
BGMAVM_PanSliderCell *cell = [[BGMAVM_PanSliderCell alloc] init];
cell.minValue = oldCell.minValue;
cell.maxValue = oldCell.maxValue;
cell.intValue = oldCell.intValue;
cell.controlSize = oldCell.controlSize;
[self setCell:cell];
}
return self;
}
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx {
context = ctx;
self.target = self;
@@ -355,17 +431,8 @@ static float const kSlidersSnapWithin = 5;
self.maxValue = kAppPanRightRawValue;
}
- (void) snap {
// Snap to the center point
float midPoint = static_cast<float>((self.maxValue + self.minValue) / 2);
if (self.floatValue > (midPoint - 2 * kSlidersSnapWithin) && self.floatValue < (midPoint + 2 * kSlidersSnapWithin)) {
self.floatValue = midPoint;
}
}
- (void) setPanPosition:(NSNumber *)panPosition {
self.intValue = panPosition.intValue;
[self snap];
}
- (void) appPanPositionChanged {
@@ -373,25 +440,8 @@ static float const kSlidersSnapWithin = 5;
DebugMsg("BGMAppVolumes::appPanPositionChanged: App pan position for %s changed to %d", appBundleID.UTF8String, self.intValue);
[self snap];
[context sendPanPositionChangeToBGMDevice:self.intValue appProcessID:appProcessID appBundleID:appBundleID];
}
@end
@implementation BGMAVM_PanSliderCell
- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped {
// Custom slider cell to get rid of the level highlight
// Just run the stock method with the values swapped to get it to do what we want
auto savedValue = self.doubleValue;
self.doubleValue = self.minValue;
[super drawBarInside:rect flipped:flipped];
self.doubleValue = savedValue;
}
@end
+104 -15
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D17a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<development version="7000" identifier="xcode"/>
@@ -60,12 +60,12 @@
</items>
<point key="canvasLocation" x="-184" y="-69.5"/>
</menu>
<customView id="MWB-XH-kFI">
<rect key="frame" x="0.0" y="0.0" width="355" height="20"/>
<customView wantsLayer="YES" id="MWB-XH-kFI">
<rect key="frame" x="0.0" y="0.0" width="269" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField identifier="AppName" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xmd-bg-huG" customClass="BGMAVM_AppNameLabel">
<rect key="frame" x="58" y="4" width="115" height="14"/>
<rect key="frame" x="42" y="28" width="115" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" title="App name here" usesSingleLineMode="YES" id="ZHF-ZW-Oqg">
<font key="font" metaFont="smallSystem"/>
@@ -74,28 +74,66 @@
</textFieldCell>
</textField>
<imageView identifier="AppIcon" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="W04-iT-IUw" customClass="BGMAVM_AppIcon">
<rect key="frame" x="36" y="3" width="16" height="16"/>
<rect key="frame" x="20" y="27" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="6QQ-oO-HxF"/>
</imageView>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1l-Ci-4md" customClass="BGMAVM_VolumeSlider">
<rect key="frame" x="179" y="2" width="74" height="15"/>
<rect key="frame" x="163" y="27" width="74" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="100" doubleValue="50" tickMarkPosition="above" sliderType="linear" id="Jmg-df-9Xl"/>
<accessibility description="Volume"/>
</slider>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2mh-uO-kOV" customClass="BGMAVM_PanSlider">
<rect key="frame" x="261" y="2" width="74" height="15"/>
<slider toolTip="Pan" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2mh-uO-kOV" customClass="BGMAVM_PanSlider">
<rect key="frame" x="163" y="7" width="74" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" minValue="-100" maxValue="100" tickMarkPosition="above" sliderType="linear" id="ccM-Mt-93g"/>
<sliderCell key="cell" controlSize="mini" continuous="YES" state="on" alignment="left" minValue="-100" maxValue="100" tickMarkPosition="below" numberOfTickMarks="1" sliderType="linear" id="ccM-Mt-93g"/>
<accessibility description="Pan"/>
</slider>
<button verticalHuggingPriority="750" fixedFrame="YES" tag="1" springLoaded="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vTG-n6-GxY" customClass="BGMAVM_ShowMoreControlsButton">
<rect key="frame" x="243" y="27" width="15" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<contentFilters>
<ciFilter name="CIAffineTransform">
<configuration>
<null key="inputImage"/>
<affineTransform key="inputTransform" m11="1" m12="0.0" m21="0.0" m22="1" tX="0.0" tY="2"/>
</configuration>
</ciFilter>
</contentFilters>
<buttonCell key="cell" type="square" title="⌃" bezelStyle="shadowlessSquare" image="F928CF85-B22A-4634-8160-BC84F45043E3" alignment="center" lineBreakMode="truncatingTail" imageScaling="proportionallyDown" inset="2" id="IXo-C7-3uE">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" 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">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<accessibility description="Pan left"/>
</textField>
<textField toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" 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">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<accessibility description="Pan right"/>
</textField>
</subviews>
<point key="canvasLocation" x="81" y="-111"/>
<point key="canvasLocation" x="117.5" y="-117.5"/>
</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"/>
<windowPositionMask key="initialPositionMask" topStrut="YES"/>
<rect key="contentRect" x="248" y="350" width="980" height="335"/>
<rect key="screenRect" x="0.0" y="0.0" width="1280" height="778"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<view key="contentView" id="HlB-hX-Y0Y">
<rect key="frame" x="0.0" y="0.0" width="980" height="335"/>
<autoresizingMask key="autoresizingMask"/>
@@ -170,7 +208,7 @@
<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="-4" y="0.0" width="572" height="243"/>
@@ -191,14 +229,14 @@
<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>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6qu-yI-r00">
<rect key="frame" x="391" y="20" width="203" height="11"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="The AirPlay Logo is a trademark of Apple Inc." id="lx7-k3-q16">
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="The AirPlay Logo is a trademark of Apple Inc." id="lx7-k3-q16">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -218,10 +256,61 @@
<color key="textColor" red="0.11543657067200695" green="0.4338699494949495" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" red="0.99215692281723022" green="0.9960784912109375" blue="0.9960784912109375" alpha="1" colorSpace="deviceRGB"/>
</textFieldCell>
<point key="canvasLocation" x="101.5" y="496"/>
<point key="canvasLocation" x="-559" y="-118"/>
</textField>
</objects>
<resources>
<image name="F928CF85-B22A-4634-8160-BC84F45043E3" 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
</mutableData>
</image>
<image name="FermataIcon" width="284" height="284"/>
</resources>
</document>
+11 -8
View File
@@ -19,6 +19,7 @@
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2016 Josh Junon
// Copyright © 2017 Andrew Tonner
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Device.cpp from Apple's SimpleAudioDriver Plug-In sample code. Also uses a few sections from Apple's
@@ -2093,11 +2094,14 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF
Float32 theRelativeVolume = mClients.GetClientRelativeVolumeRT(inClientID);
auto thePanPositionInt = mClients.GetClientPanPositionRT(inClientID);
Float32 thePanPosition = ((Float32)thePanPositionInt)/100.0f;
Float32 thePanPosition = static_cast<Float32>(thePanPositionInt) / 100.0f;
// TODO When we get around to supporting devices with more than two channels, it would be worth looking into
// kAudioFormatProperty_PanningMatrix and kAudioFormatProperty_BalanceFade in AudioFormat.h.
// TODO precompute matrix coefficients w/ volume and do everything in one pass
// Apply balance w/ crossover to the frames in the buffer.
// Apply balance w/ crossfeed to the frames in the buffer.
// Expect samples interleaved, starting with left
if (thePanPosition > 0.0f) {
for (UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2) {
@@ -2117,17 +2121,16 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF
}
}
if(theRelativeVolume != 1.)
if(theRelativeVolume != 1.0f)
{
for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++)
{
Float32 theAdjustedSample = theBuffer[i] * theRelativeVolume;
// Clamp to [-1.0, 1.0]
// Clamp to [-1, 1].
// (This way is roughly 6 times faster than using std::min and std::max because the compiler can vectorize the loop.)
const Float32 theAdjustedSampleClippedBelow = theAdjustedSample < -1. ? -1. : theAdjustedSample;
theBuffer[i] = theAdjustedSampleClippedBelow > 1. ? 1. : theAdjustedSampleClippedBelow;
const Float32 theAdjustedSampleClippedBelow = theAdjustedSample < -1.0f ? -1.0f : theAdjustedSample;
theBuffer[i] = theAdjustedSampleClippedBelow > 1.0f ? 1.0f : theAdjustedSampleClippedBelow;
}
}
}
@@ -2137,7 +2140,7 @@ bool BGM_Device::BufferIsAudible(UInt32 inIOBufferFrameSize, const void* inBuffe
// Check each frame to see if any are audible
for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++)
{
if (0. != reinterpret_cast<const Float32*>(inBuffer)[i]) {
if (0.0f != reinterpret_cast<const Float32*>(inBuffer)[i]) {
return true;
}
}
@@ -18,6 +18,7 @@
// BGMDriver
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2017 Andrew Tonner
//
// Self Include
@@ -374,7 +375,7 @@ bool BGM_ClientMap::SetClientsPanPosition(pid_t searchKey, SInt32 inPanPosition)
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
auto theSetPansInShadowMapsFunc = [&] {
// Look up the clients for the key and update their pan positions
// Look up the clients for the key and update their pan positions
auto theClients = GetClients(searchKey);
if(theClients != nullptr) {
for(auto theClient: *theClients) {
@@ -118,21 +118,24 @@ private:
void CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const;
public:
// Returns true if a client for PID inAppPID was found and its relative volume changed.
bool SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume);
// Returns true if a client for bundle ID inAppBundleID was found and its relative volume changed.
bool SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume);
// Using the template function hits LLVM Bug 23987
// TODO Switch to template function
// Returns true if a client for the key was found and its relative volume changed.
//template <typename T>
//bool SetClientsRelativeVolume(T _Null_unspecified key, Float32 inRelativeVolume);
//bool SetClientsRelativeVolume(T _Null_unspecified searchKey, Float32 inRelativeVolume);
//
//template <typename T>
//bool SetClientsPanPosition(T _Null_unspecified searchKey, SInt32 inPanPosition);
// Returns true if a client for PID inAppPID was found and its relative volume changed.
bool SetClientsPanPosition(pid_t inAppPID, SInt32 inPanPosition);
bool SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume);
// Returns true if a client for bundle ID inAppBundleID was found and its relative volume changed.
bool SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume);
// Returns true if a client for PID inAppPID was found and its pan position changed.
bool SetClientsPanPosition(pid_t inAppPID, SInt32 inPanPosition);
// Returns true if a client for bundle ID inAppBundleID was found and its pan position changed.
bool SetClientsPanPosition(CACFString inAppBundleID, SInt32 inPanPosition);
void StartIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, true); }
@@ -18,6 +18,7 @@
// BGMDriver
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2017 Andrew Tonner
//
// Self Include
+2 -2
View File
@@ -110,8 +110,8 @@ enum
// applied to kBGMAppVolumesKey_RelativeVolume when it's first set and then each of the app's samples are multiplied
// by it.
#define kBGMAppVolumesKey_RelativeVolume "rvol"
// A CFNumber<SInt32> between kAppPanLeftRawValue and kAppPanRightRawValue. A negative value has a higher proportion of left channel,
// and a positive value has a higher proportion of right channel.
// A CFNumber<SInt32> between kAppPanLeftRawValue and kAppPanRightRawValue. A negative value has a higher proportion
// of left channel, and a positive value has a higher proportion of right channel.
#define kBGMAppVolumesKey_PanPosition "ppos"
// The app's pid as a CFNumber. May be omitted if kBGMAppVolumesKey_BundleID is present.
#define kBGMAppVolumesKey_ProcessID "pid"