Merge branch 'pan' of https://github.com/rakslice/BackgroundMusic into rakslice-pan

This commit is contained in:
Kyle Neideck 2017-01-18 21:49:20 +11:00
commit a62fae6fd1
No known key found for this signature in database
GPG key ID: CAA8D9B8E39EC18C
11 changed files with 376 additions and 56 deletions

View file

@ -41,6 +41,12 @@
@end @end
@protocol BGMAppPanSubview <NSObject>
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx;
@end
// Custom classes for the UI elements in the app volume menu items // Custom classes for the UI elements in the app volume menu items
@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeSubview> @interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeSubview>
@ -55,3 +61,14 @@
@end @end
@interface BGMAVM_PanSlider : NSSlider <BGMAppPanSubview>
- (void) setPanPosition:(NSNumber*)panPosition;
@end
@interface BGMAVM_PanSliderCell : NSSliderCell
- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped;
@end

View file

@ -102,6 +102,9 @@ static float const kSlidersSnapWithin = 5;
if ([subview conformsToProtocol:@protocol(BGMAppVolumeSubview)]) { if ([subview conformsToProtocol:@protocol(BGMAppVolumeSubview)]) {
[subview performSelector:@selector(setUpWithApp:context:) withObject:app withObject:self]; [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 // 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; CFTypeRef relativeVolume;
appVolume.GetCFType(CFSTR(kBGMAppVolumesKey_RelativeVolume), relativeVolume); appVolume.GetCFType(CFSTR(kBGMAppVolumesKey_RelativeVolume), relativeVolume);
CFTypeRef panPosition;
appVolume.GetCFType(CFSTR(kBGMAppVolumesKey_PanPosition), panPosition);
// Update the slider // Update the slider
for (NSView* subview in menuItem.view.subviews) { for (NSView* subview in menuItem.view.subviews) {
if ([subview respondsToSelector:@selector(setRelativeVolume:)]) { if ([subview respondsToSelector:@selector(setRelativeVolume:)]) {
[subview performSelector:@selector(setRelativeVolume:) withObject:(__bridge NSNumber*)relativeVolume]; [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()); [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 @end
// Custom classes for the UI elements in the app volume menu items // Custom classes for the UI elements in the app volume menu items
@ -271,7 +293,7 @@ static float const kSlidersSnapWithin = 5;
- (void) snap { - (void) snap {
// Snap to the 50% point // Snap to the 50% point
float midPoint = static_cast<float>((self.maxValue - self.minValue) / 2); float midPoint = static_cast<float>((self.maxValue + self.minValue) / 2);
if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) { if (self.floatValue > (midPoint - kSlidersSnapWithin) && self.floatValue < (midPoint + kSlidersSnapWithin)) {
self.floatValue = midPoint; self.floatValue = midPoint;
} }
@ -294,3 +316,82 @@ static float const kSlidersSnapWithin = 5;
@end @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];
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<float>((self.maxValue + self.minValue) / 2);
if (self.floatValue > (midPoint - 2 * kSlidersSnapWithin) && self.floatValue < (midPoint + 2 * kSlidersSnapWithin)) {
self.floatValue = midPoint;
}
}
- (void) setPanPosition:(NSNumber *)panPosition {
self.intValue = panPosition.intValue;
[self snap];
}
- (void) appPanPositionChanged {
// 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 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

View file

@ -61,7 +61,7 @@
<point key="canvasLocation" x="-184" y="-69.5"/> <point key="canvasLocation" x="-184" y="-69.5"/>
</menu> </menu>
<customView id="MWB-XH-kFI"> <customView id="MWB-XH-kFI">
<rect key="frame" x="0.0" y="0.0" width="264" height="20"/> <rect key="frame" x="0.0" y="0.0" width="355" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField identifier="AppName" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xmd-bg-huG" customClass="BGMAVM_AppNameLabel"> <textField identifier="AppName" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xmd-bg-huG" customClass="BGMAVM_AppNameLabel">
@ -83,6 +83,11 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <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"/> <sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="100" doubleValue="50" tickMarkPosition="above" sliderType="linear" id="Jmg-df-9Xl"/>
</slider> </slider>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2mh-uO-kOV" customClass="BGMAVM_PanSlider">
<rect key="frame" x="261" y="2" width="74" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" minValue="-100" maxValue="100" tickMarkPosition="above" sliderType="linear" id="ccM-Mt-93g"/>
</slider>
</subviews> </subviews>
<point key="canvasLocation" x="81" y="-111"/> <point key="canvasLocation" x="81" y="-111"/>
</customView> </customView>

View file

@ -2092,6 +2092,31 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF
Float32* theBuffer = reinterpret_cast<Float32*>(ioBuffer); Float32* theBuffer = reinterpret_cast<Float32*>(ioBuffer);
Float32 theRelativeVolume = mClients.GetClientRelativeVolumeRT(inClientID); 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.) if(theRelativeVolume != 1.)
{ {
for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++) for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i++)

View file

@ -48,5 +48,6 @@ void BGM_Client::Copy(const BGM_Client& inClient)
mDoingIO = inClient.mDoingIO; mDoingIO = inClient.mDoingIO;
mIsMusicPlayer = inClient.mIsMusicPlayer; mIsMusicPlayer = inClient.mIsMusicPlayer;
mRelativeVolume = inClient.mRelativeVolume; mRelativeVolume = inClient.mRelativeVolume;
mPanPosition = inClient.mPanPosition;
} }

View file

@ -72,6 +72,9 @@ public:
// mRelativeVolumeCurve is applied this this value when it's set. // mRelativeVolumeCurve is applied this this value when it's set.
Float32 mRelativeVolume = 1.0; 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 #pragma clang assume_nonnull end

View file

@ -37,14 +37,16 @@ void BGM_ClientMap::AddClient(BGM_Client inClient)
{ {
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex); 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(); auto pastClientItr = inClient.mBundleID.IsValid() ? mPastClientMap.find(inClient.mBundleID) : mPastClientMap.end();
if(pastClientItr != 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.mRelativeVolume,
pastClientItr->second.mPanPosition,
inClient.mClientID); inClient.mClientID);
inClient.mRelativeVolume = pastClientItr->second.mRelativeVolume; inClient.mRelativeVolume = pastClientItr->second.mRelativeVolume;
inClient.mPanPosition = pastClientItr->second.mPanPosition;
} }
// Add the new client to the shadow maps // 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 void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const
{ {
// Only include clients set to a non-default volume // Only include clients set to a non-default volume or pan
if(inClient.mRelativeVolume != 1.0) if(inClient.mRelativeVolume != 1.0 || inClient.mPanPosition != 0)
{ {
CACFDictionary theAppVolume(false); CACFDictionary theAppVolume(false);
@ -244,6 +246,8 @@ void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolu
// Reverse the volume conversion from SetClientsRelativeVolumes // Reverse the volume conversion from SetClientsRelativeVolumes
theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume),
inVolumeCurve.ConvertScalarToRaw(inClient.mRelativeVolume / 4)); inVolumeCurve.ConvertScalarToRaw(inClient.mRelativeVolume / 4));
theAppVolume.AddSInt32(CFSTR(kBGMAppVolumesKey_PanPosition),
inClient.mPanPosition);
ioAppVolumes.AppendDictionary(theAppVolume.GetDict()); ioAppVolumes.AppendDictionary(theAppVolume.GetDict());
} }
@ -251,28 +255,75 @@ void BGM_ClientMap::CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolu
// TODO: Combine the SetClientsRelativeVolume methods? Their code is very similar. // TODO: Combine the SetClientsRelativeVolume methods? Their code is very similar.
bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume) 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;
}
std::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(pid_t inAppPid) {
return GetClientsFromMap(mClientMapByPIDShadow, inAppPid);
}
std::vector<BGM_Client*> * _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<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; bool didChangeVolume = false;
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex); CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
auto theSetVolumesInShadowMapsFunc = [&] { auto theSetVolumesInShadowMapsFunc = [&] {
// Look up the clients for the PID and update their volumes // Look up the clients for the key and update their volumes
auto theClientItr = mClientMapByPIDShadow.find(inAppPID);
if(theClientItr != mClientMapByPIDShadow.end()) auto theClients = GetClients(searchKey);
if(theClients != nullptr)
{ {
std::vector<BGM_Client*> theClients = theClientItr->second; for(BGM_Client* theClient : *theClients)
for(BGM_Client* theClient : theClients)
{ {
theClient->mRelativeVolume = inRelativeVolume; theClient->mRelativeVolume = inRelativeVolume;
DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by pid (%d)", ShowSetRelativeVolumeMessage(searchKey, theClient);
theClient->mRelativeVolume,
theClient->mClientID,
theClient->mProcessID);
didChangeVolume = true; didChangeVolume = true;
} }
@ -286,29 +337,23 @@ bool BGM_ClientMap::SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelati
return didChangeVolume; return didChangeVolume;
} }
bool BGM_ClientMap::SetClientsRelativeVolume(CACFString searchKey, Float32 inRelativeVolume)
bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume)
{ {
bool didChangeVolume = false; bool didChangeVolume = false;
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex); CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
auto theSetVolumesInShadowMapsFunc = [&] { auto theSetVolumesInShadowMapsFunc = [&] {
// Look up the clients for the bundle ID and update their volumes // Look up the clients for the key and update their volumes
auto theClientItr = mClientMapByBundleIDShadow.find(inAppBundleID);
if(theClientItr != mClientMapByBundleIDShadow.end()) auto theClients = GetClients(searchKey);
if(theClients != nullptr)
{ {
std::vector<BGM_Client*> theClients = theClientItr->second; for(BGM_Client* theClient : *theClients)
for(BGM_Client* theClient : theClients)
{ {
theClient->mRelativeVolume = inRelativeVolume; theClient->mRelativeVolume = inRelativeVolume;
DebugMsg("BGM_ClientMap::SetClientsRelativeVolume: Set volume %f for client %u by bundle ID (%s)", ShowSetRelativeVolumeMessage(searchKey, theClient);
theClient->mRelativeVolume,
theClient->mClientID,
CFStringGetCStringPtr(inAppBundleID.GetCFString(), kCFStringEncodingUTF8));
didChangeVolume = true; didChangeVolume = true;
} }
@ -322,6 +367,62 @@ bool BGM_ClientMap::SetClientsRelativeVolume(CACFString inAppBundleID, Float3
return didChangeVolume; 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) void BGM_ClientMap::UpdateClientIOStateNonRT(UInt32 inClientID, bool inDoingIO)
{ {
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex); CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);

View file

@ -118,11 +118,23 @@ private:
void CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const; void CopyClientIntoAppVolumesArray(BGM_Client inClient, CAVolumeCurve inVolumeCurve, CACFArray& ioAppVolumes) const;
public: 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); 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); bool SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume);
// Using the template function hits LLVM Bug 23987
// TODO Switch to template function
// Returns true if a client for the key was found and its relative volume changed.
//template <typename T>
//bool SetClientsRelativeVolume(T _Null_unspecified key, Float32 inRelativeVolume);
// 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 StartIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, true); }
void StopIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, false); } void StopIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, false); }
@ -136,6 +148,11 @@ private:
// mutex must be locked when calling this method. // mutex must be locked when calling this method.
void SwapInShadowMapsRT(); 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: private:
BGM_TaskQueue* mTaskQueue; BGM_TaskQueue* mTaskQueue;

