diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 1c8c5c432..128c9bd13 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -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]; diff --git a/Audio/Chain/OutputNode.h b/Audio/Chain/OutputNode.h index 99ceda99b..ca24ee32d 100644 --- a/Audio/Chain/OutputNode.h +++ b/Audio/Chain/OutputNode.h @@ -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; diff --git a/Audio/Chain/OutputNode.m b/Audio/Chain/OutputNode.m index f77ff56a1..90900f449 100644 --- a/Audio/Chain/OutputNode.m +++ b/Audio/Chain/OutputNode.m @@ -99,6 +99,14 @@ [output resume]; } +- (void)fadeOut { + [output fadeOut]; +} + +- (void)fadeIn { + [output fadeIn]; +} + - (void)incrementAmountPlayed:(double)seconds { amountPlayed += seconds; amountPlayedInterval += seconds; diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index 66fb26b23..41316d4e3 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -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; diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index 237634c4b..1b43c9b13 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -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