aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2022-11-01 22:29:29 -0700
committerChris Robinson <[email protected]>2022-11-02 00:14:21 -0700
commit5bbfc92ffe5c121341d986143facd432ec874053 (patch)
tree64bafc3a26f1af313fcc511f4ed3a904694ec97d
parentbc7f5260475586b833a568c7b3ac48ac9f89be3b (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.cpp102
-rw-r--r--core/voice.cpp76
-rw-r--r--core/voice.h2
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;