Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion BGMApp/BGMApp/BGMAudioDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ bool BGMAudioDevice::CanBeOutputDeviceInBGMApp() const
canBeDefault;
}

#pragma mark Device Type

bool BGMAudioDevice::IsAggregate() const
{
try
{
return GetTransportType() == kAudioDeviceTransportTypeAggregate;
}
catch(CAException)
{
return false;
}
}

#pragma mark Available Controls

bool BGMAudioDevice::HasSettableMasterVolume(AudioObjectPropertyScope inScope) const
Expand Down Expand Up @@ -115,6 +129,12 @@ bool BGMAudioDevice::HasSettableMasterMute(AudioObjectPropertyScope inScope)
void BGMAudioDevice::CopyMuteFrom(const BGMAudioDevice inDevice,
AudioObjectPropertyScope inScope)
{
// Skip aggregate devices for the same reason as in CopyVolumeFrom.
if(IsAggregate() || inDevice.IsAggregate())
{
return;
}

// TODO: Support for devices that have per-channel mute controls but no master mute control
if(HasSettableMasterMute(inScope) && inDevice.HasMuteControl(inScope, kMasterChannel))
{
Expand All @@ -127,6 +147,17 @@ void BGMAudioDevice::CopyMuteFrom(const BGMAudioDevice inDevice,
void BGMAudioDevice::CopyVolumeFrom(const BGMAudioDevice inDevice,
AudioObjectPropertyScope inScope)
{
// Don't try to copy volume to/from aggregate devices. Aggregate devices rely on macOS to
// manage their virtual volume control through the sub-devices. Using the deprecated
// AudioHardwareService APIs (which we use for virtual master volume) to write to an aggregate
// device can corrupt its volume control state, causing the volume slider to become permanently
// disabled in System Settings. See https://github.com/kyleneideck/BackgroundMusic/issues/848
if(IsAggregate() || inDevice.IsAggregate())
{
DebugMsg("BGMAudioDevice::CopyVolumeFrom: Skipping volume copy for aggregate device");
return;
}

// Get the volume of the other device.
bool didGetVolume = false;
Float32 volume = FLT_MIN;
Expand Down Expand Up @@ -155,7 +186,7 @@ void BGMAudioDevice::CopyVolumeFrom(const BGMAudioDevice inDevice,

if(numChannels > 0) // Avoid divide by zero.
{
volume /= numChannels;
volume /= static_cast<Float32>(numChannels);
}
}

Expand Down
8 changes: 8 additions & 0 deletions BGMApp/BGMApp/BGMAudioDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class BGMAudioDevice
*/
bool CanBeOutputDeviceInBGMApp() const;

#pragma mark Device Type

/*!
@return True if this device is an aggregate device (i.e. a device created in Audio MIDI Setup
that combines multiple audio devices).
*/
bool IsAggregate() const;

#pragma mark Available Controls

bool HasSettableMasterVolume(AudioObjectPropertyScope inScope) const;
Expand Down
2 changes: 1 addition & 1 deletion BGMApp/BGMApp/BGMAutoPauseMusic.mm
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ - (void) queueUnpauseBlock {
// TODO: Fading in and out would make short pauses a lot less jarring because, if they were short enough, we wouldn't
// actually pause the music player. So you'd hear a dip in the music's volume rather than a gap.
UInt64 unpauseDelayNsec =
static_cast<UInt64>((wentSilent - wentAudible) * kUnpauseDelayWeightingFactor);
static_cast<UInt64>(static_cast<Float64>(wentSilent - wentAudible) * kUnpauseDelayWeightingFactor);

// Convert from absolute time to nanos.
mach_timebase_info_data_t info;
Expand Down
16 changes: 14 additions & 2 deletions BGMApp/BGMApp/BGMDeviceControlsList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,21 @@ bool BGMDeviceControlsList::MatchControlsListOf(AudioObjectID inDeviceID)

// Check which controls the other device has.
BGMAudioDevice device(inDeviceID);
bool hasMute = device.HasSettableMasterMute(inScope);

bool hasVolume =
// Aggregate devices (created in Audio MIDI Setup) rely on macOS to provide virtual volume
// control through the sub-devices. The deprecated AudioHardwareService APIs we use to detect
// volume support can return incorrect results for aggregate devices on newer macOS versions,
// which causes us to incorrectly disable BGMDevice's volume control. This then triggers
// PropagateControlListChange (which toggles the default device through a null device), and
// that can permanently break the aggregate device's volume slider in System Settings.
//
// To avoid this, always report that aggregate devices have volume and mute controls, since
// macOS provides virtual volume/mute for them regardless of what the HAL reports.
bool isAggregate = device.IsAggregate();

bool hasMute = isAggregate || device.HasSettableMasterMute(inScope);

bool hasVolume = isAggregate ||
device.HasSettableMasterVolume(inScope) || device.HasSettableVirtualMasterVolume(inScope);

if(!hasVolume)
Expand Down
6 changes: 3 additions & 3 deletions BGMApp/BGMApp/BGMPlayThrough.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ OSStatus BGMPlayThrough::WaitForOutputDeviceToStart() noexcept

DebugMsg("BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f "
"ms after entering WaitForOutputDeviceToStart.",
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,
static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * static_cast<Float64>(base) / NSEC_PER_MSEC,
static_cast<Float64>(startedBy - startedAt) * static_cast<Float64>(base) / NSEC_PER_MSEC);
}

// Figure out which error code to return.
Expand Down Expand Up @@ -1121,7 +1121,7 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
refCon->mInToOutSampleOffset);

// Recalculate the in-to-out offset and read head.
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - lastInputSampleTime;
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - static_cast<Float64>(lastInputSampleTime);
readHeadSampleTime = static_cast<CARingBuffer::SampleTime>(
inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);
}
Expand Down
40 changes: 17 additions & 23 deletions BGMDriver/BGMDriver/BGM_Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1361,8 +1361,8 @@ void BGM_Device::GetZeroTimeStamp(Float64& outSampleTime, UInt64& outHostTime, U
}

// set the return values
outSampleTime = mLoopbackTime.numberTimeStamps * kLoopbackRingBufferFrameSize;
outHostTime = static_cast<UInt64>(mLoopbackTime.anchorHostTime + (static_cast<Float64>(mLoopbackTime.numberTimeStamps) * theHostTicksPerRingBuffer));
outSampleTime = static_cast<Float64>(mLoopbackTime.numberTimeStamps) * kLoopbackRingBufferFrameSize;
outHostTime = static_cast<UInt64>(static_cast<Float64>(mLoopbackTime.anchorHostTime) + (static_cast<Float64>(mLoopbackTime.numberTimeStamps) * theHostTicksPerRingBuffer));
// TODO: I think we should increment outSeed whenever this device switches to/from having a wrapped engine
outSeed = 1;
}
Expand Down Expand Up @@ -1523,16 +1523,13 @@ void BGM_Device::EndIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize
void BGM_Device::ReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, void* outBuffer)
{
// Wrap the provided buffer in an AudioBufferList.
AudioBufferList abl = {
.mNumberBuffers = 1,
.mBuffers[0] = {
.mNumberChannels = 2,
// Each frame is 2 Float32 samples (one per channel). The number of frames * the number
// of bytes per frame = the size of outBuffer in bytes.
.mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2),
.mData = outBuffer
}
};
AudioBufferList abl;
abl.mNumberBuffers = 1;
abl.mBuffers[0].mNumberChannels = 2;
// Each frame is 2 Float32 samples (one per channel). The number of frames * the number
// of bytes per frame = the size of outBuffer in bytes.
abl.mBuffers[0].mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2);
abl.mBuffers[0].mData = outBuffer;

