mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-09-20 14:31:56 +00:00
Merge branch 'pan' of https://github.com/rakslice/BackgroundMusic into rakslice-pan
This commit is contained in:
commit
a62fae6fd1
11 changed files with 376 additions and 56 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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++)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 { };
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue