From cdea147010c35df6eaa00a22ea7cd5739049b8a3 Mon Sep 17 00:00:00 2001 From: Kyle Neideck Date: Sat, 21 Jan 2017 17:04:01 +1100 Subject: [PATCH] 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. --- BGMApp/BGMApp/BGMAppVolumes.h | 26 +-- BGMApp/BGMApp/BGMAppVolumes.mm | 198 +++++++++++------- BGMApp/BGMApp/Base.lproj/MainMenu.xib | 119 +++++++++-- BGMDriver/BGMDriver/BGM_Device.cpp | 19 +- .../BGMDriver/DeviceClients/BGM_ClientMap.cpp | 3 +- .../BGMDriver/DeviceClients/BGM_ClientMap.h | 17 +- .../BGMDriver/DeviceClients/BGM_Clients.cpp | 1 + SharedSource/BGM_Types.h | 4 +- 8 files changed, 263 insertions(+), 124 deletions(-) diff --git a/BGMApp/BGMApp/BGMAppVolumes.h b/BGMApp/BGMApp/BGMAppVolumes.h index b675e8d..7139f06 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.h +++ b/BGMApp/BGMApp/BGMAppVolumes.h @@ -35,40 +35,32 @@ // Protocol for the UI custom classes -@protocol BGMAppVolumeSubview +@protocol BGMAppVolumeMenuItemSubview -- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx; - -@end - -@protocol BGMAppPanSubview - -- (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 +@interface BGMAVM_AppIcon : NSImageView @end -@interface BGMAVM_AppNameLabel : NSTextField +@interface BGMAVM_AppNameLabel : NSTextField @end -@interface BGMAVM_VolumeSlider : NSSlider +@interface BGMAVM_ShowMoreControlsButton : NSButton +@end + +@interface BGMAVM_VolumeSlider : NSSlider - (void) setRelativeVolume:(NSNumber*)relativeVolume; @end -@interface BGMAVM_PanSlider : NSSlider +@interface BGMAVM_PanSlider : NSSlider - (void) setPanPosition:(NSNumber*)panPosition; @end -@interface BGMAVM_PanSliderCell : NSSliderCell - -- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped; - -@end diff --git a/BGMApp/BGMApp/BGMAppVolumes.mm b/BGMApp/BGMApp/BGMAppVolumes.mm index c107cef..4badf47 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.mm +++ b/BGMApp/BGMApp/BGMAppVolumes.mm @@ -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*)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*)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*)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((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((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 - - diff --git a/BGMApp/BGMApp/Base.lproj/MainMenu.xib b/BGMApp/BGMApp/Base.lproj/MainMenu.xib index dde65a1..09ed26d 100644 --- a/BGMApp/BGMApp/Base.lproj/MainMenu.xib +++ b/BGMApp/BGMApp/Base.lproj/MainMenu.xib @@ -1,5 +1,5 @@ - + @@ -60,12 +60,12 @@ - - + + - + @@ -74,28 +74,66 @@ - + - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -170,7 +208,7 @@ - + @@ -191,14 +229,14 @@ - + - + @@ -218,10 +256,61 @@ - + + + +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 + + diff --git a/BGMDriver/BGMDriver/BGM_Device.cpp b/BGMDriver/BGMDriver/BGM_Device.cpp index e6e143a..d0e7760 100644 --- a/BGMDriver/BGMDriver/BGM_Device.cpp +++ b/BGMDriver/BGMDriver/BGM_Device.cpp @@ -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(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(inBuffer)[i]) { + if (0.0f != reinterpret_cast(inBuffer)[i]) { return true; } } diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp index c71c520..2368ac3 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp @@ -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) { diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h index 4685033..8ed241c 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h @@ -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 - //bool SetClientsRelativeVolume(T _Null_unspecified key, Float32 inRelativeVolume); + //bool SetClientsRelativeVolume(T _Null_unspecified searchKey, Float32 inRelativeVolume); + // + //template + //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); } diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp index af38d2b..c81d85b 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp @@ -18,6 +18,7 @@ // BGMDriver // // Copyright © 2016 Kyle Neideck +// Copyright © 2017 Andrew Tonner // // Self Include diff --git a/SharedSource/BGM_Types.h b/SharedSource/BGM_Types.h index d92958f..238d09f 100644 --- a/SharedSource/BGM_Types.h +++ b/SharedSource/BGM_Types.h @@ -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 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 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"