From 7de8284dd0743d9e3abae59eb4e0ecbe2813a774 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Oct 2017 12:08:02 -0700 Subject: [PATCH] Add a method to fade out sources --- include/AL/alure2.h | 12 ++++++++++ src/context.cpp | 28 ++++++++++++++++++++++ src/context.h | 2 ++ src/source.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---- src/source.h | 7 ++++++ 5 files changed, 114 insertions(+), 4 deletions(-) diff --git a/include/AL/alure2.h b/include/AL/alure2.h index 9a28f66..696e5f9 100644 --- a/include/AL/alure2.h +++ b/include/AL/alure2.h @@ -972,6 +972,18 @@ public: /** Stops playback, releasing the buffer or decoder reference. */ void stop(); + /** + * Fades the source to the specified gain over the given duration, at which + * point playback will stop. This gain is in addition to the base gain, and + * must be greater than 0 and less than 1. The duration must also be + * greater than 0. + * + * Fading is updated during calls to \c Context::update, which should be + * called regularly (30 to 50 times per second) for the fading to be + * smooth. + */ + void fadeOutToStop(ALfloat gain, std::chrono::milliseconds duration); + /** Pauses the source if it is playing. */ void pause(); diff --git a/src/context.cpp b/src/context.cpp index d3dd74d..c229200 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1147,6 +1147,16 @@ Source ContextImpl::createSource() } +void ContextImpl::addFadingSource(SourceImpl *source) +{ + auto iter = std::lower_bound(mFadingSources.begin(), mFadingSources.end(), source, + [](SourceImpl *lhs, SourceImpl *rhs) -> bool + { return lhs < rhs; } + ); + if(iter == mFadingSources.end() || *iter != source) + mFadingSources.insert(iter, source); +} + void ContextImpl::addPlayingSource(SourceImpl *source, ALuint id) { auto iter = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source, @@ -1169,6 +1179,13 @@ void ContextImpl::addPlayingSource(SourceImpl *source) void ContextImpl::removePlayingSource(SourceImpl *source) { + auto iter = std::lower_bound(mFadingSources.begin(), mFadingSources.end(), source, + [](SourceImpl *lhs, SourceImpl *rhs) -> bool + { return lhs < rhs; } + ); + if(iter != mFadingSources.end() && *iter == source) + mFadingSources.erase(iter); + auto iter0 = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source, [](const SourceBufferUpdateEntry &lhs, SourceImpl *rhs) -> bool { return lhs.mSource < rhs; } @@ -1319,6 +1336,16 @@ void ContextImpl::setDistanceModel(DistanceModel model) void ContextImpl::update() { CheckContext(this); + if(!mFadingSources.empty()) + { + auto cur_time = std::chrono::steady_clock::now(); + mFadingSources.erase( + std::remove_if(mFadingSources.begin(), mFadingSources.end(), + [cur_time](SourceImpl *source) -> bool + { return !source->fadeUpdate(cur_time); } + ), mFadingSources.end() + ); + } mPlaySources.erase( std::remove_if(mPlaySources.begin(), mPlaySources.end(), [](const SourceBufferUpdateEntry &entry) -> bool @@ -1331,6 +1358,7 @@ void ContextImpl::update() { return !entry.mSource->playUpdate(); } ), mStreamSources.end() ); + if(!mWakeInterval.load(std::memory_order_relaxed).count()) { // For performance reasons, don't wait for the thread's mutex. This diff --git a/src/context.h b/src/context.h index c7bb290..1ee23f6 100644 --- a/src/context.h +++ b/src/context.h @@ -135,6 +135,7 @@ private: Vector mFreeSources; Vector mPlaySources; Vector mStreamSources; + Vector mFadingSources; Vector mStreamingSources; std::mutex mSourceStreamMutex; @@ -229,6 +230,7 @@ public: ALuint getSourceId(ALuint maxprio); void insertSourceId(ALuint id) { mSourceIds.push(id); } + void addFadingSource(SourceImpl *source); void addPlayingSource(SourceImpl *source, ALuint id); void addPlayingSource(SourceImpl *source); void removePlayingSource(SourceImpl *source); diff --git a/src/source.cpp b/src/source.cpp index abceee1..3d4897f 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -184,6 +184,9 @@ void SourceImpl::resetProperties() mGroupPitch = 1.0f; mGroupGain = 1.0f; + mFadeGainTarget = 1.0f; + mFadeGain = 1.0f; + mPaused.store(false, std::memory_order_release); mOffset = 0; mPitch = 1.0f; @@ -236,7 +239,7 @@ void SourceImpl::applyProperties(bool looping, ALuint offset) const alSourcei(mId, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); alSourcei(mId, AL_SAMPLE_OFFSET, offset); alSourcef(mId, AL_PITCH, mPitch * mGroupPitch); - alSourcef(mId, AL_GAIN, mGain * mGroupGain); + alSourcef(mId, AL_GAIN, mGain * mGroupGain * mFadeGain); alSourcef(mId, AL_MIN_GAIN, mMinGain); alSourcef(mId, AL_MAX_GAIN, mMaxGain); alSourcef(mId, AL_REFERENCE_DISTANCE, mRefDist); @@ -300,7 +303,7 @@ void SourceImpl::unsetGroup() if(mId) { alSourcef(mId, AL_PITCH, mPitch * mGroupPitch); - alSourcef(mId, AL_GAIN, mGain * mGroupGain); + alSourcef(mId, AL_GAIN, mGain * mGroupGain * mFadeGain); } } @@ -309,7 +312,7 @@ void SourceImpl::groupPropUpdate(ALfloat gain, ALfloat pitch) if(mId) { alSourcef(mId, AL_PITCH, mPitch * pitch); - alSourcef(mId, AL_GAIN, mGain * gain); + alSourcef(mId, AL_GAIN, mGain * gain * mFadeGain); } mGroupPitch = pitch; mGroupGain = gain; @@ -327,6 +330,9 @@ void SourceImpl::play(Buffer buffer) mContext->removeStream(this); mIsAsync.store(false, std::memory_order_release); + mFadeGainTarget = mFadeGain = 1.0f; + mFadeTimeTarget = mLastFadeTime = std::chrono::steady_clock::now(); + if(mId == 0) { mId = mContext->getSourceId(mPriority); @@ -369,6 +375,9 @@ void SourceImpl::play(SharedPtr decoder, ALuint updatelen, ALuint queue mContext->removeStream(this); mIsAsync.store(false, std::memory_order_release); + mFadeGainTarget = mFadeGain = 1.0f; + mFadeTimeTarget = mLastFadeTime = std::chrono::steady_clock::now(); + if(mId == 0) { mId = mContext->getSourceId(mPriority); @@ -448,6 +457,22 @@ void SourceImpl::stop() } +void SourceImpl::fadeOutToStop(ALfloat gain, std::chrono::milliseconds duration) +{ + if(!(gain < 1.0f && gain >= 0.0f)) + throw std::runtime_error("Fade gain target out of range"); + if(duration.count() <= 0) + throw std::runtime_error("Fade duration out of range"); + CheckContext(mContext); + + mFadeGainTarget = std::max(gain, 0.0001f); + mLastFadeTime = std::chrono::steady_clock::now(); + mFadeTimeTarget = mLastFadeTime + duration; + + mContext->addFadingSource(this); +} + + void SourceImpl::checkPaused() { if(mPaused.load(std::memory_order_acquire) || mId == 0) @@ -520,6 +545,41 @@ bool SourceImpl::isPaused() const } +bool SourceImpl::fadeUpdate(std::chrono::steady_clock::time_point cur_fade_time) +{ + if((cur_fade_time - mFadeTimeTarget).count() >= 0) + { + mLastFadeTime = mFadeTimeTarget; + mFadeGain = 1.0f; + if(mFadeGainTarget >= 1.0f) + { + alSourcef(mId, AL_GAIN, mGain * mGroupGain); + return false; + } + makeStopped(true); + return false; + } + + float mult = std::pow(mFadeGainTarget/mFadeGain, + float(1.0/Seconds(mFadeTimeTarget-mLastFadeTime).count()) + ); + + std::chrono::steady_clock::duration duration = cur_fade_time - mLastFadeTime; + mLastFadeTime = cur_fade_time; + + float gain = mFadeGain * std::pow(mult, (float)Seconds(duration).count()); + if(EXPECT(gain == mFadeGain, false)) + { + // Ensure the gain keeps moving toward its target, in case precision + // loss results in no change with small steps. + gain = std::nextafter(gain, mFadeGainTarget); + } + mFadeGain = gain; + + alSourcef(mId, AL_GAIN, mGain * mGroupGain * mFadeGain); + return true; +} + bool SourceImpl::playUpdate(ALuint id) { ALint state = -1; @@ -786,7 +846,7 @@ void SourceImpl::setGain(ALfloat gain) throw std::runtime_error("Gain out of range"); CheckContext(mContext); if(mId != 0) - alSourcef(mId, AL_GAIN, gain * mGroupGain); + alSourcef(mId, AL_GAIN, gain * mGroupGain * mFadeGain); mGain = gain; } @@ -1275,6 +1335,7 @@ using BoolTriple = std::tuple; DECL_THUNK1(void, Source, play,, Buffer) DECL_THUNK3(void, Source, play,, SharedPtr, ALuint, ALuint) DECL_THUNK0(void, Source, stop,) +DECL_THUNK2(void, Source, fadeOutToStop,, ALfloat, std::chrono::milliseconds) DECL_THUNK0(void, Source, pause,) DECL_THUNK0(void, Source, resume,) DECL_THUNK0(bool, Source, isPlaying, const) diff --git a/src/source.h b/src/source.h index 8a2a72f..f2a58a0 100644 --- a/src/source.h +++ b/src/source.h @@ -38,6 +38,11 @@ class SourceImpl { ALfloat mGroupPitch; ALfloat mGroupGain; + std::chrono::steady_clock::time_point mLastFadeTime; + std::chrono::steady_clock::time_point mFadeTimeTarget; + ALfloat mFadeGainTarget; + ALfloat mFadeGain; + mutable std::mutex mMutex; std::atomic mIsAsync; @@ -84,6 +89,7 @@ public: ALuint getId() const { return mId; } + bool fadeUpdate(std::chrono::steady_clock::time_point cur_fade_time); bool playUpdate(ALuint id); bool playUpdate(); bool updateAsync(); @@ -99,6 +105,7 @@ public: void play(Buffer buffer); void play(SharedPtr decoder, ALuint updatelen, ALuint queuesize); void stop(); + void fadeOutToStop(ALfloat gain, std::chrono::milliseconds duration); void pause(); void resume(); -- 2.11.4.GIT