Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FAudio_internal.c
1881 lines | 43.6 KiB | text/x-c | CLexer
1881 lines | 43.6 KiB | text/x-c | CLexer
r0 | /* FAudio - XAudio Reimplementation for FNA | |||
* | ||||
* Copyright (c) 2011-2020 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 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_FFMPEG | ||||
if (voice->src.ffmpeg != NULL) | ||||
{ | ||||
/* Always 0, per the spec */ | ||||
LOG_FUNC_EXIT(voice->audio) | ||||
return 0; | ||||
} | ||||
#endif /* HAVE_FFMPEG */ | ||||
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 ) | ||||
{ | ||||
voice->src.callback->OnBufferStart( | ||||
voice->src.callback, | ||||
buffer->pContext | ||||
); | ||||
} | ||||
} | ||||
/* 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 ) | ||||
{ | ||||
voice->src.callback->OnLoopEnd( | ||||
voice->src.callback, | ||||
buffer->pContext | ||||
); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
#ifdef HAVE_FFMPEG | ||||
if (voice->src.ffmpeg != NULL) | ||||
{ | ||||
FAudio_FFMPEG_reset(voice); | ||||
} | ||||
#endif /* HAVE_FFMPEG */ | ||||
/* 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) | ||||
{ | ||||
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 | ||||
); | ||||
} | ||||
} | ||||
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 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; | ||||
LOG_FUNC_EXIT(voice->audio) | ||||
return (float*) dstParams.pBuffer; | ||||
} | ||||
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) | ||||
/* Calculate the resample stepping value */ | ||||
if (voice->src.resampleFreq != voice->src.freqRatio * voice->src.format->nSamplesPerSec) | ||||
{ | ||||
FAudio_PlatformLockMutex(voice->sendLock); | ||||
LOG_MUTEX_LOCK(voice->audio, voice->sendLock) | ||||
out = (voice->sends.SendCount == 0) ? | ||||
voice->audio->master : /* Barf */ | ||||
voice->sends.pSends->pOutputVoice; | ||||
FAudio_PlatformUnlockMutex(voice->sendLock); | ||||
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) | ||||
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 */ | ||||
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; | ||||
FAudio_PlatformLockMutex(voice->src.bufferLock); | ||||
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock) | ||||
/* First voice callback */ | ||||
if ( voice->src.callback != NULL && | ||||
voice->src.callback->OnVoiceProcessingPassStart != NULL ) | ||||
{ | ||||
voice->src.callback->OnVoiceProcessingPassStart( | ||||
voice->src.callback, | ||||
FAudio_INTERNAL_GetBytesRequested(voice, (uint32_t) toDecode) | ||||
); | ||||
} | ||||
/* Nothing to do? */ | ||||
if (voice->src.bufferList == NULL) | ||||
{ | ||||
FAudio_PlatformUnlockMutex(voice->src.bufferLock); | ||||
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) | ||||
if ( voice->src.callback != NULL && | ||||
voice->src.callback->OnVoiceProcessingPassEnd != NULL) | ||||
{ | ||||
voice->src.callback->OnVoiceProcessingPassEnd( | ||||
voice->src.callback | ||||
); | ||||
} | ||||
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) | ||||
{ | ||||
voice->src.callback->OnVoiceProcessingPassEnd( | ||||
voice->src.callback | ||||
); | ||||
} | ||||
/* Nothing to resample? */ | ||||
if (toDecode == 0) | ||||
{ | ||||
FAudio_PlatformUnlockMutex(voice->src.bufferLock); | ||||
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock) | ||||
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 | ||||
{ | ||||
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: | ||||
FAudio_PlatformLockMutex(voice->sendLock); | ||||
LOG_MUTEX_LOCK(voice->audio, voice->sendLock) | ||||
/* 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; | ||||
} | ||||
/* 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) | ||||
/* 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, | ||||
voice->volume, | ||||
finalSamples, | ||||
stream, | ||||
voice->channelVolume, | ||||
voice->sendCoefficients[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) | ||||
/* Nothing to do? */ | ||||
if (voice->sends.SendCount == 0) | ||||
{ | ||||
goto end; | ||||
} | ||||
/* Resample */ | ||||
if (voice->mix.resampleStep == FIXED_ONE) | ||||
{ | ||||
/* Actually, just use the existing buffer... */ | ||||
finalSamples = voice->mix.inputCache; | ||||
} | ||||
else | ||||
{ | ||||
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) | ||||
/* 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, | ||||
1.0f, | ||||
finalSamples, | ||||
stream, | ||||
voice->channelVolume, | ||||
voice->sendCoefficients[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) | ||||
{ | ||||
voice->src.callback->OnBufferEnd( | ||||
voice->src.callback, | ||||
entry->buffer.pContext | ||||
); | ||||
} | ||||
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_PlatformUnlockMutex(audio->sourceLock); | ||||
LOG_MUTEX_UNLOCK(audio, audio->sourceLock) | ||||
FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource); | ||||
if (audio->processingSource->src.active) | ||||
{ | ||||
FAudio_INTERNAL_MixSource(audio->processingSource); | ||||
FAudio_INTERNAL_FlushPendingBuffers(audio->processingSource); | ||||
} | ||||
FAudio_PlatformLockMutex(audio->sourceLock); | ||||
LOG_MUTEX_LOCK(audio, audio->sourceLock) | ||||
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) | ||||
if (samples > audio->decodeSamples) | ||||
{ | ||||
audio->decodeSamples = samples; | ||||
audio->decodeCache = (float*) audio->pRealloc( | ||||
audio->decodeCache, | ||||
sizeof(float) * audio->decodeSamples | ||||
); | ||||
} | ||||
LOG_FUNC_EXIT(audio) | ||||
} | ||||
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) | ||||
} | ||||
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) | ||||
} | ||||
void FAudio_INTERNAL_AllocEffectChain( | ||||
FAudioVoice *voice, | ||||
const FAudioEffectChain *pEffectChain | ||||
) { | ||||
uint32_t i; | ||||
LOG_FUNC_ENTER(voice->audio) | ||||
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 channelCount; | ||||
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; | ||||
} | ||||
channelCount = voice->src.format->nChannels; | ||||
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; | ||||
} | ||||
channelCount = voice->mix.inputChannels; | ||||
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; | ||||
} | ||||
} | ||||
/* Allocate resample cache */ | ||||
FAudio_INTERNAL_ResizeResampleCache( | ||||
voice->audio, | ||||
newResampleSamples * channelCount | ||||
); | ||||
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: */ | ||||