|
|
/* 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"
|
|
|
|
|
|
#define MAKE_SUBFORMAT_GUID(guid, fmt) \
|
|
|
FAudioGUID DATAFORMAT_SUBTYPE_##guid = \
|
|
|
{ \
|
|
|
(uint16_t) (fmt), \
|
|
|
0x0000, \
|
|
|
0x0010, \
|
|
|
{ \
|
|
|
0x80, \
|
|
|
0x00, \
|
|
|
0x00, \
|
|
|
0xAA, \
|
|
|
0x00, \
|
|
|
0x38, \
|
|
|
0x9B, \
|
|
|
0x71 \
|
|
|
} \
|
|
|
}
|
|
|
MAKE_SUBFORMAT_GUID(PCM, 1);
|
|
|
MAKE_SUBFORMAT_GUID(ADPCM, 2);
|
|
|
MAKE_SUBFORMAT_GUID(IEEE_FLOAT, 3);
|
|
|
MAKE_SUBFORMAT_GUID(XMAUDIO2, FAUDIO_FORMAT_XMAUDIO2);
|
|
|
MAKE_SUBFORMAT_GUID(WMAUDIO2, FAUDIO_FORMAT_WMAUDIO2);
|
|
|
MAKE_SUBFORMAT_GUID(WMAUDIO3, FAUDIO_FORMAT_WMAUDIO3);
|
|
|
MAKE_SUBFORMAT_GUID(WMAUDIO_LOSSLESS, FAUDIO_FORMAT_WMAUDIO_LOSSLESS);
|
|
|
#undef MAKE_SUBFORMAT_GUID
|
|
|
|
|
|
#ifdef FAUDIO_DUMP_VOICES
|
|
|
static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice);
|
|
|
static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice);
|
|
|
static void FAudio_DUMPVOICE_WriteBuffer(
|
|
|
const FAudioSourceVoice *voice,
|
|
|
const FAudioBuffer *pBuffer,
|
|
|
const FAudioBufferWMA *pBufferWMA,
|
|
|
const uint32_t playBegin,
|
|
|
const uint32_t playLength
|
|
|
);
|
|
|
#endif /* FAUDIO_DUMP_VOICES */
|
|
|
|
|
|
/* FAudio Version */
|
|
|
|
|
|
uint32_t FAudioLinkedVersion(void)
|
|
|
{
|
|
|
return FAUDIO_COMPILED_VERSION;
|
|
|
}
|
|
|
|
|
|
/* FAudio Interface */
|
|
|
|
|
|
uint32_t FAudioCreate(
|
|
|
FAudio **ppFAudio,
|
|
|
uint32_t Flags,
|
|
|
FAudioProcessor XAudio2Processor
|
|
|
) {
|
|
|
FAudioCOMConstructEXT(ppFAudio, FAUDIO_TARGET_VERSION);
|
|
|
FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioCOMConstructEXT(FAudio **ppFAudio, uint8_t version)
|
|
|
{
|
|
|
return FAudioCOMConstructWithCustomAllocatorEXT(
|
|
|
ppFAudio,
|
|
|
version,
|
|
|
FAudio_malloc,
|
|
|
FAudio_free,
|
|
|
FAudio_realloc
|
|
|
);
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioCreateWithCustomAllocatorEXT(
|
|
|
FAudio **ppFAudio,
|
|
|
uint32_t Flags,
|
|
|
FAudioProcessor XAudio2Processor,
|
|
|
FAudioMallocFunc customMalloc,
|
|
|
FAudioFreeFunc customFree,
|
|
|
FAudioReallocFunc customRealloc
|
|
|
) {
|
|
|
FAudioCOMConstructWithCustomAllocatorEXT(
|
|
|
ppFAudio,
|
|
|
FAUDIO_TARGET_VERSION,
|
|
|
customMalloc,
|
|
|
customFree,
|
|
|
customRealloc
|
|
|
);
|
|
|
FAudio_Initialize(*ppFAudio, Flags, XAudio2Processor);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioCOMConstructWithCustomAllocatorEXT(
|
|
|
FAudio **ppFAudio,
|
|
|
uint8_t version,
|
|
|
FAudioMallocFunc customMalloc,
|
|
|
FAudioFreeFunc customFree,
|
|
|
FAudioReallocFunc customRealloc
|
|
|
) {
|
|
|
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
|
|
|
FAudioDebugConfiguration debugInit = {0};
|
|
|
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
|
|
|
FAudio_PlatformAddRef();
|
|
|
*ppFAudio = (FAudio*) customMalloc(sizeof(FAudio));
|
|
|
FAudio_zero(*ppFAudio, sizeof(FAudio));
|
|
|
(*ppFAudio)->version = version;
|
|
|
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
|
|
|
FAudio_SetDebugConfiguration(*ppFAudio, &debugInit, NULL);
|
|
|
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
|
|
|
(*ppFAudio)->sourceLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->sourceLock)
|
|
|
(*ppFAudio)->submixLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->submixLock)
|
|
|
(*ppFAudio)->callbackLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->callbackLock)
|
|
|
(*ppFAudio)->operationLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE((*ppFAudio), (*ppFAudio)->operationLock)
|
|
|
(*ppFAudio)->pMalloc = customMalloc;
|
|
|
(*ppFAudio)->pFree = customFree;
|
|
|
(*ppFAudio)->pRealloc = customRealloc;
|
|
|
(*ppFAudio)->refcount = 1;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_AddRef(FAudio *audio)
|
|
|
{
|
|
|
LOG_API_ENTER(audio)
|
|
|
audio->refcount += 1;
|
|
|
LOG_API_EXIT(audio)
|
|
|
return audio->refcount;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_Release(FAudio *audio)
|
|
|
{
|
|
|
uint32_t refcount;
|
|
|
LOG_API_ENTER(audio)
|
|
|
audio->refcount -= 1;
|
|
|
refcount = audio->refcount;
|
|
|
if (audio->refcount == 0)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_ClearAll(audio);
|
|
|
FAudio_StopEngine(audio);
|
|
|
audio->pFree(audio->decodeCache);
|
|
|
audio->pFree(audio->resampleCache);
|
|
|
audio->pFree(audio->effectChainCache);
|
|
|
LOG_MUTEX_DESTROY(audio, audio->sourceLock)
|
|
|
FAudio_PlatformDestroyMutex(audio->sourceLock);
|
|
|
LOG_MUTEX_DESTROY(audio, audio->submixLock)
|
|
|
FAudio_PlatformDestroyMutex(audio->submixLock);
|
|
|
LOG_MUTEX_DESTROY(audio, audio->callbackLock)
|
|
|
FAudio_PlatformDestroyMutex(audio->callbackLock);
|
|
|
LOG_MUTEX_DESTROY(audio, audio->operationLock)
|
|
|
FAudio_PlatformDestroyMutex(audio->operationLock);
|
|
|
audio->pFree(audio);
|
|
|
FAudio_PlatformRelease();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LOG_API_EXIT(audio)
|
|
|
}
|
|
|
return refcount;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_GetDeviceCount(FAudio *audio, uint32_t *pCount)
|
|
|
{
|
|
|
LOG_API_ENTER(audio)
|
|
|
*pCount = FAudio_PlatformGetDeviceCount();
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_GetDeviceDetails(
|
|
|
FAudio *audio,
|
|
|
uint32_t Index,
|
|
|
FAudioDeviceDetails *pDeviceDetails
|
|
|
) {
|
|
|
uint32_t result;
|
|
|
LOG_API_ENTER(audio)
|
|
|
result = FAudio_PlatformGetDeviceDetails(Index, pDeviceDetails);
|
|
|
LOG_API_EXIT(audio)
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_Initialize(
|
|
|
FAudio *audio,
|
|
|
uint32_t Flags,
|
|
|
FAudioProcessor XAudio2Processor
|
|
|
) {
|
|
|
LOG_API_ENTER(audio)
|
|
|
FAudio_assert(Flags == 0);
|
|
|
FAudio_assert(XAudio2Processor == FAUDIO_DEFAULT_PROCESSOR);
|
|
|
|
|
|
audio->initFlags = Flags;
|
|
|
|
|
|
/* FIXME: This is lazy... */
|
|
|
audio->decodeCache = (float*) audio->pMalloc(sizeof(float));
|
|
|
audio->resampleCache = (float*) audio->pMalloc(sizeof(float));
|
|
|
audio->decodeSamples = 1;
|
|
|
audio->resampleSamples = 1;
|
|
|
|
|
|
FAudio_StartEngine(audio);
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_RegisterForCallbacks(
|
|
|
FAudio *audio,
|
|
|
FAudioEngineCallback *pCallback
|
|
|
) {
|
|
|
LOG_API_ENTER(audio)
|
|
|
LinkedList_AddEntry(
|
|
|
&audio->callbacks,
|
|
|
pCallback,
|
|
|
audio->callbackLock,
|
|
|
audio->pMalloc
|
|
|
);
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudio_UnregisterForCallbacks(
|
|
|
FAudio *audio,
|
|
|
FAudioEngineCallback *pCallback
|
|
|
) {
|
|
|
LOG_API_ENTER(audio)
|
|
|
LinkedList_RemoveEntry(
|
|
|
&audio->callbacks,
|
|
|
pCallback,
|
|
|
audio->callbackLock,
|
|
|
audio->pFree
|
|
|
);
|
|
|
LOG_API_EXIT(audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CreateSourceVoice(
|
|
|
FAudio *audio,
|
|
|
FAudioSourceVoice **ppSourceVoice,
|
|
|
const FAudioWaveFormatEx *pSourceFormat,
|
|
|
uint32_t Flags,
|
|
|
float MaxFrequencyRatio,
|
|
|
FAudioVoiceCallback *pCallback,
|
|
|
const FAudioVoiceSends *pSendList,
|
|
|
const FAudioEffectChain *pEffectChain
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
LOG_FORMAT(audio, pSourceFormat)
|
|
|
|
|
|
*ppSourceVoice = (FAudioSourceVoice*) audio->pMalloc(sizeof(FAudioVoice));
|
|
|
FAudio_zero(*ppSourceVoice, sizeof(FAudioSourceVoice));
|
|
|
(*ppSourceVoice)->audio = audio;
|
|
|
(*ppSourceVoice)->type = FAUDIO_VOICE_SOURCE;
|
|
|
(*ppSourceVoice)->flags = Flags;
|
|
|
(*ppSourceVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE;
|
|
|
(*ppSourceVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
|
|
|
(*ppSourceVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
|
|
|
(*ppSourceVoice)->sendLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->sendLock)
|
|
|
(*ppSourceVoice)->effectLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->effectLock)
|
|
|
(*ppSourceVoice)->filterLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->filterLock)
|
|
|
(*ppSourceVoice)->volumeLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->volumeLock)
|
|
|
|
|
|
/* Source Properties */
|
|
|
FAudio_assert(MaxFrequencyRatio <= FAUDIO_MAX_FREQ_RATIO);
|
|
|
(*ppSourceVoice)->src.maxFreqRatio = MaxFrequencyRatio;
|
|
|
|
|
|
if ( pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM ||
|
|
|
pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT ||
|
|
|
pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2 ||
|
|
|
pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 )
|
|
|
{
|
|
|
FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) audio->pMalloc(
|
|
|
sizeof(FAudioWaveFormatExtensible)
|
|
|
);
|
|
|
/* convert PCM to EXTENSIBLE */
|
|
|
fmtex->Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE;
|
|
|
fmtex->Format.nChannels = pSourceFormat->nChannels;
|
|
|
fmtex->Format.nSamplesPerSec = pSourceFormat->nSamplesPerSec;
|
|
|
fmtex->Format.nAvgBytesPerSec = pSourceFormat->nAvgBytesPerSec;
|
|
|
fmtex->Format.nBlockAlign = pSourceFormat->nBlockAlign;
|
|
|
fmtex->Format.wBitsPerSample = pSourceFormat->wBitsPerSample;
|
|
|
fmtex->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);
|
|
|
fmtex->Samples.wValidBitsPerSample = pSourceFormat->wBitsPerSample;
|
|
|
fmtex->dwChannelMask = 0;
|
|
|
if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM)
|
|
|
{
|
|
|
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_PCM, sizeof(FAudioGUID));
|
|
|
}
|
|
|
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT)
|
|
|
{
|
|
|
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID));
|
|
|
}
|
|
|
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2)
|
|
|
{
|
|
|
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_XMAUDIO2, sizeof(FAudioGUID));
|
|
|
}
|
|
|
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2)
|
|
|
{
|
|
|
FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO2, sizeof(FAudioGUID));
|
|
|
}
|
|
|
(*ppSourceVoice)->src.format = &fmtex->Format;
|
|
|
}
|
|
|
else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) audio->pMalloc(
|
|
|
sizeof(FAudioADPCMWaveFormat)
|
|
|
);
|
|
|
|
|
|
/* Copy what we can, ideally the sizes match! */
|
|
|
size_t cbSize = sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize;
|
|
|
FAudio_memcpy(
|
|
|
fmtex,
|
|
|
pSourceFormat,
|
|
|
FAudio_min(cbSize, sizeof(FAudioADPCMWaveFormat))
|
|
|
);
|
|
|
if (cbSize < sizeof(FAudioADPCMWaveFormat))
|
|
|
{
|
|
|
FAudio_zero(
|
|
|
((uint8_t*) fmtex) + cbSize,
|
|
|
sizeof(FAudioADPCMWaveFormat) - cbSize
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* XAudio2 does not validate this input! */
|
|
|
fmtex->wfx.cbSize = sizeof(FAudioADPCMWaveFormat) - sizeof(FAudioWaveFormatEx);
|
|
|
fmtex->wSamplesPerBlock = ((
|
|
|
fmtex->wfx.nBlockAlign / fmtex->wfx.nChannels
|
|
|
) - 6) * 2;
|
|
|
(*ppSourceVoice)->src.format = &fmtex->wfx;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* direct copy anything else */
|
|
|
(*ppSourceVoice)->src.format = (FAudioWaveFormatEx*) audio->pMalloc(
|
|
|
sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize
|
|
|
);
|
|
|
FAudio_memcpy(
|
|
|
(*ppSourceVoice)->src.format,
|
|
|
pSourceFormat,
|
|
|
sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize
|
|
|
);
|
|
|
}
|
|
|
|
|
|
(*ppSourceVoice)->src.callback = pCallback;
|
|
|
(*ppSourceVoice)->src.active = 0;
|
|
|
(*ppSourceVoice)->src.freqRatio = 1.0f;
|
|
|
(*ppSourceVoice)->src.totalSamples = 0;
|
|
|
(*ppSourceVoice)->src.bufferList = NULL;
|
|
|
(*ppSourceVoice)->src.flushList = NULL;
|
|
|
(*ppSourceVoice)->src.bufferLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSourceVoice)->src.bufferLock)
|
|
|
|
|
|
if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE)
|
|
|
{
|
|
|
FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) (*ppSourceVoice)->src.format;
|
|
|
|
|
|
#define COMPARE_GUID(type) \
|
|
|
(FAudio_memcmp( \
|
|
|
&fmtex->SubFormat, \
|
|
|
&DATAFORMAT_SUBTYPE_##type, \
|
|
|
sizeof(FAudioGUID) \
|
|
|
) == 0)
|
|
|
if (COMPARE_GUID(PCM))
|
|
|
{
|
|
|
#define DECODER(bit) \
|
|
|
if (fmtex->Format.wBitsPerSample == bit) \
|
|
|
{ \
|
|
|
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM##bit; \
|
|
|
}
|
|
|
DECODER(16)
|
|
|
else DECODER(8)
|
|
|
else DECODER(24)
|
|
|
else DECODER(32)
|
|
|
else
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
audio,
|
|
|
"Unrecognized wBitsPerSample: %d",
|
|
|
fmtex->Format.wBitsPerSample
|
|
|
)
|
|
|
FAudio_assert(0 && "Unrecognized wBitsPerSample!");
|
|
|
}
|
|
|
#undef DECODER
|
|
|
}
|
|
|
else if (COMPARE_GUID(IEEE_FLOAT))
|
|
|
{
|
|
|
/* FIXME: Weird behavior!
|
|
|
* Prototype creates a source with the IEEE_FLOAT tag,
|
|
|
* but it's actually PCM16. It seems to prioritize
|
|
|
* wBitsPerSample over the format tag. Not sure if we
|
|
|
* should fold this section into the section above...?
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (fmtex->Format.wBitsPerSample == 16)
|
|
|
{
|
|
|
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM16;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodePCM32F;
|
|
|
}
|
|
|
}
|
|
|
else if ( COMPARE_GUID(WMAUDIO2) ||
|
|
|
COMPARE_GUID(WMAUDIO3) ||
|
|
|
COMPARE_GUID(WMAUDIO_LOSSLESS) ||
|
|
|
COMPARE_GUID(XMAUDIO2) )
|
|
|
{
|
|
|
#ifdef HAVE_FFMPEG
|
|
|
if (FAudio_FFMPEG_init(*ppSourceVoice, fmtex->SubFormat.Data1) != 0)
|
|
|
{
|
|
|
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR;
|
|
|
}
|
|
|
#else
|
|
|
FAudio_assert(0 && "xWMA is not supported!");
|
|
|
(*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR;
|
|
|
#endif /* HAVE_FFMPEG */
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "Unsupported WAVEFORMATEXTENSIBLE subtype!");
|
|
|
}
|
|
|
#undef COMPARE_GUID
|
|
|
}
|
|
|
else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
(*ppSourceVoice)->src.decode = ((*ppSourceVoice)->src.format->nChannels == 2) ?
|
|
|
FAudio_INTERNAL_DecodeStereoMSADPCM :
|
|
|
FAudio_INTERNAL_DecodeMonoMSADPCM;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "Unsupported format tag!");
|
|
|
}
|
|
|
|
|
|
if ((*ppSourceVoice)->src.format->nChannels == 1)
|
|
|
{
|
|
|
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleMono;
|
|
|
}
|
|
|
else if ((*ppSourceVoice)->src.format->nChannels == 2)
|
|
|
{
|
|
|
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleStereo;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
(*ppSourceVoice)->src.resample = FAudio_INTERNAL_ResampleGeneric;
|
|
|
}
|
|
|
|
|
|
(*ppSourceVoice)->src.curBufferOffset = 0;
|
|
|
|
|
|
/* Sends/Effects */
|
|
|
FAudio_INTERNAL_VoiceOutputFrequency(*ppSourceVoice, pSendList);
|
|
|
FAudioVoice_SetEffectChain(*ppSourceVoice, pEffectChain);
|
|
|
FAudioVoice_SetOutputVoices(*ppSourceVoice, pSendList);
|
|
|
|
|
|
/* Default Levels */
|
|
|
(*ppSourceVoice)->volume = 1.0f;
|
|
|
(*ppSourceVoice)->channelVolume = (float*) audio->pMalloc(
|
|
|
sizeof(float) * (*ppSourceVoice)->outputChannels
|
|
|
);
|
|
|
for (i = 0; i < (*ppSourceVoice)->outputChannels; i += 1)
|
|
|
{
|
|
|
(*ppSourceVoice)->channelVolume[i] = 1.0f;
|
|
|
}
|
|
|
|
|
|
/* Filters */
|
|
|
if (Flags & FAUDIO_VOICE_USEFILTER)
|
|
|
{
|
|
|
(*ppSourceVoice)->filterState = (FAudioFilterState*) audio->pMalloc(
|
|
|
sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels
|
|
|
);
|
|
|
FAudio_zero(
|
|
|
(*ppSourceVoice)->filterState,
|
|
|
sizeof(FAudioFilterState) * (*ppSourceVoice)->src.format->nChannels
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* Sample Storage */
|
|
|
(*ppSourceVoice)->src.decodeSamples = (uint32_t) (FAudio_ceil(
|
|
|
(double) audio->updateSize *
|
|
|
(double) MaxFrequencyRatio *
|
|
|
(double) (*ppSourceVoice)->src.format->nSamplesPerSec /
|
|
|
(double) audio->master->master.inputSampleRate
|
|
|
)) + EXTRA_DECODE_PADDING * (*ppSourceVoice)->src.format->nChannels;
|
|
|
FAudio_INTERNAL_ResizeDecodeCache(
|
|
|
audio,
|
|
|
((*ppSourceVoice)->src.decodeSamples + EXTRA_DECODE_PADDING) * (*ppSourceVoice)->src.format->nChannels
|
|
|
);
|
|
|
|
|
|
LOG_INFO(audio, "-> %p", (void*) (*ppSourceVoice))
|
|
|
|
|
|
/* Add to list, finally. */
|
|
|
LinkedList_PrependEntry(
|
|
|
&audio->sources,
|
|
|
*ppSourceVoice,
|
|
|
audio->sourceLock,
|
|
|
audio->pMalloc
|
|
|
);
|
|
|
FAudio_AddRef(audio);
|
|
|
|
|
|
#ifdef FAUDIO_DUMP_VOICES
|
|
|
FAudio_DUMPVOICE_Init(*ppSourceVoice);
|
|
|
#endif /* FAUDIO_DUMP_VOICES */
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CreateSubmixVoice(
|
|
|
FAudio *audio,
|
|
|
FAudioSubmixVoice **ppSubmixVoice,
|
|
|
uint32_t InputChannels,
|
|
|
uint32_t InputSampleRate,
|
|
|
uint32_t Flags,
|
|
|
uint32_t ProcessingStage,
|
|
|
const FAudioVoiceSends *pSendList,
|
|
|
const FAudioEffectChain *pEffectChain
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
|
|
|
*ppSubmixVoice = (FAudioSubmixVoice*) audio->pMalloc(sizeof(FAudioVoice));
|
|
|
FAudio_zero(*ppSubmixVoice, sizeof(FAudioSubmixVoice));
|
|
|
(*ppSubmixVoice)->audio = audio;
|
|
|
(*ppSubmixVoice)->type = FAUDIO_VOICE_SUBMIX;
|
|
|
(*ppSubmixVoice)->flags = Flags;
|
|
|
(*ppSubmixVoice)->filter.Type = FAUDIO_DEFAULT_FILTER_TYPE;
|
|
|
(*ppSubmixVoice)->filter.Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
|
|
|
(*ppSubmixVoice)->filter.OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
|
|
|
(*ppSubmixVoice)->sendLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->sendLock)
|
|
|
(*ppSubmixVoice)->effectLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->effectLock)
|
|
|
(*ppSubmixVoice)->filterLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->filterLock)
|
|
|
(*ppSubmixVoice)->volumeLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppSubmixVoice)->volumeLock)
|
|
|
|
|
|
/* Submix Properties */
|
|
|
(*ppSubmixVoice)->mix.inputChannels = InputChannels;
|
|
|
(*ppSubmixVoice)->mix.inputSampleRate = InputSampleRate;
|
|
|
(*ppSubmixVoice)->mix.processingStage = ProcessingStage;
|
|
|
|
|
|
/* Resampler */
|
|
|
if (InputChannels == 1)
|
|
|
{
|
|
|
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleMono;
|
|
|
}
|
|
|
else if (InputChannels == 2)
|
|
|
{
|
|
|
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleStereo;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
(*ppSubmixVoice)->mix.resample = FAudio_INTERNAL_ResampleGeneric;
|
|
|
}
|
|
|
|
|
|
/* Sample Storage */
|
|
|
(*ppSubmixVoice)->mix.inputSamples = ((uint32_t) FAudio_ceil(
|
|
|
audio->updateSize *
|
|
|
(double) InputSampleRate /
|
|
|
(double) audio->master->master.inputSampleRate
|
|
|
) + EXTRA_DECODE_PADDING) * InputChannels;
|
|
|
(*ppSubmixVoice)->mix.inputCache = (float*) audio->pMalloc(
|
|
|
sizeof(float) * (*ppSubmixVoice)->mix.inputSamples
|
|
|
);
|
|
|
FAudio_zero( /* Zero this now, for the first update */
|
|
|
(*ppSubmixVoice)->mix.inputCache,
|
|
|
sizeof(float) * (*ppSubmixVoice)->mix.inputSamples
|
|
|
);
|
|
|
|
|
|
/* Sends/Effects */
|
|
|
FAudio_INTERNAL_VoiceOutputFrequency(*ppSubmixVoice, pSendList);
|
|
|
FAudioVoice_SetEffectChain(*ppSubmixVoice, pEffectChain);
|
|
|
FAudioVoice_SetOutputVoices(*ppSubmixVoice, pSendList);
|
|
|
|
|
|
/* Default Levels */
|
|
|
(*ppSubmixVoice)->volume = 1.0f;
|
|
|
(*ppSubmixVoice)->channelVolume = (float*) audio->pMalloc(
|
|
|
sizeof(float) * (*ppSubmixVoice)->outputChannels
|
|
|
);
|
|
|
for (i = 0; i < (*ppSubmixVoice)->outputChannels; i += 1)
|
|
|
{
|
|
|
(*ppSubmixVoice)->channelVolume[i] = 1.0f;
|
|
|
}
|
|
|
|
|
|
/* Filters */
|
|
|
if (Flags & FAUDIO_VOICE_USEFILTER)
|
|
|
{
|
|
|
(*ppSubmixVoice)->filterState = (FAudioFilterState*) audio->pMalloc(
|
|
|
sizeof(FAudioFilterState) * InputChannels
|
|
|
);
|
|
|
FAudio_zero(
|
|
|
(*ppSubmixVoice)->filterState,
|
|
|
sizeof(FAudioFilterState) * InputChannels
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* Add to list, finally. */
|
|
|
FAudio_INTERNAL_InsertSubmixSorted(
|
|
|
&audio->submixes,
|
|
|
*ppSubmixVoice,
|
|
|
audio->submixLock,
|
|
|
audio->pMalloc
|
|
|
);
|
|
|
FAudio_AddRef(audio);
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CreateMasteringVoice(
|
|
|
FAudio *audio,
|
|
|
FAudioMasteringVoice **ppMasteringVoice,
|
|
|
uint32_t InputChannels,
|
|
|
uint32_t InputSampleRate,
|
|
|
uint32_t Flags,
|
|
|
uint32_t DeviceIndex,
|
|
|
const FAudioEffectChain *pEffectChain
|
|
|
) {
|
|
|
FAudioDeviceDetails details;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
|
|
|
/* For now we only support one allocated master voice at a time */
|
|
|
FAudio_assert(audio->master == NULL);
|
|
|
|
|
|
if (FAudio_GetDeviceDetails(audio, DeviceIndex, &details) != 0)
|
|
|
{
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
*ppMasteringVoice = (FAudioMasteringVoice*) audio->pMalloc(sizeof(FAudioVoice));
|
|
|
FAudio_zero(*ppMasteringVoice, sizeof(FAudioMasteringVoice));
|
|
|
(*ppMasteringVoice)->audio = audio;
|
|
|
(*ppMasteringVoice)->type = FAUDIO_VOICE_MASTER;
|
|
|
(*ppMasteringVoice)->flags = Flags;
|
|
|
(*ppMasteringVoice)->effectLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->effectLock)
|
|
|
(*ppMasteringVoice)->volumeLock = FAudio_PlatformCreateMutex();
|
|
|
LOG_MUTEX_CREATE(audio, (*ppMasteringVoice)->volumeLock)
|
|
|
|
|
|
/* Default Levels */
|
|
|
(*ppMasteringVoice)->volume = 1.0f;
|
|
|
|
|
|
/* Master Properties */
|
|
|
(*ppMasteringVoice)->master.inputChannels = (InputChannels == FAUDIO_DEFAULT_CHANNELS) ?
|
|
|
details.OutputFormat.Format.nChannels :
|
|
|
InputChannels;
|
|
|
(*ppMasteringVoice)->master.inputSampleRate = (InputSampleRate == FAUDIO_DEFAULT_SAMPLERATE) ?
|
|
|
details.OutputFormat.Format.nSamplesPerSec :
|
|
|
InputSampleRate;
|
|
|
|
|
|
/* Sends/Effects */
|
|
|
FAudio_zero(&(*ppMasteringVoice)->sends, sizeof(FAudioVoiceSends));
|
|
|
FAudioVoice_SetEffectChain(*ppMasteringVoice, pEffectChain);
|
|
|
|
|
|
/* This is now safe enough to assign */
|
|
|
audio->master = *ppMasteringVoice;
|
|
|
|
|
|
/* Build the device format.
|
|
|
* The most unintuitive part of this is the use of outputChannels
|
|
|
* instead of master.inputChannels. Bizarrely, the effect chain can
|
|
|
* dictate the _actual_ output channel count, and when the channel count
|
|
|
* mismatches, we have to add a staging buffer for effects to process on
|
|
|
* before ultimately copying the final result to the device. ARGH.
|
|
|
*/
|
|
|
WriteWaveFormatExtensible(
|
|
|
&audio->mixFormat,
|
|
|
audio->master->outputChannels,
|
|
|
audio->master->master.inputSampleRate
|
|
|
);
|
|
|
|
|
|
/* Platform Device */
|
|
|
FAudio_AddRef(audio);
|
|
|
FAudio_PlatformInit(
|
|
|
audio,
|
|
|
audio->initFlags,
|
|
|
DeviceIndex,
|
|
|
&audio->mixFormat,
|
|
|
&audio->updateSize,
|
|
|
&audio->platform
|
|
|
);
|
|
|
if (audio->platform == NULL)
|
|
|
{
|
|
|
FAudioVoice_DestroyVoice(*ppMasteringVoice);
|
|
|
*ppMasteringVoice = NULL;
|
|
|
|
|
|
/* Not the best code, but it's probably true? */
|
|
|
return FAUDIO_E_DEVICE_INVALIDATED;
|
|
|
}
|
|
|
audio->master->outputChannels = audio->mixFormat.Format.nChannels;
|
|
|
audio->master->master.inputSampleRate = audio->mixFormat.Format.nSamplesPerSec;
|
|
|
|
|
|
/* Effect Chain Cache */
|
|
|
if ((*ppMasteringVoice)->master.inputChannels != (*ppMasteringVoice)->outputChannels)
|
|
|
{
|
|
|
(*ppMasteringVoice)->master.effectCache = (float*) audio->pMalloc(
|
|
|
sizeof(float) *
|
|
|
audio->updateSize *
|
|
|
(*ppMasteringVoice)->master.inputChannels
|
|
|
);
|
|
|
}
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CreateMasteringVoice8(
|
|
|
FAudio *audio,
|
|
|
FAudioMasteringVoice **ppMasteringVoice,
|
|
|
uint32_t InputChannels,
|
|
|
uint32_t InputSampleRate,
|
|
|
uint32_t Flags,
|
|
|
uint16_t *szDeviceId,
|
|
|
const FAudioEffectChain *pEffectChain,
|
|
|
FAudioStreamCategory StreamCategory
|
|
|
) {
|
|
|
uint32_t DeviceIndex, retval;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
|
|
|
/* Eventually, we'll want the old CreateMastering to call the new one.
|
|
|
* That will depend on us being able to use DeviceID though.
|
|
|
* For now, use our little ID hack to turn szDeviceId into DeviceIndex.
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (szDeviceId == NULL || szDeviceId[0] == 0)
|
|
|
{
|
|
|
DeviceIndex = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
DeviceIndex = szDeviceId[0] - L'0';
|
|
|
if (DeviceIndex > FAudio_PlatformGetDeviceCount())
|
|
|
{
|
|
|
DeviceIndex = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Note that StreamCategory is ignored! */
|
|
|
retval = FAudio_CreateMasteringVoice(
|
|
|
audio,
|
|
|
ppMasteringVoice,
|
|
|
InputChannels,
|
|
|
InputSampleRate,
|
|
|
Flags,
|
|
|
DeviceIndex,
|
|
|
pEffectChain
|
|
|
);
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
void FAudio_SetEngineProcedureEXT(
|
|
|
FAudio *audio,
|
|
|
FAudioEngineProcedureEXT clientEngineProc,
|
|
|
void *user
|
|
|
) {
|
|
|
LOG_API_ENTER(audio)
|
|
|
audio->pClientEngineProc = clientEngineProc;
|
|
|
audio->clientEngineUser = user;
|
|
|
LOG_API_EXIT(audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_StartEngine(FAudio *audio)
|
|
|
{
|
|
|
LOG_API_ENTER(audio)
|
|
|
audio->active = 1;
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudio_StopEngine(FAudio *audio)
|
|
|
{
|
|
|
LOG_API_ENTER(audio)
|
|
|
audio->active = 0;
|
|
|
FAudio_OPERATIONSET_CommitAll(audio);
|
|
|
FAudio_OPERATIONSET_Execute(audio);
|
|
|
LOG_API_EXIT(audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CommitOperationSet(FAudio *audio, uint32_t OperationSet)
|
|
|
{
|
|
|
LOG_API_ENTER(audio)
|
|
|
if (OperationSet == FAUDIO_COMMIT_ALL)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_CommitAll(audio);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_OPERATIONSET_Commit(audio, OperationSet);
|
|
|
}
|
|
|
LOG_API_EXIT(audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudio_CommitChanges(FAudio *audio)
|
|
|
{
|
|
|
FAudio_Log(
|
|
|
"IF YOU CAN READ THIS, YOUR PROGRAM IS ABOUT TO BREAK!"
|
|
|
"\n\nEither you or somebody else is using FAudio_CommitChanges,"
|
|
|
"\nwhen they should be using FAudio_CommitOperationSet instead."
|
|
|
"\n\nIf your program calls this, move to CommitOperationSet."
|
|
|
"\n\nIf somebody else is calling this, find out who it is and"
|
|
|
"\nfile a bug report with them ASAP."
|
|
|
);
|
|
|
|
|
|
/* Seriously, this is like the worst possible thing short of no-oping.
|
|
|
* For the love-a Pete, just migrate, do it, what is wrong with you
|
|
|
*/
|
|
|
return FAudio_CommitOperationSet(audio, FAUDIO_COMMIT_ALL);
|
|
|
}
|
|
|
|
|
|
void FAudio_GetPerformanceData(
|
|
|
FAudio *audio,
|
|
|
FAudioPerformanceData *pPerfData
|
|
|
) {
|
|
|
LinkedList *list;
|
|
|
FAudioSourceVoice *source;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
|
|
|
FAudio_zero(pPerfData, sizeof(FAudioPerformanceData));
|
|
|
|
|
|
FAudio_PlatformLockMutex(audio->sourceLock);
|
|
|
LOG_MUTEX_LOCK(audio, audio->sourceLock)
|
|
|
list = audio->sources;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
source = (FAudioSourceVoice*) list->entry;
|
|
|
pPerfData->TotalSourceVoiceCount += 1;
|
|
|
if (source->src.active)
|
|
|
{
|
|
|
pPerfData->ActiveSourceVoiceCount += 1;
|
|
|
}
|
|
|
list = list->next;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(audio->sourceLock);
|
|
|
LOG_MUTEX_UNLOCK(audio, audio->sourceLock)
|
|
|
|
|
|
FAudio_PlatformLockMutex(audio->submixLock);
|
|
|
LOG_MUTEX_LOCK(audio, audio->submixLock)
|
|
|
list = audio->submixes;
|
|
|
while (list != NULL)
|
|
|
{
|
|
|
pPerfData->ActiveSubmixVoiceCount += 1;
|
|
|
list = list->next;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(audio->submixLock);
|
|
|
LOG_MUTEX_UNLOCK(audio, audio->submixLock)
|
|
|
|
|
|
if (audio->master != NULL)
|
|
|
{
|
|
|
/* estimate, should use real latency from platform */
|
|
|
pPerfData->CurrentLatencyInSamples = 2 * audio->updateSize;
|
|
|
}
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
}
|
|
|
|
|
|
void FAudio_SetDebugConfiguration(
|
|
|
FAudio *audio,
|
|
|
FAudioDebugConfiguration *pDebugConfiguration,
|
|
|
void* pReserved
|
|
|
) {
|
|
|
#ifndef FAUDIO_DISABLE_DEBUGCONFIGURATION
|
|
|
char *env;
|
|
|
|
|
|
LOG_API_ENTER(audio)
|
|
|
|
|
|
FAudio_memcpy(
|
|
|
&audio->debug,
|
|
|
pDebugConfiguration,
|
|
|
sizeof(FAudioDebugConfiguration)
|
|
|
);
|
|
|
|
|
|
env = FAudio_getenv("FAUDIO_LOG_EVERYTHING");
|
|
|
if (env != NULL && *env == '1')
|
|
|
{
|
|
|
audio->debug.TraceMask = (
|
|
|
FAUDIO_LOG_ERRORS |
|
|
|
FAUDIO_LOG_WARNINGS |
|
|
|
FAUDIO_LOG_INFO |
|
|
|
FAUDIO_LOG_DETAIL |
|
|
|
FAUDIO_LOG_API_CALLS |
|
|
|
FAUDIO_LOG_FUNC_CALLS |
|
|
|
FAUDIO_LOG_TIMING |
|
|
|
FAUDIO_LOG_LOCKS |
|
|
|
FAUDIO_LOG_MEMORY |
|
|
|
FAUDIO_LOG_STREAMING
|
|
|
);
|
|
|
audio->debug.LogThreadID = 1;
|
|
|
audio->debug.LogFunctionName = 1;
|
|
|
audio->debug.LogTiming = 1;
|
|
|
}
|
|
|
|
|
|
#define CHECK_ENV(type) \
|
|
|
env = FAudio_getenv("FAUDIO_LOG_" #type); \
|
|
|
if (env != NULL) \
|
|
|
{ \
|
|
|
if (*env == '1') \
|
|
|
{ \
|
|
|
audio->debug.TraceMask |= FAUDIO_LOG_##type; \
|
|
|
} \
|
|
|
else \
|
|
|
{ \
|
|
|
audio->debug.TraceMask &= ~FAUDIO_LOG_##type; \
|
|
|
} \
|
|
|
}
|
|
|
CHECK_ENV(ERRORS)
|
|
|
CHECK_ENV(WARNINGS)
|
|
|
CHECK_ENV(INFO)
|
|
|
CHECK_ENV(DETAIL)
|
|
|
CHECK_ENV(API_CALLS)
|
|
|
CHECK_ENV(FUNC_CALLS)
|
|
|
CHECK_ENV(TIMING)
|
|
|
CHECK_ENV(LOCKS)
|
|
|
CHECK_ENV(MEMORY)
|
|
|
CHECK_ENV(STREAMING)
|
|
|
#undef CHECK_ENV
|
|
|
#define CHECK_ENV(envvar, boolvar) \
|
|
|
env = FAudio_getenv("FAUDIO_LOG_LOG" #envvar); \
|
|
|
if (env != NULL) \
|
|
|
{ \
|
|
|
audio->debug.Log##boolvar = (*env == '1'); \
|
|
|
}
|
|
|
CHECK_ENV(THREADID, ThreadID)
|
|
|
CHECK_ENV(FILELINE, Fileline)
|
|
|
CHECK_ENV(FUNCTIONNAME, FunctionName)
|
|
|
CHECK_ENV(TIMING, Timing)
|
|
|
#undef CHECK_ENV
|
|
|
|
|
|
LOG_API_EXIT(audio)
|
|
|
#endif /* FAUDIO_DISABLE_DEBUGCONFIGURATION */
|
|
|
}
|
|
|
|
|
|
void FAudio_GetProcessingQuantum(
|
|
|
FAudio *audio,
|
|
|
uint32_t *quantumNumerator,
|
|
|
uint32_t *quantumDenominator
|
|
|
) {
|
|
|
FAudio_assert(audio->master != NULL);
|
|
|
if (quantumNumerator != NULL)
|
|
|
{
|
|
|
*quantumNumerator = audio->updateSize;
|
|
|
}
|
|
|
if (quantumDenominator != NULL)
|
|
|
{
|
|
|
*quantumDenominator = audio->master->master.inputSampleRate;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* FAudioVoice Interface */
|
|
|
|
|
|
void FAudioVoice_GetVoiceDetails(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioVoiceDetails *pVoiceDetails
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
pVoiceDetails->CreationFlags = voice->flags;
|
|
|
pVoiceDetails->ActiveFlags = voice->flags;
|
|
|
if (voice->type == FAUDIO_VOICE_SOURCE)
|
|
|
{
|
|
|
pVoiceDetails->InputChannels = voice->src.format->nChannels;
|
|
|
pVoiceDetails->InputSampleRate = voice->src.format->nSamplesPerSec;
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_SUBMIX)
|
|
|
{
|
|
|
pVoiceDetails->InputChannels = voice->mix.inputChannels;
|
|
|
pVoiceDetails->InputSampleRate = voice->mix.inputSampleRate;
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
pVoiceDetails->InputChannels = voice->master.inputChannels;
|
|
|
pVoiceDetails->InputSampleRate = voice->master.inputSampleRate;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(0 && "Unknown voice type!");
|
|
|
}
|
|
|
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetOutputVoices(
|
|
|
FAudioVoice *voice,
|
|
|
const FAudioVoiceSends *pSendList
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
uint32_t outChannels;
|
|
|
FAudioVoiceSends defaultSends;
|
|
|
FAudioSendDescriptor defaultSend;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
if (FAudio_INTERNAL_VoiceOutputFrequency(voice, pSendList) != 0)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"%s",
|
|
|
"Changing the sample rate while an effect chain is attached is invalid!"
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
/* FIXME: This is lazy... */
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendCoefficients[i]);
|
|
|
}
|
|
|
if (voice->sendCoefficients != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendCoefficients);
|
|
|
}
|
|
|
if (voice->sendMix != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendMix);
|
|
|
}
|
|
|
if (voice->sendFilter != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendFilter);
|
|
|
voice->sendFilter = NULL;
|
|
|
}
|
|
|
if (voice->sendFilterState != NULL)
|
|
|
{
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (voice->sendFilterState[i] != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendFilterState[i]);
|
|
|
}
|
|
|
}
|
|
|
voice->audio->pFree(voice->sendFilterState);
|
|
|
voice->sendFilterState = NULL;
|
|
|
}
|
|
|
if (voice->sends.pSends != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sends.pSends);
|
|
|
}
|
|
|
|
|
|
if (pSendList == NULL)
|
|
|
{
|
|
|
/* Default to the mastering voice as output */
|
|
|
defaultSend.Flags = 0;
|
|
|
defaultSend.pOutputVoice = voice->audio->master;
|
|
|
defaultSends.SendCount = 1;
|
|
|
defaultSends.pSends = &defaultSend;
|
|
|
pSendList = &defaultSends;
|
|
|
}
|
|
|
else if (pSendList->SendCount == 0)
|
|
|
{
|
|
|
/* No sends? Nothing to do... */
|
|
|
voice->sendCoefficients = NULL;
|
|
|
voice->sendMix = NULL;
|
|
|
FAudio_zero(&voice->sends, sizeof(FAudioVoiceSends));
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* Copy send list */
|
|
|
voice->sends.SendCount = pSendList->SendCount;
|
|
|
voice->sends.pSends = (FAudioSendDescriptor*) voice->audio->pMalloc(
|
|
|
pSendList->SendCount * sizeof(FAudioSendDescriptor)
|
|
|
);
|
|
|
FAudio_memcpy(
|
|
|
voice->sends.pSends,
|
|
|
pSendList->pSends,
|
|
|
pSendList->SendCount * sizeof(FAudioSendDescriptor)
|
|
|
);
|
|
|
|
|
|
/* Allocate/Reset default output matrix, mixer function, filters */
|
|
|
voice->sendCoefficients = (float**) voice->audio->pMalloc(
|
|
|
sizeof(float*) * pSendList->SendCount
|
|
|
);
|
|
|
voice->sendMix = (FAudioMixCallback*) voice->audio->pMalloc(
|
|
|
sizeof(FAudioMixCallback) * pSendList->SendCount
|
|
|
);
|
|
|
for (i = 0; i < pSendList->SendCount; i += 1)
|
|
|
{
|
|
|
if (pSendList->pSends[i].pOutputVoice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
outChannels = pSendList->pSends[i].pOutputVoice->master.inputChannels;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
outChannels = pSendList->pSends[i].pOutputVoice->mix.inputChannels;
|
|
|
}
|
|
|
voice->sendCoefficients[i] = (float*) voice->audio->pMalloc(
|
|
|
sizeof(float) * voice->outputChannels * outChannels
|
|
|
);
|
|
|
|
|
|
FAudio_assert(voice->outputChannels > 0 && voice->outputChannels < 9);
|
|
|
FAudio_assert(outChannels > 0 && outChannels < 9);
|
|
|
FAudio_memcpy(
|
|
|
voice->sendCoefficients[i],
|
|
|
FAUDIO_INTERNAL_MATRIX_DEFAULTS[voice->outputChannels - 1][outChannels - 1],
|
|
|
voice->outputChannels * outChannels * sizeof(float)
|
|
|
);
|
|
|
|
|
|
if (voice->outputChannels == 1)
|
|
|
{
|
|
|
if (outChannels == 1)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_1out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 2)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_2out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 6)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_6out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 8)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_1in_8out_Scalar;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
|
|
|
}
|
|
|
}
|
|
|
else if (voice->outputChannels == 2)
|
|
|
{
|
|
|
if (outChannels == 1)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_1out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 2)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_2out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 6)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_6out_Scalar;
|
|
|
}
|
|
|
else if (outChannels == 8)
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_2in_8out_Scalar;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
voice->sendMix[i] = FAudio_INTERNAL_Mix_Generic_Scalar;
|
|
|
}
|
|
|
|
|
|
if (pSendList->pSends[i].Flags & FAUDIO_SEND_USEFILTER)
|
|
|
{
|
|
|
/* Allocate the whole send filter array if needed... */
|
|
|
if (voice->sendFilter == NULL)
|
|
|
{
|
|
|
voice->sendFilter = (FAudioFilterParameters*) voice->audio->pMalloc(
|
|
|
sizeof(FAudioFilterParameters) * pSendList->SendCount
|
|
|
);
|
|
|
}
|
|
|
if (voice->sendFilterState == NULL)
|
|
|
{
|
|
|
voice->sendFilterState = (FAudioFilterState**) voice->audio->pMalloc(
|
|
|
sizeof(FAudioFilterState*) * pSendList->SendCount
|
|
|
);
|
|
|
FAudio_zero(
|
|
|
voice->sendFilterState,
|
|
|
sizeof(FAudioFilterState*) * pSendList->SendCount
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* ... then fill in this send's filter data */
|
|
|
voice->sendFilter[i].Type = FAUDIO_DEFAULT_FILTER_TYPE;
|
|
|
voice->sendFilter[i].Frequency = FAUDIO_DEFAULT_FILTER_FREQUENCY;
|
|
|
voice->sendFilter[i].OneOverQ = FAUDIO_DEFAULT_FILTER_ONEOVERQ;
|
|
|
voice->sendFilterState[i] = (FAudioFilterState*) voice->audio->pMalloc(
|
|
|
sizeof(FAudioFilterState) * outChannels
|
|
|
);
|
|
|
FAudio_zero(
|
|
|
voice->sendFilterState[i],
|
|
|
sizeof(FAudioFilterState) * outChannels
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetEffectChain(
|
|
|
FAudioVoice *voice,
|
|
|
const FAudioEffectChain *pEffectChain
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
FAPO *fapo;
|
|
|
uint32_t channelCount;
|
|
|
FAudioVoiceDetails voiceDetails;
|
|
|
FAPORegistrationProperties *pProps;
|
|
|
FAudioWaveFormatExtensible srcFmt, dstFmt;
|
|
|
FAPOLockForProcessBufferParameters srcLockParams, dstLockParams;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
FAudioVoice_GetVoiceDetails(voice, &voiceDetails);
|
|
|
|
|
|
/* SetEffectChain must not change the number of output channels once the voice has been created */
|
|
|
if (pEffectChain == NULL && voice->outputChannels != 0)
|
|
|
{
|
|
|
/* cannot remove an effect chain that changes the number of channels */
|
|
|
if (voice->outputChannels != voiceDetails.InputChannels)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"%s",
|
|
|
"Cannot remove effect chain that changes the number of channels"
|
|
|
)
|
|
|
FAudio_assert(0 && "Cannot remove effect chain that changes the number of channels");
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (pEffectChain != NULL && voice->outputChannels != 0)
|
|
|
{
|
|
|
uint32_t lst = pEffectChain->EffectCount - 1;
|
|
|
|
|
|
/* new effect chain must have same number of output channels */
|
|
|
if (voice->outputChannels != pEffectChain->pEffectDescriptors[lst].OutputChannels)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"%s",
|
|
|
"New effect chain must have same number of output channels as the old chain"
|
|
|
)
|
|
|
FAudio_assert(0 && "New effect chain must have same number of output channels as the old chain");
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
|
|
|
if (pEffectChain == NULL)
|
|
|
{
|
|
|
FAudio_INTERNAL_FreeEffectChain(voice);
|
|
|
FAudio_zero(&voice->effects, sizeof(voice->effects));
|
|
|
voice->outputChannels = voiceDetails.InputChannels;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Validate incoming chain before changing the current chain */
|
|
|
|
|
|
/* These are always the same, so just write them now. */
|
|
|
srcLockParams.pFormat = &srcFmt.Format;
|
|
|
dstLockParams.pFormat = &dstFmt.Format;
|
|
|
if (voice->type == FAUDIO_VOICE_SOURCE)
|
|
|
{
|
|
|
srcLockParams.MaxFrameCount = voice->src.resampleSamples;
|
|
|
dstLockParams.MaxFrameCount = voice->src.resampleSamples;
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_SUBMIX)
|
|
|
{
|
|
|
srcLockParams.MaxFrameCount = voice->mix.outputSamples;
|
|
|
dstLockParams.MaxFrameCount = voice->mix.outputSamples;
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
srcLockParams.MaxFrameCount = voice->audio->updateSize;
|
|
|
dstLockParams.MaxFrameCount = voice->audio->updateSize;
|
|
|
}
|
|
|
|
|
|
/* The first source is the voice input data... */
|
|
|
srcFmt.Format.wBitsPerSample = 32;
|
|
|
srcFmt.Format.wFormatTag = FAUDIO_FORMAT_EXTENSIBLE;
|
|
|
srcFmt.Format.nChannels = voiceDetails.InputChannels;
|
|
|
srcFmt.Format.nSamplesPerSec = voiceDetails.InputSampleRate;
|
|
|
srcFmt.Format.nBlockAlign = srcFmt.Format.nChannels * (srcFmt.Format.wBitsPerSample / 8);
|
|
|
srcFmt.Format.nAvgBytesPerSec = srcFmt.Format.nSamplesPerSec * srcFmt.Format.nBlockAlign;
|
|
|
srcFmt.Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx);
|
|
|
srcFmt.Samples.wValidBitsPerSample = srcFmt.Format.wBitsPerSample;
|
|
|
srcFmt.dwChannelMask = 0;
|
|
|
FAudio_memcpy(&srcFmt.SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID));
|
|
|
FAudio_memcpy(&dstFmt, &srcFmt, sizeof(srcFmt));
|
|
|
|
|
|
for (i = 0; i < pEffectChain->EffectCount; i += 1)
|
|
|
{
|
|
|
fapo = pEffectChain->pEffectDescriptors[i].pEffect;
|
|
|
|
|
|
/* ... then we get this effect's format... */
|
|
|
dstFmt.Format.nChannels = pEffectChain->pEffectDescriptors[i].OutputChannels;
|
|
|
dstFmt.Format.nBlockAlign = dstFmt.Format.nChannels * (dstFmt.Format.wBitsPerSample / 8);
|
|
|
dstFmt.Format.nAvgBytesPerSec = dstFmt.Format.nSamplesPerSec * dstFmt.Format.nBlockAlign;
|
|
|
|
|
|
if (fapo->LockForProcess(fapo, 1, &srcLockParams, 1, &dstLockParams))
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"%s",
|
|
|
"Effect output format not supported"
|
|
|
)
|
|
|
FAudio_assert(0 && "Effect output format not supported");
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_UNSUPPORTED_FORMAT;
|
|
|
}
|
|
|
|
|
|
/* Okay, now this effect is the source and the next
|
|
|
* effect will be the destination. Repeat until no
|
|
|
* effects left.
|
|
|
*/
|
|
|
FAudio_memcpy(&srcFmt, &dstFmt, sizeof(srcFmt));
|
|
|
}
|
|
|
|
|
|
FAudio_INTERNAL_FreeEffectChain(voice);
|
|
|
FAudio_INTERNAL_AllocEffectChain(
|
|
|
voice,
|
|
|
pEffectChain
|
|
|
);
|
|
|
|
|
|
/* check if in-place processing is supported */
|
|
|
channelCount = voiceDetails.InputChannels;
|
|
|
for (i = 0; i < voice->effects.count; i += 1)
|
|
|
{
|
|
|
fapo = voice->effects.desc[i].pEffect;
|
|
|
if (fapo->GetRegistrationProperties(fapo, &pProps) == 0)
|
|
|
{
|
|
|
voice->effects.inPlaceProcessing[i] = (pProps->Flags & FAPO_FLAG_INPLACE_SUPPORTED) == FAPO_FLAG_INPLACE_SUPPORTED;
|
|
|
voice->effects.inPlaceProcessing[i] &= (channelCount == voice->effects.desc[i].OutputChannels);
|
|
|
channelCount = voice->effects.desc[i].OutputChannels;
|
|
|
|
|
|
/* Fails if in-place processing is mandatory and
|
|
|
* the chain forces us to do otherwise...
|
|
|
*/
|
|
|
FAudio_assert(
|
|
|
!(pProps->Flags & FAPO_FLAG_INPLACE_REQUIRED) ||
|
|
|
voice->effects.inPlaceProcessing[i]
|
|
|
);
|
|
|
|
|
|
voice->audio->pFree(pProps);
|
|
|
}
|
|
|
}
|
|
|
voice->outputChannels = channelCount;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_EnableEffect(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t EffectIndex,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueEnableEffect(
|
|
|
voice,
|
|
|
EffectIndex,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
voice->effects.desc[EffectIndex].InitialState = 1;
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_DisableEffect(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t EffectIndex,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueDisableEffect(
|
|
|
voice,
|
|
|
EffectIndex,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
voice->effects.desc[EffectIndex].InitialState = 0;
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetEffectState(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t EffectIndex,
|
|
|
int32_t *pEnabled
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
*pEnabled = voice->effects.desc[EffectIndex].InitialState;
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetEffectParameters(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t EffectIndex,
|
|
|
const void *pParameters,
|
|
|
uint32_t ParametersByteSize,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetEffectParameters(
|
|
|
voice,
|
|
|
EffectIndex,
|
|
|
pParameters,
|
|
|
ParametersByteSize,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
if (voice->effects.parameters[EffectIndex] == NULL)
|
|
|
{
|
|
|
voice->effects.parameters[EffectIndex] = voice->audio->pMalloc(
|
|
|
ParametersByteSize
|
|
|
);
|
|
|
voice->effects.parameterSizes[EffectIndex] = ParametersByteSize;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
if (voice->effects.parameterSizes[EffectIndex] < ParametersByteSize)
|
|
|
{
|
|
|
voice->effects.parameters[EffectIndex] = voice->audio->pRealloc(
|
|
|
voice->effects.parameters[EffectIndex],
|
|
|
ParametersByteSize
|
|
|
);
|
|
|
voice->effects.parameterSizes[EffectIndex] = ParametersByteSize;
|
|
|
}
|
|
|
FAudio_memcpy(
|
|
|
voice->effects.parameters[EffectIndex],
|
|
|
pParameters,
|
|
|
ParametersByteSize
|
|
|
);
|
|
|
voice->effects.parameterUpdates[EffectIndex] = 1;
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_GetEffectParameters(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t EffectIndex,
|
|
|
void *pParameters,
|
|
|
uint32_t ParametersByteSize
|
|
|
) {
|
|
|
FAPO *fapo;
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
fapo = voice->effects.desc[EffectIndex].pEffect;
|
|
|
fapo->GetParameters(fapo, pParameters, ParametersByteSize);
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetFilterParameters(
|
|
|
FAudioVoice *voice,
|
|
|
const FAudioFilterParameters *pParameters,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetFilterParameters(
|
|
|
voice,
|
|
|
pParameters,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* MSDN: "This method is usable only on source and submix voices and
|
|
|
* has no effect on mastering voices."
|
|
|
*/
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
if (!(voice->flags & FAUDIO_VOICE_USEFILTER))
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
|
|
|
FAudio_memcpy(
|
|
|
&voice->filter,
|
|
|
pParameters,
|
|
|
sizeof(FAudioFilterParameters)
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
|
|
|
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetFilterParameters(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioFilterParameters *pParameters
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
/* MSDN: "This method is usable only on source and submix voices and
|
|
|
* has no effect on mastering voices."
|
|
|
*/
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!(voice->flags & FAUDIO_VOICE_USEFILTER))
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
|
|
|
FAudio_memcpy(
|
|
|
pParameters,
|
|
|
&voice->filter,
|
|
|
sizeof(FAudioFilterParameters)
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetOutputFilterParameters(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioVoice *pDestinationVoice,
|
|
|
const FAudioFilterParameters *pParameters,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetOutputFilterParameters(
|
|
|
voice,
|
|
|
pDestinationVoice,
|
|
|
pParameters,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* MSDN: "This method is usable only on source and submix voices and
|
|
|
* has no effect on mastering voices."
|
|
|
*/
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
/* Find the send index */
|
|
|
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
|
|
|
{
|
|
|
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
|
|
|
}
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (i >= voice->sends.SendCount)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"Destination not attached to source: %p %p",
|
|
|
(void*) voice,
|
|
|
(void*) pDestinationVoice
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER))
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* Set the filter parameters, finally. */
|
|
|
FAudio_memcpy(
|
|
|
&voice->sendFilter[i],
|
|
|
pParameters,
|
|
|
sizeof(FAudioFilterParameters)
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetOutputFilterParameters(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioVoice *pDestinationVoice,
|
|
|
FAudioFilterParameters *pParameters
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
/* MSDN: "This method is usable only on source and submix voices and
|
|
|
* has no effect on mastering voices."
|
|
|
*/
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
/* Find the send index */
|
|
|
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
|
|
|
{
|
|
|
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
|
|
|
}
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (i >= voice->sends.SendCount)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"Destination not attached to source: %p %p",
|
|
|
(void*) voice,
|
|
|
(void*) pDestinationVoice
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!(voice->sends.pSends[i].Flags & FAUDIO_SEND_USEFILTER))
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* Set the filter parameters, finally. */
|
|
|
FAudio_memcpy(
|
|
|
pParameters,
|
|
|
&voice->sendFilter[i],
|
|
|
sizeof(FAudioFilterParameters)
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetVolume(
|
|
|
FAudioVoice *voice,
|
|
|
float Volume,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetVolume(
|
|
|
voice,
|
|
|
Volume,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
voice->volume = FAudio_clamp(
|
|
|
Volume,
|
|
|
-FAUDIO_MAX_VOLUME_LEVEL,
|
|
|
FAUDIO_MAX_VOLUME_LEVEL
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetVolume(
|
|
|
FAudioVoice *voice,
|
|
|
float *pVolume
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
*pVolume = voice->volume;
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetChannelVolumes(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t Channels,
|
|
|
const float *pVolumes,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetChannelVolumes(
|
|
|
voice,
|
|
|
Channels,
|
|
|
pVolumes,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
if (pVolumes == NULL)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
if (voice->audio->version > 7 && Channels != voice->outputChannels)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
|
|
|
FAudio_memcpy(
|
|
|
voice->channelVolume,
|
|
|
pVolumes,
|
|
|
sizeof(float) * Channels
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetChannelVolumes(
|
|
|
FAudioVoice *voice,
|
|
|
uint32_t Channels,
|
|
|
float *pVolumes
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_PlatformLockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
|
|
|
FAudio_memcpy(
|
|
|
pVolumes,
|
|
|
voice->channelVolume,
|
|
|
sizeof(float) * Channels
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioVoice_SetOutputMatrix(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioVoice *pDestinationVoice,
|
|
|
uint32_t SourceChannels,
|
|
|
uint32_t DestinationChannels,
|
|
|
const float *pLevelMatrix,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetOutputMatrix(
|
|
|
voice,
|
|
|
pDestinationVoice,
|
|
|
SourceChannels,
|
|
|
DestinationChannels,
|
|
|
pLevelMatrix,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
/* Find the send index */
|
|
|
if (pDestinationVoice == NULL && voice->sends.SendCount == 1)
|
|
|
{
|
|
|
pDestinationVoice = voice->sends.pSends[0].pOutputVoice;
|
|
|
}
|
|
|
FAudio_assert(pDestinationVoice != NULL);
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (i >= voice->sends.SendCount)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"Destination not attached to source: %p %p",
|
|
|
(void*) voice,
|
|
|
(void*) pDestinationVoice
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
/* Verify the Source/Destination channel count */
|
|
|
FAudio_assert(SourceChannels == voice->outputChannels);
|
|
|
|
|
|
if (pDestinationVoice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
FAudio_assert(DestinationChannels == pDestinationVoice->master.inputChannels);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(DestinationChannels == pDestinationVoice->mix.inputChannels);
|
|
|
}
|
|
|
|
|
|
/* Set the matrix values, finally */
|
|
|
FAudio_memcpy(
|
|
|
voice->sendCoefficients[i],
|
|
|
pLevelMatrix,
|
|
|
sizeof(float) * SourceChannels * DestinationChannels
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_GetOutputMatrix(
|
|
|
FAudioVoice *voice,
|
|
|
FAudioVoice *pDestinationVoice,
|
|
|
uint32_t SourceChannels,
|
|
|
uint32_t DestinationChannels,
|
|
|
float *pLevelMatrix
|
|
|
) {
|
|
|
uint32_t i;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
/* Find the send index */
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (pDestinationVoice == voice->sends.pSends[i].pOutputVoice)
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (i >= voice->sends.SendCount)
|
|
|
{
|
|
|
LOG_ERROR(
|
|
|
voice->audio,
|
|
|
"Destination not attached to source: %p %p",
|
|
|
(void*) voice,
|
|
|
(void*) pDestinationVoice
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* Verify the Source/Destination channel count */
|
|
|
if (voice->type == FAUDIO_VOICE_SOURCE)
|
|
|
{
|
|
|
FAudio_assert(SourceChannels == voice->src.format->nChannels);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(SourceChannels == voice->mix.inputChannels);
|
|
|
}
|
|
|
if (pDestinationVoice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
FAudio_assert(DestinationChannels == pDestinationVoice->master.inputChannels);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FAudio_assert(DestinationChannels == pDestinationVoice->mix.inputChannels);
|
|
|
}
|
|
|
|
|
|
/* Get the matrix values, finally */
|
|
|
FAudio_memcpy(
|
|
|
pLevelMatrix,
|
|
|
voice->sendCoefficients[i],
|
|
|
sizeof(float) * SourceChannels * DestinationChannels
|
|
|
);
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
void FAudioVoice_DestroyVoice(FAudioVoice *voice)
|
|
|
{
|
|
|
uint32_t i;
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
/* TODO: Check for dependencies and remove from audio graph first! */
|
|
|
FAudio_OPERATIONSET_ClearAllForVoice(voice);
|
|
|
|
|
|
if (voice->type == FAUDIO_VOICE_SOURCE)
|
|
|
{
|
|
|
FAudioBufferEntry *entry, *next;
|
|
|
|
|
|
#ifdef FAUDIO_DUMP_VOICES
|
|
|
FAudio_DUMPVOICE_Finalize((FAudioSourceVoice*) voice);
|
|
|
#endif /* FAUDIO_DUMP_VOICES */
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
|
while (voice == voice->audio->processingSource)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
FAudio_PlatformLockMutex(voice->audio->sourceLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock)
|
|
|
}
|
|
|
LinkedList_RemoveEntry(
|
|
|
&voice->audio->sources,
|
|
|
voice,
|
|
|
voice->audio->sourceLock,
|
|
|
voice->audio->pFree
|
|
|
);
|
|
|
FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
|
|
|
|
|
|
entry = voice->src.bufferList;
|
|
|
while (entry != NULL)
|
|
|
{
|
|
|
next = entry->next;
|
|
|
voice->audio->pFree(entry);
|
|
|
entry = next;
|
|
|
}
|
|
|
|
|
|
entry = voice->src.flushList;
|
|
|
while (entry != NULL)
|
|
|
{
|
|
|
next = entry->next;
|
|
|
voice->audio->pFree(entry);
|
|
|
entry = next;
|
|
|
}
|
|
|
|
|
|
voice->audio->pFree(voice->src.format);
|
|
|
LOG_MUTEX_DESTROY(voice->audio, voice->src.bufferLock)
|
|
|
FAudio_PlatformDestroyMutex(voice->src.bufferLock);
|
|
|
#ifdef HAVE_FFMPEG
|
|
|
if (voice->src.ffmpeg)
|
|
|
{
|
|
|
FAudio_FFMPEG_free(voice);
|
|
|
}
|
|
|
#endif /* HAVE_FFMPEG */
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_SUBMIX)
|
|
|
{
|
|
|
/* Remove submix from list */
|
|
|
LinkedList_RemoveEntry(
|
|
|
&voice->audio->submixes,
|
|
|
voice,
|
|
|
voice->audio->submixLock,
|
|
|
voice->audio->pFree
|
|
|
);
|
|
|
|
|
|
/* Delete submix data */
|
|
|
voice->audio->pFree(voice->mix.inputCache);
|
|
|
}
|
|
|
else if (voice->type == FAUDIO_VOICE_MASTER)
|
|
|
{
|
|
|
if (voice->audio->platform != NULL)
|
|
|
{
|
|
|
FAudio_PlatformQuit(voice->audio->platform);
|
|
|
voice->audio->platform = NULL;
|
|
|
}
|
|
|
if (voice->master.effectCache != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->master.effectCache);
|
|
|
}
|
|
|
voice->audio->master = NULL;
|
|
|
}
|
|
|
|
|
|
if (voice->sendLock != NULL)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendCoefficients[i]);
|
|
|
}
|
|
|
if (voice->sendCoefficients != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendCoefficients);
|
|
|
}
|
|
|
if (voice->sendMix != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendMix);
|
|
|
}
|
|
|
if (voice->sendFilter != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendFilter);
|
|
|
}
|
|
|
if (voice->sendFilterState != NULL)
|
|
|
{
|
|
|
for (i = 0; i < voice->sends.SendCount; i += 1)
|
|
|
{
|
|
|
if (voice->sendFilterState[i] != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sendFilterState[i]);
|
|
|
}
|
|
|
}
|
|
|
voice->audio->pFree(voice->sendFilterState);
|
|
|
}
|
|
|
if (voice->sends.pSends != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->sends.pSends);
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_MUTEX_DESTROY(voice->audio, voice->sendLock)
|
|
|
FAudio_PlatformDestroyMutex(voice->sendLock);
|
|
|
}
|
|
|
|
|
|
if (voice->effectLock != NULL)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->effectLock)
|
|
|
FAudio_INTERNAL_FreeEffectChain(voice);
|
|
|
FAudio_PlatformUnlockMutex(voice->effectLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock)
|
|
|
LOG_MUTEX_DESTROY(voice->audio, voice->effectLock)
|
|
|
FAudio_PlatformDestroyMutex(voice->effectLock);
|
|
|
}
|
|
|
|
|
|
if (voice->filterLock != NULL)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->filterLock)
|
|
|
if (voice->filterState != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->filterState);
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(voice->filterLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->filterLock)
|
|
|
LOG_MUTEX_DESTROY(voice->audio, voice->filterLock)
|
|
|
FAudio_PlatformDestroyMutex(voice->filterLock);
|
|
|
}
|
|
|
|
|
|
if (voice->volumeLock != NULL)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->volumeLock)
|
|
|
if (voice->channelVolume != NULL)
|
|
|
{
|
|
|
voice->audio->pFree(voice->channelVolume);
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(voice->volumeLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->volumeLock)
|
|
|
LOG_MUTEX_DESTROY(voice->audio, voice->volumeLock)
|
|
|
FAudio_PlatformDestroyMutex(voice->volumeLock);
|
|
|
}
|
|
|
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
FAudio_Release(voice->audio);
|
|
|
voice->audio->pFree(voice);
|
|
|
}
|
|
|
|
|
|
/* FAudioSourceVoice Interface */
|
|
|
|
|
|
uint32_t FAudioSourceVoice_Start(
|
|
|
FAudioSourceVoice *voice,
|
|
|
uint32_t Flags,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueStart(
|
|
|
voice,
|
|
|
Flags,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
FAudio_assert(Flags == 0);
|
|
|
voice->src.active = 1;
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_Stop(
|
|
|
FAudioSourceVoice *voice,
|
|
|
uint32_t Flags,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueStop(
|
|
|
voice,
|
|
|
Flags,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
if (Flags & FAUDIO_PLAY_TAILS)
|
|
|
{
|
|
|
voice->src.active = 2;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
voice->src.active = 0;
|
|
|
}
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_SubmitSourceBuffer(
|
|
|
FAudioSourceVoice *voice,
|
|
|
const FAudioBuffer *pBuffer,
|
|
|
const FAudioBufferWMA *pBufferWMA
|
|
|
) {
|
|
|
uint32_t adpcmMask, *adpcmByteCount;
|
|
|
uint32_t playBegin, playLength, loopBegin, loopLength;
|
|
|
FAudioBufferEntry *entry, *list;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
LOG_INFO(
|
|
|
voice->audio,
|
|
|
"%p: {Flags: 0x%x, AudioBytes: %u, pAudioData: %p, Play: %u + %u, Loop: %u + %u x %u}",
|
|
|
(void*) voice,
|
|
|
pBuffer->Flags,
|
|
|
pBuffer->AudioBytes,
|
|
|
(const void*) pBuffer->pAudioData,
|
|
|
pBuffer->PlayBegin,
|
|
|
pBuffer->PlayLength,
|
|
|
pBuffer->LoopBegin,
|
|
|
pBuffer->LoopLength,
|
|
|
pBuffer->LoopCount
|
|
|
)
|
|
|
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
#ifdef HAVE_FFMPEG
|
|
|
FAudio_assert( (voice->src.ffmpeg != NULL && pBufferWMA != NULL) ||
|
|
|
(voice->src.ffmpeg == NULL && pBufferWMA == NULL) );
|
|
|
#endif /* HAVE_FFMPEG */
|
|
|
|
|
|
/* Start off with whatever they just sent us... */
|
|
|
playBegin = pBuffer->PlayBegin;
|
|
|
playLength = pBuffer->PlayLength;
|
|
|
loopBegin = pBuffer->LoopBegin;
|
|
|
loopLength = pBuffer->LoopLength;
|
|
|
|
|
|
/* "LoopBegin/LoopLength must be zero if LoopCount is 0" */
|
|
|
if (pBuffer->LoopCount == 0 && (loopBegin > 0 || loopLength > 0))
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
/* PlayLength Default */
|
|
|
if (playLength == 0)
|
|
|
{
|
|
|
if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
FAudioADPCMWaveFormat *fmtex = (FAudioADPCMWaveFormat*) voice->src.format;
|
|
|
playLength = (
|
|
|
pBuffer->AudioBytes /
|
|
|
fmtex->wfx.nBlockAlign *
|
|
|
fmtex->wSamplesPerBlock
|
|
|
) - playBegin;
|
|
|
}
|
|
|
else if (pBufferWMA != NULL)
|
|
|
{
|
|
|
playLength = (
|
|
|
pBufferWMA->pDecodedPacketCumulativeBytes[pBufferWMA->PacketCount - 1] /
|
|
|
(voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8)
|
|
|
) - playBegin;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
playLength = (
|
|
|
pBuffer->AudioBytes /
|
|
|
voice->src.format->nBlockAlign
|
|
|
) - playBegin;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (pBuffer->LoopCount > 0)
|
|
|
{
|
|
|
/* "The value of LoopBegin must be less than PlayBegin + PlayLength" */
|
|
|
if (loopBegin >= (playBegin + playLength))
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
|
|
|
/* LoopLength Default */
|
|
|
if (loopLength == 0)
|
|
|
{
|
|
|
loopLength = playBegin + playLength - loopBegin;
|
|
|
}
|
|
|
|
|
|
/* "The value of LoopBegin + LoopLength must be greater than PlayBegin
|
|
|
* and less than PlayBegin + PlayLength"
|
|
|
*/
|
|
|
if ( voice->audio->version > 7 && (
|
|
|
(loopBegin + loopLength) <= playBegin ||
|
|
|
(loopBegin + loopLength) > (playBegin + playLength)) )
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* For ADPCM, round down to the nearest sample block size */
|
|
|
if (voice->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM)
|
|
|
{
|
|
|
adpcmMask = ((FAudioADPCMWaveFormat*) voice->src.format)->wSamplesPerBlock;
|
|
|
playBegin -= playBegin % adpcmMask;
|
|
|
playLength -= playLength % adpcmMask;
|
|
|
loopBegin -= loopBegin % adpcmMask;
|
|
|
loopLength -= loopLength % adpcmMask;
|
|
|
|
|
|
/* This is basically a const_cast... */
|
|
|
adpcmByteCount = (uint32_t*) &pBuffer->AudioBytes;
|
|
|
*adpcmByteCount = (
|
|
|
pBuffer->AudioBytes / voice->src.format->nBlockAlign
|
|
|
) * voice->src.format->nBlockAlign;
|
|
|
}
|
|
|
|
|
|
/* Allocate, now that we have valid input */
|
|
|
entry = (FAudioBufferEntry*) voice->audio->pMalloc(sizeof(FAudioBufferEntry));
|
|
|
FAudio_memcpy(&entry->buffer, pBuffer, sizeof(FAudioBuffer));
|
|
|
entry->buffer.PlayBegin = playBegin;
|
|
|
entry->buffer.PlayLength = playLength;
|
|
|
entry->buffer.LoopBegin = loopBegin;
|
|
|
entry->buffer.LoopLength = loopLength;
|
|
|
if (pBufferWMA != NULL)
|
|
|
{
|
|
|
FAudio_memcpy(&entry->bufferWMA, pBufferWMA, sizeof(FAudioBufferWMA));
|
|
|
}
|
|
|
entry->next = NULL;
|
|
|
|
|
|
if ( voice->audio->version <= 7 && (
|
|
|
entry->buffer.LoopCount > 0 &&
|
|
|
entry->buffer.LoopBegin + entry->buffer.LoopLength <= entry->buffer.PlayBegin))
|
|
|
{
|
|
|
entry->buffer.LoopCount = 0;
|
|
|
}
|
|
|
|
|
|
#ifdef FAUDIO_DUMP_VOICES
|
|
|
/* dumping current buffer, append into "data" section */
|
|
|
if (pBuffer->pAudioData != NULL && playLength > 0)
|
|
|
{
|
|
|
FAudio_DUMPVOICE_WriteBuffer(voice, pBuffer, pBufferWMA, playBegin, playLength);
|
|
|
}
|
|
|
#endif /* FAUDIO_DUMP_VOICES */
|
|
|
|
|
|
/* Submit! */
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
if (voice->src.bufferList == NULL)
|
|
|
{
|
|
|
voice->src.bufferList = entry;
|
|
|
voice->src.curBufferOffset = entry->buffer.PlayBegin;
|
|
|
voice->src.newBuffer = 1;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
list = voice->src.bufferList;
|
|
|
while (list->next != NULL)
|
|
|
{
|
|
|
list = list->next;
|
|
|
}
|
|
|
list->next = entry;
|
|
|
|
|
|
/* For some bizarre reason we get scenarios where a buffer is freed, only to
|
|
|
* have the allocator give us the exact same address and somehow get a single
|
|
|
* buffer referencing itself. I don't even know.
|
|
|
*/
|
|
|
FAudio_assert(list != entry);
|
|
|
}
|
|
|
LOG_INFO(
|
|
|
voice->audio,
|
|
|
"%p: appended buffer %p",
|
|
|
(void*) voice,
|
|
|
(void*) &entry->buffer
|
|
|
)
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_FlushSourceBuffers(
|
|
|
FAudioSourceVoice *voice
|
|
|
) {
|
|
|
FAudioBufferEntry *entry, *latest;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
|
|
/* If the source is playing, don't flush the active buffer */
|
|
|
entry = voice->src.bufferList;
|
|
|
if ((voice->src.active == 1) && entry != NULL && !voice->src.newBuffer)
|
|
|
{
|
|
|
entry = entry->next;
|
|
|
voice->src.bufferList->next = NULL;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
voice->src.curBufferOffset = 0;
|
|
|
voice->src.bufferList = NULL;
|
|
|
voice->src.newBuffer = 0;
|
|
|
}
|
|
|
|
|
|
/* Move them to the pending flush list */
|
|
|
if (entry != NULL)
|
|
|
{
|
|
|
if (voice->src.flushList == NULL)
|
|
|
{
|
|
|
voice->src.flushList = entry;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
latest = voice->src.flushList;
|
|
|
while (latest->next != NULL)
|
|
|
{
|
|
|
latest = latest->next;
|
|
|
}
|
|
|
latest->next = entry;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_Discontinuity(
|
|
|
FAudioSourceVoice *voice
|
|
|
) {
|
|
|
FAudioBufferEntry *buf;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
|
|
if (voice->src.bufferList != NULL)
|
|
|
{
|
|
|
for (buf = voice->src.bufferList; buf->next != NULL; buf = buf->next);
|
|
|
buf->buffer.Flags |= FAUDIO_END_OF_STREAM;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_ExitLoop(
|
|
|
FAudioSourceVoice *voice,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueExitLoop(
|
|
|
voice,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
|
|
if (voice->src.bufferList != NULL)
|
|
|
{
|
|
|
voice->src.bufferList->buffer.LoopCount = 0;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioSourceVoice_GetState(
|
|
|
FAudioSourceVoice *voice,
|
|
|
FAudioVoiceState *pVoiceState,
|
|
|
uint32_t Flags
|
|
|
) {
|
|
|
FAudioBufferEntry *entry;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
|
|
if (!(Flags & FAUDIO_VOICE_NOSAMPLESPLAYED))
|
|
|
{
|
|
|
pVoiceState->SamplesPlayed = voice->src.totalSamples;
|
|
|
}
|
|
|
|
|
|
pVoiceState->BuffersQueued = 0;
|
|
|
pVoiceState->pCurrentBufferContext = NULL;
|
|
|
if (voice->src.bufferList != NULL)
|
|
|
{
|
|
|
entry = voice->src.bufferList;
|
|
|
if (!voice->src.newBuffer)
|
|
|
{
|
|
|
pVoiceState->pCurrentBufferContext = entry->buffer.pContext;
|
|
|
}
|
|
|
do
|
|
|
{
|
|
|
pVoiceState->BuffersQueued += 1;
|
|
|
entry = entry->next;
|
|
|
} while (entry != NULL);
|
|
|
}
|
|
|
|
|
|
/* Pending flushed buffers also count */
|
|
|
entry = voice->src.flushList;
|
|
|
while (entry != NULL)
|
|
|
{
|
|
|
pVoiceState->BuffersQueued += 1;
|
|
|
entry = entry->next;
|
|
|
}
|
|
|
|
|
|
LOG_INFO(
|
|
|
voice->audio,
|
|
|
"-> {pCurrentBufferContext: %p, BuffersQueued: %u, SamplesPlayed: %"FAudio_PRIu64"}",
|
|
|
pVoiceState->pCurrentBufferContext, pVoiceState->BuffersQueued,
|
|
|
pVoiceState->SamplesPlayed
|
|
|
)
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_SetFrequencyRatio(
|
|
|
FAudioSourceVoice *voice,
|
|
|
float Ratio,
|
|
|
uint32_t OperationSet
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
|
|
|
if (OperationSet != FAUDIO_COMMIT_NOW && voice->audio->active)
|
|
|
{
|
|
|
FAudio_OPERATIONSET_QueueSetFrequencyRatio(
|
|
|
voice,
|
|
|
Ratio,
|
|
|
OperationSet
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
if (voice->flags & FAUDIO_VOICE_NOPITCH)
|
|
|
{
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
voice->src.freqRatio = FAudio_clamp(
|
|
|
Ratio,
|
|
|
FAUDIO_MIN_FREQ_RATIO,
|
|
|
voice->src.maxFreqRatio
|
|
|
);
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
void FAudioSourceVoice_GetFrequencyRatio(
|
|
|
FAudioSourceVoice *voice,
|
|
|
float *pRatio
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
|
|
|
*pRatio = voice->src.freqRatio;
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
}
|
|
|
|
|
|
uint32_t FAudioSourceVoice_SetSourceSampleRate(
|
|
|
FAudioSourceVoice *voice,
|
|
|
uint32_t NewSourceSampleRate
|
|
|
) {
|
|
|
uint32_t outSampleRate;
|
|
|
uint32_t newDecodeSamples, newResampleSamples;
|
|
|
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE);
|
|
|
FAudio_assert( NewSourceSampleRate >= FAUDIO_MIN_SAMPLE_RATE &&
|
|
|
NewSourceSampleRate <= FAUDIO_MAX_SAMPLE_RATE );
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->src.bufferLock)
|
|
|
if ( voice->audio->version > 7 &&
|
|
|
voice->src.bufferList != NULL )
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return FAUDIO_E_INVALID_CALL;
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex(voice->src.bufferLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->src.bufferLock)
|
|
|
|
|
|
voice->src.format->nSamplesPerSec = NewSourceSampleRate;
|
|
|
|
|
|
/* Resize decode cache */
|
|
|
newDecodeSamples = (uint32_t) FAudio_ceil(
|
|
|
voice->audio->updateSize *
|
|
|
(double) voice->src.maxFreqRatio *
|
|
|
(double) NewSourceSampleRate /
|
|
|
(double) voice->audio->master->master.inputSampleRate
|
|
|
) + EXTRA_DECODE_PADDING * voice->src.format->nChannels;
|
|
|
FAudio_INTERNAL_ResizeDecodeCache(
|
|
|
voice->audio,
|
|
|
(newDecodeSamples + EXTRA_DECODE_PADDING) * voice->src.format->nChannels
|
|
|
);
|
|
|
voice->src.decodeSamples = newDecodeSamples;
|
|
|
|
|
|
FAudio_PlatformLockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_LOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
if (voice->sends.SendCount == 0)
|
|
|
{
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
outSampleRate = voice->sends.pSends[0].pOutputVoice->type == FAUDIO_VOICE_MASTER ?
|
|
|
voice->sends.pSends[0].pOutputVoice->master.inputSampleRate :
|
|
|
voice->sends.pSends[0].pOutputVoice->mix.inputSampleRate;
|
|
|
|
|
|
FAudio_PlatformUnlockMutex(voice->sendLock);
|
|
|
LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock)
|
|
|
|
|
|
/* Resize resample cache */
|
|
|
newResampleSamples = (uint32_t) (FAudio_ceil(
|
|
|
(double) voice->audio->updateSize *
|
|
|
(double) outSampleRate /
|
|
|
(double) voice->audio->master->master.inputSampleRate
|
|
|
));
|
|
|
FAudio_INTERNAL_ResizeResampleCache(
|
|
|
voice->audio,
|
|
|
newResampleSamples * voice->src.format->nChannels
|
|
|
);
|
|
|
voice->src.resampleSamples = newResampleSamples;
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/* FAudioMasteringVoice Interface */
|
|
|
|
|
|
FAUDIOAPI uint32_t FAudioMasteringVoice_GetChannelMask(
|
|
|
FAudioMasteringVoice *voice,
|
|
|
uint32_t *pChannelMask
|
|
|
) {
|
|
|
LOG_API_ENTER(voice->audio)
|
|
|
FAudio_assert(voice->type == FAUDIO_VOICE_MASTER);
|
|
|
FAudio_assert(pChannelMask != NULL);
|
|
|
|
|
|
*pChannelMask = voice->audio->mixFormat.dwChannelMask;
|
|
|
LOG_API_EXIT(voice->audio)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
#ifdef FAUDIO_DUMP_VOICES
|
|
|
|
|
|
static inline FAudioIOStreamOut *DumpVoices_fopen(
|
|
|
const FAudioSourceVoice *voice,
|
|
|
const FAudioWaveFormatEx *format,
|
|
|
const char *mode,
|
|
|
const char *ext
|
|
|
) {
|
|
|
char loc[64];
|
|
|
uint16_t format_tag = format->wFormatTag;
|
|
|
uint16_t format_ex_tag = 0;
|
|
|
if (format->wFormatTag == FAUDIO_FORMAT_EXTENSIBLE)
|
|
|
{
|
|
|
/* get the GUID of the extended subformat */
|
|
|
const FAudioWaveFormatExtensible *format_ex =
|
|
|
(const FAudioWaveFormatExtensible*) format;
|
|
|
format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1);
|
|
|
}
|
|
|
FAudio_snprintf(
|
|
|
loc,
|
|
|
sizeof(loc),
|
|
|
"FA_fmt_0x%04X_0x%04X_0x%016lX%s.wav",
|
|
|
format_tag,
|
|
|
format_ex_tag,
|
|
|
(uint64_t) voice,
|
|
|
ext
|
|
|
);
|
|
|
FAudioIOStreamOut *fileOut = FAudio_fopen_out(loc, mode);
|
|
|
return fileOut;
|
|
|
}
|
|
|
|
|
|
static inline void DumpVoices_finalize_section(
|
|
|
const FAudioSourceVoice *voice,
|
|
|
const FAudioWaveFormatEx *format,
|
|
|
const char *section /* one of "data" or "dpds" */
|
|
|
) {
|
|
|
/* data file only contains the real data bytes */
|
|
|
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "rb", section);
|
|
|
if (!io_data)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex((FAudioMutex) io_data->lock);
|
|
|
size_t file_size_data = io_data->size(io_data->data);
|
|
|
if (file_size_data == 0)
|
|
|
{
|
|
|
/* nothing to do */
|
|
|
/* close data file */
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
|
|
|
FAudio_close_out(io_data);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* we got some data: append data section to main file */
|
|
|
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "ab", "");
|
|
|
if (!io)
|
|
|
{
|
|
|
/* close data file */
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
|
|
|
FAudio_close_out(io_data);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/* data sub-chunk - 8 bytes + data */
|
|
|
/* SubChunk2ID - 4 --> "data" or "dpds" */
|
|
|
io->write(io->data, section, 4, 1);
|
|
|
/* Subchunk2Size - 4 */
|
|
|
uint32_t chunk_size = (uint32_t)file_size_data;
|
|
|
io->write(io->data, &chunk_size, 4, 1);
|
|
|
/* data */
|
|
|
/* fill in data bytes */
|
|
|
uint8_t buffer[1024*1024];
|
|
|
size_t count;
|
|
|
while((count = io_data->read(io_data->data, (void*) buffer, 1, 1024*1024)) > 0)
|
|
|
{
|
|
|
io->write(io->data, (void*) buffer, 1, count);
|
|
|
}
|
|
|
|
|
|
/* close data file */
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
|
|
|
FAudio_close_out(io_data);
|
|
|
/* close main file */
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
|
|
|
FAudio_close_out(io);
|
|
|
}
|
|
|
|
|
|
static void FAudio_DUMPVOICE_Init(const FAudioSourceVoice *voice)
|
|
|
{
|
|
|
const FAudioWaveFormatEx *format = voice->src.format;
|
|
|
|
|
|
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "wb", "");
|
|
|
if (!io)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex((FAudioMutex) io->lock);
|
|
|
/* another GREAT ressource
|
|
|
* https://wiki.multimedia.cx/index.php/Microsoft_xWMA
|
|
|
*/
|
|
|
|
|
|
|
|
|
/* wave file format taken from
|
|
|
* http://soundfile.sapp.org/doc/WaveFormat
|
|
|
* https://sites.google.com/site/musicgapi/technical-documents/wav-file-format
|
|
|
* |52 49|46 46|52 4A|02 00|
|
|
|
* |c1 sz|af|nc|sp rt|bt rt|
|
|
|
* |ba|bs|da ta|c2 sz|
|
|
|
|
|
|
* | R I F F |chunk size |W A V E |f m t |
|
|
|
* 19026
|
|
|
* | 52 49 46 46 52 4A 02 00 57 41 56 45 66 6D 74 20 | RIFFRJ..WAVEfmt
|
|
|
|
|
|
* | subchnk size|fmt |nChan |samplerate |byte rate |
|
|
|
* | 50 | 2 |2 |11025 |11289 |
|
|
|
* | 32 00 00 00 02 00 02 00 11 2B 00 00 19 2C 00 00 | 2........+...,..
|
|
|
|
|
|
* |blkaln|bps |efmt |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
|
|
|
* | 512 |4 |32 |500 |7 |256 |0 |512 |
|
|
|
* | 512 |4 |32 |459252 |256 |
|
|
|
* | 00 02|04 00 20 00 F4 01 07 00 00 01 00 00 00 02 | .... .ô.........
|
|
|
|
|
|
* | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
|
|
|
* |
|
|
|
* | 00 FF 00 00 00 00 C0 00 40 00 F0 00 00 00 CC 01 | .ÿ....À.@.ð...Ì.
|
|
|
|
|
|
* | XXXXXXXXXXXXXXXXXX|d a t a |chunk size |XXXXX |
|
|
|
* | | |18944 | |
|
|
|
* | 30 FF 88 01 18 FF 64 61 74 61 00 4A 02 00 00 00 | 0ÿ...ÿdata.J....
|
|
|
*/
|
|
|
|
|
|
uint16_t cbSize = format->cbSize;
|
|
|
const char *formatFourcc = "WAVE";
|
|
|
uint16_t wFormatTag = format->wFormatTag;
|
|
|
/* special handling for WMAUDIO2 */
|
|
|
if (wFormatTag == FAUDIO_FORMAT_EXTENSIBLE && cbSize >= 22)
|
|
|
{
|
|
|
const FAudioWaveFormatExtensible *format_ex =
|
|
|
(const FAudioWaveFormatExtensible*) format;
|
|
|
uint16_t format_ex_tag = (uint16_t) (format_ex->SubFormat.Data1);
|
|
|
if (format_ex_tag == FAUDIO_FORMAT_WMAUDIO2)
|
|
|
{
|
|
|
cbSize = 0;
|
|
|
formatFourcc = "XWMA";
|
|
|
wFormatTag = FAUDIO_FORMAT_WMAUDIO2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
{ /* RIFF chunk descriptor - 12 byte */
|
|
|
/* ChunkID - 4 */
|
|
|
io->write(io->data, "RIFF", 4, 1);
|
|
|
/* ChunkSize - 4 */
|
|
|
uint32_t filesize = 0; /* the real file size is written in finalize step */
|
|
|
io->write(io->data, &filesize, 4, 1);
|
|
|
/* Format - 4 */
|
|
|
io->write(io->data, formatFourcc, 4, 1);
|
|
|
}
|
|
|
{ /* fmt sub-chunk 24 */
|
|
|
/* Subchunk1ID - 4 */
|
|
|
io->write(io->data, "fmt ", 4, 1);
|
|
|
/* Subchunk1Size - 4 */
|
|
|
/* 18 byte for WAVEFORMATEX and cbSize for WAVEFORMATEXTENDED */
|
|
|
uint32_t chunk_data_size = 18 + (uint32_t) cbSize;
|
|
|
io->write(io->data, &chunk_data_size, 4, 1);
|
|
|
/* AudioFormat - 2 */
|
|
|
io->write(io->data, &wFormatTag, 2, 1);
|
|
|
/* NumChannels - 2 */
|
|
|
io->write(io->data, &format->nChannels, 2, 1);
|
|
|
/* SampleRate - 4 */
|
|
|
io->write(io->data, &format->nSamplesPerSec, 4, 1);
|
|
|
/* ByteRate - 4 */
|
|
|
/* SampleRate * NumChannels * BitsPerSample/8 */
|
|
|
io->write(io->data, &format->nAvgBytesPerSec, 4, 1);
|
|
|
/* BlockAlign - 2 */
|
|
|
/* NumChannels * BitsPerSample/8 */
|
|
|
io->write(io->data, &format->nBlockAlign, 2, 1);
|
|
|
/* BitsPerSample - 2 */
|
|
|
io->write(io->data, &format->wBitsPerSample, 2, 1);
|
|
|
}
|
|
|
/* in case of extensible audio format write the additional data to the file */
|
|
|
{
|
|
|
/* always write the cbSize */
|
|
|
io->write(io->data, &cbSize, 2, 1);
|
|
|
|
|
|
if (cbSize >= 22)
|
|
|
{
|
|
|
/* we have a WAVEFORMATEXTENSIBLE struct to write */
|
|
|
const FAudioWaveFormatExtensible *format_ex =
|
|
|
(const FAudioWaveFormatExtensible*) format;
|
|
|
io->write(io->data, &format_ex->Samples.wValidBitsPerSample, 2, 1);
|
|
|
io->write(io->data, &format_ex->dwChannelMask, 4, 1);
|
|
|
/* write FAudioGUID */
|
|
|
io->write(io->data, &format_ex->SubFormat.Data1, 4, 1);
|
|
|
io->write(io->data, &format_ex->SubFormat.Data2, 2, 1);
|
|
|
io->write(io->data, &format_ex->SubFormat.Data3, 2, 1);
|
|
|
io->write(io->data, &format_ex->SubFormat.Data4, 1, 8);
|
|
|
}
|
|
|
if (format->cbSize > 22)
|
|
|
{
|
|
|
/* fill up the remaining cbSize bytes with zeros */
|
|
|
uint8_t zero = 0;
|
|
|
for (uint16_t i=23; i<=format->cbSize; i++)
|
|
|
{
|
|
|
io->write(io->data, &zero, 1, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
{ /* dpds sub-chunk - optional - 8 bytes + bufferWMA uint32_t samples */
|
|
|
/* create file to hold the bufferWMA samples */
|
|
|
FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, format, "wb", "dpds");
|
|
|
FAudio_close_out(io_dpds);
|
|
|
/* io_dpds file will be filled by SubmitBuffer */
|
|
|
}
|
|
|
{ /* data sub-chunk - 8 bytes + data */
|
|
|
/* create file to hold the data samples */
|
|
|
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, format, "wb", "data");
|
|
|
FAudio_close_out(io_data);
|
|
|
/* io_data file will be filled by SubmitBuffer */
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
|
|
|
FAudio_close_out(io);
|
|
|
}
|
|
|
|
|
|
static void FAudio_DUMPVOICE_Finalize(const FAudioSourceVoice *voice)
|
|
|
{
|
|
|
const FAudioWaveFormatEx *format = voice->src.format;
|
|
|
|
|
|
/* add dpds subchunk - optional */
|
|
|
DumpVoices_finalize_section(voice, format, "dpds");
|
|
|
/* add data subchunk */
|
|
|
DumpVoices_finalize_section(voice, format, "data");
|
|
|
|
|
|
/* open main file to update filesize */
|
|
|
FAudioIOStreamOut *io = DumpVoices_fopen(voice, format, "r+b", "");
|
|
|
if (!io)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
FAudio_PlatformLockMutex((FAudioMutex) io->lock);
|
|
|
size_t file_size = io->size(io->data);
|
|
|
if (file_size >= 44)
|
|
|
{
|
|
|
/* update filesize */
|
|
|
uint32_t chunk_size = (uint32_t)(file_size - 8);
|
|
|
io->seek(io->data, 4, FAUDIO_SEEK_SET);
|
|
|
io->write(io->data, &chunk_size, 4, 1);
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock);
|
|
|
FAudio_close_out(io);
|
|
|
}
|
|
|
|
|
|
static void FAudio_DUMPVOICE_WriteBuffer(
|
|
|
const FAudioSourceVoice *voice,
|
|
|
const FAudioBuffer *pBuffer,
|
|
|
const FAudioBufferWMA *pBufferWMA,
|
|
|
const uint32_t playBegin,
|
|
|
const uint32_t playLength
|
|
|
) {
|
|
|
FAudioIOStreamOut *io_data = DumpVoices_fopen(voice, voice->src.format, "ab", "data");
|
|
|
if (io_data == NULL)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
FAudio_PlatformLockMutex((FAudioMutex) io_data->lock);
|
|
|
if (pBufferWMA != NULL)
|
|
|
{
|
|
|
/* dump encoded buffer contents */
|
|
|
if (pBufferWMA->PacketCount > 0)
|
|
|
{
|
|
|
FAudioIOStreamOut *io_dpds = DumpVoices_fopen(voice, voice->src.format, "ab", "dpds");
|
|
|
if (io_dpds)
|
|
|
{
|
|
|
FAudio_PlatformLockMutex((FAudioMutex) io_dpds->lock);
|
|
|
/* write to dpds file */
|
|
|
io_dpds->write(io_dpds->data, pBufferWMA->pDecodedPacketCumulativeBytes, sizeof(uint32_t), pBufferWMA->PacketCount);
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io_dpds->lock);
|
|
|
FAudio_close_out(io_dpds);
|
|
|
}
|
|
|
/* write buffer contents to data file */
|
|
|
io_data->write(io_data->data, pBuffer->pAudioData, sizeof(uint8_t), pBuffer->AudioBytes);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* dump unencoded buffer contents */
|
|
|
uint16_t bytesPerFrame = (voice->src.format->nChannels * voice->src.format->wBitsPerSample / 8);
|
|
|
FAudio_assert(bytesPerFrame > 0);
|
|
|
const void *pAudioDataBegin = pBuffer->pAudioData + playBegin*bytesPerFrame;
|
|
|
io_data->write(io_data->data, pAudioDataBegin, bytesPerFrame, playLength);
|
|
|
}
|
|
|
FAudio_PlatformUnlockMutex((FAudioMutex) io_data->lock);
|
|
|
FAudio_close_out(io_data);
|
|
|
}
|
|
|
|
|
|
#endif /* FAUDIO_DUMP_VOICES */
|
|
|
|
|
|
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
|
|
|
|