Core Audio: Add a slight fading to operations

Add 10 millisecond fade to seeking, pausing and unpausing, and stopping
on command.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-03-07 23:22:16 -08:00
parent ff5a1c6c2c
commit 46aac2fa91
5 changed files with 86 additions and 3 deletions

View file

@ -75,6 +75,7 @@
[self waitUntilCallbacksExit];
if(output) {
[output fadeOut];
[output setShouldContinue:NO];
[output close];
}
@ -83,6 +84,9 @@
}
[output setupWithInterval:resumeInterval];
[output setVolume:volume];
if(resumeInterval) {
[output fadeIn];
}
@synchronized(chainQueue) {
for(id anObject in chainQueue) {
[anObject setShouldContinue:NO];
@ -166,7 +170,7 @@
}
- (void)pause {
[output pause];
[output fadeOut];
[self setPlaybackStatus:CogStatusPaused waitUntilDone:YES];
}
@ -178,6 +182,7 @@
[self launchOutputThread];
}
[output fadeIn];
[output resume];
[self setPlaybackStatus:CogStatusPlaying waitUntilDone:YES];

View file

@ -49,6 +49,9 @@
- (void)close;
- (void)seek:(double)time;
- (void)fadeOut;
- (void)fadeIn;
- (AudioChunk *)readChunk:(size_t)amount;
- (void)setFormat:(AudioStreamBasicDescription *)f channelConfig:(uint32_t)channelConfig;

View file

@ -99,6 +99,14 @@
[output resume];
}
- (void)fadeOut {
[output fadeOut];
}
- (void)fadeIn {
[output fadeIn];
}
- (void)incrementAmountPlayed:(double)seconds {
amountPlayed += seconds;
amountPlayedInterval += seconds;

View file

@ -57,6 +57,11 @@ using std::atomic_long;
BOOL commandStop;
BOOL resetting;
BOOL fading, faded;
float fadeLevel;
float fadeStep;
float fadeTarget;
BOOL eqEnabled;
BOOL eqInitialized;
@ -110,6 +115,9 @@ using std::atomic_long;
- (void)resume;
- (void)stop;
- (void)fadeOut;
- (void)fadeIn;
- (double)latency;
- (double)volume;

View file

@ -534,6 +534,29 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
}
}
static BOOL fadeAudio(float * samples, size_t channels, size_t count, float * fadeLevel, float fadeStep, float fadeTarget) {
float _fadeLevel = *fadeLevel;
BOOL towardZero = fadeStep < 0.0;
BOOL stopping = NO;
for(size_t i = 0; i < count; ++i) {
for(size_t j = 0; j < channels; ++j) {
samples[j] *= _fadeLevel;
}
_fadeLevel += fadeStep;
if(towardZero && _fadeLevel <= fadeTarget) {
_fadeLevel = fadeTarget;
fadeStep = 0.0;
stopping = YES;
} else if(!towardZero && _fadeLevel >= fadeTarget) {
_fadeLevel = fadeTarget;
fadeStep = 0.0;
stopping = YES;
}
}
*fadeLevel = _fadeLevel;
return stopping;
}
- (void)renderAndConvert {
if(resetStreamFormat) {
[self updateStreamFormat];
@ -583,7 +606,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
OutputCoreAudio *_self = (__bridge OutputCoreAudio *)refCon;
int renderedSamples = 0;
if(_self->resetting) {
if(_self->resetting || _self->faded) {
inputData->mBuffers[0].mDataByteSize = frameCount * format->mBytesPerPacket;
bzero(inputData->mBuffers[0].mData, inputData->mBuffers[0].mDataByteSize);
inputData->mBuffers[0].mNumberChannels = channels;
@ -610,7 +633,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
renderedSamples += inputTodo;
}
if(_self->stopping || _self->resetting) {
if(_self->stopping || _self->resetting || _self->faded) {
break;
}
}
@ -630,6 +653,17 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
scale_by_volume((float*)inputData->mBuffers[0].mData, renderedSamples * channels, volumeScale * _self->volume);
if(_self->fading) {
BOOL faded = fadeAudio((float*)inputData->mBuffers[0].mData, channels, renderedSamples, &_self->fadeLevel, _self->fadeStep, _self->fadeTarget);
if(faded) {
if(_self->fadeStep < 0.0f) {
_self->faded = YES;
}
_self->fading = NO;
_self->fadeStep = 0.0f;
}
}
inputData->mBuffers[0].mDataByteSize = renderedSamples * format->mBytesPerPacket;
inputData->mBuffers[0].mNumberChannels = channels;
@ -667,6 +701,12 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
outputDeviceID = -1;
restarted = NO;
fadeTarget = 1.0f;
fadeLevel = 1.0f;
fadeStep = 0.0f;
fading = NO;
faded = NO;
AudioComponentDescription desc;
NSError *err;
@ -788,6 +828,11 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
compareVal = [outputController getTotalLatency];
usleep(5000);
} while(!commandStop && compareVal > 0 && compareMax-- > 0);
} else {
[self fadeOut];
while(fading && !faded) {
usleep(5000);
}
}
[_au stopHardware];
_au = nil;
@ -850,4 +895,18 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
return deviceChannelConfig;
}
// 10 milliseconds
- (void)fadeOut {
fadeTarget = 0.0f;
fadeStep = ((fadeTarget - fadeLevel) / deviceFormat.mSampleRate) * (1000.0f / 10.0f);
fading = YES;
}
- (void)fadeIn {
fadeTarget = 1.0;
fadeStep = ((fadeTarget - fadeLevel) / deviceFormat.mSampleRate) * (1000.0f / 10.0f);
fading = YES;
faded = NO;
}
@end