1968 lines
47 KiB
C
1968 lines
47 KiB
C
/* FAudio - XAudio Reimplementation for FNA
|
|
*
|
|
* Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team
|
|
*
|
|
* This software is provided 'as-is', without any express or implied warranty.
|
|
* In no event will the authors be held liable for any damages arising from
|
|
* the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software in a
|
|
* product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
*
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
*
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
* Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
|
|
*
|
|
*/
|
|
|
|
#include "FAudio_internal.h"
|
|
|
|
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
|
|
void WINAPIV FAudio_INTERNAL_debug(
|
|
FAudio *audio,
|
|
const char *file,
|
|
uint32_t line,
|
|
const char *func,
|
|
const char *fmt,
|
|
...
|
|
) {
|
|
char output[1024];
|
|
char *out = output;
|
|
va_list va;
|
|
out[0] = '\0';
|
|
|
|
/* Logging extras */
|
|
if (audio->debug.LogThreadID)
|
|
{
|
|
out += FAudio_snprintf(
|
|
out,
|
|
sizeof(output) - (out - output),
|
|
"0x%" FAudio_PRIx64 " ",
|
|
FAudio_PlatformGetThreadID()
|
|
);
|
|
}
|
|
if (audio->debug.LogFileline)
|
|
{
|
|
out += FAudio_snprintf(
|
|
out,
|
|
sizeof(output) - (out - output),
|
|
"%s:%u ",
|
|
file,
|
|
line
|
|
);
|
|
}
|
|
if (audio->debug.LogFunctionName)
|
|
{
|
|
out += FAudio_snprintf(
|
|
out,
|
|
sizeof(output) - (out - output),
|
|
"%s ",
|
|
func
|
|
);
|
|
}
|
|
if (audio->debug.LogTiming)
|
|
{
|
|
out += FAudio_snprintf(
|
|
out,
|
|
sizeof(output) - (out - output),
|
|
"%dms ",
|
|
FAudio_timems()
|
|
);
|
|
}
|
|
|
|
/* The actual message... */
|
|
va_start(va, fmt);
|
|
FAudio_vsnprintf(
|
|
out,
|
|
sizeof(output) - (out - output),
|
|
fmt,
|
|
va
|
|
);
|
|
va_end(va);
|
|
|
|
/* Print, finally. */
|
|
FAudio_Log(output);
|
|
}
|
|
|
|
static const char *get_wformattag_string(const FAudioWaveFormatEx *fmt)
|
|
{
|
|
#define FMT_STRING(suffix) \
|
|
if (fmt->wFormatTag == FAUDIO_FORMAT_##suffix) \
|
|
{ \
|
|
return #suffix; \
|
|
}
|
|
FMT_STRING(PCM)
|
|
FMT_STRING(MSADPCM)
|
|
FMT_STRING(IEEE_FLOAT)
|
|
FMT_STRING(XMAUDIO2)
|
|
FMT_STRING(WMAUDIO2)
|
|
FMT_STRING(EXTENSIBLE)
|
|
#undef FMT_STRING
|
|
return "UNKNOWN!";
|
|
}
|
|
|
|
static const char *get_subformat_string(const FAudioWaveFormatEx *fmt)
|
|
{
|
|
const FAudioWaveFormatExtensible *fmtex = (const FAudioWaveFormatExtensible*) fmt;
|
|
|
|
if (fmt->wFormatTag != FAUDIO_FORMAT_EXTENSIBLE)
|
|
{
|
|
return "N/A";
|
|
}
|
|
if (!FAudio_memcmp(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID)))
|
|
{
|
|
return "IEEE_FLOAT";
|
|
}
|
|
if (!FAudio_memcmp(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_PCM, sizeof(FAudioGUID)))
|
|
{
|
|
return "PCM";
|
|
}
|
|
return "UNKNOWN!";
|
|
}
|
|
|
|
void FAudio_INTERNAL_debug_fmt(
|
|
FAudio *audio,
|
|
const char *file,
|
|
uint32_t line,
|
|
const char *func,
|
|
const FAudioWaveFormatEx *fmt
|
|
) {
|
|
FAudio_INTERNAL_debug(
|
|
audio,
|
|
file,
|
|
line,
|
|
func,
|
|
(
|
|
"{"
|
|
"wFormatTag: 0x%x %s, "
|
|
"nChannels: %u, "
|
|
"nSamplesPerSec: %u, "
|
|
"wBitsPerSample: %u, "
|
|
"nBlockAlign: %u, "
|
|
"SubFormat: %s"
|
|
"}"
|
|
),
|
|
fmt->wFormatTag,
|
|
get_wformattag_string(fmt),
|
|
fmt->nChannels,
|
|
fmt->nSamplesPerSec,
|
|
fmt->wBitsPerSample,
|
|
fmt->nBlockAlign,
|
|
get_subformat_string(fmt)
|
|
);
|
|
}
|
|
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
|
|
|
|
void LinkedList_AddEntry(
|
|
LinkedList **start,
|
|
void* toAdd,
|
|
FAudioMutex lock,
|
|
FAudioMallocFunc pMalloc
|
|
) {
|
|
LinkedList *newEntry, *latest;
|
|
newEntry = (LinkedList*) pMalloc(sizeof(LinkedList));
|
|
newEntry->entry = toAdd;
|
|
newEntry->next = NULL;
|
|
FAudio_PlatformLockMutex(lock);
|
|
if (*start == NULL)
|
|
{
|
|
*start = newEntry;
|
|
}
|
|
else
|
|
{
|
|
latest = *start;
|
|
while (latest->next != NULL)
|
|
{
|
|
latest = latest->next;
|
|
}
|
|
latest->next = newEntry;
|
|
}
|
|
FAudio_PlatformUnlockMutex(lock);
|
|
}
|
|
|
|
void LinkedList_PrependEntry(
|
|
LinkedList **start,
|
|
void* toAdd,
|
|
FAudioMutex lock,
|
|
FAudioMallocFunc pMalloc
|
|
) {
|
|
LinkedList *newEntry;
|
|
newEntry = (LinkedList*) pMalloc(sizeof(LinkedList));
|
|
newEntry->entry = toAdd;
|
|
FAudio_PlatformLockMutex(lock);
|
|
newEntry->next = *start;
|
|
*start = newEntry;
|
|
FAudio_PlatformUnlockMutex(lock);
|
|
}
|
|
|
|
void LinkedList_RemoveEntry(
|
|
LinkedList **start,
|
|
void* toRemove,
|
|
FAudioMutex lock,
|
|
FAudioFreeFunc pFree
|
|
) {
|
|
LinkedList *latest, *prev;
|
|
latest = *start;
|
|
prev = latest;
|
|
FAudio_PlatformLockMutex(lock);
|
|
while (latest != NULL)
|
|
{
|
|
if (latest->entry == toRemove)
|
|
{
|
|
if (latest == prev) /* First in list */
|
|
{
|
|
*start = latest->next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = latest->next;
|
|
}
|
|
pFree(latest);
|
|
FAudio_PlatformUnlockMutex(lock);
|
|
return;
|
|
}
|
|
prev = latest;
|
|
latest = latest->next;
|
|
}
|
|
FAudio_PlatformUnlockMutex(lock);
|
|
FAudio_assert(0 && "LinkedList element not found!");
|
|
}
|
|
|
|
void FAudio_INTERNAL_InsertSubmixSorted(
|
|
LinkedList **start,
|
|
FAudioSubmixVoice *toAdd,
|
|
FAudioMutex lock,
|
|
FAudioMallocFunc pMalloc
|
|
) {
|
|
LinkedList *newEntry, *latest;
|
|
newEntry = (LinkedList*) pMalloc(sizeof(LinkedList));
|
|
newEntry->entry = toAdd;
|
|
newEntry->next = NULL;
|
|
FAudio_PlatformLockMutex(lock);
|
|
if (*start == NULL)
|
|
{
|
|
*start = newEntry;
|
|
}
|
|
else
|
|
{
|
|
latest = *start;
|
|
|
|
/* Special case if the new stage is lower than everyone else */
|
|
if (toAdd->mix.processingStage < ((FAudioSubmixVoice*) latest->entry)->mix.processingStage)
|
|
{
|
|
newEntry->next = latest;
|
|
*start = newEntry;
|
|
}
|
|
else
|
|
{
|
|
/* If we got here, we know that the new stage is
|
|
* _at least_ as high as the first submix in the list.
|
|
*
|
|
* Each loop iteration checks to see if the new stage
|
|
* is smaller than `latest->next`, meaning it fits
|
|
* between `latest` and `latest->next`.
|
|
*/
|
|
while (latest->next != NULL)
|
|
{
|
|
if (toAdd->mix.processingStage < ((FAudioSubmixVoice *) latest->next->entry)->mix.processingStage)
|
|
{
|
|
newEntry->next = latest->next;
|
|
latest->next = newEntry;
|
|
break;
|
|
}
|
|
latest = latest->next;
|
|
}
|
|
/* If newEntry didn't get a `next` value, that means
|
|
* it didn't fall in between any stages and `latest`
|
|
* is the last entry in the list. Add it to the end!
|
|
*/
|
|
if (newEntry->next == NULL)
|
|
{
|
|
latest->next = newEntry;
|
|
}
|
|
}
|
|
}
|
|
FAudio_PlatformUnlockMutex(lock);
|
|
}
|
|
|
|
static uint32_t FAudio_INTERNAL_GetBytesRequested(
|
|
FAudioSourceVoice *voice,
|
|
uint32_t decoding
|
|
) {
|
|
uint32_t end, result;
|
|
FAudioBuffer *buffer;
|
|
FAudioWaveFormatExtensible *fmt;
|
|
FAudioBufferEntry *list = voice->src.bufferList;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
#ifdef HAVE_WMADEC
|
|
if (voice->src.wmadec != NULL)
|
|
{
|
|
/* Always 0, per the spec */
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return 0;
|
|
}
|
|
#endif /* HAVE_WMADEC */
|
|
while (list != NULL && decoding > 0)
|
|
{
|
|
buffer = &list->buffer;
|
|
if (buffer->LoopCount > 0)
|
|
{
|
|
end = (
|
|
/* Current loop... */
|
|
((buffer->LoopBegin + buffer->LoopLength) - voice->src.curBufferOffset) +
|
|
/* Remaining loops... */
|
|
(buffer->LoopLength * buffer->LoopCount - 1) +
|
|
/* ... Final iteration */
|
|
buffer->PlayLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
end = (buffer->PlayBegin + buffer->PlayLength) - voice->src.curBufferOffset;
|
|
}
|
|
if (end > decoding)
|
|
{
|
|
decoding = 0;
|
|
break;
|
|
}
|
|
decoding -= end;
|
|
list = list->next;
|
|
}
|
|
|
|
/* Convert samples to bytes, factoring block alignment */
|
|
if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
{
|
|
fmt = (FAudioWaveFormatExtensible*) voice->src.format;
|
|
result = (
|
|
(decoding / fmt->Samples.wSamplesPerBlock) +
|
|
((decoding % fmt->Samples.wSamplesPerBlock) > 0)
|
|
) * voice->src.format->nBlockAlign;
|
|
}
|
|
else
|
|
{
|
|
result = decoding * voice->src.format->nBlockAlign;
|
|
}
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return result;
|
|
}
|
|
|
|
static void FAudio_INTERNAL_DecodeBuffers(
|
|
FAudioSourceVoice *voice,
|
|
uint64_t *toDecode
|
|
) {
|
|
uint32_t end, endRead, decoding, decoded = 0;
|
|
FAudioBuffer *buffer = &voice->src.bufferList->buffer;
|
|
FAudioBufferEntry *toDelete;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
/* This should never go past the max ratio size */
|
|
FAudio_assert(*toDecode <= voice->src.decodeSamples);
|
|
|
|
while (decoded < *toDecode && buffer != NULL)
|
|
{
|
|
decoding = (uint32_t) *toDecode - decoded;
|
|
|
|
/* Start-of-buffer behavior */
|
|
if (voice->src.newBuffer)
|
|
{
|
|
voice->src.newBuffer = 0;
|
|
if ( voice->src.callback != NULL &&
|
|
voice->src.callback->OnBufferStart != NULL )
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
voice->src.callback->OnBufferStart(
|
|
voice->src.callback,
|
|
buffer->pContext
|
|
);
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
}
|
|
}
|
|
|
|
/* Check for end-of-buffer */
|
|
end = (buffer->LoopCount > 0) ?
|
|
(buffer->LoopBegin + buffer->LoopLength) :
|
|
buffer->PlayBegin + buffer->PlayLength;
|
|
endRead = FAudio_min(
|
|
end - voice->src.curBufferOffset,
|
|
decoding
|
|
);
|
|
|
|
/* Decode... */
|
|
voice->src.decode(
|
|
voice,
|
|
buffer,
|
|
voice->audio->decodeCache + (
|
|
decoded * voice->src.format->nChannels
|
|
),
|
|
endRead
|
|
);
|
|
|
|
LOG_INFO(
|
|
voice->audio,
|
|
"Voice %p, buffer %p, decoded %u samples from [%u,%u)",
|
|
(void*) voice,
|
|
(void*) buffer,
|
|
endRead,
|
|
voice->src.curBufferOffset,
|
|
voice->src.curBufferOffset + endRead
|
|
)
|
|
|
|
decoded += endRead;
|
|
voice->src.curBufferOffset += endRead;
|
|
voice->src.totalSamples += endRead;
|
|
|
|
/* End-of-buffer behavior */
|
|
if (endRead < decoding)
|
|
{
|
|
if (buffer->LoopCount > 0)
|
|
{
|
|
voice->src.curBufferOffset = buffer->LoopBegin;
|
|
if (buffer->LoopCount < FAUDIO_LOOP_INFINITE)
|
|
{
|
|
buffer->LoopCount -= 1;
|
|
}
|
|
if ( voice->src.callback != NULL &&
|
|
voice->src.callback->OnLoopEnd != NULL )
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
voice->src.callback->OnLoopEnd(
|
|
voice->src.callback,
|
|
buffer->pContext
|
|
);
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_WMADEC
|
|
if (voice->src.wmadec != NULL)
|
|
{
|
|
FAudio_WMADEC_end_buffer(voice);
|
|
}
|
|
#endif /* HAVE_WMADEC */
|
|
/* For EOS we can stop storing fraction offsets */
|
|
if (buffer->Flags & FAUDIO_END_OF_STREAM)
|
|
{
|
|
voice->src.curBufferOffsetDec = 0;
|
|
voice->src.totalSamples = 0;
|
|
}
|
|
|
|
LOG_INFO(
|
|
voice->audio,
|
|
"Voice %p, finished with buffer %p",
|
|
(void*) voice,
|
|
(void*) buffer
|
|
)
|
|
|
|
/* Change active buffer, delete finished buffer */
|
|
toDelete = voice->src.bufferList;
|
|
voice->src.bufferList = voice->src.bufferList->next;
|
|
if (voice->src.bufferList != NULL)
|
|
{
|
|
buffer = &voice->src.bufferList->buffer;
|
|
voice->src.curBufferOffset = buffer->PlayBegin;
|
|
}
|
|
else
|
|
{
|
|
buffer = NULL;
|
|
|
|
/* FIXME: I keep going past the buffer so fuck it */
|
|
FAudio_zero(
|
|
voice->audio->decodeCache + (
|
|
decoded *
|
|
voice->src.format->nChannels
|
|
),
|
|
sizeof(float) * (
|
|
(*toDecode - decoded) *
|
|
voice->src.format->nChannels
|
|
)
|
|
);
|
|
}
|
|
|
|
/* Callbacks */
|
|
if (voice->src.callback != NULL)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
if (voice->src.callback->OnBufferEnd != NULL)
|
|
{
|
|
voice->src.callback->OnBufferEnd(
|
|
voice->src.callback,
|
|
toDelete->buffer.pContext
|
|
);
|
|
}
|
|
if ( toDelete->buffer.Flags & FAUDIO_END_OF_STREAM &&
|
|
voice->src.callback->OnStreamEnd != NULL )
|
|
{
|
|
voice->src.callback->OnStreamEnd(
|
|
voice->src.callback
|
|
);
|
|
}
|
|
|
|
/* One last chance at redemption */
|
|
if (buffer == NULL && voice->src.bufferList != NULL)
|
|
{
|
|
buffer = &voice->src.bufferList->buffer;
|
|
voice->src.curBufferOffset = buffer->PlayBegin;
|
|
}
|
|
|
|
if (buffer != NULL && voice->src.callback->OnBufferStart != NULL)
|
|
{
|
|
voice->src.callback->OnBufferStart(
|
|
voice->src.callback,
|
|
buffer->pContext
|
|
);
|
|
}
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
}
|
|
|
|
voice->audio->pFree(toDelete);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ... FIXME: I keep going past the buffer so fuck it */
|
|
if (buffer)
|
|
{
|
|
end = (buffer->LoopCount > 0) ?
|
|
(buffer->LoopBegin + buffer->LoopLength) :
|
|
buffer->PlayBegin + buffer->PlayLength;
|
|
endRead = FAudio_min(
|
|
end - voice->src.curBufferOffset,
|
|
EXTRA_DECODE_PADDING
|
|
);
|
|
|
|
voice->src.decode(
|
|
voice,
|
|
buffer,
|
|
voice->audio->decodeCache + (
|
|
decoded * voice->src.format->nChannels
|
|
),
|
|
endRead
|
|
);
|
|
/* Do NOT increment curBufferOffset! */
|
|
|
|
if (endRead < EXTRA_DECODE_PADDING)
|
|
{
|
|
FAudio_zero(
|
|
voice->audio->decodeCache + (
|
|
decoded * voice->src.format->nChannels
|
|
),
|
|
sizeof(float) * (
|
|
(EXTRA_DECODE_PADDING - endRead) *
|
|
voice->src.format->nChannels
|
|
)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FAudio_zero(
|
|
voice->audio->decodeCache + (
|
|
decoded * voice->src.format->nChannels
|
|
),
|
|
sizeof(float) * (
|
|
EXTRA_DECODE_PADDING *
|
|
voice->src.format->nChannels
|
|
)
|
|
);
|
|
}
|
|
|
|
*toDecode = decoded;
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
static inline void FAudio_INTERNAL_FilterVoice(
|
|
FAudio *audio,
|
|
const FAudioFilterParameters *filter,
|
|
FAudioFilterState *filterState,
|
|
float *samples,
|
|
uint32_t numSamples,
|
|
uint16_t numChannels
|
|
) {
|
|
uint32_t j, ci;
|
|
|
|
LOG_FUNC_ENTER(audio)
|
|
|
|
/* Apply a digital state-variable filter to the voice.
|
|
* The difference equations of the filter are:
|
|
*
|
|
* Yl(n) = F Yb(n - 1) + Yl(n - 1)
|
|
* Yh(n) = x(n) - Yl(n) - OneOverQ Yb(n - 1)
|
|
* Yb(n) = F Yh(n) + Yb(n - 1)
|
|
* Yn(n) = Yl(n) + Yh(n)
|
|
*
|
|
* Please note that FAudioFilterParameters.Frequency is defined as:
|
|
*
|
|
* (2 * sin(pi * (desired filter cutoff frequency) / sampleRate))
|
|
*
|
|
* - @JohanSmet
|
|
*/
|
|
|
|
for (j = 0; j < numSamples; j += 1)
|
|
for (ci = 0; ci < numChannels; ci += 1)
|
|
{
|
|
filterState[ci][FAudioLowPassFilter] = filterState[ci][FAudioLowPassFilter] + (filter->Frequency * filterState[ci][FAudioBandPassFilter]);
|
|
filterState[ci][FAudioHighPassFilter] = samples[j * numChannels + ci] - filterState[ci][FAudioLowPassFilter] - (filter->OneOverQ * filterState[ci][FAudioBandPassFilter]);
|
|
filterState[ci][FAudioBandPassFilter] = (filter->Frequency * filterState[ci][FAudioHighPassFilter]) + filterState[ci][FAudioBandPassFilter];
|
|
filterState[ci][FAudioNotchFilter] = filterState[ci][FAudioHighPassFilter] + filterState[ci][FAudioLowPassFilter];
|
|
samples[j * numChannels + ci] = filterState[ci][filter->Type];
|
|
}
|
|
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
static void FAudio_INTERNAL_ResizeEffectChainCache(FAudio *audio, uint32_t samples)
|
|
{
|
|
LOG_FUNC_ENTER(audio)
|
|
if (samples > audio->effectChainSamples)
|
|
{
|
|
audio->effectChainSamples = samples;
|
|
audio->effectChainCache = (float*) audio->pRealloc(
|
|
audio->effectChainCache,
|
|
sizeof(float) * audio->effectChainSamples
|
|
);
|
|
}
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
static inline float *FAudio_INTERNAL_ProcessEffectChain(
|
|
FAudioVoice *voice,
|
|
float *buffer,
|
|
uint32_t *samples
|
|
) {
|
|
uint32_t i;
|
|
FAPO *fapo;
|
|
FAPOProcessBufferParameters srcParams, dstParams;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
/* Set up the buffer to be written into */
|
|
srcParams.pBuffer = buffer;
|
|
srcParams.BufferFlags = FAPO_BUFFER_SILENT;
|
|
srcParams.ValidFrameCount = *samples;
|
|
for (i = 0; i < srcParams.ValidFrameCount; i += 1)
|
|
{
|
|
if (buffer[i] != 0.0f) /* Arbitrary! */
|
|
{
|
|
srcParams.BufferFlags = FAPO_BUFFER_VALID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Initialize output parameters to something sane */
|
|
dstParams.pBuffer = srcParams.pBuffer;
|
|
dstParams.BufferFlags = FAPO_BUFFER_VALID;
|
|
dstParams.ValidFrameCount = srcParams.ValidFrameCount;
|
|
|
|
/* Update parameters, process! */
|
|
for (i = 0; i < voice->effects.count; i += 1)
|
|
{
|
|
fapo = voice->effects.desc[i].pEffect;
|
|
|
|
if (!voice->effects.inPlaceProcessing[i])
|
|
{
|
|
if (dstParams.pBuffer == buffer)
|
|
{
|
|
FAudio_INTERNAL_ResizeEffectChainCache(
|
|
voice->audio,
|
|
voice->effects.desc[i].OutputChannels * srcParams.ValidFrameCount
|
|
);
|
|
dstParams.pBuffer = voice->audio->effectChainCache;
|
|
}
|
|
else
|
|
{
|
|
/* FIXME: What if this is smaller because
|
|
* inputChannels < desc[i].OutputChannels?
|
|
*/
|
|
dstParams.pBuffer = buffer;
|
|
}
|
|
|
|
FAudio_zero(
|
|
dstParams.pBuffer,
|
|
voice->effects.desc[i].OutputChannels * srcParams.ValidFrameCount * sizeof(float)
|
|
);
|
|
}
|
|
|
|
if (voice->effects.parameterUpdates[i])
|
|
{
|
|
fapo->SetParameters(
|
|
fapo,
|
|
voice->effects.parameters[i],
|
|
voice->effects.parameterSizes[i]
|
|
);
|
|
voice->effects.parameterUpdates[i] = 0;
|
|
}
|
|
|
|
fapo->Process(
|
|
fapo,
|
|
1,
|
|
&srcParams,
|
|
1,
|
|
&dstParams,
|
|
voice->effects.desc[i].InitialState
|
|
);
|
|
|
|
FAudio_memcpy(&srcParams, &dstParams, sizeof(dstParams));
|
|
}
|
|
|
|
*samples = dstParams.ValidFrameCount;
|
|
|
|
/* Save the output buffer-flags so the mixer-function can determine when it's save to stop processing the effect chain */
|
|
voice->effects.state = dstParams.BufferFlags;
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return (float*) dstParams.pBuffer;
|
|
}
|
|
|
|
static void FAudio_INTERNAL_ResizeResampleCache(FAudio *audio, uint32_t samples)
|
|
{
|
|
LOG_FUNC_ENTER(audio)
|
|
if (samples > audio->resampleSamples)
|
|
{
|
|
audio->resampleSamples = samples;
|
|
audio->resampleCache = (float*) audio->pRealloc(
|
|
audio->resampleCache,
|
|
sizeof(float) * audio->resampleSamples
|
|
);
|
|
}
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
static void FAudio_INTERNAL_MixSource(FAudioSourceVoice *voice)
|
|
{
|
|
/* Iterators */
|
|
uint32_t i;
|
|
/* Decode/Resample variables */
|
|
uint64_t toDecode;
|
|
uint64_t toResample;
|
|
/* Output mix variables */
|
|
float *stream;
|
|
uint32_t mixed;
|
|
uint32_t oChan;
|
|
FAudioVoice *out;
|
|
uint32_t outputRate;
|
|
double stepd;
|
|
float *finalSamples;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
/* Calculate the resample stepping value */
|
|
if (voice->src.resampleFreq != voice->src.freqRatio * voice->src.format->nSamplesPerSec)
|
|
{
|
|
out = (voice->sends.SendCount == 0) ?
|
|
voice->audio->master : /* Barf */
|
|
voice->sends.pSends->pOutputVoice;
|
|
outputRate = (out->type == FAUDIO_VOICE_MASTER) ?
|
|
out->master.inputSampleRate :
|
|
out->mix.inputSampleRate;
|
|
stepd = (
|
|
voice->src.freqRatio *
|
|
(double) voice->src.format->nSamplesPerSec /
|
|
(double) outputRate
|
|
);
|
|
voice->src.resampleStep = DOUBLE_TO_FIXED(stepd);
|
|
voice->src.resampleFreq = voice->src.freqRatio * voice->src.format->nSamplesPerSec;
|
|
}
|
|
|
|
if (voice->src.active == 2)
|
|
{
|
|
/* We're just playing tails, skip all buffer stuff */
|
|
FAudio_INTERNAL_ResizeResampleCache(
|
|
voice->audio,
|
|
voice->src.resampleSamples * voice->src.format->nChannels
|
|
);
|
|
mixed = voice->src.resampleSamples;
|
|
FAudio_zero(
|
|
voice->audio->resampleCache,
|
|
mixed * voice->src.format->nChannels * sizeof(float)
|
|
);
|
|
finalSamples = voice->audio->resampleCache;
|
|
goto sendwork;
|
|
}
|
|
|
|
/* Base decode size, int to fixed... */
|
|
toDecode = voice->src.resampleSamples * voice->src.resampleStep;
|
|
/* ... rounded up based on current offset... */
|
|
toDecode += voice->src.curBufferOffsetDec + FIXED_FRACTION_MASK;
|
|
/* ... fixed to int, truncating extra fraction from rounding. */
|
|
toDecode >>= FIXED_PRECISION;
|
|
|
|
/* First voice callback */
|
|
if ( voice->src.callback != NULL &&
|
|
voice->src.callback->OnVoiceProcessingPassStart != NULL )
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
voice->src.callback->OnVoiceProcessingPassStart(
|
|
voice->src.callback,
|
|
FAudio_INTERNAL_GetBytesRequested(voice, (uint32_t) toDecode)
|
|
);
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
}
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
/* Nothing to do? */
|
|
if (voice->src.bufferList == NULL)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
if (voice->effects.count > 0 && voice->effects.state != FAPO_BUFFER_SILENT)
|
|
{
|
|
/* do not stop while the effect chain generates a non-silent buffer */
|
|
FAudio_INTERNAL_ResizeResampleCache(
|
|
voice->audio,
|
|
voice->src.resampleSamples * voice->src.format->nChannels
|
|
);
|
|
mixed = voice->src.resampleSamples;
|
|
FAudio_zero(
|
|
voice->audio->resampleCache,
|
|
mixed * voice->src.format->nChannels * sizeof(float)
|
|
);
|
|
finalSamples = voice->audio->resampleCache;
|
|
goto sendwork;
|
|
}
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
if ( voice->src.callback != NULL &&
|
|
voice->src.callback->OnVoiceProcessingPassEnd != NULL)
|
|
{
|
|
voice->src.callback->OnVoiceProcessingPassEnd(
|
|
voice->src.callback
|
|
);
|
|
}
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return;
|
|
}
|
|
|
|
/* Decode... */
|
|
FAudio_INTERNAL_DecodeBuffers(voice, &toDecode);
|
|
|
|
/* Subtract any padding samples from the total, if applicable */
|
|
if ( voice->src.curBufferOffsetDec > 0 &&
|
|
voice->src.totalSamples > 0 )
|
|
{
|
|
voice->src.totalSamples -= 1;
|
|
}
|
|
|
|
/* Okay, we're done messing with client data */
|
|
if ( voice->src.callback != NULL &&
|
|
voice->src.callback->OnVoiceProcessingPassEnd != NULL)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
voice->src.callback->OnVoiceProcessingPassEnd(
|
|
voice->src.callback
|
|
);
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
}
|
|
|
|
/* Nothing to resample? */
|
|
if (toDecode == 0)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return;
|
|
}
|
|
|
|
/* int to fixed... */
|
|
toResample = toDecode << FIXED_PRECISION;
|
|
/* ... round back down based on current offset... */
|
|
toResample -= voice->src.curBufferOffsetDec;
|
|
/* ... but also ceil for any fraction value... */
|
|
toResample += FIXED_FRACTION_MASK;
|
|
/* ... undo step size, fixed to int. */
|
|
toResample /= voice->src.resampleStep;
|
|
/* Add the padding, for some reason this helps? */
|
|
toResample += EXTRA_DECODE_PADDING;
|
|
/* FIXME: I feel like this should be an assert but I suck */
|
|
toResample = FAudio_min(toResample, voice->src.resampleSamples);
|
|
|
|
/* Resample... */
|
|
if (voice->src.resampleStep == FIXED_ONE)
|
|
{
|
|
/* Actually, just use the existing buffer... */
|
|
finalSamples = voice->audio->decodeCache;
|
|
}
|
|
else
|
|
{
|
|
FAudio_INTERNAL_ResizeResampleCache(
|
|
voice->audio,
|
|
voice->src.resampleSamples * voice->src.format->nChannels
|
|
);
|
|
voice->src.resample(
|
|
voice->audio->decodeCache,
|
|
voice->audio->resampleCache,
|
|
&voice->src.resampleOffset,
|
|
voice->src.resampleStep,
|
|
toResample,
|
|
(uint8_t) voice->src.format->nChannels
|
|
);
|
|
finalSamples = voice->audio->resampleCache;
|
|
}
|
|
|
|
/* Update buffer offsets */
|
|
if (voice->src.bufferList != NULL)
|
|
{
|
|
/* Increment fixed offset by resample size, int to fixed... */
|
|
voice->src.curBufferOffsetDec += toResample * voice->src.resampleStep;
|
|
/* ... chop off any ints we got from the above increment */
|
|
voice->src.curBufferOffsetDec &= FIXED_FRACTION_MASK;
|
|
|
|
/* Dec >0? We need one frame from the past...
|
|
* FIXME: We can't go back to a prev buffer though?
|
|
*/
|
|
if ( voice->src.curBufferOffsetDec > 0 &&
|
|
voice->src.curBufferOffset > 0 )
|
|
{
|
|
voice->src.curBufferOffset -= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
voice->src.curBufferOffsetDec = 0;
|
|
voice->src.curBufferOffset = 0;
|
|
}
|
|
|
|
/* Done with buffers, finally. */
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
mixed = (uint32_t) toResample;
|
|
|
|
sendwork:
|
|
|
|
/* Filters */
|
|
if (voice->flags & FAUDIO_VOICE_USEFILTER)
|
|
{
|
|
FAudio_PlatformLockMutex(voice->filterLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
|
|
FAudio_INTERNAL_FilterVoice(
|
|
voice->audio,
|
|
&voice->filter,
|
|
voice->filterState,
|
|
finalSamples,
|
|
mixed,
|
|
voice->src.format->nChannels
|
|
);
|
|
FAudio_PlatformUnlockMutex(voice->filterLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
|
|
}
|
|
|
|
/* Process effect chain */
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
if (voice->effects.count > 0)
|
|
{
|
|
/* If we didn't get the full size of the update, we have to fill
|
|
* it with silence so the effect can process a whole update
|
|
*/
|
|
if (mixed < voice->src.resampleSamples)
|
|
{
|
|
FAudio_zero(
|
|
finalSamples + (mixed * voice->src.format->nChannels),
|
|
(voice->src.resampleSamples - mixed) * voice->src.format->nChannels * sizeof(float)
|
|
);
|
|
mixed = voice->src.resampleSamples;
|
|
}
|
|
finalSamples = FAudio_INTERNAL_ProcessEffectChain(
|
|
voice,
|
|
finalSamples,
|
|
&mixed
|
|
);
|
|
}
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
|
/* Nowhere to send it? Just skip the rest...*/
|
|
if (voice->sends.SendCount == 0)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return;
|
|
}
|
|
|
|
/* Send float cache to sends */
|
|
FAudio_PlatformLockMutex(voice->volumeLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
{
|
|
out = voice->sends.pSends[i].pOutputVoice;
|
|
if (out->type == FAUDIO_VOICE_MASTER)
|
|
{
|
|
stream = out->master.output;
|
|
oChan = out->master.inputChannels;
|
|
}
|
|
else
|
|
{
|
|
stream = out->mix.inputCache;
|
|
oChan = out->mix.inputChannels;
|
|
}
|
|
|
|
voice->sendMix[i](
|
|
mixed,
|
|
voice->outputChannels,
|
|
oChan,
|
|
finalSamples,
|
|
stream,
|
|
voice->mixCoefficients[i]
|
|
);
|
|
|
|
if (voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER)
|
|
{
|
|
FAudio_INTERNAL_FilterVoice(
|
|
voice->audio,
|
|
&voice->sendFilter[i],
|
|
voice->sendFilterState[i],
|
|
stream,
|
|
mixed,
|
|
oChan
|
|
);
|
|
}
|
|
}
|
|
FAudio_PlatformUnlockMutex(voice->volumeLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
static void FAudio_INTERNAL_MixSubmix(FAudioSubmixVoice *voice)
|
|
{
|
|
uint32_t i;
|
|
float *stream;
|
|
uint32_t oChan;
|
|
FAudioVoice *out;
|
|
uint32_t resampled;
|
|
uint64_t resampleOffset = 0;
|
|
float *finalSamples;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
/* Resample */
|
|
if (voice->mix.resampleStep == FIXED_ONE)
|
|
{
|
|
/* Actually, just use the existing buffer... */
|
|
finalSamples = voice->mix.inputCache;
|
|
}
|
|
else
|
|
{
|
|
FAudio_INTERNAL_ResizeResampleCache(
|
|
voice->audio,
|
|
voice->mix.outputSamples * voice->mix.inputChannels
|
|
);
|
|
voice->mix.resample(
|
|
voice->mix.inputCache,
|
|
voice->audio->resampleCache,
|
|
&resampleOffset,
|
|
voice->mix.resampleStep,
|
|
voice->mix.outputSamples,
|
|
(uint8_t) voice->mix.inputChannels
|
|
);
|
|
finalSamples = voice->audio->resampleCache;
|
|
}
|
|
resampled = voice->mix.outputSamples * voice->mix.inputChannels;
|
|
|
|
/* Submix overall volume is applied _before_ effects/filters, blech! */
|
|
if (voice->volume != 1.0f)
|
|
{
|
|
FAudio_INTERNAL_Amplify(
|
|
finalSamples,
|
|
resampled,
|
|
voice->volume
|
|
);
|
|
}
|
|
resampled /= voice->mix.inputChannels;
|
|
|
|
/* Filters */
|
|
if (voice->flags & FAUDIO_VOICE_USEFILTER)
|
|
{
|
|
FAudio_PlatformLockMutex(voice->filterLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
|
|
FAudio_INTERNAL_FilterVoice(
|
|
voice->audio,
|
|
&voice->filter,
|
|
voice->filterState,
|
|
finalSamples,
|
|
resampled,
|
|
voice->mix.inputChannels
|
|
);
|
|
FAudio_PlatformUnlockMutex(voice->filterLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
|
|
}
|
|
|
|
/* Process effect chain */
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
if (voice->effects.count > 0)
|
|
{
|
|
finalSamples = FAudio_INTERNAL_ProcessEffectChain(
|
|
voice,
|
|
finalSamples,
|
|
&resampled
|
|
);
|
|
}
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
|
/* Nothing more to do? */
|
|
if (voice->sends.SendCount == 0)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
/* Send float cache to sends */
|
|
FAudio_PlatformLockMutex(voice->volumeLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
{
|
|
out = voice->sends.pSends[i].pOutputVoice;
|
|
if (out->type == FAUDIO_VOICE_MASTER)
|
|
{
|
|
stream = out->master.output;
|
|
oChan = out->master.inputChannels;
|
|
}
|
|
else
|
|
{
|
|
stream = out->mix.inputCache;
|
|
oChan = out->mix.inputChannels;
|
|
}
|
|
|
|
voice->sendMix[i](
|
|
resampled,
|
|
voice->outputChannels,
|
|
oChan,
|
|
finalSamples,
|
|
stream,
|
|
voice->mixCoefficients[i]
|
|
);
|
|
|
|
if (voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER)
|
|
{
|
|
FAudio_INTERNAL_FilterVoice(
|
|
voice->audio,
|
|
&voice->sendFilter[i],
|
|
voice->sendFilterState[i],
|
|
stream,
|
|
resampled,
|
|
oChan
|
|
);
|
|
}
|
|
}
|
|
FAudio_PlatformUnlockMutex(voice->volumeLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
|
|
|
|
/* Zero this at the end, for the next update */
|
|
end:
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
FAudio_zero(
|
|
voice->mix.inputCache,
|
|
sizeof(float) * voice->mix.inputSamples
|
|
);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
static void FAudio_INTERNAL_FlushPendingBuffers(FAudioSourceVoice *voice)
|
|
{
|
|
FAudioBufferEntry *entry;
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
/* Remove pending flushed buffers and send an event for each one */
|
|
while (voice->src.flushList != NULL)
|
|
{
|
|
entry = voice->src.flushList;
|
|
voice->src.flushList = voice->src.flushList->next;
|
|
|
|
if (voice->src.callback != NULL && voice->src.callback->OnBufferEnd != NULL)
|
|
{
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
voice->src.callback->OnBufferEnd(
|
|
voice->src.callback,
|
|
entry->buffer.pContext
|
|
);
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
}
|
|
voice->audio->pFree(entry);
|
|
}
|
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
}
|
|
|
|
static void FAUDIOCALL FAudio_INTERNAL_GenerateOutput(FAudio *audio, float *output)
|
|
{
|
|
uint32_t totalSamples;
|
|
LinkedList *list;
|
|
float *effectOut;
|
|
FAudioEngineCallback *callback;
|
|
|
|
LOG_FUNC_ENTER(audio)
|
|
if (!audio->active)
|
|
{
|
|
LOG_FUNC_EXIT(audio)
|
|
return;
|
|
}
|
|
|
|
/* Apply any committed changes */
|
|
FAudio_OPERATIONSET_Execute(audio);
|
|
|
|
/* ProcessingPassStart callbacks */
|
|
FAudio_PlatformLockMutex(audio->callbackLock);
|
|
LOG_MUTEX_LOCK(audio, audio->callbackLock)
|
|
list = audio->callbacks;
|
|
while (list != NULL)
|
|
{
|
|
callback = (FAudioEngineCallback*) list->entry;
|
|
if (callback->OnProcessingPassStart != NULL)
|
|
{
|
|
callback->OnProcessingPassStart(
|
|
callback
|
|
);
|
|
}
|
|
list = list->next;
|
|
}
|
|
FAudio_PlatformUnlockMutex(audio->callbackLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->callbackLock)
|
|
|
|
/* Writes to master will directly write to output, but ONLY if there
|
|
* isn't any channel-changing effect processing to do first.
|
|
*/
|
|
if (audio->master->master.effectCache != NULL)
|
|
{
|
|
audio->master->master.output = audio->master->master.effectCache;
|
|
FAudio_zero(
|
|
audio->master->master.effectCache,
|
|
(
|
|
sizeof(float) *
|
|
audio->updateSize *
|
|
audio->master->master.inputChannels
|
|
)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
audio->master->master.output = output;
|
|
}
|
|
|
|
/* Mix sources */
|
|
FAudio_PlatformLockMutex(audio->sourceLock);
|
|
LOG_MUTEX_LOCK(audio, audio->sourceLock)
|
|
list = audio->sources;
|
|
while (list != NULL)
|
|
{
|
|
audio->processingSource = (FAudioSourceVoice*) list->entry;
|
|
|
|
FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource);
|
|
if (audio->processingSource->src.active)
|
|
{
|
|
FAudio_INTERNAL_MixSource(audio->processingSource);
|
|
FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource);
|
|
}
|
|
|
|
list = list->next;
|
|
}
|
|
audio->processingSource = NULL;
|
|
FAudio_PlatformUnlockMutex(audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->sourceLock)
|
|
|
|
/* Mix submixes, ordered by processing stage */
|
|
FAudio_PlatformLockMutex(audio->submixLock);
|
|
LOG_MUTEX_LOCK(audio, audio->submixLock)
|
|
list = audio->submixes;
|
|
while (list != NULL)
|
|
{
|
|
FAudio_INTERNAL_MixSubmix((FAudioSubmixVoice*) list->entry);
|
|
list = list->next;
|
|
}
|
|
FAudio_PlatformUnlockMutex(audio->submixLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->submixLock)
|
|
|
|
/* Apply master volume */
|
|
if (audio->master->volume != 1.0f)
|
|
{
|
|
FAudio_INTERNAL_Amplify(
|
|
audio->master->master.output,
|
|
audio->updateSize * audio->master->master.inputChannels,
|
|
audio->master->volume
|
|
);
|
|
}
|
|
|
|
/* Process master effect chain */
|
|
FAudio_PlatformLockMutex(audio->master->effectLock);
|
|
LOG_MUTEX_LOCK(audio, audio->master->effectLock)
|
|
if (audio->master->effects.count > 0)
|
|
{
|
|
totalSamples = audio->updateSize;
|
|
effectOut = FAudio_INTERNAL_ProcessEffectChain(
|
|
audio->master,
|
|
audio->master->master.output,
|
|
&totalSamples
|
|
);
|
|
|
|
if (effectOut != output)
|
|
{
|
|
FAudio_memcpy(
|
|
output,
|
|
effectOut,
|
|
totalSamples * audio->master->outputChannels * sizeof(float)
|
|
);
|
|
}
|
|
if (totalSamples < audio->updateSize)
|
|
{
|
|
FAudio_zero(
|
|
output + (totalSamples * audio->master->outputChannels),
|
|
(audio->updateSize - totalSamples) * sizeof(float)
|
|
);
|
|
}
|
|
}
|
|
FAudio_PlatformUnlockMutex(audio->master->effectLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->master->effectLock)
|
|
|
|
/* OnProcessingPassEnd callbacks */
|
|
FAudio_PlatformLockMutex(audio->callbackLock);
|
|
LOG_MUTEX_LOCK(audio, audio->callbackLock)
|
|
list = audio->callbacks;
|
|
while (list != NULL)
|
|
{
|
|
callback = (FAudioEngineCallback*) list->entry;
|
|
if (callback->OnProcessingPassEnd != NULL)
|
|
{
|
|
callback->OnProcessingPassEnd(
|
|
callback
|
|
);
|
|
}
|
|
list = list->next;
|
|
}
|
|
FAudio_PlatformUnlockMutex(audio->callbackLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->callbackLock)
|
|
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_UpdateEngine(FAudio *audio, float *output)
|
|
{
|
|
LOG_FUNC_ENTER(audio)
|
|
if (audio->pClientEngineProc)
|
|
{
|
|
audio->pClientEngineProc(
|
|
&FAudio_INTERNAL_GenerateOutput,
|
|
audio,
|
|
output,
|
|
audio->clientEngineUser
|
|
);
|
|
}
|
|
else
|
|
{
|
|
FAudio_INTERNAL_GenerateOutput(audio, output);
|
|
}
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_ResizeDecodeCache(FAudio *audio, uint32_t samples)
|
|
{
|
|
LOG_FUNC_ENTER(audio)
|
|
FAudio_PlatformLockMutex(audio->sourceLock);
|
|
LOG_MUTEX_LOCK(audio, audio->sourceLock)
|
|
if (samples > audio->decodeSamples)
|
|
{
|
|
audio->decodeSamples = samples;
|
|
audio->decodeCache = (float*) audio->pRealloc(
|
|
audio->decodeCache,
|
|
sizeof(float) * audio->decodeSamples
|
|
);
|
|
}
|
|
FAudio_PlatformUnlockMutex(audio->sourceLock);
|
|
LOG_MUTEX_UNLOCK(audio, audio->sourceLock)
|
|
LOG_FUNC_EXIT(audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_AllocEffectChain(
|
|
FAudioVoice *voice,
|
|
const FAudioEffectChain *pEffectChain
|
|
) {
|
|
uint32_t i;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
voice->effects.state = FAPO_BUFFER_VALID;
|
|
voice->effects.count = pEffectChain->EffectCount;
|
|
if (voice->effects.count == 0)
|
|
{
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < pEffectChain->EffectCount; i += 1)
|
|
{
|
|
pEffectChain->pEffectDescriptors[i].pEffect->AddRef(pEffectChain->pEffectDescriptors[i].pEffect);
|
|
}
|
|
|
|
voice->effects.desc = (FAudioEffectDescriptor*) voice->audio->pMalloc(
|
|
voice->effects.count * sizeof(FAudioEffectDescriptor)
|
|
);
|
|
FAudio_memcpy(
|
|
voice->effects.desc,
|
|
pEffectChain->pEffectDescriptors,
|
|
voice->effects.count * sizeof(FAudioEffectDescriptor)
|
|
);
|
|
#define ALLOC_EFFECT_PROPERTY(prop, type) \
|
|
voice->effects.prop = (type*) voice->audio->pMalloc( \
|
|
voice->effects.count * sizeof(type) \
|
|
); \
|
|
FAudio_zero( \
|
|
voice->effects.prop, \
|
|
voice->effects.count * sizeof(type) \
|
|
);
|
|
ALLOC_EFFECT_PROPERTY(parameters, void*)
|
|
ALLOC_EFFECT_PROPERTY(parameterSizes, uint32_t)
|
|
ALLOC_EFFECT_PROPERTY(parameterUpdates, uint8_t)
|
|
ALLOC_EFFECT_PROPERTY(inPlaceProcessing, uint8_t)
|
|
#undef ALLOC_EFFECT_PROPERTY
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_FreeEffectChain(FAudioVoice *voice)
|
|
{
|
|
uint32_t i;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
if (voice->effects.count == 0)
|
|
{
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < voice->effects.count; i += 1)
|
|
{
|
|
voice->effects.desc[i].pEffect->UnlockForProcess(voice->effects.desc[i].pEffect);
|
|
voice->effects.desc[i].pEffect->Release(voice->effects.desc[i].pEffect);
|
|
}
|
|
|
|
voice->audio->pFree(voice->effects.desc);
|
|
voice->audio->pFree(voice->effects.parameters);
|
|
voice->audio->pFree(voice->effects.parameterSizes);
|
|
voice->audio->pFree(voice->effects.parameterUpdates);
|
|
voice->audio->pFree(voice->effects.inPlaceProcessing);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
uint32_t FAudio_INTERNAL_VoiceOutputFrequency(
|
|
FAudioVoice *voice,
|
|
const FAudioVoiceSends *pSendList
|
|
) {
|
|
uint32_t outSampleRate;
|
|
uint32_t newResampleSamples;
|
|
uint64_t resampleSanityCheck;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
if ((pSendList == NULL) || (pSendList->SendCount == 0))
|
|
{
|
|
/* When we're deliberately given no sends, use master rate! */
|
|
outSampleRate = voice->audio->master->master.inputSampleRate;
|
|
}
|
|
else
|
|
{
|
|
outSampleRate = pSendList->pSends[0].pOutputVoice->type == FAUDIO_VOICE_MASTER ?
|
|
pSendList->pSends[0].pOutputVoice->master.inputSampleRate :
|
|
pSendList->pSends[0].pOutputVoice->mix.inputSampleRate;
|
|
}
|
|
newResampleSamples = (uint32_t) FAudio_ceil(
|
|
voice->audio->updateSize *
|
|
(double) outSampleRate /
|
|
(double) voice->audio->master->master.inputSampleRate
|
|
);
|
|
if (voice->type == FAUDIO_VOICE_SOURCE)
|
|
{
|
|
if ( (voice->src.resampleSamples != 0) &&
|
|
(newResampleSamples != voice->src.resampleSamples) &&
|
|
(voice->effects.count > 0) )
|
|
{
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return FAUDIO_E_INVALID_CALL;
|
|
}
|
|
voice->src.resampleSamples = newResampleSamples;
|
|
}
|
|
else /* (voice->type == FAUDIO_VOICE_SUBMIX) */
|
|
{
|
|
if ( (voice->mix.outputSamples != 0) &&
|
|
(newResampleSamples != voice->mix.outputSamples) &&
|
|
(voice->effects.count > 0) )
|
|
{
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return FAUDIO_E_INVALID_CALL;
|
|
}
|
|
voice->mix.outputSamples = newResampleSamples;
|
|
|
|
voice->mix.resampleStep = DOUBLE_TO_FIXED((
|
|
(double) voice->mix.inputSampleRate /
|
|
(double) outSampleRate
|
|
));
|
|
|
|
/* Because we used ceil earlier, there's a chance that
|
|
* downsampling submixes will go past the number of samples
|
|
* available. Sources can do this thanks to padding, but we
|
|
* don't have that luxury for submixes, so unfortunately we
|
|
* just have to undo the ceil and turn it into a floor.
|
|
* -flibit
|
|
*/
|
|
resampleSanityCheck = (
|
|
voice->mix.resampleStep * voice->mix.outputSamples
|
|
) >> FIXED_PRECISION;
|
|
if (resampleSanityCheck > (voice->mix.inputSamples / voice->mix.inputChannels))
|
|
{
|
|
voice->mix.outputSamples -= 1;
|
|
}
|
|
}
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
return 0;
|
|
}
|
|
|
|
const float FAUDIO_INTERNAL_MATRIX_DEFAULTS[8][8][64] =
|
|
{
|
|
#include "matrix_defaults.inl"
|
|
};
|
|
|
|
/* PCM Decoding */
|
|
|
|
void FAudio_INTERNAL_DecodePCM8(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
FAudio_INTERNAL_Convert_U8_To_F32(
|
|
((uint8_t*) buffer->pAudioData) + (
|
|
voice->src.curBufferOffset * voice->src.format->nChannels
|
|
),
|
|
decodeCache,
|
|
samples * voice->src.format->nChannels
|
|
);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_DecodePCM16(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
FAudio_INTERNAL_Convert_S16_To_F32(
|
|
((int16_t*) buffer->pAudioData) + (
|
|
voice->src.curBufferOffset * voice->src.format->nChannels
|
|
),
|
|
decodeCache,
|
|
samples * voice->src.format->nChannels
|
|
);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_DecodePCM24(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
uint32_t i, j;
|
|
const uint8_t *buf;
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
/* FIXME: Uh... is this something that can be SIMD-ified? */
|
|
buf = buffer->pAudioData + (
|
|
voice->src.curBufferOffset * voice->src.format->nBlockAlign
|
|
);
|
|
for (i = 0; i < samples; i += 1, buf += voice->src.format->nBlockAlign)
|
|
for (j = 0; j < voice->src.format->nChannels; j += 1)
|
|
{
|
|
*decodeCache++ = ((int32_t) (
|
|
((uint32_t) buf[(j * 3) + 2] << 24) |
|
|
((uint32_t) buf[(j * 3) + 1] << 16) |
|
|
((uint32_t) buf[(j * 3) + 0] << 8)
|
|
) >> 8) / 8388607.0f;
|
|
}
|
|
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_DecodePCM32(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
FAudio_INTERNAL_Convert_S32_To_F32(
|
|
((int32_t*) buffer->pAudioData) + (
|
|
voice->src.curBufferOffset * voice->src.format->nChannels
|
|
),
|
|
decodeCache,
|
|
samples * voice->src.format->nChannels
|
|
);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_DecodePCM32F(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
FAudio_memcpy(
|
|
decodeCache,
|
|
((float*) buffer->pAudioData) + (
|
|
voice->src.curBufferOffset * voice->src.format->nChannels
|
|
),
|
|
sizeof(float) * samples * voice->src.format->nChannels
|
|
);
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
/* MSADPCM Decoding */
|
|
|
|
static inline int16_t FAudio_INTERNAL_ParseNibble(
|
|
uint8_t nibble,
|
|
uint8_t predictor,
|
|
int16_t *delta,
|
|
int16_t *sample1,
|
|
int16_t *sample2
|
|
) {
|
|
static const int32_t AdaptionTable[16] =
|
|
{
|
|
230, 230, 230, 230, 307, 409, 512, 614,
|
|
768, 614, 512, 409, 307, 230, 230, 230
|
|
};
|
|
static const int32_t AdaptCoeff_1[7] =
|
|
{
|
|
256, 512, 0, 192, 240, 460, 392
|
|
};
|
|
static const int32_t AdaptCoeff_2[7] =
|
|
{
|
|
0, -256, 0, 64, 0, -208, -232
|
|
};
|
|
|
|
int8_t signedNibble;
|
|
int32_t sampleInt;
|
|
int16_t sample;
|
|
|
|
signedNibble = (int8_t) nibble;
|
|
if (signedNibble & 0x08)
|
|
{
|
|
signedNibble -= 0x10;
|
|
}
|
|
|
|
sampleInt = (
|
|
(*sample1 * AdaptCoeff_1[predictor]) +
|
|
(*sample2 * AdaptCoeff_2[predictor])
|
|
) / 256;
|
|
sampleInt += signedNibble * (*delta);
|
|
sample = FAudio_clamp(sampleInt, -32768, 32767);
|
|
|
|
*sample2 = *sample1;
|
|
*sample1 = sample;
|
|
*delta = (int16_t) (AdaptionTable[nibble] * (int32_t) (*delta) / 256);
|
|
if (*delta < 16)
|
|
{
|
|
*delta = 16;
|
|
}
|
|
return sample;
|
|
}
|
|
|
|
#define READ(item, type) \
|
|
item = *((type*) *buf); \
|
|
*buf += sizeof(type);
|
|
|
|
static inline void FAudio_INTERNAL_DecodeMonoMSADPCMBlock(
|
|
uint8_t **buf,
|
|
int16_t *blockCache,
|
|
uint32_t align
|
|
) {
|
|
uint32_t i;
|
|
|
|
/* Temp storage for ADPCM blocks */
|
|
uint8_t predictor;
|
|
int16_t delta;
|
|
int16_t sample1;
|
|
int16_t sample2;
|
|
|
|
/* Preamble */
|
|
READ(predictor, uint8_t)
|
|
READ(delta, int16_t)
|
|
READ(sample1, int16_t)
|
|
READ(sample2, int16_t)
|
|
align -= 7;
|
|
|
|
/* Samples */
|
|
*blockCache++ = sample2;
|
|
*blockCache++ = sample1;
|
|
for (i = 0; i < align; i += 1, *buf += 1)
|
|
{
|
|
*blockCache++ = FAudio_INTERNAL_ParseNibble(
|
|
*(*buf) >> 4,
|
|
predictor,
|
|
&delta,
|
|
&sample1,
|
|
&sample2
|
|
);
|
|
*blockCache++ = FAudio_INTERNAL_ParseNibble(
|
|
*(*buf) & 0x0F,
|
|
predictor,
|
|
&delta,
|
|
&sample1,
|
|
&sample2
|
|
);
|
|
}
|
|
}
|
|
|
|
static inline void FAudio_INTERNAL_DecodeStereoMSADPCMBlock(
|
|
uint8_t **buf,
|
|
int16_t *blockCache,
|
|
uint32_t align
|
|
) {
|
|
uint32_t i;
|
|
|
|
/* Temp storage for ADPCM blocks */
|
|
uint8_t l_predictor;
|
|
uint8_t r_predictor;
|
|
int16_t l_delta;
|
|
int16_t r_delta;
|
|
int16_t l_sample1;
|
|
int16_t r_sample1;
|
|
int16_t l_sample2;
|
|
int16_t r_sample2;
|
|
|
|
/* Preamble */
|
|
READ(l_predictor, uint8_t)
|
|
READ(r_predictor, uint8_t)
|
|
READ(l_delta, int16_t)
|
|
READ(r_delta, int16_t)
|
|
READ(l_sample1, int16_t)
|
|
READ(r_sample1, int16_t)
|
|
READ(l_sample2, int16_t)
|
|
READ(r_sample2, int16_t)
|
|
align -= 14;
|
|
|
|
/* Samples */
|
|
*blockCache++ = l_sample2;
|
|
*blockCache++ = r_sample2;
|
|
*blockCache++ = l_sample1;
|
|
*blockCache++ = r_sample1;
|
|
for (i = 0; i < align; i += 1, *buf += 1)
|
|
{
|
|
*blockCache++ = FAudio_INTERNAL_ParseNibble(
|
|
*(*buf) >> 4,
|
|
l_predictor,
|
|
&l_delta,
|
|
&l_sample1,
|
|
&l_sample2
|
|
);
|
|
*blockCache++ = FAudio_INTERNAL_ParseNibble(
|
|
*(*buf) & 0x0F,
|
|
r_predictor,
|
|
&r_delta,
|
|
&r_sample1,
|
|
&r_sample2
|
|
);
|
|
}
|
|
}
|
|
|
|
#undef READ
|
|
|
|
void FAudio_INTERNAL_DecodeMonoMSADPCM(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
/* Loop variables */
|
|
uint32_t copy, done = 0;
|
|
|
|
/* Read pointers */
|
|
uint8_t *buf;
|
|
int32_t midOffset;
|
|
|
|
/* PCM block cache */
|
|
int16_t blockCache[1012]; /* Max block size */
|
|
|
|
/* Block size */
|
|
uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
/* Where are we starting? */
|
|
buf = (uint8_t*) buffer->pAudioData + (
|
|
(voice->src.curBufferOffset / bsize) *
|
|
voice->src.format->nBlockAlign
|
|
);
|
|
|
|
/* Are we starting in the middle? */
|
|
midOffset = (voice->src.curBufferOffset % bsize);
|
|
|
|
/* Read in each block directly to the decode cache */
|
|
while (done < samples)
|
|
{
|
|
copy = FAudio_min(samples - done, bsize - midOffset);
|
|
FAudio_INTERNAL_DecodeMonoMSADPCMBlock(
|
|
&buf,
|
|
blockCache,
|
|
voice->src.format->nBlockAlign
|
|
);
|
|
FAudio_INTERNAL_Convert_S16_To_F32(
|
|
blockCache + midOffset,
|
|
decodeCache,
|
|
copy
|
|
);
|
|
decodeCache += copy;
|
|
done += copy;
|
|
midOffset = 0;
|
|
}
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
void FAudio_INTERNAL_DecodeStereoMSADPCM(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
/* Loop variables */
|
|
uint32_t copy, done = 0;
|
|
|
|
/* Read pointers */
|
|
uint8_t *buf;
|
|
int32_t midOffset;
|
|
|
|
/* PCM block cache */
|
|
int16_t blockCache[2024]; /* Max block size */
|
|
|
|
/* Align, block size */
|
|
uint32_t bsize = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock;
|
|
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
|
|
/* Where are we starting? */
|
|
buf = (uint8_t*) buffer->pAudioData + (
|
|
(voice->src.curBufferOffset / bsize) *
|
|
voice->src.format->nBlockAlign
|
|
);
|
|
|
|
/* Are we starting in the middle? */
|
|
midOffset = (voice->src.curBufferOffset % bsize);
|
|
|
|
/* Read in each block directly to the decode cache */
|
|
while (done < samples)
|
|
{
|
|
copy = FAudio_min(samples - done, bsize - midOffset);
|
|
FAudio_INTERNAL_DecodeStereoMSADPCMBlock(
|
|
&buf,
|
|
blockCache,
|
|
voice->src.format->nBlockAlign
|
|
);
|
|
FAudio_INTERNAL_Convert_S16_To_F32(
|
|
blockCache + (midOffset * 2),
|
|
decodeCache,
|
|
copy * 2
|
|
);
|
|
decodeCache += copy * 2;
|
|
done += copy;
|
|
midOffset = 0;
|
|
}
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
/* Fallback WMA decoder, get ready for spam! */
|
|
|
|
void FAudio_INTERNAL_DecodeWMAERROR(
|
|
FAudioVoice *voice,
|
|
FAudioBuffer *buffer,
|
|
float *decodeCache,
|
|
uint32_t samples
|
|
) {
|
|
LOG_FUNC_ENTER(voice->audio)
|
|
LOG_ERROR(voice->audio, "%s", "WMA IS NOT SUPPORTED IN THIS BUILD!")
|
|
FAudio_zero(decodeCache, samples * voice->src.format->nChannels * sizeof(float));
|
|
LOG_FUNC_EXIT(voice->audio)
|
|
}
|
|
|
|
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
|