View file

@ -296,6 +296,13 @@ Float32 BGM_Clients::GetClientRelativeVolumeRT(UInt32 inClientID) const
return (didGetClient ? theClient.mRelativeVolume : 1.0); 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 BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
{ {
bool didChangeAppVolumes = false; bool didChangeAppVolumes = false;
@ -320,38 +327,70 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
BGM_InvalidClientRelativeVolumeException(), BGM_InvalidClientRelativeVolumeException(),
"BGM_Clients::SetClientsRelativeVolumes: App volume was sent without PID or bundle ID for app"); "BGM_Clients::SetClientsRelativeVolumes: App volume was sent without PID or bundle ID for app");
Float32 theRelativeVolume; bool didGetVolume;
{ {
SInt32 theRawRelativeVolume; 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) {
BGM_InvalidClientRelativeVolumeException(), ThrowIf(didGetVolume && (theRawRelativeVolume < kAppRelativeVolumeMinRawValue || theRawRelativeVolume > kAppRelativeVolumeMaxRawValue),
"BGM_Clients::SetClientsRelativeVolumes: Relative volume for app missing or out of valid range"); 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 bool didGetPanPosition;
//
// TODO: Should we always try both in case an app has multiple clients?
if(mClientMap.SetClientsRelativeVolume(theAppPID, theRelativeVolume))
{ {
didChangeAppVolumes = true; SInt32 thePanPosition;
} didGetPanPosition = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePanPosition);
else if(mClientMap.SetClientsRelativeVolume(theAppBundleID, theRelativeVolume)) if (didGetPanPosition) {
{ ThrowIf(didGetPanPosition && (thePanPosition < kAppPanLeftRawValue || thePanPosition > kAppPanRightRawValue),
didChangeAppVolumes = true; BGM_InvalidClientPanPositionException(),
} "BGM_Clients::SetClientsRelativeVolumes: Pan position for app out of valid range");
else
{ if(mClientMap.SetClientsPanPosition(theAppPID, thePanPosition))
// 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. 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; return didChangeAppVolumes;

View file

@ -95,6 +95,7 @@ public:
bool IsMusicPlayerRT(const UInt32 inClientID) const; bool IsMusicPlayerRT(const UInt32 inClientID) const;
Float32 GetClientRelativeVolumeRT(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 // Copies the current and past clients into an array in the format expected for
// kAudioDeviceCustomPropertyAppVolumes. (Except that CACFArray and CACFDictionary are used instead // kAudioDeviceCustomPropertyAppVolumes. (Except that CACFArray and CACFDictionary are used instead
@ -102,7 +103,8 @@ public:
CACFArray CopyClientRelativeVolumesAsAppVolumes() const { return mClientMap.CopyClientRelativeVolumesAsAppVolumes(mRelativeVolumeCurve); }; CACFArray CopyClientRelativeVolumesAsAppVolumes() const { return mClientMap.CopyClientRelativeVolumesAsAppVolumes(mRelativeVolumeCurve); };
// inAppVolumes is an array of dicts with the keys kBGMAppVolumesKey_ProcessID, // 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. // each app by PID or bundle ID, sets the volume and applies mRelativeVolumeCurve to it.
// //
// Returns true if any clients' relative volumes were changed. // Returns true if any clients' relative volumes were changed.

View file

@ -110,6 +110,9 @@ enum
// applied to kBGMAppVolumesKey_RelativeVolume when it's first set and then each of the app's samples are multiplied // applied to kBGMAppVolumesKey_RelativeVolume when it's first set and then each of the app's samples are multiplied
// by it. // by it.
#define kBGMAppVolumesKey_RelativeVolume "rvol" #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. // The app's pid as a CFNumber. May be omitted if kBGMAppVolumesKey_BundleID is present.
#define kBGMAppVolumesKey_ProcessID "pid" #define kBGMAppVolumesKey_ProcessID "pid"
// The app's bundle ID as a CFString. May be omitted if kBGMAppVolumesKey_ProcessID is present. // 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 kAppRelativeVolumeMinDbValue -96.0f
#define kAppRelativeVolumeMaxDbValue 0.0f #define kAppRelativeVolumeMaxDbValue 0.0f
// Pan position values
#define kAppPanLeftRawValue -100
#define kAppPanCenterRawValue 0
#define kAppPanRightRawValue 100
#pragma mark BGMDevice Custom Property Addresses #pragma mark BGMDevice Custom Property Addresses
// For convenience. // For convenience.
@ -173,6 +181,7 @@ enum {
class BGM_InvalidClientException { }; class BGM_InvalidClientException { };
class BGM_InvalidClientPIDException { }; class BGM_InvalidClientPIDException { };
class BGM_InvalidClientRelativeVolumeException { }; class BGM_InvalidClientRelativeVolumeException { };
class BGM_InvalidClientPanPositionException { };
class BGM_DeviceNotSetException { }; class BGM_DeviceNotSetException { };
class BGM_RuntimeException { }; class BGM_RuntimeException { };