aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2023-02-12 03:15:40 -0800
committerChris Robinson <[email protected]>2023-02-12 03:15:40 -0800
commit63f840d31f09922ddef632f233342cbba46b09cf (patch)
tree79c128b3b6c6c01e9700de22afa7e5246f738584
parent2001d93b03dd2af62e341b91ab2bee9b92da03bc (diff)
Separate decoding and mixing from resampling
-rw-r--r--al/buffer.cpp12
-rw-r--r--alc/alu.cpp6
-rw-r--r--core/device.h5
-rw-r--r--core/voice.cpp584
-rw-r--r--core/voice.h2
5 files changed, 314 insertions, 295 deletions
diff --git a/al/buffer.cpp b/al/buffer.cpp
index 6bf3ecbc..ff416fda 100644
--- a/al/buffer.cpp
+++ b/al/buffer.cpp
@@ -703,9 +703,15 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
(IsUHJ(*DstChannels) ? 1 : 0)};
- static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad};
- al::vector<al::byte,16>(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) *
- size_t{line_size}).swap(ALBuf->mData);
+ /* The maximum number of samples a callback buffer may need to store is a
+ * full mixing line * max pitch * channel count, since it may need to hold
+ * a full line's worth of sample frames before downsampling. An additional
+ * MaxResamplerEdge is needed for "future" samples during resampling (the
+ * voice will hold a history for the past samples).
+ */
+ static constexpr uint line_size{DeviceBase::MixerLineSize*MaxPitch + MaxResamplerEdge};
+ decltype(ALBuf->mData)(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) * size_t{line_size})
+ .swap(ALBuf->mData);
#ifdef ALSOFT_EAX
eax_x_ram_clear(*context->mALDevice, *ALBuf);
diff --git a/alc/alu.cpp b/alc/alu.cpp
index 2fe87ab6..7af21245 100644
--- a/alc/alu.cpp
+++ b/alc/alu.cpp
@@ -108,12 +108,6 @@ namespace {
using uint = unsigned int;
using namespace std::chrono;
-constexpr uint MaxPitch{10};
-
-static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
-static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
- "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
-
using namespace std::placeholders;
float InitConeScale()
diff --git a/core/device.h b/core/device.h
index e4c64a6d..32cf04c3 100644
--- a/core/device.h
+++ b/core/device.h
@@ -222,13 +222,12 @@ struct DeviceBase {
AmbiRotateMatrix mAmbiRotateMatrix2{};
/* Temp storage used for mixer processing. */
- static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding +
- DecoderBase::sMaxPadding};
+ static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
static constexpr size_t MixerChannelsMax{16};
using MixerBufferLine = std::array<float,MixerLineSize>;
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
+ alignas(16) std::array<float,MixerLineSize+MaxResamplerPadding> mResampleData;
- alignas(16) float ResampledData[BufferLineSize];
alignas(16) float FilteredData[BufferLineSize];
union {
alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
diff --git a/core/voice.cpp b/core/voice.cpp
index f84c5555..fc5c864e 100644
--- a/core/voice.cpp
+++ b/core/voice.cpp
@@ -7,6 +7,7 @@
#include <array>
#include <atomic>
#include <cassert>
+#include <climits>
#include <cstdint>
#include <iterator>
#include <memory>
@@ -54,6 +55,10 @@ static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15),
"DeviceBase::MixerBufferLine must be a multiple of 16 bytes");
static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4");
+static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
+static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
+ "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
+
Resampler ResamplerDefault{Resampler::Cubic};
namespace {
@@ -188,11 +193,6 @@ void SendSourceStoppedEvent(ContextBase *context, uint id)
}
-void CopyResample(const InterpState*, const float *RESTRICT src, uint, const uint,
- const al::span<float> dst)
-{ std::copy_n(src, dst.size(), dst.begin()); }
-
-
const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst,
const al::span<const float> src, int type)
{
@@ -221,35 +221,21 @@ const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *ds
template<FmtType Type>
-inline void LoadSamples(const al::span<float*> dstSamples, const size_t dstOffset,
- const al::byte *src, const size_t srcOffset, const FmtChannels srcChans, const size_t srcStep,
- const size_t samples) noexcept
+inline void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan,
+ const size_t srcOffset, const size_t srcStep, const size_t samples) noexcept
{
constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits<Type>::Type)};
- auto s = src + srcOffset*srcStep*sampleSize;
- if(srcChans == FmtUHJ2 || srcChans == FmtSuperStereo)
- {
- al::LoadSampleArray<Type>(dstSamples[0]+dstOffset, s, srcStep, samples);
- al::LoadSampleArray<Type>(dstSamples[1]+dstOffset, s+sampleSize, srcStep, samples);
- std::fill_n(dstSamples[2]+dstOffset, samples, 0.0f);
- }
- else
- {
- for(auto *dst : dstSamples)
- {
- al::LoadSampleArray<Type>(dst+dstOffset, s, srcStep, samples);
- s += sampleSize;
- }
- }
+ auto s = src + (srcOffset*srcStep + srcChan)*sampleSize;
+
+ al::LoadSampleArray<Type>(dstSamples, s, srcStep, samples);
}
-void LoadSamples(const al::span<float*> dstSamples, const size_t dstOffset, const al::byte *src,
- const size_t srcOffset, const FmtType srcType, const FmtChannels srcChans,
- const size_t srcStep, const size_t samples) noexcept
+void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan,
+ const size_t srcOffset, const FmtType srcType, const size_t srcStep, const size_t samples)
+ noexcept
{
#define HANDLE_FMT(T) case T: \
- LoadSamples<T>(dstSamples, dstOffset, src, srcOffset, srcChans, srcStep, \
- samples); \
+ LoadSamples<T>(dstSamples, src, srcChan, srcOffset, srcStep, samples); \
break
switch(srcType)
@@ -265,75 +251,78 @@ 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 dataPosInt, const FmtType sampleType, const size_t srcChannel,
const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad,
- const al::span<float*> voiceSamples)
+ float *voiceSamples)
{
- const size_t loopStart{buffer->mLoopStart};
- const size_t loopEnd{buffer->mLoopEnd};
-
if(!bufferLoopItem)
{
/* Load what's left to play from the buffer */
- const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)};
- LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType,
- sampleChannels, srcStep, remaining);
- samplesLoaded += remaining;
+ if(buffer->mSampleLen > dataPosInt) [[likely]]
+ {
+ const size_t buffer_remaining{buffer->mSampleLen - dataPosInt};
+ const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer_remaining)};
+ LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt,
+ sampleType, srcStep, remaining);
+ samplesLoaded += remaining;
+ }
if(const size_t toFill{samplesToLoad - samplesLoaded})
{
- for(auto *chanbuffer : voiceSamples)
- {
- auto srcsamples = chanbuffer + samplesLoaded - 1;
- std::fill_n(srcsamples + 1, toFill, *srcsamples);
- }
+ auto srcsamples = voiceSamples + samplesLoaded;
+ std::fill_n(srcsamples, toFill, *(srcsamples-1));
}
}
else
{
+ const size_t loopStart{buffer->mLoopStart};
+ const size_t loopEnd{buffer->mLoopEnd};
ASSUME(loopEnd > loopStart);
+ const size_t intPos{(dataPosInt < loopEnd) ? dataPosInt
+ : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart)};
+
/* Load what's left of this loop iteration */
const size_t remaining{minz(samplesToLoad-samplesLoaded, loopEnd-dataPosInt)};
- LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType,
- sampleChannels, srcStep, remaining);
+ LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, intPos, sampleType,
+ srcStep, remaining);
samplesLoaded += remaining;
/* Load repeats of the loop to fill the buffer. */
const size_t loopSize{loopEnd - loopStart};
while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)})
{
- LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType,
- sampleChannels, srcStep, toFill);
+ LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, loopStart,
+ sampleType, srcStep, toFill);
samplesLoaded += toFill;
}
}
}
-void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples,
- const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep,
- size_t samplesLoaded, const size_t samplesToLoad, const al::span<float*> voiceSamples)
+void LoadBufferCallback(VoiceBufferItem *buffer, const size_t dataPosInt,
+ const size_t numCallbackSamples, const FmtType sampleType, const size_t srcChannel,
+ const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, float *voiceSamples)
{
/* Load what's left to play from the buffer */
- const size_t remaining{minz(samplesToLoad-samplesLoaded, numCallbackSamples)};
- LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, 0, sampleType, sampleChannels,
- srcStep, remaining);
- samplesLoaded += remaining;
+ if(numCallbackSamples > dataPosInt) [[likely]]
+ {
+ const size_t remaining{minz(samplesToLoad-samplesLoaded, numCallbackSamples-dataPosInt)};
+ LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt,
+ sampleType, srcStep, remaining);
+ samplesLoaded += remaining;
+ }
if(const size_t toFill{samplesToLoad - samplesLoaded})
{
- for(auto *chanbuffer : voiceSamples)
- {
- auto srcsamples = chanbuffer + remaining;
- std::fill_n(srcsamples, toFill, *(srcsamples-1));
- }
+ auto srcsamples = voiceSamples + samplesLoaded;
+ std::fill_n(srcsamples, toFill, *(srcsamples-1));
}
}
void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
- size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
+ size_t dataPosInt, const FmtType sampleType, const size_t srcChannel,
const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad,
- const al::span<float*> voiceSamples)
+ float *voiceSamples)
{
/* Crawl the buffer queue to fill in the temp buffer */
while(buffer && samplesLoaded != samplesToLoad)
@@ -347,8 +336,8 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
}
const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)};
- LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType,
- sampleChannels, srcStep, remaining);
+ LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt,
+ sampleType, srcStep, remaining);
samplesLoaded += remaining;
if(samplesLoaded == samplesToLoad)
@@ -360,11 +349,8 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
}
if(const size_t toFill{samplesToLoad - samplesLoaded})
{
- for(auto *chanbuffer : voiceSamples)
- {
- auto srcsamples = chanbuffer + samplesLoaded;
- std::fill_n(srcsamples, toFill, *(srcsamples-1));
- }
+ auto srcsamples = voiceSamples + samplesLoaded;
+ std::fill_n(srcsamples, toFill, *(srcsamples-1));
}
}
@@ -499,7 +485,15 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
return;
}
- uint Counter{mFlags.test(VoiceIsFading) ? minu(SamplesToDo, 64u) : 0u};
+ /* If the static voice's current position is beyond the buffer loop end
+ * position, disable looping.
+ */
+ if(mFlags.test(VoiceIsStatic) && BufferLoopItem)
+ {
+ if(DataPosInt >= 0 && static_cast<uint>(DataPosInt) >= BufferListItem->mLoopEnd)
+ BufferLoopItem = nullptr;
+ }
+
uint OutPos{0u};
/* Check if we're doing a delayed start, and we start in this update. */
@@ -526,112 +520,100 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
OutPos = static_cast<uint>(sampleOffset);
}
- /* If the static voice's current position is beyond the buffer loop end
- * position, disable looping.
+ /* Calculate the number of samples to mix, and the number of (resampled)
+ * samples that need to be loaded (mixing samples and decoder padding).
*/
- if(mFlags.test(VoiceIsStatic) && BufferLoopItem)
- {
- if(DataPosInt >= 0 && static_cast<uint>(DataPosInt) >= BufferListItem->mLoopEnd)
- BufferLoopItem = nullptr;
- }
-
- if(!Counter)
- {
- /* No fading, just overwrite the old/current params. */
- for(auto &chandata : mChans)
- {
- {
- DirectParams &parms = chandata.mDryParams;
- if(!mFlags.test(VoiceHasHrtf))
- parms.Gains.Current = parms.Gains.Target;
- else
- parms.Hrtf.Old = parms.Hrtf.Target;
- }
- for(uint send{0};send < NumSends;++send)
- {
- if(mSend[send].Buffer.empty())
- continue;
-
- SendParams &parms = chandata.mWetParams[send];
- parms.Gains.Current = parms.Gains.Target;
- }
- }
- }
+ const uint samplesToMix{SamplesToDo - OutPos};
+ const uint samplesToLoad{samplesToMix + mDecoderPadding};
+ /* Get a span of pointers to hold the floating point, deinterlaced,
+ * resampled buffer data.
+ */
std::array<float*,DeviceBase::MixerChannelsMax> SamplePointers;
const al::span<float*> MixingSamples{SamplePointers.data(), mChans.size()};
- auto offset_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float*
- { return bufline.data() + MaxResamplerEdge; };
+ auto get_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float*
+ { return bufline.data(); };
std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(),
- MixingSamples.begin(), offset_bufferline);
+ MixingSamples.begin(), get_bufferline);
- const ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ?
- CopyResample : mResampler};
- const uint PostPadding{MaxResamplerEdge + mDecoderPadding};
- uint buffers_done{0u};
- do {
- /* Figure out how many buffer samples will be needed */
- uint DstBufferSize{SamplesToDo - OutPos};
- uint SrcBufferSize;
+ /* If there's a matching sample step and no phase offset, use a simple copy
+ * for resampling.
+ */
+ const ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0)
+ ? ResamplerFunc{[](const InterpState*, const float *RESTRICT src, uint, const uint,
+ const al::span<float> dst) { std::copy_n(src, dst.size(), dst.begin()); }}
+ : mResampler};
- if(increment <= MixerFracOne)
- {
- /* Calculate the last written dst sample pos. */
- uint64_t DataSize64{DstBufferSize - 1};
- /* Calculate the last read src sample pos. */
- DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
- /* +1 to get the src sample count, include padding. */
- DataSize64 += 1 + PostPadding;
-
- /* Result is guaranteed to be <= BufferLineSize+PostPadding since
- * we won't use more src samples than dst samples+padding.
- */
- SrcBufferSize = static_cast<uint>(DataSize64);
- }
- else
+ /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels
+ * (3rd channel is generated from decoding).
+ */
+ const size_t realChannels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u
+ : MixingSamples.size()};
+ for(size_t chan{0};chan < realChannels;++chan)
+ {
+ const auto prevSamples = al::as_span(mPrevSamples[chan]);
+ const auto resampleBuffer = std::copy(prevSamples.cbegin(), prevSamples.cend(),
+ Device->mResampleData.begin()) - MaxResamplerEdge;
+ const uint callbackBase{static_cast<uint>(maxi(DataPosInt, 0))};
+ int intPos{DataPosInt};
+ uint fracPos{DataPosFrac};
+
+ /* Load samples for this channel from the available buffer(s), with
+ * resampling.
+ */
+ for(uint samplesLoaded{0};samplesLoaded < samplesToLoad;)
{
- uint64_t DataSize64{DstBufferSize};
- /* Calculate the end src sample pos, include padding. */
- DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
- DataSize64 += PostPadding;
+ using ResampleBufferType = decltype(DeviceBase::mResampleData);
+ static constexpr uint srcSizeMax{ResampleBufferType{}.size() - MaxResamplerEdge};
- if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge)
- SrcBufferSize = static_cast<uint>(DataSize64);
- else
+ /* Calculate the number of dst samples that can be loaded this
+ * iteration, given the available resampler buffer size.
+ */
+ auto calc_buffer_sizes = [fracPos,increment](uint dstBufferSize)
{
- /* If the source size got saturated, we can't fill the desired
- * dst size. Figure out how many samples we can actually mix.
+ /* If ext=true, calculate the last written dst pos from the dst
+ * count, convert to the last read src pos, then add one to get
+ * the src count.
+ *
+ * If ext=false, convert the dst count to src count directly.
+ *
+ * Without this, the src count could be short by one when
+ * increment < 1.0, or not have a full src at the end when
+ * increment > 1.0.
*/
- SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge;
+ const bool ext{increment <= MixerFracOne};
+ uint64_t dataSize64{dstBufferSize - ext};
+ dataSize64 = (dataSize64*increment + fracPos) >> MixerFracBits;
+ /* Also include resampler padding. */
+ dataSize64 += ext + MaxResamplerEdge;
+
+ if(dataSize64 <= srcSizeMax)
+ return std::make_pair(dstBufferSize, static_cast<uint>(dataSize64));
- DataSize64 = SrcBufferSize - PostPadding;
- DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
- if(DataSize64 < DstBufferSize)
+ /* If the source size got saturated, we can't fill the desired
+ * dst size. Figure out how many dst samples we can fill.
+ */
+ dataSize64 = srcSizeMax - MaxResamplerEdge;
+ dataSize64 = ((dataSize64<<MixerFracBits) - fracPos) / increment;
+ if(dataSize64 < dstBufferSize)
{
- /* Some mixers require being 16-byte aligned, so also limit
- * to a multiple of 4 samples to maintain alignment.
- */
- DstBufferSize = static_cast<uint>(DataSize64) & ~3u;
- /* If the voice is stopping, only one mixing iteration will
- * be done, so ensure it fades out completely this mix.
+ /* Some resamplers require the destination being 16-byte
+ * aligned, so limit to a multiple of 4 samples to maintain
+ * alignment.
*/
- if(vstate == Stopping) [[unlikely]]
- Counter = std::min(Counter, DstBufferSize);
+ dstBufferSize = static_cast<uint>(dataSize64) & ~3u;
}
- ASSUME(DstBufferSize > 0);
- }
- }
-
- float **voiceSamples{};
- if(!BufferListItem) [[unlikely]]
- {
- const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
- auto prevSamples = mPrevSamples.data();
- SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
- for(auto *chanbuffer : MixingSamples)
+ return std::make_pair(dstBufferSize, srcSizeMax);
+ };
+ const auto bufferSizes = calc_buffer_sizes(samplesToLoad - samplesLoaded);
+ const auto dstBufferSize = bufferSizes.first;
+ const auto srcBufferSize = bufferSizes.second;
+
+ /* Load the necessary samples from the given buffer(s). */
+ if(!BufferListItem)
{
- auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding,
- chanbuffer-MaxResamplerEdge);
+ const uint avail{minu(srcBufferSize, MaxResamplerEdge)};
+ const uint tofill{maxu(srcBufferSize, MaxResamplerEdge)};
/* When loading from a voice that ended prematurely, only take
* the samples that get closest to 0 amplitude. This helps
@@ -639,161 +621,209 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
*/
auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
{ return std::abs(lhs) < std::abs(rhs); };
- auto srciter = std::min_element(chanbuffer, srcend, abs_lt);
-
- std::fill(srciter+1, chanbuffer + SrcBufferSize, *srciter);
+ auto srciter = std::min_element(resampleBuffer, resampleBuffer+avail, abs_lt);
- std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(),
- prevSamples->data());
- ++prevSamples;
+ std::fill(srciter+1, resampleBuffer+tofill, *srciter);
}
- }
- else
- {
- auto prevSamples = mPrevSamples.data();
- for(auto *chanbuffer : MixingSamples)
- {
- std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge);
- ++prevSamples;
- }
-
- size_t samplesLoaded{0};
- if(DataPosInt < 0) [[unlikely]]
+ else
{
- if(static_cast<uint>(-DataPosInt) >= SrcBufferSize)
- goto skip_mix;
+ size_t srcSampleDelay{0};
+ if(intPos < 0) [[unlikely]]
+ {
+ /* If the current position is negative, there's that many
+ * silent samples to load before using the buffer.
+ */
+ srcSampleDelay = static_cast<uint>(-intPos);
+ if(srcSampleDelay >= srcBufferSize)
+ {
+ /* If the number of silent source samples exceeds the
+ * number to load, the output will be silent.
+ */
+ std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f);
+ std::fill_n(resampleBuffer, srcBufferSize, 0.0f);
+ goto skip_resample;
+ }
- 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))};
+ std::fill_n(resampleBuffer, srcSampleDelay, 0.0f);
+ }
+ const uint uintPos{static_cast<uint>(maxi(intPos, 0))};
- if(mFlags.test(VoiceIsStatic))
- LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType,
- mFmtChannels, mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples);
- else if(mFlags.test(VoiceIsCallback))
- {
- const size_t remaining{SrcBufferSize - samplesLoaded};
- if(!mFlags.test(VoiceCallbackStopped) && remaining > mNumCallbackSamples)
+ if(mFlags.test(VoiceIsStatic))
+ LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan,
+ mFrameStep, srcSampleDelay, srcBufferSize, resampleBuffer);
+ else if(mFlags.test(VoiceIsCallback))
{
- const size_t byteOffset{mNumCallbackSamples*mFrameSize};
- const size_t needBytes{remaining*mFrameSize - byteOffset};
-
- const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
- &BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
- if(gotBytes < 0)
- mFlags.set(VoiceCallbackStopped);
- else if(static_cast<uint>(gotBytes) < needBytes)
+ const size_t bufferOffset{uintPos - callbackBase};
+ const size_t getTotal{bufferOffset + srcBufferSize - srcSampleDelay};
+ if(!mFlags.test(VoiceCallbackStopped) && getTotal > mNumCallbackSamples)
{
- mFlags.set(VoiceCallbackStopped);
- mNumCallbackSamples += static_cast<uint>(gotBytes) / mFrameSize;
+ const size_t byteOffset{mNumCallbackSamples*mFrameSize};
+ const size_t needBytes{getTotal*mFrameSize - byteOffset};
+
+ const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
+ &BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
+ if(gotBytes < 0)
+ mFlags.set(VoiceCallbackStopped);
+ else if(static_cast<uint>(gotBytes) < needBytes)
+ {
+ mFlags.set(VoiceCallbackStopped);
+ mNumCallbackSamples += static_cast<uint>(gotBytes) / mFrameSize;
+ }
+ else
+ mNumCallbackSamples = static_cast<uint>(getTotal);
}
- else
- mNumCallbackSamples = static_cast<uint>(remaining);
+ LoadBufferCallback(BufferListItem, bufferOffset, mNumCallbackSamples,
+ mFmtType, chan, mFrameStep, srcSampleDelay, srcBufferSize, resampleBuffer);
}
- LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels,
- mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples);
+ else
+ LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan,
+ mFrameStep, srcSampleDelay, srcBufferSize, resampleBuffer);
}
- else
- LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosUInt, mFmtType, mFmtChannels,
- mFrameStep, samplesLoaded, SrcBufferSize, MixingSamples);
- const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
- if(mDecoder)
- {
- SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
- mDecoder->decode(MixingSamples, SrcBufferSize,
- (vstate == Playing) ? srcOffset : 0);
- }
+ Resample(&mResampleState, resampleBuffer, fracPos, increment,
+ {MixingSamples[chan]+samplesLoaded, dstBufferSize});
/* Store the last source samples used for next time. */
if(vstate == Playing) [[likely]]
{
- prevSamples = mPrevSamples.data();
- for(auto *chanbuffer : MixingSamples)
+ /* Only store samples for the end of the mix, excluding what
+ * gets loaded for decoder padding.
+ */
+ const uint loadEnd{samplesLoaded + dstBufferSize};
+ if(samplesToMix > samplesLoaded && samplesToMix <= loadEnd) [[likely]]
{
- std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(),
- prevSamples->data());
- ++prevSamples;
+ const size_t dstOffset{samplesToMix - samplesLoaded};
+ const size_t srcOffset{(dstOffset*increment + fracPos) >> MixerFracBits};
+ std::copy_n(resampleBuffer-MaxResamplerEdge+srcOffset, prevSamples.size(),
+ prevSamples.begin());
}
}
+
+ skip_resample:
+ samplesLoaded += dstBufferSize;
+ if(samplesLoaded < samplesToLoad)
+ {
+ fracPos += dstBufferSize*increment;
+ const uint srcOffset{fracPos >> MixerFracBits};
+ fracPos &= MixerFracMask;
+ intPos += srcOffset;
+
+ /* If more samples need to be loaded, copy the back of the
+ * resampleBuffer to the front to reuse it. prevSamples isn't
+ * reliable since it's only updated for the end of the mix.
+ */
+ std::copy(resampleBuffer-MaxResamplerEdge+srcOffset,
+ resampleBuffer+MaxResamplerEdge+srcOffset, resampleBuffer-MaxResamplerEdge);
+ }
}
+ }
+ for(auto &samples : MixingSamples.subspan(realChannels))
+ std::fill_n(samples, samplesToLoad, 0.0f);
+
+ if(mDecoder)
+ mDecoder->decode(MixingSamples, samplesToMix, (vstate==Playing) ? samplesToMix : 0);
- voiceSamples = MixingSamples.begin();
+ if(mFlags.test(VoiceIsAmbisonic))
+ {
+ auto voiceSamples = MixingSamples.begin();
for(auto &chandata : mChans)
{
- /* Resample, then apply ambisonic upsampling as needed. */
- float *ResampledData{Device->ResampledData};
- Resample(&mResampleState, *voiceSamples, DataPosFrac, increment,
- {ResampledData, DstBufferSize});
+ chandata.mAmbiSplitter.processScale({*voiceSamples, samplesToMix},
+ chandata.mAmbiHFScale, chandata.mAmbiLFScale);
++voiceSamples;
+ }
+ }
- if(mFlags.test(VoiceIsAmbisonic))
- chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize},
- chandata.mAmbiHFScale, chandata.mAmbiLFScale);
-
- /* Now filter and mix to the appropriate outputs. */
- const al::span<float,BufferLineSize> FilterBuf{Device->FilteredData};
+ const uint Counter{mFlags.test(VoiceIsFading) ? minu(samplesToMix, 64u) : 0u};
+ if(!Counter)
+ {
+ /* No fading, just overwrite the old/current params. */
+ for(auto &chandata : mChans)
+ {
{
DirectParams &parms = chandata.mDryParams;
- const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
- {ResampledData, DstBufferSize}, mDirect.FilterType)};
-
- if(mFlags.test(VoiceHasHrtf))
- {
- const float TargetGain{parms.Hrtf.Target.Gain * (vstate == Playing)};
- DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos,
- (vstate == Playing), Device);
- }
+ if(!mFlags.test(VoiceHasHrtf))
+ parms.Gains.Current = parms.Gains.Target;
else
- {
- const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data()
- : SilentTarget.data()};
- if(mFlags.test(VoiceHasNfc))
- DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms,
- TargetGains, Counter, OutPos, Device);
- else
- MixSamples({samples, DstBufferSize}, mDirect.Buffer,
- parms.Gains.Current.data(), TargetGains, Counter, OutPos);
- }
+ parms.Hrtf.Old = parms.Hrtf.Target;
}
-
for(uint send{0};send < NumSends;++send)
{
if(mSend[send].Buffer.empty())
continue;
SendParams &parms = chandata.mWetParams[send];
- const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
- {ResampledData, DstBufferSize}, mSend[send].FilterType)};
+ parms.Gains.Current = parms.Gains.Target;
+ }
+ }
+ }
+ auto voiceSamples = MixingSamples.begin();
+ for(auto &chandata : mChans)
+ {
+ /* Now filter and mix to the appropriate outputs. */
+ const al::span<float,BufferLineSize> FilterBuf{Device->FilteredData};
+ {
+ DirectParams &parms = chandata.mDryParams;
+ const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
+ {*voiceSamples, samplesToMix}, mDirect.FilterType)};
+
+ if(mFlags.test(VoiceHasHrtf))
+ {
+ const float TargetGain{parms.Hrtf.Target.Gain * (vstate == Playing)};
+ DoHrtfMix(samples, samplesToMix, parms, TargetGain, Counter, OutPos,
+ (vstate == Playing), Device);
+ }
+ else
+ {
const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data()
: SilentTarget.data()};
- MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
- parms.Gains.Current.data(), TargetGains, Counter, OutPos);
+ if(mFlags.test(VoiceHasNfc))
+ DoNfcMix({samples, samplesToMix}, mDirect.Buffer.data(), parms,
+ TargetGains, Counter, OutPos, Device);
+ else
+ MixSamples({samples, samplesToMix}, mDirect.Buffer,
+ parms.Gains.Current.data(), TargetGains, Counter, OutPos);
}
}
- skip_mix:
- /* If the voice is stopping, we're now done. */
- if(vstate == Stopping) [[unlikely]]
- break;
- /* Update positions */
- DataPosFrac += increment*DstBufferSize;
- const uint SrcSamplesDone{DataPosFrac>>MixerFracBits};
- DataPosInt += SrcSamplesDone;
- DataPosFrac &= MixerFracMask;
+ for(uint send{0};send < NumSends;++send)
+ {
+ if(mSend[send].Buffer.empty())
+ continue;
- OutPos += DstBufferSize;
- Counter = maxu(DstBufferSize, Counter) - DstBufferSize;
+ SendParams &parms = chandata.mWetParams[send];
+ const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(),
+ {*voiceSamples, samplesToMix}, mSend[send].FilterType)};
- /* Do nothing extra when there's no buffers, or if the voice position
- * is still negative.
- */
- if(!BufferListItem || DataPosInt < 0) [[unlikely]]
- continue;
+ const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data()
+ : SilentTarget.data()};
+ MixSamples({samples, samplesToMix}, mSend[send].Buffer,
+ parms.Gains.Current.data(), TargetGains, Counter, OutPos);
+ }
+
+ ++voiceSamples;
+ }
+
+ mFlags.set(VoiceIsFading);
+ /* Don't update positions and buffers if we were stopping. */
+ if(vstate == Stopping) [[unlikely]]
+ {
+ mPlayState.store(Stopped, std::memory_order_release);
+ return;
+ }
+
+ /* Update positions */
+ DataPosFrac += increment*samplesToMix;
+ const uint SrcSamplesDone{DataPosFrac>>MixerFracBits};
+ DataPosInt += SrcSamplesDone;
+ DataPosFrac &= MixerFracMask;
+
+ /* Update voice positions and buffers as needed. */
+ uint buffers_done{0u};
+ if(BufferListItem && DataPosInt >= 0) [[likely]]
+ {
if(mFlags.test(VoiceIsStatic))
{
if(BufferLoopItem)
@@ -813,10 +843,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
{
/* Handle non-looping static source */
if(static_cast<uint>(DataPosInt) >= BufferListItem->mSampleLen)
- {
BufferListItem = nullptr;
- break;
- }
}
}
else if(mFlags.test(VoiceIsCallback))
@@ -850,18 +877,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi
if(!BufferListItem) BufferListItem = BufferLoopItem;
} while(BufferListItem);
}
- } while(OutPos < SamplesToDo);
-
- mFlags.set(VoiceIsFading);
-
- /* Don't update positions and buffers if we were stopping. */
- if(vstate == Stopping) [[unlikely]]
- {
- mPlayState.store(Stopped, std::memory_order_release);
- return;
}
- /* Capture the source ID in case it's reset for stopping. */
+ /* Capture the source ID in case it gets reset for stopping. */
const uint SourceID{mSourceID.load(std::memory_order_relaxed)};
/* Update voice info */
diff --git a/core/voice.h b/core/voice.h
index df0c8c9e..cf7345a1 100644
--- a/core/voice.h
+++ b/core/voice.h
@@ -49,6 +49,8 @@ enum class DirectMode : unsigned char {
};
+constexpr uint MaxPitch{10};
+
/* Maximum number of extra source samples that may need to be loaded, for
* resampling or conversion purposes.
*/