aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/mixvoice.c
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/mixvoice.c')
-rw-r--r--Alc/mixvoice.c762
1 files changed, 762 insertions, 0 deletions
diff --git a/Alc/mixvoice.c b/Alc/mixvoice.c
new file mode 100644
index 00000000..d019b898
--- /dev/null
+++ b/Alc/mixvoice.c
@@ -0,0 +1,762 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2007 by authors.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "alMain.h"
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "alSource.h"
+#include "alBuffer.h"
+#include "alListener.h"
+#include "alAuxEffectSlot.h"
+#include "sample_cvt.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "ringbuffer.h"
+
+#include "cpu_caps.h"
+#include "mixer/defs.h"
+
+
+static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE,
+ "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!");
+
+extern inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALsizei *restrict pos_arr, ALsizei size);
+
+
+/* BSinc24 requires up to 23 extra samples before the current position, and 24 after. */
+static_assert(MAX_RESAMPLE_PADDING >= 24, "MAX_RESAMPLE_PADDING must be at least 24!");
+
+
+enum Resampler ResamplerDefault = LinearResampler;
+
+MixerFunc MixSamples = Mix_C;
+RowMixerFunc MixRowSamples = MixRow_C;
+static HrtfMixerFunc MixHrtfSamples = MixHrtf_C;
+static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_C;
+
+static MixerFunc SelectMixer(void)
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return Mix_Neon;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return Mix_SSE;
+#endif
+ return Mix_C;
+}
+
+static RowMixerFunc SelectRowMixer(void)
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixRow_Neon;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixRow_SSE;
+#endif
+ return MixRow_C;
+}
+
+static inline HrtfMixerFunc SelectHrtfMixer(void)
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixHrtf_Neon;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixHrtf_SSE;
+#endif
+ return MixHrtf_C;
+}
+
+static inline HrtfMixerBlendFunc SelectHrtfBlendMixer(void)
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixHrtfBlend_Neon;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixHrtfBlend_SSE;
+#endif
+ return MixHrtfBlend_C;
+}
+
+ResamplerFunc SelectResampler(enum Resampler resampler)
+{
+ switch(resampler)
+ {
+ case PointResampler:
+ return Resample_point_C;
+ case LinearResampler:
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return Resample_lerp_Neon;
+#endif
+#ifdef HAVE_SSE4_1
+ if((CPUCapFlags&CPU_CAP_SSE4_1))
+ return Resample_lerp_SSE41;
+#endif
+#ifdef HAVE_SSE2
+ if((CPUCapFlags&CPU_CAP_SSE2))
+ return Resample_lerp_SSE2;
+#endif
+ return Resample_lerp_C;
+ case FIR4Resampler:
+ return Resample_cubic_C;
+ case BSinc12Resampler:
+ case BSinc24Resampler:
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return Resample_bsinc_Neon;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return Resample_bsinc_SSE;
+#endif
+ return Resample_bsinc_C;
+ }
+
+ return Resample_point_C;
+}
+
+
+void aluInitMixer(void)
+{
+ const char *str;
+
+ if(ConfigValueStr(NULL, NULL, "resampler", &str))
+ {
+ if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0)
+ ResamplerDefault = PointResampler;
+ else if(strcasecmp(str, "linear") == 0)
+ ResamplerDefault = LinearResampler;
+ else if(strcasecmp(str, "cubic") == 0)
+ ResamplerDefault = FIR4Resampler;
+ else if(strcasecmp(str, "bsinc12") == 0)
+ ResamplerDefault = BSinc12Resampler;
+ else if(strcasecmp(str, "bsinc24") == 0)
+ ResamplerDefault = BSinc24Resampler;
+ else if(strcasecmp(str, "bsinc") == 0)
+ {
+ WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
+ ResamplerDefault = BSinc12Resampler;
+ }
+ else if(strcasecmp(str, "sinc4") == 0 || strcasecmp(str, "sinc8") == 0)
+ {
+ WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
+ ResamplerDefault = FIR4Resampler;
+ }
+ else
+ {
+ char *end;
+ long n = strtol(str, &end, 0);
+ if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler))
+ ResamplerDefault = n;
+ else
+ WARN("Invalid resampler: %s\n", str);
+ }
+ }
+
+ MixHrtfBlendSamples = SelectHrtfBlendMixer();
+ MixHrtfSamples = SelectHrtfMixer();
+ MixSamples = SelectMixer();
+ MixRowSamples = SelectRowMixer();
+}
+
+
+static void SendAsyncEvent(ALCcontext *context, ALuint enumtype, ALenum type,
+ ALuint objid, ALuint param, const char *msg)
+{
+ AsyncEvent evt = ASYNC_EVENT(enumtype);
+ evt.u.user.type = type;
+ evt.u.user.id = objid;
+ evt.u.user.param = param;
+ strcpy(evt.u.user.msg, msg);
+ if(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) == 1)
+ alsem_post(&context->EventSem);
+}
+
+
+static inline ALfloat Sample_ALubyte(ALubyte val)
+{ return (val-128) * (1.0f/128.0f); }
+
+static inline ALfloat Sample_ALshort(ALshort val)
+{ return val * (1.0f/32768.0f); }
+
+static inline ALfloat Sample_ALfloat(ALfloat val)
+{ return val; }
+
+static inline ALfloat Sample_ALdouble(ALdouble val)
+{ return (ALfloat)val; }
+
+typedef ALubyte ALmulaw;
+static inline ALfloat Sample_ALmulaw(ALmulaw val)
+{ return muLawDecompressionTable[val] * (1.0f/32768.0f); }
+
+typedef ALubyte ALalaw;
+static inline ALfloat Sample_ALalaw(ALalaw val)
+{ return aLawDecompressionTable[val] * (1.0f/32768.0f); }
+
+#define DECL_TEMPLATE(T) \
+static inline void Load_##T(ALfloat *restrict dst, const T *restrict src, \
+ ALint srcstep, ALsizei samples) \
+{ \
+ ALsizei i; \
+ for(i = 0;i < samples;i++) \
+ dst[i] += Sample_##T(src[i*srcstep]); \
+}
+
+DECL_TEMPLATE(ALubyte)
+DECL_TEMPLATE(ALshort)
+DECL_TEMPLATE(ALfloat)
+DECL_TEMPLATE(ALdouble)
+DECL_TEMPLATE(ALmulaw)
+DECL_TEMPLATE(ALalaw)
+
+#undef DECL_TEMPLATE
+
+static void LoadSamples(ALfloat *restrict dst, const ALvoid *restrict src, ALint srcstep,
+ enum FmtType srctype, ALsizei samples)
+{
+#define HANDLE_FMT(ET, ST) case ET: Load_##ST(dst, src, srcstep, samples); break
+ switch(srctype)
+ {
+ HANDLE_FMT(FmtUByte, ALubyte);
+ HANDLE_FMT(FmtShort, ALshort);
+ HANDLE_FMT(FmtFloat, ALfloat);
+ HANDLE_FMT(FmtDouble, ALdouble);
+ HANDLE_FMT(FmtMulaw, ALmulaw);
+ HANDLE_FMT(FmtAlaw, ALalaw);
+ }
+#undef HANDLE_FMT
+}
+
+
+static const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter,
+ ALfloat *restrict dst, const ALfloat *restrict src,
+ ALsizei numsamples, enum ActiveFilters type)
+{
+ ALsizei i;
+ switch(type)
+ {
+ case AF_None:
+ BiquadFilter_passthru(lpfilter, numsamples);
+ BiquadFilter_passthru(hpfilter, numsamples);
+ break;
+
+ case AF_LowPass:
+ BiquadFilter_process(lpfilter, dst, src, numsamples);
+ BiquadFilter_passthru(hpfilter, numsamples);
+ return dst;
+ case AF_HighPass:
+ BiquadFilter_passthru(lpfilter, numsamples);
+ BiquadFilter_process(hpfilter, dst, src, numsamples);
+ return dst;
+
+ case AF_BandPass:
+ for(i = 0;i < numsamples;)
+ {
+ ALfloat temp[256];
+ ALsizei todo = mini(256, numsamples-i);
+
+ BiquadFilter_process(lpfilter, temp, src+i, todo);
+ BiquadFilter_process(hpfilter, dst+i, temp, todo);
+ i += todo;
+ }
+ return dst;
+ }
+ return src;
+}
+
+
+/* This function uses these device temp buffers. */
+#define SOURCE_DATA_BUF 0
+#define RESAMPLED_BUF 1
+#define FILTERED_BUF 2
+#define NFC_DATA_BUF 3
+ALboolean MixSource(ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsizei SamplesToDo)
+{
+ ALCdevice *Device = Context->Device;
+ ALbufferlistitem *BufferListItem;
+ ALbufferlistitem *BufferLoopItem;
+ ALsizei NumChannels, SampleSize;
+ ALbitfieldSOFT enabledevt;
+ ALsizei buffers_done = 0;
+ ResamplerFunc Resample;
+ ALsizei DataPosInt;
+ ALsizei DataPosFrac;
+ ALint64 DataSize64;
+ ALint increment;
+ ALsizei Counter;
+ ALsizei OutPos;
+ ALsizei IrSize;
+ bool isplaying;
+ bool firstpass;
+ bool isstatic;
+ ALsizei chan;
+ ALsizei send;
+
+ /* Get source info */
+ isplaying = true; /* Will only be called while playing. */
+ isstatic = !!(voice->Flags&VOICE_IS_STATIC);
+ DataPosInt = ATOMIC_LOAD(&voice->position, almemory_order_acquire);
+ DataPosFrac = ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed);
+ BufferListItem = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
+ BufferLoopItem = ATOMIC_LOAD(&voice->loop_buffer, almemory_order_relaxed);
+ NumChannels = voice->NumChannels;
+ SampleSize = voice->SampleSize;
+ increment = voice->Step;
+
+ IrSize = (Device->HrtfHandle ? Device->HrtfHandle->irSize : 0);
+
+ Resample = ((increment == FRACTIONONE && DataPosFrac == 0) ?
+ Resample_copy_C : voice->Resampler);
+
+ Counter = (voice->Flags&VOICE_IS_FADING) ? SamplesToDo : 0;
+ firstpass = true;
+ OutPos = 0;
+
+ do {
+ ALsizei SrcBufferSize, DstBufferSize;
+
+ /* Figure out how many buffer samples will be needed */
+ DataSize64 = SamplesToDo-OutPos;
+ DataSize64 *= increment;
+ DataSize64 += DataPosFrac+FRACTIONMASK;
+ DataSize64 >>= FRACTIONBITS;
+ DataSize64 += MAX_RESAMPLE_PADDING*2;
+ SrcBufferSize = (ALsizei)mini64(DataSize64, BUFFERSIZE);
+
+ /* Figure out how many samples we can actually mix from this. */
+ DataSize64 = SrcBufferSize;
+ DataSize64 -= MAX_RESAMPLE_PADDING*2;
+ DataSize64 <<= FRACTIONBITS;
+ DataSize64 -= DataPosFrac;
+ DstBufferSize = (ALsizei)mini64((DataSize64+(increment-1)) / increment,
+ SamplesToDo - OutPos);
+
+ /* Some mixers like having a multiple of 4, so try to give that unless
+ * this is the last update. */
+ if(DstBufferSize < SamplesToDo-OutPos)
+ DstBufferSize &= ~3;
+
+ /* It's impossible to have a buffer list item with no entries. */
+ assert(BufferListItem->num_buffers > 0);
+
+ for(chan = 0;chan < NumChannels;chan++)
+ {
+ const ALfloat *ResampledData;
+ ALfloat *SrcData = Device->TempBuffer[SOURCE_DATA_BUF];
+ ALsizei FilledAmt;
+
+ /* Load the previous samples into the source data first, and clear the rest. */
+ memcpy(SrcData, voice->PrevSamples[chan], MAX_RESAMPLE_PADDING*sizeof(ALfloat));
+ memset(SrcData+MAX_RESAMPLE_PADDING, 0, (BUFFERSIZE-MAX_RESAMPLE_PADDING)*
+ sizeof(ALfloat));
+ FilledAmt = MAX_RESAMPLE_PADDING;
+
+ 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;
+ const ALsizei LoopSize = LoopEnd - LoopStart;
+
+ /* If current pos is beyond the loop range, do not loop */
+ if(!BufferLoopItem || DataPosInt >= LoopEnd)
+ {
+ ALsizei SizeToDo = SrcBufferSize - FilledAmt;
+ ALsizei CompLen = 0;
+ ALsizei i;
+
+ BufferLoopItem = NULL;
+
+ for(i = 0;i < BufferListItem->num_buffers;i++)
+ {
+ const ALbuffer *buffer = BufferListItem->buffers[i];
+ const ALubyte *Data = buffer->data;
+ ALsizei DataSize;
+
+ if(DataPosInt >= buffer->SampleLen)
+ continue;
+
+ /* Load what's left to play from the buffer */
+ DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
+ CompLen = maxi(CompLen, DataSize);
+
+ LoadSamples(&SrcData[FilledAmt],
+ &Data[(DataPosInt*NumChannels + chan)*SampleSize],
+ NumChannels, buffer->FmtType, DataSize
+ );
+ }
+ FilledAmt += CompLen;
+ }
+ else
+ {
+ ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopEnd - DataPosInt);
+ ALsizei CompLen = 0;
+ ALsizei i;
+
+ for(i = 0;i < BufferListItem->num_buffers;i++)
+ {
+ const ALbuffer *buffer = BufferListItem->buffers[i];
+ const ALubyte *Data = buffer->data;
+ ALsizei DataSize;
+
+ if(DataPosInt >= buffer->SampleLen)
+ continue;
+
+ /* Load what's left of this loop iteration */
+ DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
+ CompLen = maxi(CompLen, DataSize);
+
+ LoadSamples(&SrcData[FilledAmt],
+ &Data[(DataPosInt*NumChannels + chan)*SampleSize],
+ NumChannels, buffer->FmtType, DataSize
+ );
+ }
+ FilledAmt += CompLen;
+
+ while(SrcBufferSize > FilledAmt)
+ {
+ const ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopSize);
+
+ CompLen = 0;
+ for(i = 0;i < BufferListItem->num_buffers;i++)
+ {
+ const ALbuffer *buffer = BufferListItem->buffers[i];
+ const ALubyte *Data = buffer->data;
+ ALsizei DataSize;
+
+ if(LoopStart >= buffer->SampleLen)
+ continue;
+
+ DataSize = mini(SizeToDo, buffer->SampleLen - LoopStart);
+ CompLen = maxi(CompLen, DataSize);
+
+ LoadSamples(&SrcData[FilledAmt],
+ &Data[(LoopStart*NumChannels + chan)*SampleSize],
+ NumChannels, buffer->FmtType, DataSize
+ );
+ }
+ FilledAmt += CompLen;
+ }
+ }
+ }
+ else
+ {
+ /* Crawl the buffer queue to fill in the temp buffer */
+ ALbufferlistitem *tmpiter = BufferListItem;
+ ALsizei pos = DataPosInt;
+
+ while(tmpiter && SrcBufferSize > FilledAmt)
+ {
+ ALsizei SizeToDo = SrcBufferSize - FilledAmt;
+ ALsizei CompLen = 0;
+ ALsizei i;
+
+ for(i = 0;i < tmpiter->num_buffers;i++)
+ {
+ const ALbuffer *ALBuffer = tmpiter->buffers[i];
+ ALsizei DataSize = ALBuffer ? ALBuffer->SampleLen : 0;
+
+ if(DataSize > pos)
+ {
+ const ALubyte *Data = ALBuffer->data;
+ Data += (pos*NumChannels + chan)*SampleSize;
+
+ DataSize = mini(SizeToDo, DataSize - pos);
+ CompLen = maxi(CompLen, DataSize);
+
+ LoadSamples(&SrcData[FilledAmt], Data, NumChannels,
+ ALBuffer->FmtType, DataSize);
+ }
+ }
+ if(UNLIKELY(!CompLen))
+ pos -= tmpiter->max_samples;
+ else
+ {
+ FilledAmt += CompLen;
+ if(SrcBufferSize <= FilledAmt)
+ break;
+ pos = 0;
+ }
+ tmpiter = ATOMIC_LOAD(&tmpiter->next, almemory_order_acquire);
+ if(!tmpiter) tmpiter = BufferLoopItem;
+ }
+ }
+
+ /* Store the last source samples used for next time. */
+ memcpy(voice->PrevSamples[chan],
+ &SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS],
+ MAX_RESAMPLE_PADDING*sizeof(ALfloat)
+ );
+
+ /* Now resample, then filter and mix to the appropriate outputs. */
+ ResampledData = Resample(&voice->ResampleState,
+ &SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment,
+ Device->TempBuffer[RESAMPLED_BUF], DstBufferSize
+ );
+ {
+ DirectParams *parms = &voice->Direct.Params[chan];
+ const ALfloat *samples;
+
+ samples = DoFilters(
+ &parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
+ ResampledData, DstBufferSize, voice->Direct.FilterType
+ );
+ if(!(voice->Flags&VOICE_HAS_HRTF))
+ {
+ if(!Counter)
+ memcpy(parms->Gains.Current, parms->Gains.Target,
+ sizeof(parms->Gains.Current));
+ if(!(voice->Flags&VOICE_HAS_NFC))
+ MixSamples(samples, voice->Direct.Channels, voice->Direct.Buffer,
+ parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
+ DstBufferSize
+ );
+ else
+ {
+ ALfloat *nfcsamples = Device->TempBuffer[NFC_DATA_BUF];
+ ALsizei chanoffset = 0;
+
+ MixSamples(samples,
+ voice->Direct.ChannelsPerOrder[0], voice->Direct.Buffer,
+ parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
+ DstBufferSize
+ );
+ chanoffset += voice->Direct.ChannelsPerOrder[0];
+#define APPLY_NFC_MIX(order) \
+ if(voice->Direct.ChannelsPerOrder[order] > 0) \
+ { \
+ NfcFilterProcess##order(&parms->NFCtrlFilter, nfcsamples, samples, \
+ DstBufferSize); \
+ MixSamples(nfcsamples, voice->Direct.ChannelsPerOrder[order], \
+ voice->Direct.Buffer+chanoffset, parms->Gains.Current+chanoffset, \
+ parms->Gains.Target+chanoffset, Counter, OutPos, DstBufferSize \
+ ); \
+ chanoffset += voice->Direct.ChannelsPerOrder[order]; \
+ }
+ APPLY_NFC_MIX(1)
+ APPLY_NFC_MIX(2)
+ APPLY_NFC_MIX(3)
+#undef APPLY_NFC_MIX
+ }
+ }
+ else
+ {
+ MixHrtfParams hrtfparams;
+ ALsizei fademix = 0;
+ int lidx, ridx;
+
+ lidx = GetChannelIdxByName(&Device->RealOut, FrontLeft);
+ ridx = GetChannelIdxByName(&Device->RealOut, FrontRight);
+ assert(lidx != -1 && ridx != -1);
+
+ if(!Counter)
+ {
+ /* No fading, just overwrite the old HRTF params. */
+ parms->Hrtf.Old = parms->Hrtf.Target;
+ }
+ else if(!(parms->Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD))
+ {
+ /* The old HRTF params are silent, so overwrite the old
+ * coefficients with the new, and reset the old gain to
+ * 0. The future mix will then fade from silence.
+ */
+ parms->Hrtf.Old = parms->Hrtf.Target;
+ parms->Hrtf.Old.Gain = 0.0f;
+ }
+ else if(firstpass)
+ {
+ ALfloat gain;
+
+ /* Fade between the coefficients over 128 samples. */
+ fademix = mini(DstBufferSize, 128);
+
+ /* The new coefficients need to fade in completely
+ * since they're replacing the old ones. To keep the
+ * gain fading consistent, interpolate between the old
+ * and new target gains given how much of the fade time
+ * this mix handles.
+ */
+ gain = lerp(parms->Hrtf.Old.Gain, parms->Hrtf.Target.Gain,
+ minf(1.0f, (ALfloat)fademix/Counter));
+ hrtfparams.Coeffs = parms->Hrtf.Target.Coeffs;
+ hrtfparams.Delay[0] = parms->Hrtf.Target.Delay[0];
+ hrtfparams.Delay[1] = parms->Hrtf.Target.Delay[1];
+ hrtfparams.Gain = 0.0f;
+ hrtfparams.GainStep = gain / (ALfloat)fademix;
+
+ MixHrtfBlendSamples(
+ voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
+ samples, voice->Offset, OutPos, IrSize, &parms->Hrtf.Old,
+ &hrtfparams, &parms->Hrtf.State, fademix
+ );
+ /* Update the old parameters with the result. */
+ parms->Hrtf.Old = parms->Hrtf.Target;
+ if(fademix < Counter)
+ parms->Hrtf.Old.Gain = hrtfparams.Gain;
+ }
+
+ if(fademix < DstBufferSize)
+ {
+ ALsizei todo = DstBufferSize - fademix;
+ ALfloat gain = parms->Hrtf.Target.Gain;
+
+ /* Interpolate the target gain if the gain fading lasts
+ * longer than this mix.
+ */
+ if(Counter > DstBufferSize)
+ gain = lerp(parms->Hrtf.Old.Gain, gain,
+ (ALfloat)todo/(Counter-fademix));
+
+ hrtfparams.Coeffs = parms->Hrtf.Target.Coeffs;
+ hrtfparams.Delay[0] = parms->Hrtf.Target.Delay[0];
+ hrtfparams.Delay[1] = parms->Hrtf.Target.Delay[1];
+ hrtfparams.Gain = parms->Hrtf.Old.Gain;
+ hrtfparams.GainStep = (gain - parms->Hrtf.Old.Gain) / (ALfloat)todo;
+ MixHrtfSamples(
+ voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
+ samples+fademix, voice->Offset+fademix, OutPos+fademix, IrSize,
+ &hrtfparams, &parms->Hrtf.State, todo
+ );
+ /* Store the interpolated gain or the final target gain
+ * depending if the fade is done.
+ */
+ if(DstBufferSize < Counter)
+ parms->Hrtf.Old.Gain = gain;
+ else
+ parms->Hrtf.Old.Gain = parms->Hrtf.Target.Gain;
+ }
+ }
+ }
+
+ for(send = 0;send < Device->NumAuxSends;send++)
+ {
+ SendParams *parms = &voice->Send[send].Params[chan];
+ const ALfloat *samples;
+
+ if(!voice->Send[send].Buffer)
+ continue;
+
+ samples = DoFilters(
+ &parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
+ ResampledData, DstBufferSize, voice->Send[send].FilterType
+ );
+
+ if(!Counter)
+ memcpy(parms->Gains.Current, parms->Gains.Target,
+ sizeof(parms->Gains.Current));
+ MixSamples(samples, voice->Send[send].Channels, voice->Send[send].Buffer,
+ parms->Gains.Current, parms->Gains.Target, Counter, OutPos, DstBufferSize
+ );
+ }
+ }
+ /* Update positions */
+ DataPosFrac += increment*DstBufferSize;
+ DataPosInt += DataPosFrac>>FRACTIONBITS;
+ DataPosFrac &= FRACTIONMASK;
+
+ OutPos += DstBufferSize;
+ voice->Offset += DstBufferSize;
+ Counter = maxi(DstBufferSize, Counter) - DstBufferSize;
+ firstpass = false;
+
+ if(isstatic)
+ {
+ if(BufferLoopItem)
+ {
+ /* Handle looping static source */
+ const ALbuffer *Buffer = BufferListItem->buffers[0];
+ ALsizei LoopStart = Buffer->LoopStart;
+ ALsizei LoopEnd = Buffer->LoopEnd;
+ if(DataPosInt >= LoopEnd)
+ {
+ assert(LoopEnd > LoopStart);
+ DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
+ }
+ }
+ else
+ {
+ /* Handle non-looping static source */
+ if(DataPosInt >= BufferListItem->max_samples)
+ {
+ isplaying = false;
+ BufferListItem = NULL;
+ DataPosInt = 0;
+ DataPosFrac = 0;
+ break;
+ }
+ }
+ }
+ else while(1)
+ {
+ /* Handle streaming source */
+ if(BufferListItem->max_samples > DataPosInt)
+ break;
+
+ DataPosInt -= BufferListItem->max_samples;
+
+ buffers_done += BufferListItem->num_buffers;
+ BufferListItem = ATOMIC_LOAD(&BufferListItem->next, almemory_order_relaxed);
+ if(!BufferListItem && !(BufferListItem=BufferLoopItem))
+ {
+ isplaying = false;
+ DataPosInt = 0;
+ DataPosFrac = 0;
+ break;
+ }
+ }
+ } while(isplaying && OutPos < SamplesToDo);
+
+ voice->Flags |= VOICE_IS_FADING;
+
+ /* Update source info */
+ ATOMIC_STORE(&voice->position, DataPosInt, almemory_order_relaxed);
+ ATOMIC_STORE(&voice->position_fraction, DataPosFrac, almemory_order_relaxed);
+ ATOMIC_STORE(&voice->current_buffer, BufferListItem, almemory_order_release);
+
+ /* Send any events now, after the position/buffer info was updated. */
+ enabledevt = ATOMIC_LOAD(&Context->EnabledEvts, almemory_order_acquire);
+ if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
+ SendAsyncEvent(Context, EventType_BufferCompleted,
+ AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, SourceID, buffers_done, "Buffer completed"
+ );
+
+ return isplaying;
+}