// Copy the audio data from our ring buffer into the provided buffer.
CARingBufferError err =
Expand Down Expand Up @@ -1562,16 +1559,13 @@ void BGM_Device::ReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime,
void BGM_Device::WriteOutputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, const void* inBuffer)
{
// Wrap the provided buffer in an AudioBufferList.
AudioBufferList abl = {
.mNumberBuffers = 1,
.mBuffers[0] = {
.mNumberChannels = 2,
// Each frame is 2 Float32 samples (one per channel). The number of frames * the number
// of bytes per frame = the size of inBuffer in bytes.
.mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2),
.mData = const_cast<void *>(inBuffer)
}
};
AudioBufferList abl;
abl.mNumberBuffers = 1;
abl.mBuffers[0].mNumberChannels = 2;
// Each frame is 2 Float32 samples (one per channel). The number of frames * the number
// of bytes per frame = the size of inBuffer in bytes.
abl.mBuffers[0].mDataByteSize = static_cast<UInt32>(inIOBufferFrameSize * sizeof(Float32) * 2);
abl.mBuffers[0].mData = const_cast<void *>(inBuffer);

// Copy the audio data from the provided buffer into our ring buffer.
CARingBufferError err =
Expand Down Expand Up @@ -1908,7 +1902,7 @@ Float64 BGM_Device::_HW_GetSampleRate() const
CAException(kAudioHardwareUnspecifiedError),
"BGM_Device::_HW_GetSampleRate: No wrapped audio device");

