diff options
author | Chris Robinson <[email protected]> | 2022-11-01 22:29:29 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2022-11-02 00:14:21 -0700 |
commit | 5bbfc92ffe5c121341d986143facd432ec874053 (patch) | |
tree | 64bafc3a26f1af313fcc511f4ed3a904694ec97d | |
parent | bc7f5260475586b833a568c7b3ac48ac9f89be3b (diff) |
Handle negative voice positions
This allows a voice/source to start with a delay, more accurately than simply
waiting to call alSourcePlay. The delay is affected by pitch and velocity,
making it useful to simulate distant sounds that take time to be heard.
-rw-r--r-- | al/source.cpp | 102 | ||||
-rw-r--r-- | core/voice.cpp | 76 | ||||
-rw-r--r-- | core/voice.h | 2 |
3 files changed, 116 insertions, 64 deletions
diff --git a/al/source.cpp b/al/source.cpp index a8c25bb6..9a1a036c 100644 --- a/al/source.cpp +++ b/al/source.cpp @@ -193,9 +193,9 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; - uint64_t readPos{}; - ALuint refcount; - Voice *voice; + int64_t readPos{}; + uint refcount{}; + Voice *voice{}; do { refcount = device->waitForMix(); @@ -205,9 +205,8 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << 32; - readPos |= uint64_t{voice->mPositionFrac.load(std::memory_order_relaxed)} << - (32-MixerFracBits); + readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; + readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != device->MixCount.load(std::memory_order_relaxed)); @@ -218,9 +217,11 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds for(auto &item : Source->mQueue) { if(&item == Current) break; - readPos += uint64_t{item.mSampleLen} << 32; + readPos += int64_t{item.mSampleLen} << MixerFracBits; } - return static_cast<int64_t>(minu64(readPos, 0x7fffffffffffffff_u64)); + if(readPos > std::numeric_limits<int64_t>::max() >> (32-MixerFracBits)) + return std::numeric_limits<int64_t>::max(); + return readPos << (32-MixerFracBits); } /* GetSourceSecOffset @@ -232,9 +233,9 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; - uint64_t readPos{}; - ALuint refcount; - Voice *voice; + int64_t readPos{}; + uint refcount{}; + Voice *voice{}; do { refcount = device->waitForMix(); @@ -244,8 +245,8 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; - readPos |= voice->mPositionFrac.load(std::memory_order_relaxed); + readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; + readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != device->MixCount.load(std::memory_order_relaxed)); @@ -258,7 +259,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl while(BufferList != Source->mQueue.cend() && std::addressof(*BufferList) != Current) { if(!BufferFmt) BufferFmt = BufferList->mBuffer; - readPos += uint64_t{BufferList->mSampleLen} << MixerFracBits; + readPos += int64_t{BufferList->mSampleLen} << MixerFracBits; ++BufferList; } while(BufferList != Source->mQueue.cend() && !BufferFmt) @@ -281,9 +282,9 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; - ALuint readPos{}; - ALuint readPosFrac{}; - ALuint refcount; + int64_t readPos{}; + uint readPosFrac{}; + uint refcount; Voice *voice; do { @@ -321,11 +322,12 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) switch(name) { case AL_SEC_OFFSET: - offset = (readPos + readPosFrac/double{MixerFracOne}) / BufferFmt->mSampleRate; + offset = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne}; + offset /= BufferFmt->mSampleRate; break; case AL_SAMPLE_OFFSET: - offset = readPos + readPosFrac/double{MixerFracOne}; + offset = static_cast<double>(readPos) + readPosFrac/double{MixerFracOne}; break; case AL_BYTE_OFFSET: @@ -410,7 +412,8 @@ double GetSourceLength(const ALsource *source, ALenum name) struct VoicePos { - ALuint pos, frac; + int pos; + uint frac; ALbufferQueueItem *bufferitem; }; @@ -435,45 +438,73 @@ al::optional<VoicePos> GetSampleOffset(al::deque<ALbufferQueueItem> &BufferList, return al::nullopt; /* Get sample frame offset */ - ALuint offset{0u}, frac{0u}; + int64_t offset{}; + uint frac{}; double dbloff, dblfrac; switch(OffsetType) { case AL_SEC_OFFSET: dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); - offset = static_cast<ALuint>(mind(dbloff, std::numeric_limits<ALuint>::max())); - frac = static_cast<ALuint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + if(dblfrac < 0.0) + { + /* If there's a negative fraction, reduce the offset to "floor" it, + * and convert the fraction to a percentage to the next value (e.g. + * -2.75 -> -3 + 0.25). + */ + dbloff -= 1.0; + dblfrac += 1.0; + } + dbloff = clampd(dbloff, double{std::numeric_limits<int64_t>::min()}, + double{std::numeric_limits<int64_t>::max()}); + offset = static_cast<int64_t>(dbloff); + frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_SAMPLE_OFFSET: dblfrac = std::modf(Offset, &dbloff); - offset = static_cast<ALuint>(mind(dbloff, std::numeric_limits<ALuint>::max())); - frac = static_cast<ALuint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + if(dblfrac < 0.0) + { + dbloff -= 1.0; + dblfrac += 1.0; + } + dbloff = clampd(dbloff, double{std::numeric_limits<int64_t>::min()}, + double{std::numeric_limits<int64_t>::max()}); + offset = static_cast<int64_t>(dbloff); + frac = static_cast<uint>(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_BYTE_OFFSET: /* Determine the ByteOffset (and ensure it is block aligned) */ - offset = static_cast<ALuint>(Offset); if(BufferFmt->OriginalType == UserFmtIMA4) { const ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; - offset /= align * BufferFmt->channelsFromFmt(); - offset *= BufferFmt->OriginalAlign; + Offset = std::floor(Offset / align / BufferFmt->channelsFromFmt()); + Offset *= BufferFmt->OriginalAlign; } else if(BufferFmt->OriginalType == UserFmtMSADPCM) { const ALuint align{(BufferFmt->OriginalAlign-2)/2 + 7}; - offset /= align * BufferFmt->channelsFromFmt(); - offset *= BufferFmt->OriginalAlign; + Offset = std::floor(Offset / align / BufferFmt->channelsFromFmt()); + Offset *= BufferFmt->OriginalAlign; } else - offset /= BufferFmt->frameSizeFromFmt(); + Offset = std::floor(Offset / BufferFmt->channelsFromFmt()); + Offset = clampd(Offset, double{std::numeric_limits<int64_t>::min()}, + double{std::numeric_limits<int64_t>::max()}); + offset = static_cast<int64_t>(Offset); frac = 0; break; } /* Find the bufferlist item this offset belongs to. */ - ALuint totalBufferLen{0u}; + if(offset < 0) + { + if(offset < std::numeric_limits<int>::min()) + return al::nullopt; + return VoicePos{static_cast<int>(offset), frac, &BufferList.front()}; + } + + int64_t totalBufferLen{0}; for(auto &item : BufferList) { if(totalBufferLen > offset) @@ -481,7 +512,7 @@ al::optional<VoicePos> GetSampleOffset(al::deque<ALbufferQueueItem> &BufferList, if(item.mSampleLen > offset-totalBufferLen) { /* Offset is in this buffer */ - return VoicePos{offset-totalBufferLen, frac, &item}; + return VoicePos{static_cast<int>(offset-totalBufferLen), frac, &item}; } totalBufferLen += item.mSampleLen; } @@ -1283,7 +1314,7 @@ void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CHECKVAL(std::isfinite(values[0])); if(Voice *voice{GetSourceVoice(Source, Context)}) { @@ -1503,7 +1534,6 @@ void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0); if(Voice *voice{GetSourceVoice(Source, Context)}) { @@ -3171,7 +3201,7 @@ START_API_FUNC } ASSUME(voice != nullptr); - voice->mPosition.store(0u, std::memory_order_relaxed); + voice->mPosition.store(0, std::memory_order_relaxed); voice->mPositionFrac.store(0, std::memory_order_relaxed); voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); voice->mFlags.reset(); diff --git a/core/voice.cpp b/core/voice.cpp index fbf3ea4d..349c3430 100644 --- a/core/voice.cpp +++ b/core/voice.cpp @@ -247,7 +247,8 @@ void LoadSamples(const al::span<float*> dstSamples, const size_t dstOffset, cons void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem, const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, - const size_t srcStep, const size_t samplesToLoad, const al::span<float*> voiceSamples) + const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, + const al::span<float*> voiceSamples) { const size_t loopStart{buffer->mLoopStart}; const size_t loopEnd{buffer->mLoopEnd}; @@ -258,15 +259,16 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem, bufferLoopItem = nullptr; /* Load what's left to play from the buffer */ - const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)}; - LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, - srcStep, remaining); + const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + samplesLoaded += remaining; - if(const size_t toFill{samplesToLoad - remaining}) + if(const size_t toFill{samplesToLoad - samplesLoaded}) { for(auto *chanbuffer : voiceSamples) { - auto srcsamples = chanbuffer + remaining - 1; + auto srcsamples = chanbuffer + samplesLoaded - 1; std::fill_n(srcsamples + 1, toFill, *srcsamples); } } @@ -276,13 +278,13 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem, ASSUME(loopEnd > loopStart); /* Load what's left of this loop iteration */ - const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)}; - LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, - srcStep, remaining); + const size_t remaining{minz(samplesToLoad-samplesLoaded, loopEnd-dataPosInt)}; + LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, + sampleChannels, srcStep, remaining); + samplesLoaded += remaining; /* Load repeats of the loop to fill the buffer. */ const size_t loopSize{loopEnd - loopStart}; - size_t samplesLoaded{remaining}; while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) { LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType, @@ -313,10 +315,10 @@ void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, - const size_t srcStep, const size_t samplesToLoad, const al::span<float*> voiceSamples) + const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, + const al::span<float*> voiceSamples) { /* Crawl the buffer queue to fill in the temp buffer */ - size_t samplesLoaded{0}; while(buffer && samplesLoaded != samplesToLoad) { if(dataPosInt >= buffer->mSampleLen) @@ -461,7 +463,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo ASSUME(SamplesToDo > 0); /* Get voice info */ - uint DataPosInt{mPosition.load(std::memory_order_relaxed)}; + int DataPosInt{mPosition.load(std::memory_order_relaxed)}; uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; @@ -571,6 +573,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo } } + float **voiceSamples{}; if(unlikely(!BufferListItem)) { const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; @@ -604,9 +607,22 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge); ++prevSamples; } + + size_t samplesLoaded{0}; + if(unlikely(DataPosInt < 0)) + { + if(static_cast<uint>(-DataPosInt) >= SrcBufferSize) + goto skip_mix; + + samplesLoaded = static_cast<uint>(-DataPosInt); + for(auto *chanbuffer : MixingSamples) + std::fill_n(chanbuffer, samplesLoaded, 0.0f); + } + const uint DataPosUInt{static_cast<uint>(maxi(DataPosInt, 0))}; + if(mFlags.test(VoiceIsStatic)) - LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, - mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples); + LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType, + mFmtChannels, mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples); else if(mFlags.test(VoiceIsCallback)) { if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples) @@ -630,8 +646,8 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo mFrameStep, SrcBufferSize, MixingSamples); } else - LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, - mFrameStep, SrcBufferSize, MixingSamples); + LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType, mFmtChannels, + mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples); const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; if(mDecoder) @@ -640,6 +656,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo mDecoder->decode(MixingSamples, SrcBufferSize, likely(vstate == Playing) ? srcOffset : 0); } + /* Store the last source samples used for next time. */ if(likely(vstate == Playing)) { @@ -653,7 +670,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo } } - auto voiceSamples = MixingSamples.begin(); + voiceSamples = MixingSamples.begin(); for(auto &chandata : mChans) { /* Resample, then apply ambisonic upsampling as needed. */ @@ -706,6 +723,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo parms.Gains.Current.data(), TargetGains, Counter, OutPos); } } + skip_mix: /* If the voice is stopping, we're now done. */ if(unlikely(vstate == Stopping)) break; @@ -719,27 +737,31 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo OutPos += DstBufferSize; Counter = maxu(DstBufferSize, Counter) - DstBufferSize; - if(unlikely(!BufferListItem)) - { - /* Do nothing extra when there's no buffers. */ - } - else if(mFlags.test(VoiceIsStatic)) + /* Do nothing extra when there's no buffers, or if the voice position + * is still negative. + */ + if(unlikely(!BufferListItem) || unlikely(DataPosInt < 0)) + continue; + + if(mFlags.test(VoiceIsStatic)) { if(BufferLoopItem) { /* Handle looping static source */ const uint LoopStart{BufferListItem->mLoopStart}; const uint LoopEnd{BufferListItem->mLoopEnd}; - if(DataPosInt >= LoopEnd) + uint DataPosUInt{static_cast<uint>(DataPosInt)}; + if(DataPosUInt >= LoopEnd) { assert(LoopEnd > LoopStart); - DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + DataPosUInt = ((DataPosUInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + DataPosInt = static_cast<int>(DataPosUInt); } } else { /* Handle non-looping static source */ - if(DataPosInt >= BufferListItem->mSampleLen) + if(static_cast<uint>(DataPosInt) >= BufferListItem->mSampleLen) { BufferListItem = nullptr; break; @@ -767,7 +789,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo { /* Handle streaming source */ do { - if(BufferListItem->mSampleLen > DataPosInt) + if(BufferListItem->mSampleLen > static_cast<uint>(DataPosInt)) break; DataPosInt -= BufferListItem->mSampleLen; diff --git a/core/voice.h b/core/voice.h index fab551da..cf558341 100644 --- a/core/voice.h +++ b/core/voice.h @@ -197,7 +197,7 @@ struct Voice { * Source offset in samples, relative to the currently playing buffer, NOT * the whole queue. */ - std::atomic<uint> mPosition; + std::atomic<int> mPosition; /** Fractional (fixed-point) offset to the next sample. */ std::atomic<uint> mPositionFrac; |