mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-10 06:34:22 +00:00
Merge pull request #98 from rakslice/pan
This commit is contained in:
commit
8257f49b46
11 changed files with 563 additions and 104 deletions
|
@ -35,23 +35,32 @@
|
|||
|
||||
// Protocol for the UI custom classes
|
||||
|
||||
@protocol BGMAppVolumeSubview <NSObject>
|
||||
@protocol BGMAppVolumeMenuItemSubview <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 <BGMAppVolumeMenuItemSubview>
|
||||
|
||||
- (void) setPanPosition:(NSNumber*)panPosition;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -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,12 +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(BGMAppVolumeMenuItemSubview)]) {
|
||||
[(NSView<BGMAppVolumeMenuItemSubview>*)subview setUpWithApp:app context:self menuItem:appVolItem];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,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
|
||||
|
@ -169,15 +187,70 @@ static float const kSlidersSnapWithin = 5;
|
|||
CFTypeRef relativeVolume;
|
||||
appVolume.GetCFType(CFSTR(kBGMAppVolumesKey_RelativeVolume), relativeVolume);
|
||||
|
||||
CFTypeRef panPosition;
|
||||
appVolume.GetCFType(CFSTR(kBGMAppVolumesKey_PanPosition), panPosition);
|
||||
|
||||
// Update the slider
|
||||
for (NSView* subview in menuItem.view.subviews) {
|
||||
if ([subview respondsToSelector:@selector(setRelativeVolume:)]) {
|
||||
[subview performSelector:@selector(setRelativeVolume:) withObject:(__bridge NSNumber*)relativeVolume];
|
||||
}
|
||||
if ([subview respondsToSelector:@selector(setPanPosition:)]) {
|
||||
[subview performSelector:@selector(setPanPosition:) withObject:(__bridge NSNumber*)panPosition];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
|
@ -211,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);
|
||||
|
@ -224,14 +299,30 @@ static float const kSlidersSnapWithin = 5;
|
|||
[audioDevices bgmDevice].SetPropertyData_CFType(kBGMAppVolumesAddress, appVolumeChanges.AsPropertyList());
|
||||
}
|
||||
|
||||
- (void) sendPanPositionChangeToBGMDevice:(SInt32)newPanPosition appProcessID:(pid_t)appProcessID appBundleID:(NSString*)appBundleID {
|
||||
CACFDictionary appVolumeChange(true);
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), appProcessID);
|
||||
appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID), (__bridge CFStringRef)appBundleID);
|
||||
|
||||
// The values from our sliders are in [kAppPanLeftRawValue, kAppPanRightRawValue] already
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), newPanPosition);
|
||||
|
||||
CACFArray appVolumeChanges(true);
|
||||
appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());
|
||||
|
||||
[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;
|
||||
}
|
||||
|
@ -240,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;
|
||||
|
@ -249,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;
|
||||
|
@ -256,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;
|
||||
|
@ -269,9 +382,11 @@ 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
|
||||
float midPoint = static_cast<float>((self.maxValue - self.minValue) / 2);
|
||||
// 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;
|
||||
}
|
||||
|
@ -294,3 +409,39 @@ static float const kSlidersSnapWithin = 5;
|
|||
|
||||
@end
|
||||
|
||||
@implementation BGMAVM_PanSlider {
|
||||
// Will be set to -1 for apps without a pid
|
||||
pid_t appProcessID;
|
||||
NSString* appBundleID;
|
||||
BGMAppVolumes* context;
|
||||
}
|
||||
|
||||
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx menuItem:(NSMenuItem*)menuItem {
|
||||
#pragma unused (menuItem)
|
||||
|
||||
context = ctx;
|
||||
|
||||
self.target = self;
|
||||
self.action = @selector(appPanPositionChanged);
|
||||
|
||||
appProcessID = app.processIdentifier;
|
||||
appBundleID = app.bundleIdentifier;
|
||||
|
||||
self.minValue = kAppPanLeftRawValue;
|
||||
self.maxValue = kAppPanRightRawValue;
|
||||
}
|
||||
|
||||
- (void) setPanPosition:(NSNumber *)panPosition {
|
||||
self.intValue = panPosition.intValue;
|
||||
}
|
||||
|
||||
- (void) appPanPositionChanged {
|
||||
// TODO: This (sending updates to the driver) should probably be rate-limited. It uses a fair bit of CPU for me.
|
||||
|
||||
DebugMsg("BGMAppVolumes::appPanPositionChanged: App pan position for %s changed to %d", appBundleID.UTF8String, self.intValue);
|
||||
|
||||
[context sendPanPositionChangeToBGMDevice:self.intValue appProcessID:appProcessID appBundleID:appBundleID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -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="264" 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,23 +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 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="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"/>
|
||||
|
@ -165,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"/>
|
||||
|
@ -186,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"/>
|
||||
|
@ -213,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>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
//
|
||||
// Copyright © 2016, 2017 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
|
||||
|
@ -2102,17 +2103,44 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF
|
|||
Float32* theBuffer = reinterpret_cast<Float32*>(ioBuffer);
|
||||
Float32 theRelativeVolume = mClients.GetClientRelativeVolumeRT(inClientID);
|
||||
|
||||
if(theRelativeVolume != 1.)
|
||||
auto thePanPositionInt = mClients.GetClientPanPositionRT(inClientID);
|
||||
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/ 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) {
|
||||
auto L = i;
|
||||
auto R = i + 1;
|
||||
|
||||
theBuffer[R] = theBuffer[R] + theBuffer[L] * thePanPosition;
|
||||
theBuffer[L] = theBuffer[L] * (1 - thePanPosition);
|
||||
}
|
||||
} else if (thePanPosition < 0.0f) {
|
||||
for (UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2) {
|
||||
auto L = i;
|
||||
auto R = i + 1;
|
||||
|
||||
theBuffer[L] = theBuffer[L] + theBuffer[R] * (-thePanPosition);
|
||||
theBuffer[R] = theBuffer[R] * (1 + thePanPosition);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2122,7 +2150,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,5 +48,6 @@ void BGM_Client::Copy(const BGM_Client& inClient)
|
|||
mDoingIO = inClient.mDoingIO;
|
||||
mIsMusicPlayer = inClient.mIsMusicPlayer;
|
||||
mRelativeVolume = inClient.mRelativeVolume;
|
||||
mPanPosition = inClient.mPanPosition;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,9 @@ public:
|
|||
// mRelativeVolumeCurve is applied this 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
|
||||
SInt32 mPanPosition = 0;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -37,14 +38,16 @@ void BGM_ClientMap::AddClient(BGM_Client inClient)
|
|||
{
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
// If this client has been a client in the past (and has a bundle ID), copy its previous relative volume
|
||||
// If this client has been a client in the past (and has a bundle ID), copy its previous audio settings
|
||||
auto pastClientItr = inClient.mBundleID.IsValid() ? mPastClientMap.find(inClient.mBundleID) : mPastClientMap.end();
|
||||
if(pastClientItr != mPastClientMap.end())
|
||||
{
|
||||
DebugMsg("BGM_ClientMap::AddClient: Found previous volume %f for client %u",
|
||||
DebugMsg("BGM_ClientMap::AddClient: Found previous volume %f and pan %d for client %u",
|
||||
pastClientItr->second.mRelativeVolume,
|
||||
pastClientItr->second.mPanPosition,
|
||||
inClient.mClientID);
|
||||
inClient.mRelativeVolume = pastClientItr->second.mRelativeVolume;
|
||||
inClient.mPanPosition = pastClientItr->second.mPanPosition;
|
||||
}
|
||||
|
||||
// Add the new client to the shadow maps
|
||||
|
@ -234,8 +237,8 @@ CACFArray BGM_ClientMap::CopyClientRelativeVolumesAsAppVolumes(CAVolumeCurve i
|
|||
|
||||
void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const
|
||||
{
|
||||
// Only include clients set to a non-default volume
|
||||
if(inClient.mRelativeVolume != 1.0)
|
||||
// Only include clients set to a non-default volume or pan
|
||||
if(inClient.mRelativeVolume != 1.0 || inClient.mPanPosition != 0)
|
||||
{
|
||||
CACFDictionary theAppVolume(false);
|
||||
|
||||
|
@ -244,6 +247,8 @@ void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolu
|
|||
// Reverse the volume conversion from SetClientsRelativeVolumes
|
||||
theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume),
|
||||
inVolumeCurve.ConvertScalarToRaw(inClient.mRelativeVolume / 4));
|
||||
theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_PanPosition),
|
||||
inClient.mPanPosition);
|
||||
|
||||
ioAppVolumes.AppendDictionary(theAppVolume.GetDict());
|
||||
}
|
||||
|
@ -251,64 +256,75 @@ void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolu
|
|||
|
||||
// TODO: Combine the SetClientsRelativeVolume methods? Their code is very similar.
|
||||
|
||||
bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume)
|
||||
{
|
||||
bool didChangeVolume = false;
|
||||
template <typename T>
|
||||
std::vector<BGM_Client*> * _Nullable GetClientsFromMap(std::map<T, std::vector<BGM_Client*>> & map, T key) {
|
||||
auto theClientItr = map.find(key);
|
||||
if(theClientItr != map.end()) {
|
||||
return &theClientItr->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
std::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(pid_t inAppPid) {
|
||||
return GetClientsFromMap(mClientMapByPIDShadow, inAppPid);
|
||||
}
|
||||
|
||||
auto theSetVolumesInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the PID and update their volumes
|
||||
auto theClientItr = mClientMapByPIDShadow.find(inAppPID);
|
||||
std::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(CACFString inAppBundleID) {
|
||||
return GetClientsFromMap(mClientMapByBundleIDShadow, inAppBundleID);
|
||||
}
|
||||
|
||||
if(theClientItr != mClientMapByPIDShadow.end())
|
||||
{
|
||||
std::vector<BGM_Client*> theClients = theClientItr->second;
|
||||
|
||||
for(BGM_Client* theClient : theClients)
|
||||
{
|
||||
theClient->mRelativeVolume = inRelativeVolume;
|
||||
void ShowSetRelativeVolumeMessage(pid_t inAppPID, BGM_Client* theClient);
|
||||
void ShowSetRelativeVolumeMessage(CACFString inAppBundleID, BGM_Client* theClient);
|
||||
|
||||
void ShowSetRelativeVolumeMessage(pid_t inAppPID, BGM_Client* theClient) {
|
||||
(void)inAppPID;
|
||||
(void)theClient;
|
||||
DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by pid (%d)",
|
||||
theClient->mRelativeVolume,
|
||||
theClient->mClientID,
|
||||
theClient->mProcessID);
|
||||
|
||||
didChangeVolume = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theSetVolumesInShadowMapsFunc();
|
||||
SwapInShadowMaps();
|
||||
theSetVolumesInShadowMapsFunc();
|
||||
|
||||
return didChangeVolume;
|
||||
inAppPID);
|
||||
}
|
||||
|
||||
|
||||
bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume)
|
||||
{
|
||||
bool didChangeVolume = false;
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
auto theSetVolumesInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the bundle ID and update their volumes
|
||||
auto theClientItr = mClientMapByBundleIDShadow.find(inAppBundleID);
|
||||
|
||||
if(theClientItr != mClientMapByBundleIDShadow.end())
|
||||
{
|
||||
std::vector<BGM_Client*> theClients = theClientItr->second;
|
||||
|
||||
for(BGM_Client* theClient : theClients)
|
||||
{
|
||||
theClient->mRelativeVolume = inRelativeVolume;
|
||||
|
||||
void ShowSetRelativeVolumeMessage(CACFString inAppBundleID, BGM_Client* theClient) {
|
||||
(void)inAppBundleID;
|
||||
(void)theClient;
|
||||
DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by bundle ID (%s)",
|
||||
theClient->mRelativeVolume,
|
||||
theClient->mClientID,
|
||||
CFStringGetCStringPtr(inAppBundleID.GetCFString(), kCFStringEncodingUTF8));
|
||||
}
|
||||
|
||||
// Template method declarations are running into LLVM bug 23987
|
||||
// TODO: template these.
|
||||
|
||||
//bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume) {
|
||||
// return SetClientsRelativeVolumeT<pid_t>(inAppPID, inRelativeVolume);
|
||||
//}
|
||||
|
||||
//bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume) {
|
||||
// return SetClientsRelativeVolumeT<CACFString>(inAppBundleID, inRelativeVolume)
|
||||
//}
|
||||
|
||||
//template <typename T>
|
||||
//bool BGM_ClientMap::SetClientsRelativeVolume(T searchKey, Float32 inRelativeVolume)
|
||||
|
||||
bool BGM_ClientMap::SetClientsRelativeVolume(pid_t searchKey, Float32 inRelativeVolume)
|
||||
{
|
||||
bool didChangeVolume = false;
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
auto theSetVolumesInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the key and update their volumes
|
||||
|
||||
auto theClients = GetClients(searchKey);
|
||||
if(theClients != nullptr)
|
||||
{
|
||||
for(BGM_Client* theClient : *theClients)
|
||||
{
|
||||
theClient->mRelativeVolume = inRelativeVolume;
|
||||
|
||||
ShowSetRelativeVolumeMessage(searchKey, theClient);
|
||||
|
||||
didChangeVolume = true;
|
||||
}
|
||||
|
@ -322,6 +338,92 @@ bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float3
|
|||
return didChangeVolume;
|
||||
}
|
||||
|
||||
bool BGM_ClientMap::SetClientsRelativeVolume(CACFString searchKey, Float32 inRelativeVolume)
|
||||
{
|
||||
bool didChangeVolume = false;
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
auto theSetVolumesInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the key and update their volumes
|
||||
|
||||
auto theClients = GetClients(searchKey);
|
||||
if(theClients != nullptr)
|
||||
{
|
||||
for(BGM_Client* theClient : *theClients)
|
||||
{
|
||||
theClient->mRelativeVolume = inRelativeVolume;
|
||||
|
||||
ShowSetRelativeVolumeMessage(searchKey, theClient);
|
||||
|
||||
didChangeVolume = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theSetVolumesInShadowMapsFunc();
|
||||
SwapInShadowMaps();
|
||||
theSetVolumesInShadowMapsFunc();
|
||||
|
||||
return didChangeVolume;
|
||||
}
|
||||
|
||||
bool BGM_ClientMap::SetClientsPanPosition(pid_t searchKey, SInt32 inPanPosition)
|
||||
{
|
||||
bool didChangePanPosition = false;
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
auto theSetPansInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the key and update their pan positions
|
||||
auto theClients = GetClients(searchKey);
|
||||
if(theClients != nullptr) {
|
||||
for(auto theClient: *theClients) {
|
||||
theClient->mPanPosition = inPanPosition;
|
||||
|
||||
// ShowSetPanPositionsMessage(searchKey, theClient)
|
||||
|
||||
didChangePanPosition = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theSetPansInShadowMapsFunc();
|
||||
SwapInShadowMaps();
|
||||
theSetPansInShadowMapsFunc();
|
||||
|
||||
return didChangePanPosition;
|
||||
|
||||
}
|
||||
|
||||
bool BGM_ClientMap::SetClientsPanPosition(CACFString searchKey, SInt32 inPanPosition)
|
||||
{
|
||||
bool didChangePanPosition = false;
|
||||
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
||||
auto theSetPansInShadowMapsFunc = [&] {
|
||||
// Look up the clients for the key and update their pan positions
|
||||
auto theClients = GetClients(searchKey);
|
||||
if(theClients != nullptr) {
|
||||
for(auto theClient: *theClients) {
|
||||
theClient->mPanPosition = inPanPosition;
|
||||
|
||||
// ShowSetPanPositionsMessage(searchKey, theClient)
|
||||
|
||||
didChangePanPosition = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
theSetPansInShadowMapsFunc();
|
||||
SwapInShadowMaps();
|
||||
theSetPansInShadowMapsFunc();
|
||||
|
||||
return didChangePanPosition;
|
||||
|
||||
}
|
||||
|
||||
void BGM_ClientMap::UpdateClientIOStateNonRT(UInt32 inClientID, bool inDoingIO)
|
||||
{
|
||||
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
|
||||
|
|
|
@ -118,11 +118,26 @@ private:
|
|||
void CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const;
|
||||
|
||||
public:
|
||||
// Returns true if a client with PID inAppPID was found and its relative volume changed.
|
||||
// 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 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 SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume);
|
||||
// Returns true if a client with bundle ID inAppBundleID was found and its relative volume changed.
|
||||
// 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); }
|
||||
void StopIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, false); }
|
||||
|
||||
|
@ -136,6 +151,11 @@ private:
|
|||
// mutex must be locked when calling this method.
|
||||
void SwapInShadowMapsRT();
|
||||
|
||||
// Client lookup for PID inAppPID
|
||||
std::vector<BGM_Client*> * _Nullable GetClients(pid_t inAppPid);
|
||||
// Client lookup for bundle ID inAppBundleID
|
||||
std::vector<BGM_Client*> * _Nullable GetClients(CACFString inAppBundleID);
|
||||
|
||||
private:
|
||||
BGM_TaskQueue* mTaskQueue;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -296,6 +297,13 @@ Float32 BGM_Clients::GetClientRelativeVolumeRT(UInt32 inClientID) const
|
|||
return (didGetClient ? theClient.mRelativeVolume : 1.0);
|
||||
}
|
||||
|
||||
SInt32 BGM_Clients::GetClientPanPositionRT(UInt32 inClientID) const
|
||||
{
|
||||
BGM_Client theClient;
|
||||
bool didGetClient = mClientMap.GetClientRT(inClientID, &theClient);
|
||||
return (didGetClient ? theClient.mPanPosition : kAppPanCenterRawValue);
|
||||
}
|
||||
|
||||
bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
|
||||
{
|
||||
bool didChangeAppVolumes = false;
|
||||
|
@ -320,21 +328,21 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
|
|||
BGM_InvalidClientRelativeVolumeException(),
|
||||
"BGM_Clients::SetClientsRelativeVolumes: App volume was sent without PID or bundle ID for app");
|
||||
|
||||
Float32 theRelativeVolume;
|
||||
bool didGetVolume;
|
||||
{
|
||||
SInt32 theRawRelativeVolume;
|
||||
bool didGetVolume = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theRawRelativeVolume);
|
||||
didGetVolume = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theRawRelativeVolume);
|
||||
|
||||
ThrowIf(!didGetVolume || theRawRelativeVolume < kAppRelativeVolumeMinRawValue || theRawRelativeVolume > kAppRelativeVolumeMaxRawValue,
|
||||
if (didGetVolume) {
|
||||
ThrowIf(didGetVolume && (theRawRelativeVolume < kAppRelativeVolumeMinRawValue || theRawRelativeVolume > kAppRelativeVolumeMaxRawValue),
|
||||
BGM_InvalidClientRelativeVolumeException(),
|
||||
"BGM_Clients::SetClientsRelativeVolumes: Relative volume for app missing or out of valid range");
|
||||
"BGM_Clients::SetClientsRelativeVolumes: Relative volume for app out of valid range");
|
||||
|
||||
// Apply the volume curve to the raw volume
|
||||
//
|
||||
// mRelativeVolumeCurve uses the default kPow2Over1Curve transfer function, so we also multiply by 4 to
|
||||
// keep the middle volume equal to 1 (meaning apps' volumes are unchanged by default).
|
||||
theRelativeVolume = mRelativeVolumeCurve.ConvertRawToScalar(theRawRelativeVolume) * 4;
|
||||
}
|
||||
Float32 theRelativeVolume = mRelativeVolumeCurve.ConvertRawToScalar(theRawRelativeVolume) * 4;
|
||||
|
||||
// Try to update the client's volume, first by PID and then, if that fails, by bundle ID
|
||||
//
|
||||
|
@ -352,6 +360,38 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
|
|||
// TODO: The app isn't currently a client, so we should add it to the past clients map, or update its
|
||||
// past volume if it's already in there.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool didGetPanPosition;
|
||||
{
|
||||
SInt32 thePanPosition;
|
||||
didGetPanPosition = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePanPosition);
|
||||
if (didGetPanPosition) {
|
||||
ThrowIf(didGetPanPosition && (thePanPosition < kAppPanLeftRawValue || thePanPosition > kAppPanRightRawValue),
|
||||
BGM_InvalidClientPanPositionException(),
|
||||
"BGM_Clients::SetClientsRelativeVolumes: Pan position for app out of valid range");
|
||||
|
||||
if(mClientMap.SetClientsPanPosition(theAppPID, thePanPosition))
|
||||
{
|
||||
didChangeAppVolumes = true;
|
||||
}
|
||||
else if(mClientMap.SetClientsPanPosition(theAppBundleID, thePanPosition))
|
||||
{
|
||||
didChangeAppVolumes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: The app isn't currently a client, so we should add it to the past clients map, or update its
|
||||
// past pan position if it's already in there.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThrowIf(!didGetVolume && !didGetPanPosition,
|
||||
BGM_InvalidClientRelativeVolumeException(),
|
||||
"BGM_Clients::SetClientsRelativeVolumes: No volume or pan position in request");
|
||||
}
|
||||
|
||||
return didChangeAppVolumes;
|
||||
|
|
|
@ -95,6 +95,7 @@ public:
|
|||
bool IsMusicPlayerRT(const UInt32 inClientID) const;
|
||||
|
||||
Float32 GetClientRelativeVolumeRT(UInt32 inClientID) const;
|
||||
SInt32 GetClientPanPositionRT(UInt32 inClientID) const;
|
||||
|
||||
// Copies the current and past clients into an array in the format expected for
|
||||
// kAudioDeviceCustomPropertyAppVolumes. (Except that CACFArray and CACFDictionary are used instead
|
||||
|
@ -102,7 +103,8 @@ public:
|
|||
CACFArray CopyClientRelativeVolumesAsAppVolumes() const { return mClientMap.CopyClientRelativeVolumesAsAppVolumes(mRelativeVolumeCurve); };
|
||||
|
||||
// inAppVolumes is an array of dicts with the keys kBGMAppVolumesKey_ProcessID,
|
||||
// kBGMAppVolumesKey_BundleID and kBGMAppVolumesKey_RelativeVolume. This method finds the client for
|
||||
// kBGMAppVolumesKey_BundleID and optionally kBGMAppVolumesKey_RelativeVolume and
|
||||
// kBGMAppVolumesKey_PanPosition. This method finds the client for
|
||||
// each app by PID or bundle ID, sets the volume and applies mRelativeVolumeCurve to it.
|
||||
//
|
||||
// Returns true if any clients' relative volumes were changed.
|
||||
|
|
|
@ -110,6 +110,9 @@ 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.
|
||||
#define kBGMAppVolumesKey_PanPosition "ppos"
|
||||
// The app's pid as a CFNumber. May be omitted if kBGMAppVolumesKey_BundleID is present.
|
||||
#define kBGMAppVolumesKey_ProcessID "pid"
|
||||
// The app's bundle ID as a CFString. May be omitted if kBGMAppVolumesKey_ProcessID is present.
|
||||
|
@ -121,6 +124,11 @@ enum
|
|||
#define kAppRelativeVolumeMinDbValue -96.0f
|
||||
#define kAppRelativeVolumeMaxDbValue 0.0f
|
||||
|
||||
// Pan position values
|
||||
#define kAppPanLeftRawValue -100
|
||||
#define kAppPanCenterRawValue 0
|
||||
#define kAppPanRightRawValue 100
|
||||
|
||||
#pragma mark BGMDevice Custom Property Addresses
|
||||
|
||||
// For convenience.
|
||||
|
@ -174,6 +182,7 @@ enum {
|
|||
class BGM_InvalidClientException { };
|
||||
class BGM_InvalidClientPIDException { };
|
||||
class BGM_InvalidClientRelativeVolumeException { };
|
||||
class BGM_InvalidClientPanPositionException { };
|
||||
class BGM_DeviceNotSetException { };
|
||||
class BGM_RuntimeException { };
|
||||
|
||||
|
|
Loading…
Reference in a new issue