From 94c594b342d0397b8b712ee93f7355ef2d640594 Mon Sep 17 00:00:00 2001 From: rakslice Date: Mon, 2 Jan 2017 02:30:00 -0800 Subject: [PATCH 1/4] added per-app pan sliders --- BGMApp/BGMApp/BGMAppVolumes.h | 17 ++ BGMApp/BGMApp/BGMAppVolumes.mm | 116 ++++++++++++- BGMApp/BGMApp/Base.lproj/MainMenu.xib | 7 +- BGMDriver/BGMDriver/BGM_Device.cpp | 25 +++ .../BGMDriver/DeviceClients/BGM_Client.cpp | 1 + .../BGMDriver/DeviceClients/BGM_Client.h | 3 + .../BGMDriver/DeviceClients/BGM_ClientMap.cpp | 156 +++++++++++++++--- .../BGMDriver/DeviceClients/BGM_ClientMap.h | 21 ++- .../BGMDriver/DeviceClients/BGM_Clients.cpp | 87 +++++++--- .../BGMDriver/DeviceClients/BGM_Clients.h | 4 +- SharedSource/BGM_Types.h | 9 + 11 files changed, 390 insertions(+), 56 deletions(-) diff --git a/BGMApp/BGMApp/BGMAppVolumes.h b/BGMApp/BGMApp/BGMAppVolumes.h index c57dc4d..b675e8d 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.h +++ b/BGMApp/BGMApp/BGMAppVolumes.h @@ -41,6 +41,12 @@ @end +@protocol BGMAppPanSubview + +- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx; + +@end + // Custom classes for the UI elements in the app volume menu items @interface BGMAVM_AppIcon : NSImageView @@ -55,3 +61,14 @@ @end +@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 e6d8e2f..abeacf0 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.mm +++ b/BGMApp/BGMApp/BGMAppVolumes.mm @@ -102,6 +102,9 @@ static float const kSlidersSnapWithin = 5; 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]; + } } // Store the NSRunningApplication object with the menu item so when the app closes we can find the item to remove it @@ -169,11 +172,17 @@ 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]; + } } } } @@ -224,6 +233,19 @@ 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 // Custom classes for the UI elements in the app volume menu items @@ -271,7 +293,7 @@ static float const kSlidersSnapWithin = 5; - (void) snap { // Snap to the 50% point - float midPoint = static_cast((self.maxValue - self.minValue) / 2); + float midPoint = static_cast((self.maxValue + self.minValue) / 2); if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) { self.floatValue = midPoint; } @@ -294,3 +316,95 @@ 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; +} + +- (id)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder: coder]; + + [NSSlider setCellClass:[BGMAVM_PanSliderCell class]]; + + 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; + self.action = @selector(appPanPositionChanged); + + appProcessID = app.processIdentifier; + appBundleID = app.bundleIdentifier; + + self.minValue = kAppPanLeftRawValue; + 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 { + // 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); + + [self snap]; + + [context sendPanPositionChangeToBGMDevice:self.intValue appProcessID:appProcessID appBundleID:appBundleID]; +} + +@end + +@implementation BGMAVM_PanSliderCell + +- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped { + // Custom small slider cell with a single color track + CGFloat desiredHeight = 3.0; + auto color = NSColor.grayColor; + + CGFloat radius = desiredHeight / 2.0; + + // Center the bar vertically in rect + CGFloat fullHeight = rect.size.height; + CGFloat yOffset = (desiredHeight - fullHeight) / 2.0; + if (flipped) { + yOffset = -yOffset; + } + rect.origin.y += yOffset; + rect.size.height = desiredHeight; + + NSBezierPath* bg = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: radius yRadius: radius]; + [color setFill]; + [bg fill]; +} + +@end + + diff --git a/BGMApp/BGMApp/Base.lproj/MainMenu.xib b/BGMApp/BGMApp/Base.lproj/MainMenu.xib index e311fd8..dde65a1 100644 --- a/BGMApp/BGMApp/Base.lproj/MainMenu.xib +++ b/BGMApp/BGMApp/Base.lproj/MainMenu.xib @@ -61,7 +61,7 @@ - + @@ -83,6 +83,11 @@ + + + + + diff --git a/BGMDriver/BGMDriver/BGM_Device.cpp b/BGMDriver/BGMDriver/BGM_Device.cpp index cc71e38..e6e143a 100644 --- a/BGMDriver/BGMDriver/BGM_Device.cpp +++ b/BGMDriver/BGMDriver/BGM_Device.cpp @@ -2092,6 +2092,31 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF Float32* theBuffer = reinterpret_cast(ioBuffer); Float32 theRelativeVolume = mClients.GetClientRelativeVolumeRT(inClientID); + auto thePanPositionInt = mClients.GetClientPanPositionRT(inClientID); + Float32 thePanPosition = ((Float32)thePanPositionInt)/100.0f; + + // TODO precompute matrix coefficients w/ volume and do everything in one pass + + // Apply balance w/ crossover 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.) { for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++) diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.cpp index 50464f9..50e6ee5 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.cpp @@ -48,5 +48,6 @@ void BGM_Client::Copy(const BGM_Client& inClient) mDoingIO = inClient.mDoingIO; mIsMusicPlayer = inClient.mIsMusicPlayer; mRelativeVolume = inClient.mRelativeVolume; + mPanPosition = inClient.mPanPosition; } diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h index ee486bb..7144d2a 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h @@ -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 diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp index e4ddfe5..8fac9a7 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp @@ -37,14 +37,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 +236,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 +246,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,28 +255,76 @@ 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) +template +std::vector * _Nullable GetClientsFromMap(std::map> & map, T key) { + // TODO assert mShadowMapsMutex is locked? + auto theClientItr = map.find(key); + if(theClientItr != map.end()) { + return &theClientItr->second; + } + return nullptr; +} + +std::vector * _Nullable BGM_ClientMap::GetClients(pid_t inAppPid) { + return GetClientsFromMap(mClientMapByPIDShadow, inAppPid); +} + +std::vector * _Nullable BGM_ClientMap::GetClients(CACFString inAppBundleID) { + return GetClientsFromMap(mClientMapByBundleIDShadow, inAppBundleID); +} + +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, + inAppPID); +} + +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(inAppPID, inRelativeVolume); +//} + +//bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume) { +// return SetClientsRelativeVolumeT(inAppBundleID, inRelativeVolume) +//} + +//template +//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 PID and update their volumes - auto theClientItr = mClientMapByPIDShadow.find(inAppPID); + // Look up the clients for the key and update their volumes - if(theClientItr != mClientMapByPIDShadow.end()) + auto theClients = GetClients(searchKey); + if(theClients != nullptr) { - std::vector theClients = theClientItr->second; - - for(BGM_Client* theClient : theClients) + for(BGM_Client* theClient : *theClients) { theClient->mRelativeVolume = inRelativeVolume; - DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by pid (%d)", - theClient->mRelativeVolume, - theClient->mClientID, - theClient->mProcessID); + ShowSetRelativeVolumeMessage(searchKey, theClient); didChangeVolume = true; } @@ -286,29 +338,23 @@ bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelati return didChangeVolume; } - -bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume) +bool BGM_ClientMap::SetClientsRelativeVolume(CACFString searchKey, 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); + // Look up the clients for the key and update their volumes - if(theClientItr != mClientMapByBundleIDShadow.end()) + auto theClients = GetClients(searchKey); + if(theClients != nullptr) { - std::vector theClients = theClientItr->second; - - for(BGM_Client* theClient : theClients) + for(BGM_Client* theClient : *theClients) { theClient->mRelativeVolume = inRelativeVolume; - DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by bundle ID (%s)", - theClient->mRelativeVolume, - theClient->mClientID, - CFStringGetCStringPtr(inAppBundleID.GetCFString(), kCFStringEncodingUTF8)); + ShowSetRelativeVolumeMessage(searchKey, theClient); didChangeVolume = true; } @@ -322,6 +368,62 @@ bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float3 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); diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h index 77ed7e8..4685033 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h @@ -118,11 +118,23 @@ 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. + // 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); + // 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); + + // Returns true if a client for PID inAppPID was found and its relative volume changed. + bool SetClientsPanPosition(pid_t inAppPID, SInt32 inPanPosition); + // Returns true if a client for bundle ID inAppBundleID was found and its relative volume changed. + bool SetClientsPanPosition(CACFString inAppBundleID, SInt32 inPanPosition); + void StartIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, true); } void StopIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, false); } @@ -136,6 +148,11 @@ private: // mutex must be locked when calling this method. void SwapInShadowMapsRT(); + // Client lookup for PID inAppPID + std::vector * _Nullable GetClients(pid_t inAppPid); + // Client lookup for bundle ID inAppBundleID + std::vector * _Nullable GetClients(CACFString inAppBundleID); + private: BGM_TaskQueue* mTaskQueue; diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp index daf9c60..af38d2b 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp @@ -296,6 +296,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,38 +327,70 @@ 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, - BGM_InvalidClientRelativeVolumeException(), - "BGM_Clients::SetClientsRelativeVolumes: Relative volume for app missing or out of valid range"); + if (didGetVolume) { + ThrowIf(didGetVolume && (theRawRelativeVolume < kAppRelativeVolumeMinRawValue || theRawRelativeVolume > kAppRelativeVolumeMaxRawValue), + BGM_InvalidClientRelativeVolumeException(), + "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). + Float32 theRelativeVolume = mRelativeVolumeCurve.ConvertRawToScalar(theRawRelativeVolume) * 4; + + // Try to update the client's volume, first by PID and then, if that fails, by bundle ID + // + // TODO: Should we always try both in case an app has multiple clients? + if(mClientMap.SetClientsRelativeVolume(theAppPID, theRelativeVolume)) + { + didChangeAppVolumes = true; + } + else if(mClientMap.SetClientsRelativeVolume(theAppBundleID, theRelativeVolume)) + { + 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 volume if it's already in there. + } - // 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; + } } - // Try to update the client's volume, first by PID and then, if that fails, by bundle ID - // - // TODO: Should we always try both in case an app has multiple clients? - if(mClientMap.SetClientsRelativeVolume(theAppPID, theRelativeVolume)) + bool didGetPanPosition; { - didChangeAppVolumes = true; - } - else if(mClientMap.SetClientsRelativeVolume(theAppBundleID, theRelativeVolume)) - { - 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 volume if it's already in there. + 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; diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h index 7b9b8b2..02d9fe2 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Clients.h @@ -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. diff --git a/SharedSource/BGM_Types.h b/SharedSource/BGM_Types.h index 0dfb7a8..d92958f 100644 --- a/SharedSource/BGM_Types.h +++ b/SharedSource/BGM_Types.h @@ -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 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. @@ -173,6 +181,7 @@ enum { class BGM_InvalidClientException { }; class BGM_InvalidClientPIDException { }; class BGM_InvalidClientRelativeVolumeException { }; +class BGM_InvalidClientPanPositionException { }; class BGM_DeviceNotSetException { }; class BGM_RuntimeException { }; From f6ac51a3346537bbdd699d2783bd337eb5b15645 Mon Sep 17 00:00:00 2001 From: rakslice Date: Mon, 2 Jan 2017 02:43:40 -0800 Subject: [PATCH 2/4] fixed typo --- BGMDriver/BGMDriver/DeviceClients/BGM_Client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h index 7144d2a..4e68f6f 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_Client.h @@ -72,7 +72,7 @@ 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 + // The client's pan position, in the range [-100, 100] where -100 is left and 100 is right SInt32 mPanPosition = 0; }; From 61ce9ef165d562c03e319840a552b47f22fa4705 Mon Sep 17 00:00:00 2001 From: rakslice Date: Mon, 2 Jan 2017 04:21:56 -0800 Subject: [PATCH 3/4] better custom slider hack --- BGMApp/BGMApp/BGMAppVolumes.mm | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/BGMApp/BGMApp/BGMAppVolumes.mm b/BGMApp/BGMApp/BGMAppVolumes.mm index abeacf0..022fcda 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.mm +++ b/BGMApp/BGMApp/BGMAppVolumes.mm @@ -385,24 +385,13 @@ static float const kSlidersSnapWithin = 5; @implementation BGMAVM_PanSliderCell - (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped { - // Custom small slider cell with a single color track - CGFloat desiredHeight = 3.0; - auto color = NSColor.grayColor; + // Custom slider cell to get rid of the level highlight - CGFloat radius = desiredHeight / 2.0; - - // Center the bar vertically in rect - CGFloat fullHeight = rect.size.height; - CGFloat yOffset = (desiredHeight - fullHeight) / 2.0; - if (flipped) { - yOffset = -yOffset; - } - rect.origin.y += yOffset; - rect.size.height = desiredHeight; - - NSBezierPath* bg = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: radius yRadius: radius]; - [color setFill]; - [bg fill]; + // 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 From acf3976b9b2c0b62db06025c92dfde341cb952a8 Mon Sep 17 00:00:00 2001 From: Andrew Tonner Date: Mon, 9 Jan 2017 14:58:05 -0800 Subject: [PATCH 4/4] remove suprious setCellClass call; cleanup --- BGMApp/BGMApp/BGMAppVolumes.mm | 2 -- BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/BGMApp/BGMApp/BGMAppVolumes.mm b/BGMApp/BGMApp/BGMAppVolumes.mm index 022fcda..c107cef 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.mm +++ b/BGMApp/BGMApp/BGMAppVolumes.mm @@ -326,8 +326,6 @@ static float const kSlidersSnapWithin = 5; - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder: coder]; - [NSSlider setCellClass:[BGMAVM_PanSliderCell class]]; - if(self) { NSSliderCell * oldCell = [self cell]; diff --git a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp index 8fac9a7..c71c520 100644 --- a/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp +++ b/BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp @@ -257,7 +257,6 @@ void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolu template std::vector * _Nullable GetClientsFromMap(std::map> & map, T key) { - // TODO assert mShadowMapsMutex is locked? auto theClientItr = map.find(key); if(theClientItr != map.end()) { return &theClientItr->second;