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
@protocol BGMAppPanSubview <NSObject>
- (void) setUpWithApp:(NSRunningApplication*)app context:(BGMAppVolumes*)ctx;
@end
// Custom classes for the UI elements in the app volume menu items
@interface BGMAVM_AppIcon : NSImageView <BGMAppVolumeSubview>
@ -55,3 +61,14 @@
@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)]) {
[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<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)) {
self.floatValue = midPoint;
}
@ -294,3 +316,82 @@ 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];
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"/>
</menu>
<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"/>
<subviews>
<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"/>
<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 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>
<point key="canvasLocation" x="81" y="-111"/>
</customView>

View file

@ -2092,6 +2092,31 @@ void BGM_Device::ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferF
Float32* theBuffer = reinterpret_cast<Float32*>(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++)

View file

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

View file

@ -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

View file

@ -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,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)
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;
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<BGM_Client*> 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 +337,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<BGM_Client*> 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 +367,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);

View file

@ -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 <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 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<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;

View file

@ -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,21 +327,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 +359,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;

View file

@ -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.

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
// 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.
@ -173,6 +181,7 @@ enum {
class BGM_InvalidClientException { };
class BGM_InvalidClientPIDException { };
class BGM_InvalidClientRelativeVolumeException { };
class BGM_InvalidClientPanPositionException { };
class BGM_DeviceNotSetException { };
class BGM_RuntimeException { };