diff options
author | Chris Robinson <[email protected]> | 2019-03-09 21:32:14 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2019-03-09 21:32:14 -0800 |
commit | 106671d45185576e9386eaf0c27211446c8ebce5 (patch) | |
tree | 1303e386f116f5c037d610c93e0d123941b4dcde | |
parent | 87479b4b3207a0600586c919c03b5cee7af5154e (diff) |
Fade out voices that end normally
Sometimes a sound may end with non-0 amplitude, particularly if a buffer queue
underruns. This helps avoid clicks and pops for sources that don't already end
in silence.
-rw-r--r-- | Alc/mixvoice.cpp | 320 |
1 files changed, 180 insertions, 140 deletions
diff --git a/Alc/mixvoice.cpp b/Alc/mixvoice.cpp index b94a6a1e..09c19136 100644 --- a/Alc/mixvoice.cpp +++ b/Alc/mixvoice.cpp @@ -298,6 +298,148 @@ const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter, return src; } +ALsizei LoadBufferStatic(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, + const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, + ALfloat (&SrcData)[BUFFERSIZE + MAX_RESAMPLE_PADDING*2], ALsizei FilledAmt, + const ALsizei SrcBufferSize) +{ + /* TODO: For static sources, loop points are taken from the first buffer + * (should be adjusted by any buffer offset, to possibly be added later). + */ + const ALbuffer *Buffer0{BufferListItem->buffers[0]}; + const ALsizei LoopStart{Buffer0->LoopStart}; + const ALsizei LoopEnd{Buffer0->LoopEnd}; + ASSUME(LoopStart >= 0); + ASSUME(LoopEnd > LoopStart); + + /* If current pos is beyond the loop range, do not loop */ + if(!BufferLoopItem || DataPosInt >= LoopEnd) + { + const ALsizei SizeToDo{SrcBufferSize - FilledAmt}; + + BufferLoopItem = nullptr; + + auto load_buffer = [DataPosInt,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei + { + if(DataPosInt >= buffer->SampleLen) + return CompLen; + + /* Load what's left to play from the buffer */ + const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - DataPosInt)}; + CompLen = maxi(CompLen, DataSize); + + const ALbyte *Data{buffer->mData.data()}; + LoadSamples(&SrcData[FilledAmt], + &Data[(DataPosInt*NumChannels + chan)*SampleSize], + NumChannels, buffer->mFmtType, DataSize + ); + return CompLen; + }; + auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; + FilledAmt += std::accumulate(BufferListItem->buffers, buffers_end, ALsizei{0}, + load_buffer); + } + else + { + const ALsizei SizeToDo{mini(SrcBufferSize - FilledAmt, LoopEnd - DataPosInt)}; + + auto load_buffer = [DataPosInt,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei + { + if(DataPosInt >= buffer->SampleLen) + return CompLen; + + /* Load what's left of this loop iteration */ + const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - DataPosInt)}; + CompLen = maxi(CompLen, DataSize); + + const ALbyte *Data{buffer->mData.data()}; + LoadSamples(&SrcData[FilledAmt], + &Data[(DataPosInt*NumChannels + chan)*SampleSize], + NumChannels, buffer->mFmtType, DataSize + ); + return CompLen; + }; + auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; + FilledAmt += std::accumulate(BufferListItem->buffers, buffers_end, ALsizei{0}, + load_buffer); + + const ALsizei LoopSize{LoopEnd - LoopStart}; + while(SrcBufferSize > FilledAmt) + { + const ALsizei SizeToDo{mini(SrcBufferSize - FilledAmt, LoopSize)}; + + auto load_buffer_loop = [LoopStart,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei + { + if(LoopStart >= buffer->SampleLen) + return CompLen; + + const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - LoopStart)}; + CompLen = maxi(CompLen, DataSize); + + const ALbyte *Data{buffer->mData.data()}; + LoadSamples(&SrcData[FilledAmt], + &Data[(LoopStart*NumChannels + chan)*SampleSize], + NumChannels, buffer->mFmtType, DataSize + ); + return CompLen; + }; + FilledAmt += std::accumulate(BufferListItem->buffers, buffers_end, ALsizei{0}, + load_buffer_loop); + } + } + return FilledAmt; +} + +ALsizei LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem, + const ALsizei NumChannels, const ALsizei SampleSize, const ALsizei chan, ALsizei DataPosInt, + ALfloat (&SrcData)[BUFFERSIZE + MAX_RESAMPLE_PADDING*2], ALsizei FilledAmt, + const ALsizei SrcBufferSize) +{ + /* Crawl the buffer queue to fill in the temp buffer */ + ALbufferlistitem *tmpiter{BufferListItem}; + ALsizei pos{DataPosInt}; + + while(tmpiter && SrcBufferSize > FilledAmt) + { + if(pos >= tmpiter->max_samples) + { + pos -= tmpiter->max_samples; + tmpiter = tmpiter->next.load(std::memory_order_acquire); + if(!tmpiter) tmpiter = BufferLoopItem; + continue; + } + + const ALsizei SizeToDo{SrcBufferSize - FilledAmt}; + auto load_buffer = [pos,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei + { + if(!buffer) return CompLen; + ALsizei DataSize{buffer->SampleLen}; + if(pos >= DataSize) return CompLen; + + DataSize = mini(SizeToDo, DataSize - pos); + CompLen = maxi(CompLen, DataSize); + + const ALbyte *Data{buffer->mData.data()}; + Data += (pos*NumChannels + chan)*SampleSize; + + LoadSamples(&SrcData[FilledAmt], Data, NumChannels, + buffer->mFmtType, DataSize); + return CompLen; + }; + auto buffers_end = tmpiter->buffers + tmpiter->num_buffers; + FilledAmt += std::accumulate(tmpiter->buffers, buffers_end, ALsizei{0}, + load_buffer); + + if(SrcBufferSize <= FilledAmt) + break; + pos = 0; + tmpiter = tmpiter->next.load(std::memory_order_acquire); + if(!tmpiter) tmpiter = BufferLoopItem; + } + + return FilledAmt; +} + } // namespace void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo) @@ -420,142 +562,38 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const std::fill(srciter, std::end(SrcData), 0.0f); ALsizei FilledAmt{MAX_RESAMPLE_PADDING}; - if(vstate == ALvoice::Stopping) + if(vstate != ALvoice::Playing) { - srciter = std::copy(voice->PrevSamples[chan].begin()+MAX_RESAMPLE_PADDING, + std::copy(voice->PrevSamples[chan].begin()+MAX_RESAMPLE_PADDING, voice->PrevSamples[chan].end(), srciter); - std::fill(srciter, std::begin(SrcData)+SrcBufferSize, *(srciter-1)); - FilledAmt = SrcBufferSize; + FilledAmt = voice->PrevSamples[chan].size(); } else if(isstatic) { - /* TODO: For static sources, loop points are taken from the - * first buffer (should be adjusted by any buffer offset, to - * possibly be added later). - */ - const ALbuffer *Buffer0{BufferListItem->buffers[0]}; - const ALsizei LoopStart{Buffer0->LoopStart}; - const ALsizei LoopEnd{Buffer0->LoopEnd}; - ASSUME(LoopStart >= 0); - ASSUME(LoopEnd > LoopStart); - - /* If current pos is beyond the loop range, do not loop */ - if(!BufferLoopItem || DataPosInt >= LoopEnd) - { - const ALsizei SizeToDo{SrcBufferSize - FilledAmt}; - - BufferLoopItem = nullptr; - - auto load_buffer = [DataPosInt,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left to play from the buffer */ - const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - DataPosInt)}; - CompLen = maxi(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - LoadSamples(&SrcData[FilledAmt], - &Data[(DataPosInt*NumChannels + chan)*SampleSize], - NumChannels, buffer->mFmtType, DataSize - ); - return CompLen; - }; - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - FilledAmt += std::accumulate(BufferListItem->buffers, buffers_end, ALsizei{0}, - load_buffer); - } - else - { - const ALsizei SizeToDo{mini(SrcBufferSize - FilledAmt, LoopEnd - DataPosInt)}; - - auto load_buffer = [DataPosInt,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei - { - if(DataPosInt >= buffer->SampleLen) - return CompLen; - - /* Load what's left of this loop iteration */ - const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - DataPosInt)}; - CompLen = maxi(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - LoadSamples(&SrcData[FilledAmt], - &Data[(DataPosInt*NumChannels + chan)*SampleSize], - NumChannels, buffer->mFmtType, DataSize - ); - return CompLen; - }; - auto buffers_end = BufferListItem->buffers + BufferListItem->num_buffers; - FilledAmt = std::accumulate(BufferListItem->buffers, buffers_end, ALsizei{0}, load_buffer); - - const ALsizei LoopSize{LoopEnd - LoopStart}; - while(SrcBufferSize > FilledAmt) - { - const ALsizei SizeToDo{mini(SrcBufferSize - FilledAmt, LoopSize)}; - - auto load_buffer_loop = [LoopStart,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei - { - if(LoopStart >= buffer->SampleLen) - return CompLen; - - const ALsizei DataSize{mini(SizeToDo, buffer->SampleLen - LoopStart)}; - CompLen = maxi(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - LoadSamples(&SrcData[FilledAmt], - &Data[(LoopStart*NumChannels + chan)*SampleSize], - NumChannels, buffer->mFmtType, DataSize - ); - return CompLen; - }; - FilledAmt += std::accumulate(BufferListItem->buffers, buffers_end, - ALsizei{0}, load_buffer_loop); - } - } + FilledAmt = LoadBufferStatic(BufferListItem, BufferLoopItem, NumChannels, + SampleSize, chan, DataPosInt, SrcData, FilledAmt, SrcBufferSize); } else { - /* Crawl the buffer queue to fill in the temp buffer */ - ALbufferlistitem *tmpiter{BufferListItem}; - ALsizei pos{DataPosInt}; + FilledAmt = LoadBufferQueue(BufferListItem, BufferLoopItem, NumChannels, + SampleSize, chan, DataPosInt, SrcData, FilledAmt, SrcBufferSize); + } - while(tmpiter && SrcBufferSize > FilledAmt) - { - if(pos >= tmpiter->max_samples) - { - pos -= tmpiter->max_samples; - tmpiter = tmpiter->next.load(std::memory_order_acquire); - if(!tmpiter) tmpiter = BufferLoopItem; - continue; - } + if(UNLIKELY(FilledAmt < SrcBufferSize)) + { + /* If the source buffer wasn't filled, copy the last sample and + * fade it to 0 amplitude. Ideally it should have ended with + * silence, but if not this should help avoid clicks from + * sudden amplitude changes. + */ + const ALfloat sample{SrcData[FilledAmt-1]}; + const ALfloat gainstep{1.0f / (BUFFERSIZE*2)}; + ALfloat step{BUFFERSIZE*2}; - const ALsizei SizeToDo{SrcBufferSize - FilledAmt}; - auto load_buffer = [pos,&SrcData,NumChannels,SampleSize,chan,FilledAmt,SizeToDo](ALsizei CompLen, const ALbuffer *buffer) -> ALsizei - { - if(!buffer) return CompLen; - ALsizei DataSize{buffer->SampleLen}; - if(pos >= DataSize) return CompLen; - - DataSize = mini(SizeToDo, DataSize - pos); - CompLen = maxi(CompLen, DataSize); - - const ALbyte *Data{buffer->mData.data()}; - Data += (pos*NumChannels + chan)*SampleSize; - - LoadSamples(&SrcData[FilledAmt], Data, NumChannels, - buffer->mFmtType, DataSize); - return CompLen; - }; - auto buffers_end = tmpiter->buffers + tmpiter->num_buffers; - FilledAmt += std::accumulate(tmpiter->buffers, buffers_end, ALsizei{0}, - load_buffer); - - if(SrcBufferSize <= FilledAmt) - break; - pos = 0; - tmpiter = tmpiter->next.load(std::memory_order_acquire); - if(!tmpiter) tmpiter = BufferLoopItem; + for(;FilledAmt < SrcBufferSize;++FilledAmt) + { + step -= 1.0f; + SrcData[FilledAmt] = sample * gainstep*step; } } @@ -718,9 +756,9 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const voice->Offset += DstBufferSize; Counter = maxi(DstBufferSize, Counter) - DstBufferSize; - if(UNLIKELY(vstate == ALvoice::Stopping)) + if(UNLIKELY(vstate != ALvoice::Playing)) { - /* Do nothing. */ + /* Do nothing extra for fading out. */ } else if(isstatic) { @@ -728,8 +766,8 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const { /* Handle looping static source */ const ALbuffer *Buffer{BufferListItem->buffers[0]}; - ALsizei LoopStart{Buffer->LoopStart}; - ALsizei LoopEnd{Buffer->LoopEnd}; + const ALsizei LoopStart{Buffer->LoopStart}; + const ALsizei LoopEnd{Buffer->LoopEnd}; if(DataPosInt >= LoopEnd) { assert(LoopEnd > LoopStart); @@ -743,8 +781,6 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const { vstate = ALvoice::Stopped; BufferListItem = nullptr; - DataPosInt = 0; - DataPosFrac = 0; break; } } @@ -762,12 +798,10 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const if(!BufferListItem && !(BufferListItem=BufferLoopItem)) { vstate = ALvoice::Stopped; - DataPosInt = 0; - DataPosFrac = 0; break; } } - } while(vstate != ALvoice::Stopped && OutPos < SamplesToDo); + } while(OutPos < SamplesToDo); voice->Flags |= VOICE_IS_FADING; @@ -781,7 +815,13 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const /* Update source info */ voice->position.store(DataPosInt, std::memory_order_relaxed); voice->position_fraction.store(DataPosFrac, std::memory_order_relaxed); - voice->current_buffer.store(BufferListItem, std::memory_order_release); + voice->current_buffer.store(BufferListItem, std::memory_order_relaxed); + if(vstate == ALvoice::Stopped) + { + voice->loop_buffer.store(nullptr, std::memory_order_relaxed); + voice->SourceID.store(0u, std::memory_order_relaxed); + } + std::atomic_thread_fence(std::memory_order_release); /* Send any events now, after the position/buffer info was updated. */ ALbitfieldSOFT enabledevt{Context->EnabledEvts.load(std::memory_order_acquire)}; @@ -801,10 +841,10 @@ void MixSource(ALvoice *voice, const ALuint SourceID, ALCcontext *Context, const if(vstate == ALvoice::Stopped) { - voice->current_buffer.store(nullptr, std::memory_order_relaxed); - voice->loop_buffer.store(nullptr, std::memory_order_relaxed); - voice->SourceID.store(0u, std::memory_order_relaxed); - voice->PlayState.store(ALvoice::Stopped, std::memory_order_release); + /* If the voice just ended, set it to Stopping so the next render + * ensures any residual noise fades to 0 amplitude. + */ + voice->PlayState.store(ALvoice::Stopping, std::memory_order_release); SendSourceStoppedEvent(Context, SourceID); } } |