return mWrappedAudioEngine->GetSampleRate();
return static_cast<Float64>(mWrappedAudioEngine->GetSampleRate());
}

kern_return_t BGM_Device::_HW_SetSampleRate(Float64 inNewSampleRate)
Expand Down
4 changes: 2 additions & 2 deletions BGMDriver/BGMDriver/BGM_NullDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,8 @@ void BGM_NullDevice::GetZeroTimeStamp(Float64& outSampleTime,
(static_cast<Float64>(mNumberTimeStamps) * theHostTicksPerPeriod);

// Set the return values.
outSampleTime = mNumberTimeStamps * kZeroTimeStampPeriod;
outHostTime = static_cast<UInt64>(mAnchorHostTime + theHostTicksSinceAnchor);
outSampleTime = static_cast<Float64>(mNumberTimeStamps) * kZeroTimeStampPeriod;
outHostTime = static_cast<UInt64>(static_cast<Float64>(mAnchorHostTime) + theHostTicksSinceAnchor);
outSeed = 1;
}

Expand Down
2 changes: 1 addition & 1 deletion BGMDriver/BGMDriver/BGM_VolumeControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ void BGM_VolumeControl::SetVolumeRaw(SInt32 inNewVolumeRaw)
// TODO: This assumes the control should never boost the signal. (So, technically, it never
// actually applies gain, only loss.)
SInt32 theRawRange = mMaxVolumeRaw - mMinVolumeRaw;
SInt32 theSliderPositionInRawSteps = static_cast<SInt32>(theSliderPosition * theRawRange);
SInt32 theSliderPositionInRawSteps = static_cast<SInt32>(theSliderPosition * static_cast<Float32>(theRawRange));
theSliderPositionInRawSteps += mMinVolumeRaw;

mAmplitudeGain = mVolumeCurve.ConvertRawToScalar(theSliderPositionInRawSteps);
Expand Down
2 changes: 1 addition & 1 deletion BGMDriver/PublicUtility/CAVolumeCurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ Float32 CAVolumeCurve::ConvertRawToDB(SInt32 inRaw) const
SInt32 theRawStepsToAdd = std::min(theRawRange, theNumberRawSteps);

// add this many steps worth of db to the answer;
theAnswer += theRawStepsToAdd * theDBPerRaw;
theAnswer += static_cast<Float32>(theRawStepsToAdd) * theDBPerRaw;

// figure out how many steps are left
theNumberRawSteps -= theRawStepsToAdd;
Expand Down